Text Corpora Description
The given text corpora contains 3 files across four languages (Russian, Finnish, German and English). This project will focus on the English language datasets. The names of the data files are as follows:
- en_US.blogs.txt
- en_US.twitter.txt
- en_US.news.txt
Download and unzip the reference Text Corpora
if(!file.exists("Coursera-SwiftKey.zip")){
#Download the specified training data
download.file("https://d396qusza40orc.cloudfront.net/dsscapstone/dataset/Coursera-SwiftKey.zip", "Coursera-SwiftKey.zip")
}else{
print("Reference training data is already downloaded!")
}
[1] "Reference training data is already downloaded!"
## check if the file is unzipped. Unpack only if it doesn't exist
if(!file.exists("./final/en_US")){
print("Unzipping Coursera-SwiftKey.zip!")
unzip("./Coursera-SwiftKey.zip")
}
Exploratory Analysis
- Summary statistics of original datasets
- Since the data size is fairly big, we will conduct analysis with small sample (say 1%).
- In the english language, some of the most common word in sentences are the likes of articles and prepositions. However, if we want to extract important messages from the texts, we should try to eliminate these less meaningful words.
- Profanity can be in our data and we would like to get rid of it. However, I believe sometimes people express their emotions through their use of language so it can also be useful to analyze it seperately.
- Create TermDocumentMatrix and subsequently perform 1-gram, 2-gram, and 3-gram analysis
Summary Statistics of the complete dataset
Here we are collecting suammry statistics on all English files without loading them into memory.
dataFiles <- dir("./final/en_US", full.names=TRUE)
datasets.stats <- ldply(dataFiles, function(ds) {
files.name <- basename(ds)
files.size <- as.integer(format(file.info(ds)$size/1024^2))
files.linesCount <- as.integer(sub(" .*$", "", system(paste("wc -l", ds), intern=TRUE)))
files.wordsCount <- as.integer(sub(" .*$", "", system(paste("wc -w", ds), intern=TRUE)))
files.maxLineLen <- as.integer(sub(" .*$", "", system(paste("wc -L", ds), intern=TRUE)))
data.frame(files.name, files.size, files.linesCount, files.wordsCount, files.maxLineLen)
})
colnames(datasets.stats) <- c("File", "File Size (MB)", "Lines", "Words", "Max length")
datasets.stats
File File Size (MB) Lines Words Max length
1 en_US.blogs.txt 200 899288 37272578 40832
2 en_US.news.txt 196 1010242 34309642 11384
3 en_US.twitter.txt 159 2360148 30341028 140
Sampling small portion of training data for Exploratory Data Analysis
As all the text files are of relatively large size (~200 MB), hence we are trying to perform EDA on small portion (1%) of the training dataset
seed <- 1234
blogsSampleId <- as.logical(rbinom(datasets.stats$Lines[1], 1, prob=0.01))
newsSampleId <- as.logical(rbinom(datasets.stats$Lines[2], 1, prob=0.01))
twittersSampleId <- as.logical(rbinom(datasets.stats$Lines[3], 1, prob=0.01))
blogsSample <- readLines(dataFiles[1], encoding="UTF-8", skipNul=TRUE)[blogsSampleId]
newsSample <- readLines(dataFiles[2], encoding="UTF-8", skipNul=TRUE)[newsSampleId]
incomplete final line found on './final/en_US/en_US.news.txt'
twittersSample <- readLines(dataFiles[3], encoding="UTF-8", skipNul=TRUE)[twittersSampleId]
Summary statistics of samples data before data clearning
Note that lines count and words count are almost 1/10th of original dataset except for news words count which is somehow disproprotional which needs to be investigated later (mostly data cleaning would have removed huge presence of redundant terms)
samples.list <- list(blog = blogsSample, news = newsSample, twitter = twittersSample)
samples.df <- data.frame(text.source = c("blog", "news", "twitter"), lines.count = NA, words.count = NA)
samples.df$lines.count <- sapply(samples.list, length)
samples.df$words.count <- stri_count_words(samples.list)
print("Summary statistics of samples data before data clearning")
[1] "Summary statistics of samples data before data clearning"
samples.df
text.source lines.count words.count
1 blog 8877 366961
2 news 10140 35974
3 twitter 23612 301322
Data cleaning to prepare text corpus
NOTE that even 1% sample has 10k documents and 30k terms on average across blogs/news/twitters datasets (including some large no of irrelevant terms). It is costly and uneccessary to identify all unique terms in raw sample, rather sample data must be cleaned before constructing BOW (bag of words) representation such as TermDocumentMatrix and subsequent processing.
Rationale behind different kind of data cleaning: -
- Punctuations: Punctuation characters such as commas, parentheses and the like have little or no impact on word order and n-gram composition.
- Stop Words: Stopwords are words so common that their information value is very low.
- ToLower: Words themselves - not their case - are important for prediction. Note that the resulting predictive model will predict lowercase words only. For English text this is less of an issue than for a language like German that uses capitalized nouns.
- http/ftp/mail uris & Twitter hashtags and handles: These social media specific terms are not helpful for word prediction.
- Profanity words: Words dictionary has been downloaded from https://www.freewebheaders.com/full-list-of-bad-words-banned-by-google/ and it has been used to remove their occurances.
cleanUri <- function(text){
## remove html/ftp url
text <- gsub("?(f|ht)tp(s?)://(.*)[.][a-z]+", "", text)
## remove e-mail adresses
text <- gsub("[[:alnum:].-]+@[[:alnum:].-]+", "", text)
return(text)
}
cleanTwitterTagsHandles <- function(text){
## remove twitter handles
text <- gsub("@\\S+", "", text)
## remove twitter hashtags
text <- gsub("#\\S+", "", text)
return(text)
}
samplesList <- list(blogs = blogsSample, news = newsSample, twitters = twittersSample)
samplesCorpus <- VCorpus(VectorSource(samplesList))
rm(samplesList)
# Remove URIs & Twitter hashtag and handles from all types of documents/messages
samplesCorpus <- tm_map(samplesCorpus, content_transformer(cleanUri))
samplesCorpus <- tm_map(samplesCorpus, content_transformer(cleanTwitterTagsHandles))
samplesCorpus <- tm_map(samplesCorpus, removeWords, stopwords("english"))
samplesCorpus <- tm_map(samplesCorpus, content_transformer(function(x) iconv(x, from="UTF-8", to="ASCII", sub="")))
samplesCorpus <- tm_map(samplesCorpus, content_transformer(tolower))
samplesCorpus <- tm_map(samplesCorpus, removePunctuation)
samplesCorpus <- tm_map(samplesCorpus, removeNumbers)
samplesCorpus <- tm_map(samplesCorpus, stripWhitespace)
# Remove profanity words as per https://www.freewebheaders.com/full-list-of-bad-words-banned-by-google/
samplesCorpus <- tm_map(samplesCorpus, removeWords, readLines("./badWords_2018_03_26.txt"))
incomplete final line found on './badWords_2018_03_26.txt'
# Apply stemming to the resulting corpus
samplesCorpus <- tm_map(samplesCorpus, stemDocument)
Text analysis for deriving n-gram
## Tokenizers
UnigramTokenizer <- function(x) NGramTokenizer(x, Weka_control(min = 1, max = 1))
BigramTokenizer <- function(x) NGramTokenizer(x, Weka_control(min = 2, max = 2))
TrigramTokenizer <- function(x) NGramTokenizer(x, Weka_control(min = 3, max = 3))
## TermDocumentMatrix for 1/2/3-grams
tdmBlogs_1gram = TermDocumentMatrix(samplesCorpus["blogs"], control = list(tokenize = UnigramTokenizer, wordLengths = c(3,Inf)))
tdmNews_1gram = TermDocumentMatrix(samplesCorpus["news"], control = list(tokenize = UnigramTokenizer, wordLengths = c(3,Inf)))
tdmTwitters_1gram = TermDocumentMatrix(samplesCorpus["twitters"], control = list(tokenize = UnigramTokenizer, wordLengths = c(3,Inf)))
tdmBlogs_2gram = TermDocumentMatrix(samplesCorpus["blogs"], control = list(tokenize = BigramTokenizer, wordLengths = c(3,Inf)))
tdmNews_2gram = TermDocumentMatrix(samplesCorpus["news"], control = list(tokenize = BigramTokenizer, wordLengths = c(3,Inf)))
tdmTwitters_2gram = TermDocumentMatrix(samplesCorpus["twitters"], control = list(tokenize = BigramTokenizer, wordLengths = c(3,Inf)))
tdmBlogs_3gram = TermDocumentMatrix(samplesCorpus["blogs"], control = list(tokenize = TrigramTokenizer, wordLengths = c(3,Inf)))
tdmNews_3gram = TermDocumentMatrix(samplesCorpus["news"], control = list(tokenize = TrigramTokenizer, wordLengths = c(3,Inf)))
tdmTwitters_3gram = TermDocumentMatrix(samplesCorpus["twitters"], control = list(tokenize = TrigramTokenizer, wordLengths = c(3,Inf)))
Plot most frequent Unigrams
n <- 20L # show 20 most frequently occuring terms/words
# isolate top n words by decreasing frequency
blogs.top <- wfmBlogs_1gram[1:n, ]
news.top <- wfmNews_1gram[1:n, ]
twitters.top <- wfmTwitters_1gram[1:n, ]
blogs.top$word <- reorder(blogs.top$word, blogs.top$frequency)
news.top$word <- reorder(news.top$word, news.top$frequency)
twitters.top$word <- reorder(twitters.top$word, twitters.top$frequency)
# plots
g.blogs.top <- ggplot(blogs.top, aes(x = word, y = frequency))
g.blogs.top <- g.blogs.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Uni-grams in Blogs")
g.news.top <- ggplot(news.top, aes(x = word, y = frequency))
g.news.top <- g.news.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Uni-grams in News")
g.twitters.top <- ggplot(twitters.top, aes(x = word, y = frequency))
g.twitters.top <- g.twitters.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Uni-grams in Twitters")
grid.arrange(g.blogs.top, g.news.top, g.twitters.top, ncol = 3)

