Data transformation workflow

Following is the proposed preliminary workflow for the data transformation project.

Code Lists

Let’s store them in a directory and try reading them without causing pain in the fingers or wear and tear on mouse and trackpad. First, let’s create a list of files of code lists.

filenames <- list.files("./contacts/CodeList", pattern="*.xlsx", full.names = T) # We can avoid creating a separate directory for code list. But organizing may be difficult. However, this can be explored further if we want transform all the data in one go i.e. not by functions (contacts, accounts etc.).

# File paths
print(filenames)
## [1] "./contacts/CodeList/CodeList_Contact_International_Version.xlsx"
## [2] "./contacts/CodeList/CodeList_Contact_Is_Contact_Person_For.xlsx"
## [3] "./contacts/CodeList/CodeList_Contact_Personal_Addresses.xlsx"   
## [4] "./contacts/CodeList/CodeList_Contact.xlsx"

Please ensure that there are no hidden files in the directory

Now, let’s attempt reading them

sheet_names<-lapply(filenames, excel_sheets) # Creates a list of the sheet names

for(i in seq_along(filenames)){
  codelist_files<-lapply(excel_sheets(filenames[[i]]), read_excel, path = filenames[[i]]) # Reads the sheets of the excel files
  names(codelist_files)<-c(sheet_names[[i]]) # Renames them according to the sheet names extracted above
  # for(j in seq_along(sheet_names[[i]])){
  #   assign(paste0(substr(filenames[[i]],30,nchar(filenames[[i]])-5),"_",sheet_names[[i]][j]), read_excel(path=filenames[[i]], sheet = sheet_names[[i]][j]))
  # }
  }
# Names of the files imported
names(codelist_files)
##  [1] "Academic_Title"            "Additional_Academic_Title"
##  [3] "Best_Reached_By"           "CountryRegion"            
##  [5] "State"                     "Contact_Permission"       
##  [7] "Department"                "Function"                 
##  [9] "Gender"                    "Language"                 
## [11] "Marital_Status"            "Prefix"                   
## [13] "Perception_Of_Company"     "Profession"               
## [15] "Status"                    "Title"                    
## [17] "VIP_Contact"

Templates

Now we shall extract the templates. There are two templates for each file. One for SAP i.e. the file that needs to uploaded to SAP. And the other is the file that needs to be converted to the SAP template format.

We shall start with the legacy format. Since we do not have the real data, we have created a dummy. Right now, just one table of Contact. Some intentional errors have been introduced in the file.

Let us now extract the data. Below we are reading only one file having all data related to Contacts from the legacy system.

oldfilepath<-("./contacts/olddummy.xlsx")
old.data<-lapply(excel_sheets(oldfilepath), read_excel, path=oldfilepath)
names(old.data)<-excel_sheets(oldfilepath)
# Names of the files imported
names(old.data)
## [1] "Contact_o"                       "Contact_International_Version_o"
## [3] "Contact_Is_Contact_Person_For_o" "Contact_Personal_Addresses_o"   
## [5] "Contact_Notes_o"

We shall use the Contact_o i.e. old contact table and transform into the required SAP upload format after checking for possible errors.

The process to transform all the data from one segment (for e.g. Contacts or Accounts etc.) can implemented only if we guarantee error free data in the legacy system. Since that is not possible, we need to do it per table (per sheet of the excel file).

Now we shall create SAP template.

Although the file has multiple sheets, only the last sheet i.e. Field_Definitions holds enough information for us to create the template.

saptemplate<-read_excel("./contacts/Contact.xlsx", sheet = "Field_Definitions")
# First few rows of the imported data
head(saptemplate)
## # A tibble: 6 × 8
##   `Sheet Name` Header     `Property Name`   `UI Text`   `Data Type` `Max Length`
##   <chr>        <chr>      <chr>             <chr>       <chr>       <chr>       
## 1 Contact      External_… ExternalKey       External K… String      100         
## 2 Contact      Contact_ID ContactID         Contact ID  String      10          
## 3 Contact      Status     StatusCode        Status      String      2           
## 4 Contact      Title      TitleCode         Title       String      4           
## 5 Contact      Academic_… AcademicTitleCode Academic T… String      4           
## 6 Contact      Additiona… AdditionalAcadem… Additional… String      4           
## # … with 2 more variables: CodeList File Path <chr>, Mandatory <chr>

Please note that the format of the tables (sheet) has been slightly changed. Earlier the corresponding sheet name was mentioned in a row before the actual table. Now, all the rows mention the corresponding sheet name. This was done manually for convenience of data extraction

Although we will be using only one table at the moment, all the templates have been exported below.

snames<-unique(saptemplate$`Sheet Name`)

# Creates data frame for each sheet in snames
for( i in seq_along(snames)){
  colnames<-saptemplate[saptemplate$`Sheet Name`==snames[i],]$Header # Defines the column names
  df<-read.table("", col.names = colnames) # Creates an empty data frame using the column names
  assign(snames[i], df) # Assigns value of df to a data frame named in sname
  
}

Transforming and errors

Steps to check the legacy data for errors and transform into SAP compatible format.

Check the column names and sequence of the file

# Column names of the Contact table
colnames(Contact)
##  [1] "External_Key"                  "Contact_ID"                   
##  [3] "Status"                        "Title"                        
##  [5] "Academic_Title"                "Additional_Academic_Title"    
##  [7] "Prefix"                        "First_Name"                   
##  [9] "Last_Name"                     "Additional_Last_Name"         
## [11] "Initials"                      "Middle_Name"                  
## [13] "Gender"                        "Marital_Status"               
## [15] "Language"                      "Nick_Name"                    
## [17] "Date_of_Birth"                 "Birth_Name"                   
## [19] "Contact_Permission"            "Profession"                   
## [21] "Perception_Of_Company"         "Account_External_Key"         
## [23] "Account_ID"                    "Building"                     
## [25] "Floor"                         "Room"                         
## [27] "Job_Title"                     "Function"                     
## [29] "Department"                    "Department_From_Business_Card"
## [31] "VIP_Contact"                   "Phone"                        
## [33] "Mobile"                        "Fax"                          
## [35] "EMail"                         "EMail_Invalid"                
## [37] "Best_Reached_By"               "CountryRegion"                
## [39] "State_Text_Updatable"          "House_Number"                 
## [41] "Street"                        "City"                         
## [43] "Postal_Code"                   "State"                        
## [45] "Contact_Owner_External_Key"    "Contact_Owner_ID"             
## [47] "Former_CRM_reference"

Create a copy of the old file to work on.

old.copy<-old.data$Contact_o # Selecting only one table as sample
old.copy
## # A tibble: 33 × 47
##       ID CID   STATUS    TTL   AcaTTL AddtnalTTL pre   name  surname add_surname
##    <dbl> <chr> <chr>     <chr> <chr>  <chr>      <chr> <chr> <chr>   <chr>      
##  1 98320 F2371 Active    Mr.   <NA>   B.A.       von   <NA>  qefb    <NA>       
##  2 98321 F2372 Inactive  Ms.   <NA>   Prof. Dr.  von … <NA>  <NA>    wvjnweg    
##  3 98322 F2373 not known Miss  <NA>   Dr.        van   Jojq… uqheq   asdvjn     
##  4 98323 F2374 Active    Mast… B.A.   <NA>       van … ajnv… <NA>    <NA>       
##  5 98324 F2375 Inactive  Dr.   Prof.… <NA>       da    jenwv kuhanb… ajvn       
##  6 98325 F2376 not known <NA>  Dr.    <NA>       de    <NA>  <NA>    <NA>       
##  7 98326 F2377 Active    Mr.   <NA>   <NA>       de la <NA>  <NA>    niebjnwe   
##  8 98327 F2378 Inactive  Mr.   <NA>   <NA>       dos   wjvn… wjnweg  <NA>       
##  9 98328 F2379 not known Mr.   <NA>   B.A.       du    <NA>  <NA>    vneiwg     
## 10 98329 F2380 <NA>      <NA>  MBA    Prof. Dr.  el    <NA>  ejavneq jsdnw      
## # … with 23 more rows, and 37 more variables: initi <chr>, mid_name <chr>,
## #   gender <chr>, mar_sta <chr>, lang <chr>, nick_name <lgl>, dob <lgl>,
## #   birth_name <lgl>, Contact_Permission <chr>, Profession <chr>,
## #   Perception_Of_Company <chr>, Account_External_Key <chr>, Account_ID <chr>,
## #   Building <dbl>, Floor <lgl>, Room <lgl>, Job_Title <chr>, Function <chr>,
## #   Department <chr>, Department_From_Business_Card <chr>, VIP_Contact <chr>,
## #   Phone <lgl>, Mobile <lgl>, Fax <lgl>, EMail <lgl>, EMail_Invalid <lgl>, …

Rename the columns as per column name of the template.

To do this in a safe way there are two options

  • Create another column in Field Definition excel sheet that contains the corresponding column names in the legacy file.
  • Create a separate sheet where this mapping is defined

Manual typing is error prone and hence should be avoided at least in this code. For the time being, we have created a separate file. Although the other way is easier to maintain and recommended.

mapped<-read.csv("./contacts/contact_map.csv", sep=";")
x=NULL
for(i in 1:nrow(mapped)){
  x[i] = mapped[mapped$oldkey==colnames(old.copy[i]),]$Header
}
colnames(old.copy)<-x # Changing column names

Check for errors

Essential rows

saptemplate[saptemplate$`Sheet Name`=="Contact",] |> 
  filter(Mandatory=="Yes") |> 
  pull(Header) -> essential.rows # List of mandatory columns

Check if some rows have missing items for mandatory columns

essen.rows.table=read.table("", col.names = c("Item","Missing"))

for(i in seq_along(essential.rows)){
  essen.rows.table[i,2]<-sum(is.na(old.copy[,essential.rows[i]]))
  essen.rows.table[i,1]<-essential.rows[i]
} # Creates the table below

essen.rows.table
##           Item Missing
## 1 External_Key       1
## 2    Last_Name      15

Remove the rows with missing mandatory values

for(i in seq_along(essential.rows)){
  old.copy<-old.copy[!is.na(old.copy[,essential.rows[i]]),]
} # Remove the rows with missing mandatory values

Check if code listed column data are from the codelist

codelistcols<-saptemplate[saptemplate$`Sheet Name`=="Contact",] |> 
  filter(!is.na(`CodeList File Path`)) |> pull(Header) # List of columns that have a codelist

codelisted.rows.table=read.table("", col.names = c("Item","Missing", "Not_from_code"))
for(i in seq_along(codelistcols)){
  codelisted.rows.table[i,3]<-sum(!pull(old.copy[,codelistcols[i]],1) %in% c(pull(codelist_files[codelistcols[i]][[1]],Description),NA)) # Added NA else empty columns also get counted
  codelisted.rows.table[i,2]<-sum(is.na(old.copy[,codelistcols[i]]))
  codelisted.rows.table[i,1]<-codelistcols[i]
} # Creates the table below
codelisted.rows.table
##                         Item Missing Not_from_code
## 1                     Status       3            10
## 2                      Title      11             2
## 3             Academic_Title       9             2
## 4  Additional_Academic_Title       8             0
## 5                     Prefix       3             1
## 6                     Gender      11             6
## 7             Marital_Status      13             4
## 8                   Language       8             5
## 9         Contact_Permission       6             5
## 10                Profession      13             4
## 11     Perception_Of_Company      12             1
## 12                  Function       1             1
## 13                Department       4             0
## 14               VIP_Contact      16             0
## 15           Best_Reached_By      11             0
## 16             CountryRegion       8             9
## 17                     State       8             0

If values do not match, we empty the value

for(i in seq_along(codelistcols)){
  old.copy[!pull(old.copy[,codelistcols[i]],1) %in% c(pull(codelist_files[codelistcols[i]][[1]],Description),NA),codelistcols[i]]<-NA
} # Removes the value in case of mismatch

Translate into SAP language using code list

for(i in seq_along(codelistcols)){
  old.copy[,codelistcols[i]]<-
  pull(codelist_files[codelistcols[i]][[1]],2)[match(pull(old.copy,codelistcols[i]), pull(codelist_files[codelistcols[i]][[1]],Description))]
} # Matches each column with the corresponding code list and returns the value