Plot most frequent Bigrams
# isolate top n words by decreasing frequency
blogs.top <- wfmBlogs_2gram[1:n, ]
news.top <- wfmNews_2gram[1:n, ]
twitters.top <- wfmTwitters_2gram[1:n, ]
blogs.top$word <- reorder(blogs.top$word, blogs.top$frequency)
news.top$word <- reorder(news.top$word, news.top$frequency)
twitters.top$word <- reorder(twitters.top$word, twitters.top$frequency)
# plots
g.blogs.top <- ggplot(blogs.top, aes(x = word, y = frequency))
g.blogs.top <- g.blogs.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Bigrams in Blogs")
g.news.top <- ggplot(news.top, aes(x = word, y = frequency))
g.news.top <- g.news.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Bigrams in News")
g.twitters.top <- ggplot(twitters.top, aes(x = word, y = frequency))
g.twitters.top <- g.twitters.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Bigrams in Twitters")
grid.arrange(g.blogs.top, g.news.top, g.twitters.top, ncol = 3)

Plot most frequent Trigrams
# isolate top n words by decreasing frequency
blogs.top <- wfmBlogs_3gram[1:n, ]
news.top <- wfmNews_3gram[1:n, ]
twitters.top <- wfmTwitters_3gram[1:n, ]
blogs.top$word <- reorder(blogs.top$word, blogs.top$frequency)
news.top$word <- reorder(news.top$word, news.top$frequency)
twitters.top$word <- reorder(twitters.top$word, twitters.top$frequency)
# plots
g.blogs.top <- ggplot(blogs.top, aes(x = word, y = frequency))
g.blogs.top <- g.blogs.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Trigrams in Blogs")
g.news.top <- ggplot(news.top, aes(x = word, y = frequency))
g.news.top <- g.news.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Trigrams in News")
g.twitters.top <- ggplot(twitters.top, aes(x = word, y = frequency))
g.twitters.top <- g.twitters.top + geom_bar(stat = "identity") + coord_flip() +
labs(title = "Trigrams in Twitters")
grid.arrange(g.blogs.top, g.news.top, g.twitters.top, ncol = 3)