###Fix column types

dtype<-saptemplate[saptemplate$`Sheet Name`=="Contact",]$`Data Type` # List of data types. Non Exhaustive ATM

for(i in 1:ncol(old.copy)){
  if(dtype[i] == "String"){
    old.copy[,i] <- as.character(pull(old.copy,i))
  }
  if(dtype[i] == "Boolean"){
    old.copy[,i] <- as.logical(pull(old.copy,i))
    }
  if(dtype[i] == "DateTime"){
    old.copy[,i] <- lubridate::ymd_hms(pull(old.copy,i))
    }
  if(dtype[i] == "Time"){
      old.copy[,i] <- lubridate::hms(pull(old.copy,i))
    
  } # This list will increase and also change based on input date and time formats
  
}

String length

max.length<-saptemplate[saptemplate$`Sheet Name`=="Contact",]$`Max Length` # List of max lengths mentioned

colclasses<-lapply(old.copy,class) # getting column classes

for(i in 1: ncol(old.copy)){
  if(colclasses[[i]]=="character"){
    old.copy[,i]<- ifelse(nchar(pull(old.copy,i))>max.length[i], substring(pull(old.copy,i),1,max.length[i]), pull(old.copy,i))
  } # If string length is more than mentioned, trim it to the mentioned
  
}

Save file

write.csv(old.copy, "Contact.csv",row.names=FALSE) # Saving CSV file

view the exported file

contacts.sap<-read.csv("Contact.csv")
datatable(contacts.sap,options = list(scrollX = TRUE))

Viewing is for sample only. Larger files cannot be viewed in html, requires server side processing.

Questions and Comments

  • What shall we do with values that do not match items in code list? Currently we are removing those entries. But there may be cases where removing may not make sense. for e.g. (M instead of Male)
  • What shall we do with missing items in normal columns (non-coded)?
  • What shall we do with missing values in mandatory columns? Currently deleting the complete row
  • What if the country mentioned is not from the state mentioned? Shall we check that too?
  • What is the input format of logical variables (Yes/no, T/F, TRUE/FALSE etc)?
  • What is the output format (SAP) for logical variables?
  • What is the input format of date, datetime and time variables?
  • What is the output format of date, datetime and time variables?
  • What to do if a string is larger than what is allowed?
  • Where code lists are mentioned, what is the input variable? It is assumed that the input files will have descriptions and not the code
  • Where code lists are mentioned, what should be the output (Code or description)? Currently We take the description as input and convert it to code.

This document contains several explanations. And the workflow has been divided into steps. These steps may increase and decrease (by combining several steps), depending on the data quality and structure. For e.g. if the input datetime format is different in different tables, each table may require manual transformation, increasing the number of steps and complexity. If the data quality is good, it may even be possible to transform all the tables in a segment (contacts or accounts etc.) may be transformed in a single run. It will eventually be clear only after obtaining the input data. The code can further be adjusted for faster processing.