LS0tDQp0aXRsZTogIk1pbGVzdG9uZSBSZXBvcnQgZm9yIERhdGEgU2NpZW5jZSBDYXBzdG9uZSBQcm9qZWN0Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz1UUlVFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlPVRSVUUpDQpgYGANCiANCiMjU1VNTUFSWSANClRoZSB1bHRpbWF0ZSBnb2FsIG9mIHRoZSBjYXBzdG9uZSBwcm9qZWN0IGlzIHRvIGJ1aWxkIGEgcHJlZGljdGl2ZSB0ZXh0IG1vZGVsLiBUaGUgZGF0YSB1c2VkIGZvciBmb3IgdGhpcyBwcm9qZWN0IGluY2x1ZGUgYmxvZ3MsIG5ld3MgYW5kIHR3aXR0ZXJzLiBUaGUgZmluYWwgcHJvZHVjdCB3aWxsIGJlIGEgU2hpbnkgYXBwbGljYXRpb24gd2hpY2ggY2FuIGJlIHVzZWQgdG8gcHJlZGljdCB0aGUgbmV4dCB3b3JkKHMpIHdoZW4gdGhlIHVzZXIgaW5wdXRzIGEgcGhyYXNlLiBUaGlzIG1pbGVzdG9uZSByZXBvcnQgZm9yIHRoZSBjYXBzdG9uZSBwcm9qZWN0IGlzIGludGVuZGVkIHRvIHByZXNlbnQgdGhlIGRhdGEgcHJlcHJvY2Vzc2luZyBhbmQgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBhbmQgc2tldGNoIG91dCB0aGUgcGxhbnMgZm9yIHRoZSBEYXRhIFNjaWVuY2UgQ2Fwc3RvbmUgcHJvamVjdC4gIA0KDQpUaGUgcmVmZXJlbmNlIHRleHQgY29ycG9yYSBpcyBhdmFpbGFibGUgYXQgaHR0cHM6Ly9kMzk2cXVzemE0MG9yYy5jbG91ZGZyb250Lm5ldC9kc3NjYXBzdG9uZS9kYXRhc2V0L0NvdXJzZXJhLVN3aWZ0S2V5LnppcC4gQ29udGVudCBhcmNoaXZlZCBmcm9tIGhlbGlvaG9zdC5vcmcgb24gU2VwdGVtYmVyIDMwLCAyMDE2IGFuZCByZXRyaWV2ZWQgdmlhIFdheWJhY2sgTWFjaGluZSBvbiBBcHJpbCAyNCwgMjA3LiBodHRwczovL3dlYi1iZXRhLmFyY2hpdmUub3JnL3dlYi8yMDE2MDkzMDA4MzY1NS9odHRwOi8vd3d3LmNvcnBvcmEuaGVsaW9ob3N0Lm9yZy9hYm91dGNvcnB1cy5odG1sLiBUaGUgY29ycG9yYSBhcmUgY29sbGVjdGVkIGZyb20gcHVibGljbHkgYXZhaWxhYmxlIHNvdXJjZXMgYnkgYSB3ZWIgY3Jhd2xlci4NCiAgIA0KIyNUZXh0IENvcnBvcmEgRGVzY3JpcHRpb24NClRoZSBnaXZlbiB0ZXh0IGNvcnBvcmEgY29udGFpbnMgMyBmaWxlcyBhY3Jvc3MgZm91ciBsYW5ndWFnZXMgKFJ1c3NpYW4sIEZpbm5pc2gsIEdlcm1hbiBhbmQgRW5nbGlzaCkuIFRoaXMgcHJvamVjdCB3aWxsIGZvY3VzIG9uIHRoZSBFbmdsaXNoIGxhbmd1YWdlIGRhdGFzZXRzLiBUaGUgbmFtZXMgb2YgdGhlIGRhdGEgZmlsZXMgYXJlIGFzIGZvbGxvd3M6IA0KICANCjEuIGVuX1VTLmJsb2dzLnR4dCAgDQoyLiBlbl9VUy50d2l0dGVyLnR4dCAgDQozLiBlbl9VUy5uZXdzLnR4dCAgDQogIA0KIyNMb2FkIFBhY2thZ2VzDQpgYGB7cn0NCmxpYnJhcnkocGx5cikNCmxpYnJhcnkoc3RyaW5naSkNCmxpYnJhcnkoTkxQKQ0KbGlicmFyeSh0bSkNCmxpYnJhcnkod29yZGNsb3VkKQ0KbGlicmFyeShSV2VrYSkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZ3JpZCkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KYGBgDQogIA0KIyNEb3dubG9hZCBhbmQgdW56aXAgdGhlIHJlZmVyZW5jZSBUZXh0IENvcnBvcmEgDQpgYGB7cn0NCmlmKCFmaWxlLmV4aXN0cygiQ291cnNlcmEtU3dpZnRLZXkuemlwIikpew0KICAgICNEb3dubG9hZCB0aGUgc3BlY2lmaWVkIHRyYWluaW5nIGRhdGENCiAgICBkb3dubG9hZC5maWxlKCJodHRwczovL2QzOTZxdXN6YTQwb3JjLmNsb3VkZnJvbnQubmV0L2Rzc2NhcHN0b25lL2RhdGFzZXQvQ291cnNlcmEtU3dpZnRLZXkuemlwIiwgIkNvdXJzZXJhLVN3aWZ0S2V5LnppcCIpDQp9ZWxzZXsNCiAgICBwcmludCgiUmVmZXJlbmNlIHRyYWluaW5nIGRhdGEgaXMgYWxyZWFkeSBkb3dubG9hZGVkISIpDQp9DQoNCiMjIGNoZWNrIGlmIHRoZSBmaWxlIGlzIHVuemlwcGVkLiBVbnBhY2sgb25seSBpZiBpdCBkb2Vzbid0IGV4aXN0IA0KaWYoIWZpbGUuZXhpc3RzKCIuL2ZpbmFsL2VuX1VTIikpew0KICBwcmludCgiVW56aXBwaW5nIENvdXJzZXJhLVN3aWZ0S2V5LnppcCEiKQ0KICB1bnppcCgiLi9Db3Vyc2VyYS1Td2lmdEtleS56aXAiKQ0KfQ0KYGBgDQoNCiMjRXhwbG9yYXRvcnkgQW5hbHlzaXMgIA0KMS4gU3VtbWFyeSBzdGF0aXN0aWNzIG9mIG9yaWdpbmFsIGRhdGFzZXRzICANCjIuIFNpbmNlIHRoZSBkYXRhIHNpemUgaXMgZmFpcmx5IGJpZywgd2Ugd2lsbCBjb25kdWN0IGFuYWx5c2lzIHdpdGggc21hbGwgc2FtcGxlIChzYXkgMSUpLiAgDQozLiBJbiB0aGUgZW5nbGlzaCBsYW5ndWFnZSwgc29tZSBvZiB0aGUgbW9zdCBjb21tb24gd29yZCBpbiBzZW50ZW5jZXMgYXJlIHRoZSBsaWtlcyBvZiBhcnRpY2xlcyBhbmQgcHJlcG9zaXRpb25zLiBIb3dldmVyLCBpZiB3ZSB3YW50IHRvIGV4dHJhY3QgaW1wb3J0YW50IG1lc3NhZ2VzIGZyb20gdGhlIHRleHRzLCB3ZSBzaG91bGQgdHJ5IHRvIGVsaW1pbmF0ZSB0aGVzZSBsZXNzIG1lYW5pbmdmdWwgd29yZHMuICANCjQuIFByb2Zhbml0eSBjYW4gYmUgaW4gb3VyIGRhdGEgYW5kIHdlIHdvdWxkIGxpa2UgdG8gZ2V0IHJpZCBvZiBpdC4gSG93ZXZlciwgSSBiZWxpZXZlIHNvbWV0aW1lcyBwZW9wbGUgZXhwcmVzcyB0aGVpciBlbW90aW9ucyB0aHJvdWdoIHRoZWlyIHVzZSBvZiBsYW5ndWFnZSBzbyBpdCBjYW4gYWxzbyBiZSB1c2VmdWwgdG8gYW5hbHl6ZSBpdCBzZXBlcmF0ZWx5LiAgDQo1LiBDcmVhdGUgVGVybURvY3VtZW50TWF0cml4IGFuZCBzdWJzZXF1ZW50bHkgcGVyZm9ybSAxLWdyYW0sIDItZ3JhbSwgYW5kIDMtZ3JhbSBhbmFseXNpcyAgDQoNCiMjI1N1bW1hcnkgU3RhdGlzdGljcyBvZiB0aGUgY29tcGxldGUgZGF0YXNldCAgDQpIZXJlIHdlIGFyZSBjb2xsZWN0aW5nIHN1YW1tcnkgc3RhdGlzdGljcyBvbiBhbGwgRW5nbGlzaCBmaWxlcyB3aXRob3V0IGxvYWRpbmcgdGhlbSBpbnRvIG1lbW9yeS4gIA0KYGBge3J9DQpkYXRhRmlsZXMgPC0gZGlyKCIuL2ZpbmFsL2VuX1VTIiwgZnVsbC5uYW1lcz1UUlVFKQ0KZGF0YXNldHMuc3RhdHMgPC0gbGRwbHkoZGF0YUZpbGVzLCBmdW5jdGlvbihkcykgew0KCWZpbGVzLm5hbWUgPC0gYmFzZW5hbWUoZHMpDQoJZmlsZXMuc2l6ZSA8LSBhcy5pbnRlZ2VyKGZvcm1hdChmaWxlLmluZm8oZHMpJHNpemUvMTAyNF4yKSkNCiAgZmlsZXMubGluZXNDb3VudCA8LSBhcy5pbnRlZ2VyKHN1YigiIC4qJCIsICIiLCBzeXN0ZW0ocGFzdGUoIndjIC1sIiwgZHMpLCBpbnRlcm49VFJVRSkpKQ0KICBmaWxlcy53b3Jkc0NvdW50IDwtIGFzLmludGVnZXIoc3ViKCIgLiokIiwgIiIsIHN5c3RlbShwYXN0ZSgid2MgLXciLCBkcyksIGludGVybj1UUlVFKSkpDQogIGZpbGVzLm1heExpbmVMZW4gPC0gYXMuaW50ZWdlcihzdWIoIiAuKiQiLCAiIiwgc3lzdGVtKHBhc3RlKCJ3YyAtTCIsIGRzKSwgaW50ZXJuPVRSVUUpKSkNCgkNCglkYXRhLmZyYW1lKGZpbGVzLm5hbWUsIGZpbGVzLnNpemUsIGZpbGVzLmxpbmVzQ291bnQsIGZpbGVzLndvcmRzQ291bnQsIGZpbGVzLm1heExpbmVMZW4pDQp9KQ0KY29sbmFtZXMoZGF0YXNldHMuc3RhdHMpIDwtIGMoIkZpbGUiLCAiRmlsZSBTaXplIChNQikiLCAiTGluZXMiLCAiV29yZHMiLCAiTWF4IGxlbmd0aCIpDQoNCmRhdGFzZXRzLnN0YXRzDQpgYGANCg0KIyMjU2FtcGxpbmcgc21hbGwgcG9ydGlvbiBvZiB0cmFpbmluZyBkYXRhIGZvciBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzICANCkFzIGFsbCB0aGUgdGV4dCBmaWxlcyBhcmUgb2YgcmVsYXRpdmVseSBsYXJnZSBzaXplICh+MjAwIE1CKSwgaGVuY2Ugd2UgYXJlIHRyeWluZyB0byBwZXJmb3JtIEVEQSBvbiBzbWFsbCBwb3J0aW9uICgxJSkgb2YgdGhlIHRyYWluaW5nIGRhdGFzZXQgIA0KYGBge3J9DQpzZWVkIDwtIDEyMzQNCmJsb2dzU2FtcGxlSWQgPC0gYXMubG9naWNhbChyYmlub20oZGF0YXNldHMuc3RhdHMkTGluZXNbMV0sIDEsIHByb2I9MC4wMSkpDQpuZXdzU2FtcGxlSWQgPC0gYXMubG9naWNhbChyYmlub20oZGF0YXNldHMuc3RhdHMkTGluZXNbMl0sIDEsIHByb2I9MC4wMSkpDQp0d2l0dGVyc1NhbXBsZUlkIDwtIGFzLmxvZ2ljYWwocmJpbm9tKGRhdGFzZXRzLnN0YXRzJExpbmVzWzNdLCAxLCBwcm9iPTAuMDEpKQ0KDQpibG9nc1NhbXBsZSA8LSByZWFkTGluZXMoZGF0YUZpbGVzWzFdLCBlbmNvZGluZz0iVVRGLTgiLCBza2lwTnVsPVRSVUUpW2Jsb2dzU2FtcGxlSWRdDQpuZXdzU2FtcGxlIDwtIHJlYWRMaW5lcyhkYXRhRmlsZXNbMl0sIGVuY29kaW5nPSJVVEYtOCIsIHNraXBOdWw9VFJVRSlbbmV3c1NhbXBsZUlkXQ0KdHdpdHRlcnNTYW1wbGUgPC0gcmVhZExpbmVzKGRhdGFGaWxlc1szXSwgZW5jb2Rpbmc9IlVURi04Iiwgc2tpcE51bD1UUlVFKVt0d2l0dGVyc1NhbXBsZUlkXQ0KYGBgDQoNCiMjI1N1bW1hcnkgc3RhdGlzdGljcyBvZiBzYW1wbGVzIGRhdGEgYmVmb3JlIGRhdGEgY2xlYXJuaW5nICANCk5vdGUgdGhhdCBsaW5lcyBjb3VudCBhbmQgd29yZHMgY291bnQgYXJlIGFsbW9zdCAxLzEwdGggb2Ygb3JpZ2luYWwgZGF0YXNldCBleGNlcHQgZm9yIG5ld3Mgd29yZHMgY291bnQgd2hpY2ggaXMgc29tZWhvdyAgZGlzcHJvcHJvdGlvbmFsIHdoaWNoIG5lZWRzIHRvIGJlIGludmVzdGlnYXRlZCBsYXRlciAobW9zdGx5IGRhdGEgY2xlYW5pbmcgd291bGQgaGF2ZSByZW1vdmVkIGh1Z2UgcHJlc2VuY2Ugb2YgcmVkdW5kYW50IHRlcm1zKSAgDQpgYGB7cn0NCnNhbXBsZXMubGlzdCA8LSBsaXN0KGJsb2cgPSBibG9nc1NhbXBsZSwgbmV3cyA9IG5ld3NTYW1wbGUsIHR3aXR0ZXIgPSB0d2l0dGVyc1NhbXBsZSkNCg0Kc2FtcGxlcy5kZiA8LSBkYXRhLmZyYW1lKHRleHQuc291cmNlID0gYygiYmxvZyIsICJuZXdzIiwgInR3aXR0ZXIiKSwgbGluZXMuY291bnQgPSBOQSwgd29yZHMuY291bnQgPSBOQSkNCnNhbXBsZXMuZGYkbGluZXMuY291bnQgPC0gc2FwcGx5KHNhbXBsZXMubGlzdCwgbGVuZ3RoKQ0Kc2FtcGxlcy5kZiR3b3Jkcy5jb3VudCA8LSBzdHJpX2NvdW50X3dvcmRzKHNhbXBsZXMubGlzdCkNCg0KcHJpbnQoIlN1bW1hcnkgc3RhdGlzdGljcyBvZiBzYW1wbGVzIGRhdGEgYmVmb3JlIGRhdGEgY2xlYXJuaW5nIikNCnNhbXBsZXMuZGYNCmBgYA0KDQojIyNEYXRhIGNsZWFuaW5nIHRvIHByZXBhcmUgdGV4dCBjb3JwdXMNCk5PVEUgdGhhdCBldmVuIDElIHNhbXBsZSBoYXMgMTBrIGRvY3VtZW50cyBhbmQgMzBrIHRlcm1zIG9uIGF2ZXJhZ2UgYWNyb3NzIGJsb2dzL25ld3MvdHdpdHRlcnMgZGF0YXNldHMgKGluY2x1ZGluZyBzb21lIGxhcmdlIG5vIG9mIGlycmVsZXZhbnQgdGVybXMpLiBJdCBpcyBjb3N0bHkgYW5kIHVuZWNjZXNzYXJ5IHRvIGlkZW50aWZ5IGFsbCB1bmlxdWUgdGVybXMgaW4gcmF3IHNhbXBsZSwgcmF0aGVyIHNhbXBsZSBkYXRhIG11c3QgYmUgY2xlYW5lZCBiZWZvcmUgY29uc3RydWN0aW5nIEJPVyAoYmFnIG9mIHdvcmRzKSByZXByZXNlbnRhdGlvbiBzdWNoIGFzIFRlcm1Eb2N1bWVudE1hdHJpeCBhbmQgc3Vic2VxdWVudCBwcm9jZXNzaW5nLiAgDQogICANClJhdGlvbmFsZSBiZWhpbmQgZGlmZmVyZW50IGtpbmQgb2YgZGF0YSBjbGVhbmluZzogLSAgDQotIFB1bmN0dWF0aW9uczogUHVuY3R1YXRpb24gY2hhcmFjdGVycyBzdWNoIGFzIGNvbW1hcywgcGFyZW50aGVzZXMgYW5kIHRoZSBsaWtlIGhhdmUgbGl0dGxlIG9yIG5vIGltcGFjdCBvbiB3b3JkIG9yZGVyIGFuZCBuLWdyYW0gY29tcG9zaXRpb24uICANCi0gU3RvcCBXb3JkczogU3RvcHdvcmRzIGFyZSB3b3JkcyBzbyBjb21tb24gdGhhdCB0aGVpciBpbmZvcm1hdGlvbiB2YWx1ZSBpcyB2ZXJ5IGxvdy4gIA0KLSBUb0xvd2VyOiBXb3JkcyB0aGVtc2VsdmVzIC0gbm90IHRoZWlyIGNhc2UgLSBhcmUgaW1wb3J0YW50IGZvciBwcmVkaWN0aW9uLiBOb3RlIHRoYXQgdGhlIHJlc3VsdGluZyBwcmVkaWN0aXZlIG1vZGVsIHdpbGwgcHJlZGljdCBsb3dlcmNhc2Ugd29yZHMgb25seS4gRm9yIEVuZ2xpc2ggdGV4dCB0aGlzIGlzIGxlc3Mgb2YgYW4gaXNzdWUgdGhhbiBmb3IgYSBsYW5ndWFnZSBsaWtlIEdlcm1hbiB0aGF0IHVzZXMgY2FwaXRhbGl6ZWQgbm91bnMuICANCi0gaHR0cC9mdHAvbWFpbCB1cmlzICYgVHdpdHRlciBoYXNodGFncyBhbmQgaGFuZGxlczogVGhlc2Ugc29jaWFsIG1lZGlhIHNwZWNpZmljIHRlcm1zIGFyZSBub3QgaGVscGZ1bCBmb3Igd29yZCBwcmVkaWN0aW9uLiAgDQotIFByb2Zhbml0eSB3b3JkczogV29yZHMgZGljdGlvbmFyeSBoYXMgYmVlbiBkb3dubG9hZGVkIGZyb20gaHR0cHM6Ly93d3cuZnJlZXdlYmhlYWRlcnMuY29tL2Z1bGwtbGlzdC1vZi1iYWQtd29yZHMtYmFubmVkLWJ5LWdvb2dsZS8gYW5kIGl0IGhhcyBiZWVuIHVzZWQgdG8gcmVtb3ZlIHRoZWlyIG9jY3VyYW5jZXMuICANCg0KYGBge3J9DQpjbGVhblVyaSA8LSBmdW5jdGlvbih0ZXh0KXsNCiAgICAgICAgIyMgcmVtb3ZlIGh0bWwvZnRwIHVybA0KICAgICAgICB0ZXh0IDwtIGdzdWIoIj8oZnxodCl0cChzPyk6Ly8oLiopWy5dW2Etel0rIiwgIiIsIHRleHQpIA0KICAgICAgICAjIyByZW1vdmUgZS1tYWlsIGFkcmVzc2VzDQogICAgICAgIHRleHQgPC0gZ3N1YigiW1s6YWxudW06XS4tXStAW1s6YWxudW06XS4tXSsiLCAiIiwgdGV4dCkgDQogICAgICAgIHJldHVybih0ZXh0KQ0KfQ0KDQpjbGVhblR3aXR0ZXJUYWdzSGFuZGxlcyA8LSBmdW5jdGlvbih0ZXh0KXsNCiAgICAgICAgIyMgcmVtb3ZlIHR3aXR0ZXIgaGFuZGxlcw0KICAgICAgICB0ZXh0IDwtIGdzdWIoIkBcXFMrIiwgIiIsIHRleHQpDQogICAgICAgICMjIHJlbW92ZSB0d2l0dGVyIGhhc2h0YWdzDQogICAgICAgIHRleHQgPC0gZ3N1YigiI1xcUysiLCAiIiwgdGV4dCkNCiAgICAgICAgcmV0dXJuKHRleHQpDQp9DQoNCnNhbXBsZXNMaXN0IDwtIGxpc3QoYmxvZ3MgPSBibG9nc1NhbXBsZSwgbmV3cyA9IG5ld3NTYW1wbGUsIHR3aXR0ZXJzID0gdHdpdHRlcnNTYW1wbGUpDQpzYW1wbGVzQ29ycHVzIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKHNhbXBsZXNMaXN0KSkNCnJtKHNhbXBsZXNMaXN0KQ0KDQojIFJlbW92ZSBVUklzICYgVHdpdHRlciBoYXNodGFnIGFuZCBoYW5kbGVzIGZyb20gYWxsIHR5cGVzIG9mIGRvY3VtZW50cy9tZXNzYWdlcw0Kc2FtcGxlc0NvcnB1cyA8LSB0bV9tYXAoc2FtcGxlc0NvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihjbGVhblVyaSkpDQpzYW1wbGVzQ29ycHVzIDwtIHRtX21hcChzYW1wbGVzQ29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKGNsZWFuVHdpdHRlclRhZ3NIYW5kbGVzKSkNCg0Kc2FtcGxlc0NvcnB1cyA8LSB0bV9tYXAoc2FtcGxlc0NvcnB1cywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQ0Kc2FtcGxlc0NvcnB1cyA8LSB0bV9tYXAoc2FtcGxlc0NvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbih4KSBpY29udih4LCBmcm9tPSJVVEYtOCIsIHRvPSJBU0NJSSIsIHN1Yj0iIikpKQ0Kc2FtcGxlc0NvcnB1cyA8LSB0bV9tYXAoc2FtcGxlc0NvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkNCnNhbXBsZXNDb3JwdXMgPC0gdG1fbWFwKHNhbXBsZXNDb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKQ0Kc2FtcGxlc0NvcnB1cyA8LSB0bV9tYXAoc2FtcGxlc0NvcnB1cywgcmVtb3ZlTnVtYmVycykNCnNhbXBsZXNDb3JwdXMgPC0gdG1fbWFwKHNhbXBsZXNDb3JwdXMsIHN0cmlwV2hpdGVzcGFjZSkNCg0KIyBSZW1vdmUgcHJvZmFuaXR5IHdvcmRzIGFzIHBlciBodHRwczovL3d3dy5mcmVld2ViaGVhZGVycy5jb20vZnVsbC1saXN0LW9mLWJhZC13b3Jkcy1iYW5uZWQtYnktZ29vZ2xlLyANCnNhbXBsZXNDb3JwdXMgPC0gdG1fbWFwKHNhbXBsZXNDb3JwdXMsIHJlbW92ZVdvcmRzLCByZWFkTGluZXMoIi4vYmFkV29yZHNfMjAxOF8wM18yNi50eHQiKSkNCg0KIyBBcHBseSBzdGVtbWluZyB0byB0aGUgcmVzdWx0aW5nIGNvcnB1cw0Kc2FtcGxlc0NvcnB1cyA8LSB0bV9tYXAoc2FtcGxlc0NvcnB1cywgc3RlbURvY3VtZW50KQ0KDQpgYGANCg0KIyMjVGV4dCBhbmFseXNpcyBmb3IgZGVyaXZpbmcgbi1ncmFtDQpgYGB7cn0NCg0KIyMgVG9rZW5pemVycw0KVW5pZ3JhbVRva2VuaXplciA8LSBmdW5jdGlvbih4KSBOR3JhbVRva2VuaXplcih4LCBXZWthX2NvbnRyb2wobWluID0gMSwgbWF4ID0gMSkpDQpCaWdyYW1Ub2tlbml6ZXIgPC0gZnVuY3Rpb24oeCkgTkdyYW1Ub2tlbml6ZXIoeCwgV2VrYV9jb250cm9sKG1pbiA9IDIsIG1heCA9IDIpKQ0KVHJpZ3JhbVRva2VuaXplciA8LSBmdW5jdGlvbih4KSBOR3JhbVRva2VuaXplcih4LCBXZWthX2NvbnRyb2wobWluID0gMywgbWF4ID0gMykpDQoNCiMjIFRlcm1Eb2N1bWVudE1hdHJpeCBmb3IgMS8yLzMtZ3JhbXMNCnRkbUJsb2dzXzFncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbImJsb2dzIl0sIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gVW5pZ3JhbVRva2VuaXplciwgd29yZExlbmd0aHMgPSBjKDMsSW5mKSkpDQp0ZG1OZXdzXzFncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbIm5ld3MiXSwgY29udHJvbCA9IGxpc3QodG9rZW5pemUgPSBVbmlncmFtVG9rZW5pemVyLCB3b3JkTGVuZ3RocyA9IGMoMyxJbmYpKSkNCnRkbVR3aXR0ZXJzXzFncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbInR3aXR0ZXJzIl0sIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gVW5pZ3JhbVRva2VuaXplciwgd29yZExlbmd0aHMgPSBjKDMsSW5mKSkpDQoNCnRkbUJsb2dzXzJncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbImJsb2dzIl0sIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gQmlncmFtVG9rZW5pemVyLCB3b3JkTGVuZ3RocyA9IGMoMyxJbmYpKSkNCnRkbU5ld3NfMmdyYW0gPSBUZXJtRG9jdW1lbnRNYXRyaXgoc2FtcGxlc0NvcnB1c1sibmV3cyJdLCBjb250cm9sID0gbGlzdCh0b2tlbml6ZSA9IEJpZ3JhbVRva2VuaXplciwgd29yZExlbmd0aHMgPSBjKDMsSW5mKSkpDQp0ZG1Ud2l0dGVyc18yZ3JhbSA9IFRlcm1Eb2N1bWVudE1hdHJpeChzYW1wbGVzQ29ycHVzWyJ0d2l0dGVycyJdLCBjb250cm9sID0gbGlzdCh0b2tlbml6ZSA9IEJpZ3JhbVRva2VuaXplciwgd29yZExlbmd0aHMgPSBjKDMsSW5mKSkpDQoNCnRkbUJsb2dzXzNncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbImJsb2dzIl0sIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gVHJpZ3JhbVRva2VuaXplciwgd29yZExlbmd0aHMgPSBjKDMsSW5mKSkpDQp0ZG1OZXdzXzNncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbIm5ld3MiXSwgY29udHJvbCA9IGxpc3QodG9rZW5pemUgPSBUcmlncmFtVG9rZW5pemVyLCB3b3JkTGVuZ3RocyA9IGMoMyxJbmYpKSkNCnRkbVR3aXR0ZXJzXzNncmFtID0gVGVybURvY3VtZW50TWF0cml4KHNhbXBsZXNDb3JwdXNbInR3aXR0ZXJzIl0sIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gVHJpZ3JhbVRva2VuaXplciwgd29yZExlbmd0aHMgPSBjKDMsSW5mKSkpDQpgYGANCg0KIyMjRXh0cmFjdCBtb3N0IG9jY3VyaW5nIDEvMi8zLWdyYW1zIGFsb25nIHdpdGggdGhlaXIgZnJlcXVlbmN5DQpgYGB7cn0NCg0KIyBwdXQgd29yZCBjb3VudCBmcm9tIHRlcm0tZG9jdW1lbnQgbWF0cmljZXMgaW50byBkYXRhIGZyYW1lcw0Kd2ZtQmxvZ3NfMWdyYW0gPC0gZGF0YS5mcmFtZSh3b3JkID0gdGRtQmxvZ3NfMWdyYW0kZGltbmFtZXMkVGVybXMsIGZyZXF1ZW5jeSA9IHRkbUJsb2dzXzFncmFtJHYpDQp3Zm1OZXdzXzFncmFtIDwtIGRhdGEuZnJhbWUod29yZCA9IHRkbU5ld3NfMWdyYW0kZGltbmFtZXMkVGVybXMsIGZyZXF1ZW5jeSA9IHRkbU5ld3NfMWdyYW0kdikNCndmbVR3aXR0ZXJzXzFncmFtIDwtIGRhdGEuZnJhbWUod29yZCA9IHRkbVR3aXR0ZXJzXzFncmFtJGRpbW5hbWVzJFRlcm1zLCBmcmVxdWVuY3kgPSB0ZG1Ud2l0dGVyc18xZ3JhbSR2KQ0KDQp3Zm1CbG9nc18yZ3JhbSA8LSBkYXRhLmZyYW1lKHdvcmQgPSB0ZG1CbG9nc18yZ3JhbSRkaW1uYW1lcyRUZXJtcywgZnJlcXVlbmN5ID0gdGRtQmxvZ3NfMmdyYW0kdikNCndmbU5ld3NfMmdyYW0gPC0gZGF0YS5mcmFtZSh3b3JkID0gdGRtTmV3c18yZ3JhbSRkaW1uYW1lcyRUZXJtcywgZnJlcXVlbmN5ID0gdGRtTmV3c18yZ3JhbSR2KQ0Kd2ZtVHdpdHRlcnNfMmdyYW0gPC0gZGF0YS5mcmFtZSh3b3JkID0gdGRtVHdpdHRlcnNfMmdyYW0kZGltbmFtZXMkVGVybXMsIGZyZXF1ZW5jeSA9IHRkbVR3aXR0ZXJzXzJncmFtJHYpDQoNCndmbUJsb2dzXzNncmFtIDwtIGRhdGEuZnJhbWUod29yZCA9IHRkbUJsb2dzXzNncmFtJGRpbW5hbWVzJFRlcm1zLCBmcmVxdWVuY3kgPSB0ZG1CbG9nc18zZ3JhbSR2KQ0Kd2ZtTmV3c18zZ3JhbSA8LSBkYXRhLmZyYW1lKHdvcmQgPSB0ZG1OZXdzXzNncmFtJGRpbW5hbWVzJFRlcm1zLCBmcmVxdWVuY3kgPSB0ZG1OZXdzXzNncmFtJHYpDQp3Zm1Ud2l0dGVyc18zZ3JhbSA8LSBkYXRhLmZyYW1lKHdvcmQgPSB0ZG1Ud2l0dGVyc18zZ3JhbSRkaW1uYW1lcyRUZXJtcywgZnJlcXVlbmN5ID0gdGRtVHdpdHRlcnNfM2dyYW0kdikNCg0KIyByZW9yZGVyIGJ5IGRlc2NyZWFzaW5nIGZyZXF1ZW5jeQ0Kd2ZtQmxvZ3NfMWdyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1CbG9nc18xZ3JhbSwgLWZyZXF1ZW5jeSkNCndmbU5ld3NfMWdyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1OZXdzXzFncmFtLCAtZnJlcXVlbmN5KQ0Kd2ZtVHdpdHRlcnNfMWdyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1Ud2l0dGVyc18xZ3JhbSwgLWZyZXF1ZW5jeSkNCg0Kd2ZtQmxvZ3NfMmdyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1CbG9nc18yZ3JhbSwgLWZyZXF1ZW5jeSkNCndmbU5ld3NfMmdyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1OZXdzXzJncmFtLCAtZnJlcXVlbmN5KQ0Kd2ZtVHdpdHRlcnNfMmdyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1Ud2l0dGVyc18yZ3JhbSwgLWZyZXF1ZW5jeSkNCg0Kd2ZtQmxvZ3NfM2dyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1CbG9nc18zZ3JhbSwgLWZyZXF1ZW5jeSkNCndmbU5ld3NfM2dyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1OZXdzXzNncmFtLCAtZnJlcXVlbmN5KQ0Kd2ZtVHdpdHRlcnNfM2dyYW0gPC0gcGx5cjo6YXJyYW5nZSh3Zm1Ud2l0dGVyc18zZ3JhbSwgLWZyZXF1ZW5jeSkNCmBgYA0KDQojIyNQbG90IG1vc3QgZnJlcXVlbnQgVW5pZ3JhbXMNCmBgYHtyfQ0KbiA8LSAyMEwgIyBzaG93IDIwIG1vc3QgZnJlcXVlbnRseSBvY2N1cmluZyB0ZXJtcy93b3Jkcw0KDQojIGlzb2xhdGUgdG9wIG4gd29yZHMgYnkgZGVjcmVhc2luZyBmcmVxdWVuY3kNCmJsb2dzLnRvcCA8LSB3Zm1CbG9nc18xZ3JhbVsxOm4sIF0NCm5ld3MudG9wIDwtIHdmbU5ld3NfMWdyYW1bMTpuLCBdDQp0d2l0dGVycy50b3AgPC0gd2ZtVHdpdHRlcnNfMWdyYW1bMTpuLCBdDQoNCmJsb2dzLnRvcCR3b3JkIDwtIHJlb3JkZXIoYmxvZ3MudG9wJHdvcmQsIGJsb2dzLnRvcCRmcmVxdWVuY3kpDQpuZXdzLnRvcCR3b3JkIDwtIHJlb3JkZXIobmV3cy50b3Akd29yZCwgbmV3cy50b3AkZnJlcXVlbmN5KQ0KdHdpdHRlcnMudG9wJHdvcmQgPC0gcmVvcmRlcih0d2l0dGVycy50b3Akd29yZCwgdHdpdHRlcnMudG9wJGZyZXF1ZW5jeSkNCg0KIyBwbG90cw0KZy5ibG9ncy50b3AgPC0gZ2dwbG90KGJsb2dzLnRvcCwgYWVzKHggPSB3b3JkLCB5ID0gZnJlcXVlbmN5KSkNCmcuYmxvZ3MudG9wIDwtIGcuYmxvZ3MudG9wICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJVbmktZ3JhbXMgaW4gQmxvZ3MiKQ0KDQpnLm5ld3MudG9wIDwtIGdncGxvdChuZXdzLnRvcCwgYWVzKHggPSB3b3JkLCB5ID0gZnJlcXVlbmN5KSkNCmcubmV3cy50b3AgPC0gZy5uZXdzLnRvcCArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAiVW5pLWdyYW1zIGluIE5ld3MiKQ0KDQpnLnR3aXR0ZXJzLnRvcCA8LSBnZ3Bsb3QodHdpdHRlcnMudG9wLCBhZXMoeCA9IHdvcmQsIHkgPSBmcmVxdWVuY3kpKQ0KZy50d2l0dGVycy50b3AgPC0gZy50d2l0dGVycy50b3AgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyANCiAgbGFicyh0aXRsZSA9ICJVbmktZ3JhbXMgaW4gVHdpdHRlcnMiKQ0KDQpncmlkLmFycmFuZ2UoZy5ibG9ncy50b3AsIGcubmV3cy50b3AsIGcudHdpdHRlcnMudG9wLCBuY29sID0gMykNCmBgYA0KDQojIyNQbG90IG1vc3QgZnJlcXVlbnQgQmlncmFtcw0KYGBge3J9DQojIGlzb2xhdGUgdG9wIG4gd29yZHMgYnkgZGVjcmVhc2luZyBmcmVxdWVuY3kNCmJsb2dzLnRvcCA8LSB3Zm1CbG9nc18yZ3JhbVsxOm4sIF0NCm5ld3MudG9wIDwtIHdmbU5ld3NfMmdyYW1bMTpuLCBdDQp0d2l0dGVycy50b3AgPC0gd2ZtVHdpdHRlcnNfMmdyYW1bMTpuLCBdDQoNCmJsb2dzLnRvcCR3b3JkIDwtIHJlb3JkZXIoYmxvZ3MudG9wJHdvcmQsIGJsb2dzLnRvcCRmcmVxdWVuY3kpDQpuZXdzLnRvcCR3b3JkIDwtIHJlb3JkZXIobmV3cy50b3Akd29yZCwgbmV3cy50b3AkZnJlcXVlbmN5KQ0KdHdpdHRlcnMudG9wJHdvcmQgPC0gcmVvcmRlcih0d2l0dGVycy50b3Akd29yZCwgdHdpdHRlcnMudG9wJGZyZXF1ZW5jeSkNCg0KIyBwbG90cw0KZy5ibG9ncy50b3AgPC0gZ2dwbG90KGJsb2dzLnRvcCwgYWVzKHggPSB3b3JkLCB5ID0gZnJlcXVlbmN5KSkNCmcuYmxvZ3MudG9wIDwtIGcuYmxvZ3MudG9wICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJCaWdyYW1zIGluIEJsb2dzIikNCg0KZy5uZXdzLnRvcCA8LSBnZ3Bsb3QobmV3cy50b3AsIGFlcyh4ID0gd29yZCwgeSA9IGZyZXF1ZW5jeSkpDQpnLm5ld3MudG9wIDwtIGcubmV3cy50b3AgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHRpdGxlID0gIkJpZ3JhbXMgaW4gTmV3cyIpDQoNCmcudHdpdHRlcnMudG9wIDwtIGdncGxvdCh0d2l0dGVycy50b3AsIGFlcyh4ID0gd29yZCwgeSA9IGZyZXF1ZW5jeSkpDQpnLnR3aXR0ZXJzLnRvcCA8LSBnLnR3aXR0ZXJzLnRvcCArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArIA0KICBsYWJzKHRpdGxlID0gIkJpZ3JhbXMgaW4gVHdpdHRlcnMiKQ0KDQpncmlkLmFycmFuZ2UoZy5ibG9ncy50b3AsIGcubmV3cy50b3AsIGcudHdpdHRlcnMudG9wLCBuY29sID0gMykNCmBgYA0KDQojIyNQbG90IG1vc3QgZnJlcXVlbnQgVHJpZ3JhbXMNCmBgYHtyfQ0KIyBpc29sYXRlIHRvcCBuIHdvcmRzIGJ5IGRlY3JlYXNpbmcgZnJlcXVlbmN5DQpibG9ncy50b3AgPC0gd2ZtQmxvZ3NfM2dyYW1bMTpuLCBdDQpuZXdzLnRvcCA8LSB3Zm1OZXdzXzNncmFtWzE6biwgXQ0KdHdpdHRlcnMudG9wIDwtIHdmbVR3aXR0ZXJzXzNncmFtWzE6biwgXQ0KDQpibG9ncy50b3Akd29yZCA8LSByZW9yZGVyKGJsb2dzLnRvcCR3b3JkLCBibG9ncy50b3AkZnJlcXVlbmN5KQ0KbmV3cy50b3Akd29yZCA8LSByZW9yZGVyKG5ld3MudG9wJHdvcmQsIG5ld3MudG9wJGZyZXF1ZW5jeSkNCnR3aXR0ZXJzLnRvcCR3b3JkIDwtIHJlb3JkZXIodHdpdHRlcnMudG9wJHdvcmQsIHR3aXR0ZXJzLnRvcCRmcmVxdWVuY3kpDQoNCiMgcGxvdHMNCmcuYmxvZ3MudG9wIDwtIGdncGxvdChibG9ncy50b3AsIGFlcyh4ID0gd29yZCwgeSA9IGZyZXF1ZW5jeSkpDQpnLmJsb2dzLnRvcCA8LSBnLmJsb2dzLnRvcCArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAiVHJpZ3JhbXMgaW4gQmxvZ3MiKQ0KDQpnLm5ld3MudG9wIDwtIGdncGxvdChuZXdzLnRvcCwgYWVzKHggPSB3b3JkLCB5ID0gZnJlcXVlbmN5KSkNCmcubmV3cy50b3AgPC0gZy5uZXdzLnRvcCArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAiVHJpZ3JhbXMgaW4gTmV3cyIpDQoNCmcudHdpdHRlcnMudG9wIDwtIGdncGxvdCh0d2l0dGVycy50b3AsIGFlcyh4ID0gd29yZCwgeSA9IGZyZXF1ZW5jeSkpDQpnLnR3aXR0ZXJzLnRvcCA8LSBnLnR3aXR0ZXJzLnRvcCArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSArIA0KICBsYWJzKHRpdGxlID0gIlRyaWdyYW1zIGluIFR3aXR0ZXJzIikNCg0KZ3JpZC5hcnJhbmdlKGcuYmxvZ3MudG9wLCBnLm5ld3MudG9wLCBnLnR3aXR0ZXJzLnRvcCwgbmNvbCA9IDMpDQpgYGANCg0KIyMgTmV4dCBTdGVwcy4uLg0KICANClRoZSBwbGFuIGZvciBjb21wbGV0aW5nIHRoZSBDYXBzdG9uZSBQcm9qZWN0IHdvdWxkIG1vc3RseSBpbnZvbHZlIHRoZSBmb2xsb3dpbmcgdGFza3M6IC0gIA0KLSBDcmVhdGUgU2hpbnkgYXBwICANCi0gUGxhbiBmb3IgdGhlIGFwcHJvYWNoIHRvIGFyY2hpdmUgbGVhcm5pbmcvdHJhaW5pbmcgb2YgZWl0aGVyIGN1cnJlbnRseSBrbm93biBiYXNpYyBuLWdyYW0gb3IgaXRzIGJldHRlciBhbHRlcm5hdGl2ZTsgVGhpcyB3aWxsIG1vc3RseSBpbnZvbHZlIGVuc3VyaW5nIHRvIHF1YW50aWZ5IHByb2JhYmlsaXR5IGFuZCBjb25maWRlbmNlIGludGVydmFsIG9mIHByb3Bvc2VkIHdvcmQgcHJlZGljdGlvbnMgICANCi0gUHJvZmlsZSBhbmQgb3B0aW1pemUgdGhlIHNvbHV0aW9uIHRvIGFjaGlldmUgZ29vZCBwZXJmb3JtYW5jZSBhbmQgYWNjdXJhY3kgIA0KLSBGaXggc3RvcHdvcmRzIG5vdCByZW1vdmluZyBjb21tb24gbW9zdCBmcmVxdWVudCB3b3JkcyBsaWtlICdJJywgJ3RoZScsIGV0YyAgDQotIEZpbmQgdGhlIGltcGFjdC91c2VmdWxuZXNzIG9mIGV4aXN0aW5nIGRhdGEgY2xlYW5pbmcgc3RlcHMgYW5kIHJldmlzZSB0byBhY2hpZXZlIGJldHRlciBwZXJmb3JtYW5jZSBhbmQgYWNjdXJhY3kgaW4gbW9kZWxpbmcgYW5kIHByZWRpY3Rpb24gIA0KLSBJbnZlc3RpZ2F0ZSBpZiBhbnkgZGltZW50aW9uYWxpdHkgLyBzcGFyc2l0eSByZWR1Y3Rpb24gd291bGQgYmUgZmVhc2libGUgYW5kIGhlbHBmdWwgIA0KLSBJZGVudGlmeSBhcHByb3ByaWF0ZSB0cmFpbmluZyBzaXplIHRvIGJhbGFuY2UgYmFpcyBhbmQgdmFyaWFuY2UgIA0KLSBTYXZlIHNhbXBsZSB0cmFpbmluZyBkYXRhc2V0IHRvIGRpc2sgdG8gYXZvaWQgbG9hZGluZyBmdWxsIHRyYWluaW5nIGRhdGFzZXQgIA0KZGV0ZXJtaW5lIHRoZSBiZXN0IGRhdGEgc3RydWN0dXJlIGluIFIgdG8gc3RvcmUgTkdyYW0gbGFuZ3VhZ2UgbW9kZWxzDQotIE1ha2UgdGhlIGNvZGUgdGVyc2UNCg==