LS0tCnRpdGxlOiAiV29ya2Zsb3cgRGVzaWduIgphdXRob3I6ICJMYU51YmlhIENvbnN1bHRpbmcsIERhdGEgU2NpZW5jZSBUZWFtIgpkYXRlOiAiMTIvMTMvMjAyMSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiBtb25vY2hyb21lCi0tLQoKYGBge2NzcywgZWNobz1GQUxTRX0KcHJlIHsKICBtYXgtaGVpZ2h0OiAzMDBweDsKICBvdmVyZmxvdy15OiBhdXRvOwp9CgpwcmVbY2xhc3NdIHsKICBtYXgtaGVpZ2h0OiAxMDBweDsKfQpgYGAKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShkcGx5cikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoRFQpCgptdXRsc3R4bHJkcjwtZnVuY3Rpb24oKXsKICBmb3IoIGkgaW4gc2VxX2Fsb25nKHNoZWV0Lm5hKSl7CiAgY29sbmFtZXM8LXVuaXF1ZShzYXB0ZW1wbGF0ZVtzYXB0ZW1wbGF0ZSRgU2hlZXQgTmFtZWA9PXNuYW1lc1tpXSxdJEhlYWRlcikKICBkZjwtcmVhZC50YWJsZSgiIiwgY29sLm5hbWVzID0gY29sbmFtZXMpCiAgYXNzaWduKHNuYW1lc1tpXSwgZGYpCiAgCn0KfQoKYGBgCgojIyBEYXRhIHRyYW5zZm9ybWF0aW9uIHdvcmtmbG93CgpGb2xsb3dpbmcgaXMgdGhlIHByb3Bvc2VkIHByZWxpbWluYXJ5IHdvcmtmbG93IGZvciB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBwcm9qZWN0LgoKIyMjIENvZGUgTGlzdHMKCkxldCdzIHN0b3JlIHRoZW0gaW4gYSBgZGlyZWN0b3J5YCBhbmQgdHJ5IHJlYWRpbmcgdGhlbSB3aXRob3V0IGNhdXNpbmcgcGFpbiBpbiB0aGUgZmluZ2VycyBvciB3ZWFyIGFuZCB0ZWFyIG9uIG1vdXNlIGFuZCB0cmFja3BhZC4gRmlyc3QsIGxldCdzIGNyZWF0ZSBhIGxpc3Qgb2YgZmlsZXMgb2YgY29kZSBsaXN0cy4KCgpgYGB7ciBDcmVhdGUgTGlzdCBvZiBGaWxlcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKZmlsZW5hbWVzIDwtIGxpc3QuZmlsZXMoIi4vY29udGFjdHMvQ29kZUxpc3QiLCBwYXR0ZXJuPSIqLnhsc3giLCBmdWxsLm5hbWVzID0gVCkgIyBXZSBjYW4gYXZvaWQgY3JlYXRpbmcgYSBzZXBhcmF0ZSBkaXJlY3RvcnkgZm9yIGNvZGUgbGlzdC4gQnV0IG9yZ2FuaXppbmcgbWF5IGJlIGRpZmZpY3VsdC4gSG93ZXZlciwgdGhpcyBjYW4gYmUgZXhwbG9yZWQgZnVydGhlciBpZiB3ZSB3YW50IHRyYW5zZm9ybSBhbGwgdGhlIGRhdGEgaW4gb25lIGdvIGkuZS4gbm90IGJ5IGZ1bmN0aW9ucyAoY29udGFjdHMsIGFjY291bnRzIGV0Yy4pLgoKIyBGaWxlIHBhdGhzCnByaW50KGZpbGVuYW1lcykKYGBgCgoqUGxlYXNlIGVuc3VyZSB0aGF0IHRoZXJlIGFyZSBubyBoaWRkZW4gZmlsZXMgaW4gdGhlIGRpcmVjdG9yeSoKCk5vdywgbGV0J3MgYXR0ZW1wdCByZWFkaW5nIHRoZW0KCmBgYHtyIGNvZGVsaXN0cmVhZGVyLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNoZWV0X25hbWVzPC1sYXBwbHkoZmlsZW5hbWVzLCBleGNlbF9zaGVldHMpICMgQ3JlYXRlcyBhIGxpc3Qgb2YgdGhlIHNoZWV0IG5hbWVzCgpmb3IoaSBpbiBzZXFfYWxvbmcoZmlsZW5hbWVzKSl7CiAgY29kZWxpc3RfZmlsZXM8LWxhcHBseShleGNlbF9zaGVldHMoZmlsZW5hbWVzW1tpXV0pLCByZWFkX2V4Y2VsLCBwYXRoID0gZmlsZW5hbWVzW1tpXV0pICMgUmVhZHMgdGhlIHNoZWV0cyBvZiB0aGUgZXhjZWwgZmlsZXMKICBuYW1lcyhjb2RlbGlzdF9maWxlcyk8LWMoc2hlZXRfbmFtZXNbW2ldXSkgIyBSZW5hbWVzIHRoZW0gYWNjb3JkaW5nIHRvIHRoZSBzaGVldCBuYW1lcyBleHRyYWN0ZWQgYWJvdmUKICAjIGZvcihqIGluIHNlcV9hbG9uZyhzaGVldF9uYW1lc1tbaV1dKSl7CiAgIyAgIGFzc2lnbihwYXN0ZTAoc3Vic3RyKGZpbGVuYW1lc1tbaV1dLDMwLG5jaGFyKGZpbGVuYW1lc1tbaV1dKS01KSwiXyIsc2hlZXRfbmFtZXNbW2ldXVtqXSksIHJlYWRfZXhjZWwocGF0aD1maWxlbmFtZXNbW2ldXSwgc2hlZXQgPSBzaGVldF9uYW1lc1tbaV1dW2pdKSkKICAjIH0KICB9CiMgTmFtZXMgb2YgdGhlIGZpbGVzIGltcG9ydGVkCm5hbWVzKGNvZGVsaXN0X2ZpbGVzKQoKCmBgYAoKCiMjIyBUZW1wbGF0ZXMKCk5vdyB3ZSBzaGFsbCBleHRyYWN0IHRoZSB0ZW1wbGF0ZXMuIFRoZXJlIGFyZSB0d28gdGVtcGxhdGVzIGZvciBlYWNoIGZpbGUuIE9uZSBmb3IgU0FQIGkuZS4gdGhlIGZpbGUgdGhhdCBuZWVkcyB0byB1cGxvYWRlZCB0byBTQVAuIEFuZCB0aGUgb3RoZXIgaXMgdGhlIGZpbGUgdGhhdCBuZWVkcyB0byBiZSBjb252ZXJ0ZWQgdG8gdGhlIFNBUCB0ZW1wbGF0ZSBmb3JtYXQuCgpXZSBzaGFsbCBzdGFydCB3aXRoIHRoZSBsZWdhY3kgZm9ybWF0LiBTaW5jZSB3ZSBkbyBub3QgaGF2ZSB0aGUgcmVhbCBkYXRhLCB3ZSBoYXZlIGNyZWF0ZWQgYSBkdW1teS4gUmlnaHQgbm93LCBqdXN0IG9uZSB0YWJsZSBvZiBgQ29udGFjdGAuIFNvbWUgaW50ZW50aW9uYWwgZXJyb3JzIGhhdmUgYmVlbiBpbnRyb2R1Y2VkIGluIHRoZSBmaWxlLgoKTGV0IHVzIG5vdyBleHRyYWN0IHRoZSBkYXRhLiBCZWxvdyB3ZSBhcmUgcmVhZGluZyBvbmx5IG9uZSBmaWxlIGhhdmluZyBhbGwgZGF0YSByZWxhdGVkIHRvIGBDb250YWN0c2AgZnJvbSB0aGUgbGVnYWN5IHN5c3RlbS4KCmBgYHtyIHJlYWRsZWdhY3ksIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kb2xkZmlsZXBhdGg8LSgiLi9jb250YWN0cy9vbGRkdW1teS54bHN4IikKb2xkLmRhdGE8LWxhcHBseShleGNlbF9zaGVldHMob2xkZmlsZXBhdGgpLCByZWFkX2V4Y2VsLCBwYXRoPW9sZGZpbGVwYXRoKQpuYW1lcyhvbGQuZGF0YSk8LWV4Y2VsX3NoZWV0cyhvbGRmaWxlcGF0aCkKIyBOYW1lcyBvZiB0aGUgZmlsZXMgaW1wb3J0ZWQKbmFtZXMob2xkLmRhdGEpCmBgYAoKV2Ugc2hhbGwgdXNlIHRoZSBgQ29udGFjdF9vYCBpLmUuIG9sZCBjb250YWN0IHRhYmxlIGFuZCB0cmFuc2Zvcm0gaW50byB0aGUgcmVxdWlyZWQgU0FQIHVwbG9hZCBmb3JtYXQgYWZ0ZXIgY2hlY2tpbmcgZm9yIHBvc3NpYmxlIGVycm9ycy4KCipUaGUgcHJvY2VzcyB0byB0cmFuc2Zvcm0gYWxsIHRoZSBkYXRhIGZyb20gb25lIHNlZ21lbnQgKGZvciBlLmcuIENvbnRhY3RzIG9yIEFjY291bnRzIGV0Yy4pIGNhbiBpbXBsZW1lbnRlZCBvbmx5IGlmIHdlIGd1YXJhbnRlZSBlcnJvciBmcmVlIGRhdGEgaW4gdGhlIGxlZ2FjeSBzeXN0ZW0uIFNpbmNlIHRoYXQgaXMgbm90IHBvc3NpYmxlLCB3ZSBuZWVkIHRvIGRvIGl0IHBlciB0YWJsZSAocGVyIHNoZWV0IG9mIHRoZSBleGNlbCBmaWxlKS4qCgpOb3cgd2Ugc2hhbGwgY3JlYXRlIFNBUCB0ZW1wbGF0ZS4KCkFsdGhvdWdoIHRoZSBmaWxlIGhhcyBtdWx0aXBsZSBzaGVldHMsIG9ubHkgdGhlIGxhc3Qgc2hlZXQgaS5lLiBgRmllbGRfRGVmaW5pdGlvbnNgIGhvbGRzIGVub3VnaCBpbmZvcm1hdGlvbiBmb3IgdXMgdG8gY3JlYXRlIHRoZSB0ZW1wbGF0ZS4KCgpgYGB7ciByZWFkU0FQdGVtcGxhdGUsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc2FwdGVtcGxhdGU8LXJlYWRfZXhjZWwoIi4vY29udGFjdHMvQ29udGFjdC54bHN4Iiwgc2hlZXQgPSAiRmllbGRfRGVmaW5pdGlvbnMiKQojIEZpcnN0IGZldyByb3dzIG9mIHRoZSBpbXBvcnRlZCBkYXRhCmhlYWQoc2FwdGVtcGxhdGUpCmBgYAoKKlBsZWFzZSBub3RlIHRoYXQgdGhlIGZvcm1hdCBvZiB0aGUgdGFibGVzIChzaGVldCkgaGFzIGJlZW4gc2xpZ2h0bHkgY2hhbmdlZC4gRWFybGllciB0aGUgY29ycmVzcG9uZGluZyBzaGVldCBuYW1lIHdhcyBtZW50aW9uZWQgaW4gYSByb3cgYmVmb3JlIHRoZSBhY3R1YWwgdGFibGUuIE5vdywgYWxsIHRoZSByb3dzIG1lbnRpb24gdGhlIGNvcnJlc3BvbmRpbmcgc2hlZXQgbmFtZS4gVGhpcyB3YXMgZG9uZSBtYW51YWxseSBmb3IgY29udmVuaWVuY2Ugb2YgZGF0YSBleHRyYWN0aW9uKgoKCkFsdGhvdWdoIHdlIHdpbGwgYmUgdXNpbmcgb25seSBvbmUgdGFibGUgYXQgdGhlIG1vbWVudCwgYWxsIHRoZSB0ZW1wbGF0ZXMgaGF2ZSBiZWVuIGV4cG9ydGVkIGJlbG93LgoKYGBge3IgY3JlYXRlbXB0eVNBUGZpbGVzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpzbmFtZXM8LXVuaXF1ZShzYXB0ZW1wbGF0ZSRgU2hlZXQgTmFtZWApCgojIENyZWF0ZXMgZGF0YSBmcmFtZSBmb3IgZWFjaCBzaGVldCBpbiBzbmFtZXMKZm9yKCBpIGluIHNlcV9hbG9uZyhzbmFtZXMpKXsKICBjb2xuYW1lczwtc2FwdGVtcGxhdGVbc2FwdGVtcGxhdGUkYFNoZWV0IE5hbWVgPT1zbmFtZXNbaV0sXSRIZWFkZXIgIyBEZWZpbmVzIHRoZSBjb2x1bW4gbmFtZXMKICBkZjwtcmVhZC50YWJsZSgiIiwgY29sLm5hbWVzID0gY29sbmFtZXMpICMgQ3JlYXRlcyBhbiBlbXB0eSBkYXRhIGZyYW1lIHVzaW5nIHRoZSBjb2x1bW4gbmFtZXMKICBhc3NpZ24oc25hbWVzW2ldLCBkZikgIyBBc3NpZ25zIHZhbHVlIG9mIGRmIHRvIGEgZGF0YSBmcmFtZSBuYW1lZCBpbiBzbmFtZQogIAp9CmBgYAoKCgojIyBUcmFuc2Zvcm1pbmcgYW5kIGVycm9ycwoKU3RlcHMgdG8gY2hlY2sgdGhlIGxlZ2FjeSBkYXRhIGZvciBlcnJvcnMgYW5kIHRyYW5zZm9ybSBpbnRvIFNBUCBjb21wYXRpYmxlIGZvcm1hdC4KCiMjIyBDaGVjayB0aGUgY29sdW1uIG5hbWVzIGFuZCBzZXF1ZW5jZSBvZiB0aGUgZmlsZQoKCmBgYHtyIGNvbG5hbWVjaGVjaywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIENvbHVtbiBuYW1lcyBvZiB0aGUgQ29udGFjdCB0YWJsZQpjb2xuYW1lcyhDb250YWN0KQpgYGAKCgojIyMgQ3JlYXRlIGEgY29weSBvZiB0aGUgb2xkIGZpbGUgdG8gd29yayBvbi4KCmBgYHtyIGNvcHlsZWdhY3ksIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kb2xkLmNvcHk8LW9sZC5kYXRhJENvbnRhY3RfbyAjIFNlbGVjdGluZyBvbmx5IG9uZSB0YWJsZSBhcyBzYW1wbGUKb2xkLmNvcHkKYGBgCgoKIyMjIFJlbmFtZSB0aGUgY29sdW1ucyBhcyBwZXIgY29sdW1uIG5hbWUgb2YgdGhlIHRlbXBsYXRlLiAKCgpUbyBkbyB0aGlzIGluIGEgc2FmZSB3YXkgdGhlcmUgYXJlIHR3byBvcHRpb25zCgogLSBDcmVhdGUgYW5vdGhlciBjb2x1bW4gaW4gRmllbGQgRGVmaW5pdGlvbiBleGNlbCBzaGVldCB0aGF0IGNvbnRhaW5zIHRoZSBjb3JyZXNwb25kaW5nIGNvbHVtbiBuYW1lcyBpbiB0aGUgbGVnYWN5IGZpbGUuCiAtIENyZWF0ZSBhIHNlcGFyYXRlIHNoZWV0IHdoZXJlIHRoaXMgbWFwcGluZyBpcyBkZWZpbmVkCiAKTWFudWFsIHR5cGluZyBpcyBlcnJvciBwcm9uZSBhbmQgaGVuY2Ugc2hvdWxkIGJlIGF2b2lkZWQgYXQgbGVhc3QgaW4gdGhpcyBjb2RlLgpGb3IgdGhlIHRpbWUgYmVpbmcsIHdlIGhhdmUgY3JlYXRlZCBhIHNlcGFyYXRlIGZpbGUuIEFsdGhvdWdoIHRoZSBvdGhlciB3YXkgaXMgZWFzaWVyIHRvIG1haW50YWluIGFuZCByZWNvbW1lbmRlZC4KCmBgYHtyIGNvbHVtbnJlbmFtZSwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptYXBwZWQ8LXJlYWQuY3N2KCIuL2NvbnRhY3RzL2NvbnRhY3RfbWFwLmNzdiIsIHNlcD0iOyIpCng9TlVMTApmb3IoaSBpbiAxOm5yb3cobWFwcGVkKSl7CiAgeFtpXSA9IG1hcHBlZFttYXBwZWQkb2xka2V5PT1jb2xuYW1lcyhvbGQuY29weVtpXSksXSRIZWFkZXIKfQpjb2xuYW1lcyhvbGQuY29weSk8LXggIyBDaGFuZ2luZyBjb2x1bW4gbmFtZXMKYGBgCgojIyMgQ2hlY2sgZm9yIGVycm9ycwoKRXNzZW50aWFsIHJvd3MKCmBgYHtyIGxpc3RtYW5kYXRvcnksIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc2FwdGVtcGxhdGVbc2FwdGVtcGxhdGUkYFNoZWV0IE5hbWVgPT0iQ29udGFjdCIsXSB8PiAKICBmaWx0ZXIoTWFuZGF0b3J5PT0iWWVzIikgfD4gCiAgcHVsbChIZWFkZXIpIC0+IGVzc2VudGlhbC5yb3dzICMgTGlzdCBvZiBtYW5kYXRvcnkgY29sdW1ucwpgYGAKCgpDaGVjayBpZiBzb21lIHJvd3MgaGF2ZSBtaXNzaW5nIGl0ZW1zIGZvciBtYW5kYXRvcnkgY29sdW1ucwoKYGBge3IgY2hlY2tlc3NlbnRpYWxyb3dzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmVzc2VuLnJvd3MudGFibGU9cmVhZC50YWJsZSgiIiwgY29sLm5hbWVzID0gYygiSXRlbSIsIk1pc3NpbmciKSkKCmZvcihpIGluIHNlcV9hbG9uZyhlc3NlbnRpYWwucm93cykpewogIGVzc2VuLnJvd3MudGFibGVbaSwyXTwtc3VtKGlzLm5hKG9sZC5jb3B5Wyxlc3NlbnRpYWwucm93c1tpXV0pKQogIGVzc2VuLnJvd3MudGFibGVbaSwxXTwtZXNzZW50aWFsLnJvd3NbaV0KfSAjIENyZWF0ZXMgdGhlIHRhYmxlIGJlbG93Cgplc3Nlbi5yb3dzLnRhYmxlCmBgYAoKCiBSZW1vdmUgdGhlIHJvd3Mgd2l0aCBtaXNzaW5nIG1hbmRhdG9yeSB2YWx1ZXMKCmBgYHtyIHJlbW92ZWVzc2Vucm93cywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKZm9yKGkgaW4gc2VxX2Fsb25nKGVzc2VudGlhbC5yb3dzKSl7CiAgb2xkLmNvcHk8LW9sZC5jb3B5WyFpcy5uYShvbGQuY29weVssZXNzZW50aWFsLnJvd3NbaV1dKSxdCn0gIyBSZW1vdmUgdGhlIHJvd3Mgd2l0aCBtaXNzaW5nIG1hbmRhdG9yeSB2YWx1ZXMKCmBgYAoKCkNoZWNrIGlmIGNvZGUgbGlzdGVkIGNvbHVtbiBkYXRhIGFyZSBmcm9tIHRoZSBjb2RlbGlzdAoKYGBge3IgY29kZWxpc3Rjb2xjaGVjaywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjb2RlbGlzdGNvbHM8LXNhcHRlbXBsYXRlW3NhcHRlbXBsYXRlJGBTaGVldCBOYW1lYD09IkNvbnRhY3QiLF0gfD4gCiAgZmlsdGVyKCFpcy5uYShgQ29kZUxpc3QgRmlsZSBQYXRoYCkpIHw+IHB1bGwoSGVhZGVyKSAjIExpc3Qgb2YgY29sdW1ucyB0aGF0IGhhdmUgYSBjb2RlbGlzdAoKY29kZWxpc3RlZC5yb3dzLnRhYmxlPXJlYWQudGFibGUoIiIsIGNvbC5uYW1lcyA9IGMoIkl0ZW0iLCJNaXNzaW5nIiwgIk5vdF9mcm9tX2NvZGUiKSkKZm9yKGkgaW4gc2VxX2Fsb25nKGNvZGVsaXN0Y29scykpewogIGNvZGVsaXN0ZWQucm93cy50YWJsZVtpLDNdPC1zdW0oIXB1bGwob2xkLmNvcHlbLGNvZGVsaXN0Y29sc1tpXV0sMSkgJWluJSBjKHB1bGwoY29kZWxpc3RfZmlsZXNbY29kZWxpc3Rjb2xzW2ldXVtbMV1dLERlc2NyaXB0aW9uKSxOQSkpICMgQWRkZWQgTkEgZWxzZSBlbXB0eSBjb2x1bW5zIGFsc28gZ2V0IGNvdW50ZWQKICBjb2RlbGlzdGVkLnJvd3MudGFibGVbaSwyXTwtc3VtKGlzLm5hKG9sZC5jb3B5Wyxjb2RlbGlzdGNvbHNbaV1dKSkKICBjb2RlbGlzdGVkLnJvd3MudGFibGVbaSwxXTwtY29kZWxpc3Rjb2xzW2ldCn0gIyBDcmVhdGVzIHRoZSB0YWJsZSBiZWxvdwpjb2RlbGlzdGVkLnJvd3MudGFibGUKYGBgCgoKSWYgdmFsdWVzIGRvIG5vdCBtYXRjaCwgd2UgZW1wdHkgdGhlIHZhbHVlCgpgYGB7ciByZW1vdmVtaXNtYXRjaGNvZGUsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmZvcihpIGluIHNlcV9hbG9uZyhjb2RlbGlzdGNvbHMpKXsKICBvbGQuY29weVshcHVsbChvbGQuY29weVssY29kZWxpc3Rjb2xzW2ldXSwxKSAlaW4lIGMocHVsbChjb2RlbGlzdF9maWxlc1tjb2RlbGlzdGNvbHNbaV1dW1sxXV0sRGVzY3JpcHRpb24pLE5BKSxjb2RlbGlzdGNvbHNbaV1dPC1OQQp9ICMgUmVtb3ZlcyB0aGUgdmFsdWUgaW4gY2FzZSBvZiBtaXNtYXRjaAoKYGBgCgojIyMgVHJhbnNsYXRlIGludG8gU0FQIGxhbmd1YWdlIHVzaW5nIGNvZGUgbGlzdAoKCmBgYHtyIGpvaW5jb2RlbGlzdGNvZGVzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmZvcihpIGluIHNlcV9hbG9uZyhjb2RlbGlzdGNvbHMpKXsKICBvbGQuY29weVssY29kZWxpc3Rjb2xzW2ldXTwtCiAgcHVsbChjb2RlbGlzdF9maWxlc1tjb2RlbGlzdGNvbHNbaV1dW1sxXV0sMilbbWF0Y2gocHVsbChvbGQuY29weSxjb2RlbGlzdGNvbHNbaV0pLCBwdWxsKGNvZGVsaXN0X2ZpbGVzW2NvZGVsaXN0Y29sc1tpXV1bWzFdXSxEZXNjcmlwdGlvbikpXQp9ICMgTWF0Y2hlcyBlYWNoIGNvbHVtbiB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIGNvZGUgbGlzdCBhbmQgcmV0dXJucyB0aGUgdmFsdWUKYGBgCgoKIyMjRml4IGNvbHVtbiB0eXBlcwoKCmBgYHtyIGRhdGF0eXBlY2hlY2ssIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmR0eXBlPC1zYXB0ZW1wbGF0ZVtzYXB0ZW1wbGF0ZSRgU2hlZXQgTmFtZWA9PSJDb250YWN0IixdJGBEYXRhIFR5cGVgICMgTGlzdCBvZiBkYXRhIHR5cGVzLiBOb24gRXhoYXVzdGl2ZSBBVE0KCmZvcihpIGluIDE6bmNvbChvbGQuY29weSkpewogIGlmKGR0eXBlW2ldID09ICJTdHJpbmciKXsKICAgIG9sZC5jb3B5WyxpXSA8LSBhcy5jaGFyYWN0ZXIocHVsbChvbGQuY29weSxpKSkKICB9CiAgaWYoZHR5cGVbaV0gPT0gIkJvb2xlYW4iKXsKICAgIG9sZC5jb3B5WyxpXSA8LSBhcy5sb2dpY2FsKHB1bGwob2xkLmNvcHksaSkpCiAgICB9CiAgaWYoZHR5cGVbaV0gPT0gIkRhdGVUaW1lIil7CiAgICBvbGQuY29weVssaV0gPC0gbHVicmlkYXRlOjp5bWRfaG1zKHB1bGwob2xkLmNvcHksaSkpCiAgICB9CiAgaWYoZHR5cGVbaV0gPT0gIlRpbWUiKXsKICAgICAgb2xkLmNvcHlbLGldIDwtIGx1YnJpZGF0ZTo6aG1zKHB1bGwob2xkLmNvcHksaSkpCiAgICAKICB9ICMgVGhpcyBsaXN0IHdpbGwgaW5jcmVhc2UgYW5kIGFsc28gY2hhbmdlIGJhc2VkIG9uIGlucHV0IGRhdGUgYW5kIHRpbWUgZm9ybWF0cwogIAp9CgpgYGAKCgojIyMgU3RyaW5nIGxlbmd0aAoKYGBge3IgY2hlY2t0cmltc3RyaW5nbGVuZ3RoLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1heC5sZW5ndGg8LXNhcHRlbXBsYXRlW3NhcHRlbXBsYXRlJGBTaGVldCBOYW1lYD09IkNvbnRhY3QiLF0kYE1heCBMZW5ndGhgICMgTGlzdCBvZiBtYXggbGVuZ3RocyBtZW50aW9uZWQKCmNvbGNsYXNzZXM8LWxhcHBseShvbGQuY29weSxjbGFzcykgIyBnZXR0aW5nIGNvbHVtbiBjbGFzc2VzCgpmb3IoaSBpbiAxOiBuY29sKG9sZC5jb3B5KSl7CiAgaWYoY29sY2xhc3Nlc1tbaV1dPT0iY2hhcmFjdGVyIil7CiAgICBvbGQuY29weVssaV08LSBpZmVsc2UobmNoYXIocHVsbChvbGQuY29weSxpKSk+bWF4Lmxlbmd0aFtpXSwgc3Vic3RyaW5nKHB1bGwob2xkLmNvcHksaSksMSxtYXgubGVuZ3RoW2ldKSwgcHVsbChvbGQuY29weSxpKSkKICB9ICMgSWYgc3RyaW5nIGxlbmd0aCBpcyBtb3JlIHRoYW4gbWVudGlvbmVkLCB0cmltIGl0IHRvIHRoZSBtZW50aW9uZWQKICAKfQoKYGBgCgojIyMgU2F2ZSBmaWxlCgpgYGB7ciBzYXZlZmlsZSwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp3cml0ZS5jc3Yob2xkLmNvcHksICJDb250YWN0LmNzdiIscm93Lm5hbWVzPUZBTFNFKSAjIFNhdmluZyBDU1YgZmlsZQpgYGAKCnZpZXcgdGhlIGV4cG9ydGVkIGZpbGUKCmBgYHtyfQpjb250YWN0cy5zYXA8LXJlYWQuY3N2KCJDb250YWN0LmNzdiIpCmRhdGF0YWJsZShjb250YWN0cy5zYXAsb3B0aW9ucyA9IGxpc3Qoc2Nyb2xsWCA9IFRSVUUpKQpgYGAKCioqVmlld2luZyBpcyBmb3Igc2FtcGxlIG9ubHkuIExhcmdlciBmaWxlcyBjYW5ub3QgYmUgdmlld2VkIGluIGh0bWwsIHJlcXVpcmVzIHNlcnZlciBzaWRlIHByb2Nlc3NpbmcuKioKCgojIyBRdWVzdGlvbnMgYW5kIENvbW1lbnRzCgoKLSBXaGF0IHNoYWxsIHdlIGRvIHdpdGggdmFsdWVzIHRoYXQgZG8gbm90IG1hdGNoIGl0ZW1zIGluIGNvZGUgbGlzdD8gQ3VycmVudGx5IHdlIGFyZSByZW1vdmluZyB0aG9zZSBlbnRyaWVzLiBCdXQgdGhlcmUgbWF5IGJlIGNhc2VzIHdoZXJlIHJlbW92aW5nIG1heSBub3QgbWFrZSBzZW5zZS4gZm9yIGUuZy4gKE0gaW5zdGVhZCBvZiBNYWxlKQotIFdoYXQgc2hhbGwgd2UgZG8gd2l0aCBtaXNzaW5nIGl0ZW1zIGluIG5vcm1hbCBjb2x1bW5zIChub24tY29kZWQpPwotIFdoYXQgc2hhbGwgd2UgZG8gd2l0aCBtaXNzaW5nIHZhbHVlcyBpbiBtYW5kYXRvcnkgY29sdW1ucz8gQ3VycmVudGx5IGRlbGV0aW5nIHRoZSBjb21wbGV0ZSByb3cKLSBXaGF0IGlmIHRoZSBjb3VudHJ5IG1lbnRpb25lZCBpcyBub3QgZnJvbSB0aGUgc3RhdGUgbWVudGlvbmVkPyBTaGFsbCB3ZSBjaGVjayB0aGF0IHRvbz8KLSBXaGF0IGlzIHRoZSBpbnB1dCBmb3JtYXQgb2YgbG9naWNhbCB2YXJpYWJsZXMgKFllcy9ubywgVC9GLCBUUlVFL0ZBTFNFIGV0Yyk/Ci0gV2hhdCBpcyB0aGUgb3V0cHV0IGZvcm1hdCAoU0FQKSBmb3IgbG9naWNhbCB2YXJpYWJsZXM/Ci0gV2hhdCBpcyB0aGUgaW5wdXQgZm9ybWF0IG9mIGRhdGUsIGRhdGV0aW1lIGFuZCB0aW1lIHZhcmlhYmxlcz8KLSBXaGF0IGlzIHRoZSBvdXRwdXQgZm9ybWF0IG9mIGRhdGUsIGRhdGV0aW1lIGFuZCB0aW1lIHZhcmlhYmxlcz8KLSBXaGF0IHRvIGRvIGlmIGEgc3RyaW5nIGlzIGxhcmdlciB0aGFuIHdoYXQgaXMgYWxsb3dlZD8KLSBXaGVyZSBjb2RlIGxpc3RzIGFyZSBtZW50aW9uZWQsIHdoYXQgaXMgdGhlIGlucHV0IHZhcmlhYmxlPyBJdCBpcyBhc3N1bWVkIHRoYXQgdGhlIGlucHV0IGZpbGVzIHdpbGwgaGF2ZSBkZXNjcmlwdGlvbnMgYW5kIG5vdCB0aGUgY29kZQotIFdoZXJlIGNvZGUgbGlzdHMgYXJlIG1lbnRpb25lZCwgd2hhdCBzaG91bGQgYmUgdGhlIG91dHB1dCAoQ29kZSBvciBkZXNjcmlwdGlvbik/IEN1cnJlbnRseSBXZSB0YWtlIHRoZSBkZXNjcmlwdGlvbiBhcyBpbnB1dCBhbmQgY29udmVydCBpdCB0byBjb2RlLgoKClRoaXMgZG9jdW1lbnQgY29udGFpbnMgc2V2ZXJhbCBleHBsYW5hdGlvbnMuIEFuZCB0aGUgd29ya2Zsb3cgaGFzIGJlZW4gZGl2aWRlZCBpbnRvIHN0ZXBzLiBUaGVzZSBzdGVwcyBtYXkgaW5jcmVhc2UgYW5kIGRlY3JlYXNlIChieSBjb21iaW5pbmcgc2V2ZXJhbCBzdGVwcyksIGRlcGVuZGluZyBvbiB0aGUgZGF0YSBxdWFsaXR5IGFuZCBzdHJ1Y3R1cmUuIEZvciBlLmcuIGlmIHRoZSBpbnB1dCBkYXRldGltZSBmb3JtYXQgaXMgZGlmZmVyZW50IGluIGRpZmZlcmVudCB0YWJsZXMsIGVhY2ggdGFibGUgbWF5IHJlcXVpcmUgbWFudWFsIHRyYW5zZm9ybWF0aW9uLCBpbmNyZWFzaW5nIHRoZSBudW1iZXIgb2Ygc3RlcHMgYW5kIGNvbXBsZXhpdHkuIElmIHRoZSBkYXRhIHF1YWxpdHkgaXMgZ29vZCwgaXQgbWF5IGV2ZW4gYmUgcG9zc2libGUgdG8gdHJhbnNmb3JtIGFsbCB0aGUgdGFibGVzIGluIGEgc2VnbWVudCAoY29udGFjdHMgb3IgYWNjb3VudHMgZXRjLikgbWF5IGJlIHRyYW5zZm9ybWVkIGluIGEgc2luZ2xlIHJ1bi4gSXQgd2lsbCBldmVudHVhbGx5IGJlIGNsZWFyIG9ubHkgYWZ0ZXIgb2J0YWluaW5nIHRoZSBpbnB1dCBkYXRhLiBUaGUgY29kZSBjYW4gZnVydGhlciBiZSBhZGp1c3RlZCBmb3IgZmFzdGVyIHByb2Nlc3NpbmcuCgoKCgoKCgoKCgo=