(Installing and) Loading the required R packages

# install.packages("ggplot2")
library(ggplot2)

Dataset

Read and the clean the dataset:
source("Prepare The Beatles song dataset for clustering.R")
prepareTheBeatlesDatasetForClustering()
# saveRDS(object = <dataframe or another R object>, file = "<filename>") # save R object for the next session
# <dataframe or another R object> <- readRDS(file = "<filename>") # restore R object in the next session

the.beatles.songs.num <- 
  readRDS("The Beatles songs dataset (numeric), v4.1.RData")

K-Means

Outliers

Check if there are outliers in the data, using boxplots:
boxplot(<dataset>$<column name>, xlab = "<column name>") # basic boxplot for <column name>
boxplot(<dataset>) # basic boxplots for all columns
boxplot(<dataset>)$stats # basic boxplots for all columns, stats
boxplot(<dataset>)$stats[c(1, 5), ] # basic boxplots - whiskers
<output var> <- ggplot(<dataset>, # ggplot2 boxplots
+ aes(x = "",
+ y = <column name>)) + # show boxplot of <column name>
+ geom_boxplot(width = 0.5, fill = "<color>") + # boxplot width and color
+ stat_boxplot(geom ='errorbar', width = 0.15) + # show whiskers, control their width
+ guides(fill = FALSE) + # no legend (it makes no sense here)
+ xlab("") # no x-axis label (it makes no sense here)

Compare outputs of boxplot()$stats and summary(dataset):

boxplot(the.beatles.songs.num)$stats
boxplot(the.beatles.songs.num$Duration, xlab = "Duration")

Boxplotting all numeric features using the boxplotFeature() utility function:
source("Boxplotting.R")
boxplotFeature(<dataset with numeric features>, # dataframe with numeric features
+ "<numeric feature>", # numeric feature to boxplot (its name, passed as a string)
+ "<color>") # boxplot fill color

source("Boxplotting.R")
boxplotFeature(the.beatles.songs.num, "Duration", "red")
boxplotFeature(the.beatles.songs.num, "Other.releases", "chartreuse")
boxplotFeature(the.beatles.songs.num, "Covered.by", "orange")
boxplotFeature(the.beatles.songs.num, "Top.50.Billboard", "yellow")

Fix the outlier values for each variable with outliers - replace each outlier value with a specific percentile value of the data, typically 90th or 95th:
boxplot(<dataset>$<column name>, xlab = "<column name>") # basic boxplot for <column name>
boxplot.stats(<dataset>$<column name>) # examine the boxplot more closely
boxplot.stats(<dataset>$<column name>)$out # examine the outliers more closely
boxplot.stats(<dataset>$<column name>)$stats[c(1, 5)] # get the whiskers
boxplot.stats(<dataset>$<column name>)$stats[1] # get the lower whisker
boxplot.stats(<dataset>$<column name>)$stats[5] # get the upper whisker
sort(boxplot.stats(<dataset>$<column name>)$out) # get and sort the outliers
<quantiles> <- quantile(<dataset>$<column name>, # examine the 90th, 95th, ..., percentile
+ probs = seq(from = 0.9, to = 1, by = 0.025))
<new max value> <- # the value to replace the outliers
+ as.numeric(quantile(<dataset>$<column name>, # pick <percentile> closest to
+ probs = <percentile>)) # the upper whisker
<dataset>$<column name>[<dataset>$<column name> >
+ <new max value>] <- # replace the outliers
+ <new max value>
<quantiles> <- quantile(<dataset>$<column name>, # examine the 0th, 5th, ..., percentile
+ probs = seq(from = 0.0, to = 0.1, by = 0.025))
<new min value> <- # the value to replace the outliers
+ as.numeric(quantile(<dataset>$<column name>, # pick <percentile> closest to
+ probs = <percentile>)) # the lower whisker
<dataset>$<column name>[<dataset>$<column name> < <new min value>] <- # replace the outliers
+ <new min value>

Fix the outliers in the first variable manually:

boxplot(the.beatles.songs.num$Duration, xlab = "Duration")
boxplot.stats(the.beatles.songs.num$Duration)
boxplot.stats(the.beatles.songs.num$Duration)$out
boxplot.stats(the.beatles.songs.num$Duration)$stats[c(1, 5)]
sort(boxplot.stats(the.beatles.songs.num$Duration)$out)
quantile(the.beatles.songs.num$Duration, 
         probs = seq(from = 0.9, to = 1, by = 0.025))
new.max.duration <- 
  as.numeric(quantile(the.beatles.songs.num$Duration,         # the 92.5th percentile seems to be 
                      probs = 0.925))                         # a good cut-off point
the.beatles.songs.num$Duration[the.beatles.songs.num$Duration > new.max.duration] <- new.max.duration
quantile(the.beatles.songs.num$Duration, 
         probs = seq(from = 0, to = 0.1, by = 0.025))
new.min.duration <- 
  as.numeric(quantile(the.beatles.songs.num$Duration,         # the 2.5th percentile seems to be 
                      probs = 0.025))                         # a good cut-off point
the.beatles.songs.num$Duration[the.beatles.songs.num$Duration < new.min.duration] <- new.min.duration
boxplot(the.beatles.songs.num$Duration, xlab = "Duration")
# no more outliers in Duration

Call fixOutliers() for the other columns:

# source("Fix outliers.R")
# the.beatles.songs.num <- fixOutliers(the.beatles.songs.num, "<column name>")
source("Fix outliers.R")

boxplot(the.beatles.songs.num$Other.releases, xlab = "Other.releases")
boxplot.stats(the.beatles.songs.num$Other.releases)
boxplot.stats(the.beatles.songs.num$Other.releases)$out
boxplot.stats(the.beatles.songs.num$Other.releases)$stats[c(1, 5)]
the.beatles.songs.num <- fixOutliers(the.beatles.songs.num, "Other.releases")
boxplot(the.beatles.songs.num$Other.releases, xlab = "Other.releases")
# no more outliers in Other.releases
the.beatles.songs.num <- fixOutliers(the.beatles.songs.num, "Covered.by")
boxplot(the.beatles.songs.num$Covered.by, xlab = "Covered.by")
# no more outliers in Covered.by

Demonstrate an attempt to fix outliers with highly skewed data:

boxplot(the.beatles.songs.num$Top.50.Billboard, xlab = "Top.50.Billboard")
boxplot.stats(the.beatles.songs.num$Top.50.Billboard)
boxplot.stats(the.beatles.songs.num$Top.50.Billboard)$out
boxplot.stats(the.beatles.songs.num$Top.50.Billboard)$stats[c(1, 5)]
temp <- the.beatles.songs.num$Top.50.Billboard # save current Top.50.Billboard, for restoring it later
the.beatles.songs.num <- fixOutliers(the.beatles.songs.num, "Top.50.Billboard")
boxplot(the.beatles.songs.num$Top.50.Billboard, xlab = "Top.50.Billboard")
the.beatles.songs.num$Top.50.Billboard <- temp # restore Top.50.Billboard

Summarize the results so far:

summary(the.beatles.songs.num)

Patterns in the data

See if there are some patterns in the data, pairwise, to possibly indicate clusters:
pairs(~ <column 1 name> + <column 2 name> + ...,
+ data = <dataframe>)

pairs(~ Duration + Other.releases + Covered.by + Top.50.Billboard,  # no any striking pattern, i.e. 
      the.beatles.songs.num)                                        # no visual indication of clusters

K-Means with 2 variables

Try K-Means with 2 variables.

Plot the data first:
<scatterplot> <- + ggplot(<dataset>, aes(x = <num.var.1>, y = <num.var.2>)) +
+ geom_point(shape = <n>, # <n> = 1: hollow circle, no fill;
+ # <n> = 21: circle that can be filled
+ fill = <color 1>, # color of point fill (optional)
+ color = <color 2>, # color of point line (optional)
+ size = <s>) # size of point line (optional)
<scatterplot> <- <scatterplot> + xlab("<x label>") # label/caption on x-axis
<scatterplot> <- <scatterplot> + ylab("<y label>") # label/caption on y-axis
<scatterplot> <- <scatterplot> + ggtitle("<scatterplot title>") # scatterplot title
<scatterplot> # plot it

Alternatively:
<scatterplot> <-
+ ggplot(<dataset>, aes(x = <num.var.1>, y = <num.var.2>)) +
+ geom_point(shape = <n>, # <n> = 1: hollow circle, no fill;
+ # <n> = 21: circle that can be filled
+ fill = <color 1>, # color of point fill (optional)
+ color = <color 2>, # color of point line (optional)
+ size = <s>) # size of point line (optional)
<scatterplot> <- <scatterplot> +
+ labs(x = "<x label>", # label/caption on x-axis
+ y = "<y label>", # label/caption on y-axis
+ title = "<scatterplot title>") + # scatterplot title
<scatterplot> # plot it

scatterplot.Other.releases.vs.Covered.by <- 
  ggplot(the.beatles.songs.num, aes(x = Other.releases, y = Covered.by)) +
  geom_point(shape = 21, fill = "yellow", size = 2) + 
  labs(x = "Other.releases", y = "Covered.by", title = "Covered.by vs. Other.releases") +
  theme_bw()
scatterplot.Other.releases.vs.Covered.by

Subset the original data to include only the variables to be used in K-Means:
<new dataframe> <- <dataframe>[, c("<col1 name>", "<col2 name>")]
<new dataframe> <- <dataframe>[, <col1 index>:<col2 index>]
Alternatively:
<new dataframe> <- subset(<dataframe>, select = c("<col1 name>", "<col2 name>"))
<new dataframe> <- subset(<dataframe>, select = c(<col1 index>:<col2 index>))

the.beatles.songs.num.1 <- the.beatles.songs.num[, c("Other.releases", "Covered.by")]
# the.beatles.songs.num.1 <- subset(the.beatles.songs.num, select = c("Other.releases", "Covered.by"))
summary(the.beatles.songs.num.1)
head(the.beatles.songs.num.1)

Data normalization

Required by K-Means when the variables have different ranges.
range(<dataframe with numeric columns>$<numeric column 1> # check the range of <numeric column 1>
range(<dataframe with numeric columns>$<numeric column 2> # check the range of <numeric column 2>
...
# install.packages("clusterSim")
library(clusterSim)
<dataframe with numeric columns> <- # works with vectors and matrices as well
+ data.Normalization(<dataframe with numeric columns>,
+ type = "n4", # normalization: (x - min(x)) / (max(x) - min(x))
+ normalization = "column") # normalization by columns

range(the.beatles.songs.num.1$Other.releases)
range(the.beatles.songs.num.1$Covered.by)
library(clusterSim)
the.beatles.songs.num.2 <- 
  data.Normalization(the.beatles.songs.num.1, 
                     type = "n4", 
                     normalization = "column")
tail(the.beatles.songs.num.2)

K-Means for K = 3

Run K-Means for K = 3:
set.seed(<seed>)
<clusters> <- kmeans(x = <normalized dataframe>,
+ centers = 3, # K = 3
+ iter.max = 20, # max number of iterations allowed
+ nstart = 1000) # no. of initial configurations
+ # (report generated based on the best one)
<clusters>

set.seed(888)
clusters.K3 <- kmeans(x = the.beatles.songs.num.2, centers = 3, iter.max = 20, nstart = 1000)
clusters.K3

The meaning of parameters in the report:

  • withinss (within_SS) - within cluster sum of squares, i.e., sum of squared differences between individual data points in a cluster and the cluster center; it is computed for each cluster
  • totss (total_SS) - the sum of squared differences of each data point to the global sample mean
  • betweenss (between_SS) - the sum of squared differences of each cluster center to the global sample mean; the squared difference of each cluster center to the global sample mean is multiplied by the number of data points in that cluster
  • tot.withinss - the sum of squared differences between data points and cluster centers (the sum of within_SS for all the clusters)
  • between_SS / totat_SS - indicates how well the sample splits into clusters; the higher the ratio, the better clustering

Add the vector of clusters to the dataframe:
<normalized dataframe>$<new column> <- factor(<clusters>$cluster) # <clusters>: from the previous step
head(<normalized dataframe>)

the.beatles.songs.num.2$Cluster <- factor(clusters.K3$cluster)
head(the.beatles.songs.num.2)

Plot the clusters in a new scatterplot, using plotClusters() utility function:
source("Plot clusters.R")
plotClusters() <- function(dataset, # dataset with the cluster column
+ xcol, # dataset column for the x-axis, passed as a string
+ ycol, # dataset column for the y-axis, passed as a string
+ clustercol, # dataset column showing the clusters, passed as a string
+ title, # plot title
+ x.label, # x-axis label
+ y.label, # y-axis label
+ legend.label, # plot legend label
+ show.centers.flag, # plot cluster centers if TRUE
+ clusters) { # clusters computed by kmeans() in a previous step

source("Plot clusters.R")
plotClusters(the.beatles.songs.num.2,
             "Other.releases",
             "Covered.by",
             "Cluster",
             "Clusters: (Other.releases, Covered.by), normalized",
             "Other.releases",
             "Covered.by",
             "Cluster",
             TRUE,
             clusters.K3)

Optimal value for K

Find the optimal value for K, using the Elbow method (a call to the appropriate utility function); an appropriate dataframe should be passed as the parameter (just numeric variables, no clusters):
source("Elbow method.R")
<elbow parameters> <- getElbowMethodParameters(<dataframe>[, c(<n1>, <n2>, ...)]) # leave out the cluster column
<elbow parameters>
plotElbow(<elbow parameters>)

source("Elbow method.R")
elbow.2 <- getElbowMethodParameters(the.beatles.songs.num.2[, c(1,2)])   # remove the Cluster column
elbow.2
plotElbow(elbow.2)

Show differences in tot.withinss for different values of K more precisely, using the getDifferences() utility function:
source("Elbow method.R")
<diff dataframe> <-
+ data.frame(K = <n1>:<n2>,
+ diff.tot.withinss =
+ getDifferences(<elbow stats>$<tot.withinss>), # from the previous step
+ diff.ratio =
+ getDifferences(<elbow stats>$<ratio between_SS / total_SS>)) # from the previous step
names(<diff dataframe>) <- c("K", "Difference in tot.withinss", "Difference in between_SS / total_SS")
<diff dataframe>

df.differences <- data.frame(K = 2:8, 
                             getDifferences(elbow.2[, 2]), 
                             getDifferences(elbow.2[, 3]))
names(df.differences) <- c("K", "Difference in tot.withinss", "Difference in between_SS / total_SS")
df.differences

K-Means for K = 4

Run K-Means also for K = 4:

set.seed(818)
the.beatles.songs.num.2 <- the.beatles.songs.num.2[, -3]    # remove the Cluster column for the new run
clusters.K4 <- kmeans(x = the.beatles.songs.num.2, centers = 4, iter.max = 20, nstart = 1000)
clusters.K4
the.beatles.songs.num.2$Cluster <- factor(clusters.K4$cluster)
head(the.beatles.songs.num.2)
source("Plot clusters.R")
plotClusters(the.beatles.songs.num.2,
             "Other.releases",
             "Covered.by",
             "Cluster",
             "Clusters: (Other.releases, Covered.by), normalized",
             "Other.releases",
             "Covered.by",
             "Cluster",
             TRUE,
             clusters.K4)

Examine clusters more closely by looking into the cluster centers (means) and standard deviations from the centers. In doing so, use ‘regular’ (not normalized) features and the summarizeClusterStats() utility function:
source("Summary statistics about clusters.R")
<stats dataframe> <-
+ summarizeClusterStats(feature.set = <dataframe with 'regular' (not normalized) features>,
+ clusters = <clusters>$cluster, # <clusters>: result of kmeans()
+ cl.num = <K>) # <K>: number of clusters
Compare dispersion (sd) in this representation of the data and in the corresponding plots.`

source("Summary statistics about clusters.R")
clusters.K3.stats <- summarizeClusterStats(feature.set = the.beatles.songs.num.1, 
                                           clusters = clusters.K3$cluster, cl.num = 3)
clusters.K4.stats <- summarizeClusterStats(feature.set = the.beatles.songs.num.1, 
                                           clusters = clusters.K4$cluster, cl.num = 4)

K-Means with all variables

Data normalization:

library(clusterSim)
the.beatles.songs.num.4 <- 
  data.Normalization(the.beatles.songs.num, 
                     type = "n4", 
                     normalization = "column")
tail(the.beatles.songs.num.4)

Find the optimal value for K, using the Elbow method (a call to the appropriate utility function):

source("Elbow method.R")
elbow.4 <- getElbowMethodParameters(the.beatles.songs.num.4)
elbow.4
plotElbow(elbow.4)

Show differences in tot.withinss for different values of K more precisely, using the getDifferences() utility function:

df.differences <- data.frame(K = 2:8, 
                             getDifferences(elbow.4[, 2]), 
                             getDifferences(elbow.4[, 3]))
names(df.differences) <- c("K", "Difference in tot.withinss", "Difference in between_SS / total_SS")
df.differences

Run K-Means for K = 3, since K = 3 seems to be the best value for K:

set.seed(888)
clusters.K3.all.vars <- kmeans(x = the.beatles.songs.num.4, centers = 3, iter.max = 20, nstart = 1000)
clusters.K3.all.vars

Run K-Means also for K = 4, since K = 4 seems to be the next best value for K:

set.seed(888)
clusters.K4.all.vars <- kmeans(x = the.beatles.songs.num.4, centers = 4, iter.max = 20, nstart = 1000)
clusters.K4.all.vars

Examine and compare the cluster centers for K = 3 and K = 4:

clusters.K3.all.vars.stats <- summarizeClusterStats(feature.set = the.beatles.songs.num, 
                                                    clusters = clusters.K3.all.vars$cluster, cl.num = 3)
clusters.K3.all.vars.stats

clusters.K4.all.vars.stats <- summarizeClusterStats(feature.set = the.beatles.songs.num, 
                                                    clusters = clusters.K4.all.vars$cluster, cl.num = 4)
clusters.K4.all.vars.stats

Compare multiple clustering results/schemes

# install.packages("fpc")
library(fpc)
?cluster.stats
<comparison criteria> <- # specify criteria (from cluster.stats()) for comparing
+ c("<criterion 1>", # different clusterings (e.g., "max.diameter", "min.separation",
+ "<criterion 2>", ...) # "average.between", "average.within", "within.cluster.ss", ...)
<distance matrix> <-
+ dist(x = <normalized dataset>)
<comparison> <- sapply(list(<clustering 1 name> = <clustering 1>$cluster, # <clustering 1> computed by kmeans()
+ <clustering 2 name> = <clustering 2>$cluster, # <clustering 2> computed by kmeans()
+ ...)
+ FUN = function(x)
+ cluster.stats(<distance matrix>, x))[<comparison criteria>, ]
Alternative 1 - show output as a table in the console:
# install.packages("knitr")
library(knitr)
kable(x = <comparison>, format = "rst")
Alternative 2 - show output as a dataframe, using an appropriate utility function:
source("Summary statistics about clusters.R")
<comparison.df> <-
+ compareMultipleClusterings(<comparison>) # show comparison as a dataframe, using a corresponding utility function
<comparison.df>

library(fpc)
comparison.criteria <- c("max.diameter", "min.separation", "average.between", 
                         "average.within", "within.cluster.ss")  
d <- dist(x = the.beatles.songs.num.4)
comparison <- sapply(list(c.K3.var2 = clusters.K3$cluster, # clustering: 3 clusters, 2 variables
                          c.K4.var2 = clusters.K4$cluster, # clustering: 4 clusters, 2 variables
                          c.K3.var4 = clusters.K3.all.vars$cluster,  # 3 clusters, 4 variables
                          c.K4.var4 = clusters.K4.all.vars$cluster), # 4 clusters, 4 variables
                     FUN = function(x) cluster.stats(d, x))[comparison.criteria, ]

# Alternative 1 - show output as a table in the console: 
# library(knitr)
# kable(x = comparison, format = "rst")

# Alternative 2 - show output as a dataframe, using an appropriate utility function: 
comparison.df <- compareMultipleClusterings(comparison)
comparison.df
LS0tDQp0aXRsZTogIkNsdXN0ZXJpbmciDQphdXRob3I6ICJWbGFkYW4gRGV2ZWR6aWMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiMjIChJbnN0YWxsaW5nIGFuZCkgTG9hZGluZyB0aGUgcmVxdWlyZWQgUiBwYWNrYWdlcw0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCiMgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KIyMgRGF0YXNldA0KDQpSZWFkIGFuZCB0aGUgY2xlYW4gdGhlIGRhdGFzZXQ6ICANCmBzb3VyY2UoIlByZXBhcmUgVGhlIEJlYXRsZXMgc29uZyBkYXRhc2V0IGZvciBjbHVzdGVyaW5nLlIiKWAgIA0KYHByZXBhcmVUaGVCZWF0bGVzRGF0YXNldEZvckNsdXN0ZXJpbmcoKWAgIA0KYCMgc2F2ZVJEUyhvYmplY3QgPSA8ZGF0YWZyYW1lIG9yIGFub3RoZXIgUiBvYmplY3Q+LCBmaWxlID0gIjxmaWxlbmFtZT4iKSAgIyBzYXZlIFIgb2JqZWN0IGZvciB0aGUgbmV4dCBzZXNzaW9uYCAgDQpgIyA8ZGF0YWZyYW1lIG9yIGFub3RoZXIgUiBvYmplY3Q+IDwtIHJlYWRSRFMoZmlsZSA9ICI8ZmlsZW5hbWU+IikgICAgICAgICAjIHJlc3RvcmUgUiBvYmplY3QgaW4gdGhlIG5leHQgc2Vzc2lvbmAgIA0KYGBge3J9DQp0aGUuYmVhdGxlcy5zb25ncy5udW0gPC0gDQogIHJlYWRSRFMoIlRoZSBCZWF0bGVzIHNvbmdzIGRhdGFzZXQgKG51bWVyaWMpLCB2NC4xLlJEYXRhIikNCmBgYCAgDQoNCiMjIEstTWVhbnMNCg0KIyMjIE91dGxpZXJzDQoNCkNoZWNrIGlmIHRoZXJlIGFyZSBvdXRsaWVycyBpbiB0aGUgZGF0YSwgdXNpbmcgYm94cGxvdHM6ICANCmBib3hwbG90KDxkYXRhc2V0PiQ8Y29sdW1uIG5hbWU+LCB4bGFiID0gIjxjb2x1bW4gbmFtZT4iKSAgICAgICAgICAgICAgICAgICAgIyBiYXNpYyBib3hwbG90IGZvciA8Y29sdW1uIG5hbWU+YCAgDQpgYm94cGxvdCg8ZGF0YXNldD4pICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYmFzaWMgYm94cGxvdHMgZm9yIGFsbCBjb2x1bW5zYCAgDQpgYm94cGxvdCg8ZGF0YXNldD4pJHN0YXRzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYmFzaWMgYm94cGxvdHMgZm9yIGFsbCBjb2x1bW5zLCBzdGF0c2AgIA0KYGJveHBsb3QoPGRhdGFzZXQ+KSRzdGF0c1tjKDEsIDUpLCBdICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGJhc2ljIGJveHBsb3RzIC0gd2hpc2tlcnNgICANCmA8b3V0cHV0IHZhcj4gPC0gZ2dwbG90KDxkYXRhc2V0PiwgICAgICAgICAgICAgICAgICAgICAgICMgZ2dwbG90MiBib3hwbG90c2AgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSAiIiwgYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IDxjb2x1bW4gbmFtZT4pKSArICAgICAgICAjIHNob3cgYm94cGxvdCBvZiA8Y29sdW1uIG5hbWU+YCAgDQpgKyAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC41LCBmaWxsID0gIjxjb2xvcj4iKSArICAgICAgICAjIGJveHBsb3Qgd2lkdGggYW5kIGNvbG9yYCAgDQpgKyAgc3RhdF9ib3hwbG90KGdlb20gPSdlcnJvcmJhcicsIHdpZHRoID0gMC4xNSkgKyAgICAgICAjIHNob3cgd2hpc2tlcnMsIGNvbnRyb2wgdGhlaXIgd2lkdGhgICANCmArICBndWlkZXMoZmlsbCA9IEZBTFNFKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbm8gbGVnZW5kIChpdCBtYWtlcyBubyBzZW5zZSBoZXJlKWAgIA0KYCsgIHhsYWIoIiIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBubyB4LWF4aXMgbGFiZWwgKGl0IG1ha2VzIG5vIHNlbnNlIGhlcmUpYCAgDQoNCkNvbXBhcmUgb3V0cHV0cyBvZiBib3hwbG90KDxkYXRhc2V0Pikkc3RhdHMgYW5kIHN1bW1hcnkoZGF0YXNldCk6ICANCmBgYHtyfQ0KYm94cGxvdCh0aGUuYmVhdGxlcy5zb25ncy5udW0pJHN0YXRzDQpib3hwbG90KHRoZS5iZWF0bGVzLnNvbmdzLm51bSREdXJhdGlvbiwgeGxhYiA9ICJEdXJhdGlvbiIpDQpgYGAgIA0KDQpCb3hwbG90dGluZyBhbGwgbnVtZXJpYyBmZWF0dXJlcyB1c2luZyB0aGUgYm94cGxvdEZlYXR1cmUoKSB1dGlsaXR5IGZ1bmN0aW9uOiAgDQpgc291cmNlKCJCb3hwbG90dGluZy5SIilgICANCmBib3hwbG90RmVhdHVyZSg8ZGF0YXNldCB3aXRoIG51bWVyaWMgZmVhdHVyZXM+LCAgICMgZGF0YWZyYW1lIHdpdGggbnVtZXJpYyBmZWF0dXJlc2AgIA0KYCsgICAgICAgICAgICAgICI8bnVtZXJpYyBmZWF0dXJlPiIsICAgICAgICAgICAgICAgIyBudW1lcmljIGZlYXR1cmUgdG8gYm94cGxvdCAoaXRzIG5hbWUsIHBhc3NlZCBhcyBhIHN0cmluZylgICANCmArICAgICAgICAgICAgICAiPGNvbG9yPiIpICAgICAgICAgICAgICAgICAgICAgICAgICMgYm94cGxvdCBmaWxsIGNvbG9yYCAgDQpgYGB7cn0NCnNvdXJjZSgiQm94cGxvdHRpbmcuUiIpDQpib3hwbG90RmVhdHVyZSh0aGUuYmVhdGxlcy5zb25ncy5udW0sICJEdXJhdGlvbiIsICJyZWQiKQ0KYm94cGxvdEZlYXR1cmUodGhlLmJlYXRsZXMuc29uZ3MubnVtLCAiT3RoZXIucmVsZWFzZXMiLCAiY2hhcnRyZXVzZSIpDQpib3hwbG90RmVhdHVyZSh0aGUuYmVhdGxlcy5zb25ncy5udW0sICJDb3ZlcmVkLmJ5IiwgIm9yYW5nZSIpDQpib3hwbG90RmVhdHVyZSh0aGUuYmVhdGxlcy5zb25ncy5udW0sICJUb3AuNTAuQmlsbGJvYXJkIiwgInllbGxvdyIpDQpgYGAgIA0KDQpGaXggdGhlIG91dGxpZXIgdmFsdWVzIGZvciBlYWNoIHZhcmlhYmxlIHdpdGggb3V0bGllcnMgLSByZXBsYWNlIGVhY2ggb3V0bGllciB2YWx1ZSB3aXRoIGEgc3BlY2lmaWMgcGVyY2VudGlsZSB2YWx1ZSBvZiB0aGUgZGF0YSwgdHlwaWNhbGx5IDkwdGggb3IgOTV0aDogIA0KYGJveHBsb3QoPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4sIHhsYWIgPSAiPGNvbHVtbiBuYW1lPiIpICAgICMgYmFzaWMgYm94cGxvdCBmb3IgPGNvbHVtbiBuYW1lPmAgIA0KYGJveHBsb3Quc3RhdHMoPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4pICAgICAgICAgICAgICAgICAgICAgICMgZXhhbWluZSB0aGUgYm94cGxvdCBtb3JlIGNsb3NlbHlgICANCmBib3hwbG90LnN0YXRzKDxkYXRhc2V0PiQ8Y29sdW1uIG5hbWU+KSRvdXQgICAgICAgICAgICAgICAgICAjIGV4YW1pbmUgdGhlIG91dGxpZXJzIG1vcmUgY2xvc2VseWAgIA0KYGJveHBsb3Quc3RhdHMoPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4pJHN0YXRzW2MoMSwgNSldICAgICAgICMgZ2V0IHRoZSB3aGlza2Vyc2AgIA0KYGJveHBsb3Quc3RhdHMoPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4pJHN0YXRzWzFdICAgICAgICAgICAgICMgZ2V0IHRoZSBsb3dlciB3aGlza2VyYCAgDQpgYm94cGxvdC5zdGF0cyg8ZGF0YXNldD4kPGNvbHVtbiBuYW1lPikkc3RhdHNbNV0gICAgICAgICAgICAgIyBnZXQgdGhlIHVwcGVyIHdoaXNrZXJgICANCmBzb3J0KGJveHBsb3Quc3RhdHMoPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4pJG91dCkgICAgICAgICAgICAjIGdldCBhbmQgc29ydCB0aGUgb3V0bGllcnNgICANCmA8cXVhbnRpbGVzPiA8LSBxdWFudGlsZSg8ZGF0YXNldD4kPGNvbHVtbiBuYW1lPiwgICAgICAgICAgICAjIGV4YW1pbmUgdGhlIDkwdGgsIDk1dGgsIC4uLiwgcGVyY2VudGlsZWAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gc2VxKGZyb20gPSAwLjksIHRvID0gMSwgYnkgPSAwLjAyNSkpYCAgDQpgPG5ldyBtYXggdmFsdWU+IDwtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGUgdmFsdWUgdG8gcmVwbGFjZSB0aGUgb3V0bGllcnNgICANCmArIGFzLm51bWVyaWMocXVhbnRpbGUoPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4sICAgICAgICAgICAgICAjIHBpY2sgPHBlcmNlbnRpbGU+IGNsb3Nlc3QgdG9gICANCmArICAgICAgICAgICAgICAgICAgICAgcHJvYnMgPSA8cGVyY2VudGlsZT4pKSAgICAgICAgICAgICAgICAjIHRoZSB1cHBlciB3aGlza2VyYCAgDQpgPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT5bPGRhdGFzZXQ+JDxjb2x1bW4gbmFtZT4gPmAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgIDxuZXcgbWF4IHZhbHVlPl0gPC0gICAgICAgICAgICAgICAgICMgcmVwbGFjZSB0aGUgb3V0bGllcnNgICANCmArIDxuZXcgbWF4IHZhbHVlPmAgIA0KYDxxdWFudGlsZXM+IDwtIHF1YW50aWxlKDxkYXRhc2V0PiQ8Y29sdW1uIG5hbWU+LCAgICAgICAgICAgICMgZXhhbWluZSB0aGUgMHRoLCA1dGgsIC4uLiwgcGVyY2VudGlsZWAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gc2VxKGZyb20gPSAwLjAsIHRvID0gMC4xLCBieSA9IDAuMDI1KSlgICANCmA8bmV3IG1pbiB2YWx1ZT4gPC0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRoZSB2YWx1ZSB0byByZXBsYWNlIHRoZSBvdXRsaWVyc2AgIA0KYCsgYXMubnVtZXJpYyhxdWFudGlsZSg8ZGF0YXNldD4kPGNvbHVtbiBuYW1lPiwgICAgICAgICAgICAgICMgcGljayA8cGVyY2VudGlsZT4gY2xvc2VzdCB0b2AgIA0KYCsgICAgICAgICAgICAgICAgICAgICBwcm9icyA9IDxwZXJjZW50aWxlPikpICAgICAgICAgICAgICAgICMgdGhlIGxvd2VyIHdoaXNrZXJgICANCmA8ZGF0YXNldD4kPGNvbHVtbiBuYW1lPls8ZGF0YXNldD4kPGNvbHVtbiBuYW1lPiA8IDxuZXcgbWluIHZhbHVlPl0gPC0gIyByZXBsYWNlIHRoZSBvdXRsaWVyc2AgIA0KYCsgPG5ldyBtaW4gdmFsdWU+YCAgDQoNCkZpeCB0aGUgb3V0bGllcnMgaW4gdGhlIGZpcnN0IHZhcmlhYmxlIG1hbnVhbGx5OiAgDQpgYGB7cn0NCmJveHBsb3QodGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uLCB4bGFiID0gIkR1cmF0aW9uIikNCmJveHBsb3Quc3RhdHModGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uKQ0KYm94cGxvdC5zdGF0cyh0aGUuYmVhdGxlcy5zb25ncy5udW0kRHVyYXRpb24pJG91dA0KYm94cGxvdC5zdGF0cyh0aGUuYmVhdGxlcy5zb25ncy5udW0kRHVyYXRpb24pJHN0YXRzW2MoMSwgNSldDQpzb3J0KGJveHBsb3Quc3RhdHModGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uKSRvdXQpDQpxdWFudGlsZSh0aGUuYmVhdGxlcy5zb25ncy5udW0kRHVyYXRpb24sIA0KICAgICAgICAgcHJvYnMgPSBzZXEoZnJvbSA9IDAuOSwgdG8gPSAxLCBieSA9IDAuMDI1KSkNCm5ldy5tYXguZHVyYXRpb24gPC0gDQogIGFzLm51bWVyaWMocXVhbnRpbGUodGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uLCAgICAgICAgICMgdGhlIDkyLjV0aCBwZXJjZW50aWxlIHNlZW1zIHRvIGJlIA0KICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gMC45MjUpKSAgICAgICAgICAgICAgICAgICAgICAgICAjIGEgZ29vZCBjdXQtb2ZmIHBvaW50DQp0aGUuYmVhdGxlcy5zb25ncy5udW0kRHVyYXRpb25bdGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uID4gbmV3Lm1heC5kdXJhdGlvbl0gPC0gbmV3Lm1heC5kdXJhdGlvbg0KcXVhbnRpbGUodGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uLCANCiAgICAgICAgIHByb2JzID0gc2VxKGZyb20gPSAwLCB0byA9IDAuMSwgYnkgPSAwLjAyNSkpDQpuZXcubWluLmR1cmF0aW9uIDwtIA0KICBhcy5udW1lcmljKHF1YW50aWxlKHRoZS5iZWF0bGVzLnNvbmdzLm51bSREdXJhdGlvbiwgICAgICAgICAjIHRoZSAyLjV0aCBwZXJjZW50aWxlIHNlZW1zIHRvIGJlIA0KICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gMC4wMjUpKSAgICAgICAgICAgICAgICAgICAgICAgICAjIGEgZ29vZCBjdXQtb2ZmIHBvaW50DQp0aGUuYmVhdGxlcy5zb25ncy5udW0kRHVyYXRpb25bdGhlLmJlYXRsZXMuc29uZ3MubnVtJER1cmF0aW9uIDwgbmV3Lm1pbi5kdXJhdGlvbl0gPC0gbmV3Lm1pbi5kdXJhdGlvbg0KYm94cGxvdCh0aGUuYmVhdGxlcy5zb25ncy5udW0kRHVyYXRpb24sIHhsYWIgPSAiRHVyYXRpb24iKQ0KIyBubyBtb3JlIG91dGxpZXJzIGluIER1cmF0aW9uDQpgYGAgIA0KDQpDYWxsIGZpeE91dGxpZXJzKCkgZm9yIHRoZSBvdGhlciBjb2x1bW5zOg0KYGBge3J9DQojIHNvdXJjZSgiRml4IG91dGxpZXJzLlIiKQ0KIyB0aGUuYmVhdGxlcy5zb25ncy5udW0gPC0gZml4T3V0bGllcnModGhlLmJlYXRsZXMuc29uZ3MubnVtLCAiPGNvbHVtbiBuYW1lPiIpDQpzb3VyY2UoIkZpeCBvdXRsaWVycy5SIikNCg0KYm94cGxvdCh0aGUuYmVhdGxlcy5zb25ncy5udW0kT3RoZXIucmVsZWFzZXMsIHhsYWIgPSAiT3RoZXIucmVsZWFzZXMiKQ0KYm94cGxvdC5zdGF0cyh0aGUuYmVhdGxlcy5zb25ncy5udW0kT3RoZXIucmVsZWFzZXMpDQpib3hwbG90LnN0YXRzKHRoZS5iZWF0bGVzLnNvbmdzLm51bSRPdGhlci5yZWxlYXNlcykkb3V0DQpib3hwbG90LnN0YXRzKHRoZS5iZWF0bGVzLnNvbmdzLm51bSRPdGhlci5yZWxlYXNlcykkc3RhdHNbYygxLCA1KV0NCnRoZS5iZWF0bGVzLnNvbmdzLm51bSA8LSBmaXhPdXRsaWVycyh0aGUuYmVhdGxlcy5zb25ncy5udW0sICJPdGhlci5yZWxlYXNlcyIpDQpib3hwbG90KHRoZS5iZWF0bGVzLnNvbmdzLm51bSRPdGhlci5yZWxlYXNlcywgeGxhYiA9ICJPdGhlci5yZWxlYXNlcyIpDQojIG5vIG1vcmUgb3V0bGllcnMgaW4gT3RoZXIucmVsZWFzZXMNCmBgYCAgDQoNCmBgYHtyfQ0KdGhlLmJlYXRsZXMuc29uZ3MubnVtIDwtIGZpeE91dGxpZXJzKHRoZS5iZWF0bGVzLnNvbmdzLm51bSwgIkNvdmVyZWQuYnkiKQ0KYm94cGxvdCh0aGUuYmVhdGxlcy5zb25ncy5udW0kQ292ZXJlZC5ieSwgeGxhYiA9ICJDb3ZlcmVkLmJ5IikNCiMgbm8gbW9yZSBvdXRsaWVycyBpbiBDb3ZlcmVkLmJ5DQpgYGAgIA0KDQpEZW1vbnN0cmF0ZSBhbiBhdHRlbXB0IHRvIGZpeCBvdXRsaWVycyB3aXRoIGhpZ2hseSBza2V3ZWQgZGF0YTogIA0KYGBge3J9DQpib3hwbG90KHRoZS5iZWF0bGVzLnNvbmdzLm51bSRUb3AuNTAuQmlsbGJvYXJkLCB4bGFiID0gIlRvcC41MC5CaWxsYm9hcmQiKQ0KYm94cGxvdC5zdGF0cyh0aGUuYmVhdGxlcy5zb25ncy5udW0kVG9wLjUwLkJpbGxib2FyZCkNCmJveHBsb3Quc3RhdHModGhlLmJlYXRsZXMuc29uZ3MubnVtJFRvcC41MC5CaWxsYm9hcmQpJG91dA0KYm94cGxvdC5zdGF0cyh0aGUuYmVhdGxlcy5zb25ncy5udW0kVG9wLjUwLkJpbGxib2FyZCkkc3RhdHNbYygxLCA1KV0NCnRlbXAgPC0gdGhlLmJlYXRsZXMuc29uZ3MubnVtJFRvcC41MC5CaWxsYm9hcmQgIyBzYXZlIGN1cnJlbnQgVG9wLjUwLkJpbGxib2FyZCwgZm9yIHJlc3RvcmluZyBpdCBsYXRlcg0KdGhlLmJlYXRsZXMuc29uZ3MubnVtIDwtIGZpeE91dGxpZXJzKHRoZS5iZWF0bGVzLnNvbmdzLm51bSwgIlRvcC41MC5CaWxsYm9hcmQiKQ0KYm94cGxvdCh0aGUuYmVhdGxlcy5zb25ncy5udW0kVG9wLjUwLkJpbGxib2FyZCwgeGxhYiA9ICJUb3AuNTAuQmlsbGJvYXJkIikNCnRoZS5iZWF0bGVzLnNvbmdzLm51bSRUb3AuNTAuQmlsbGJvYXJkIDwtIHRlbXAgIyByZXN0b3JlIFRvcC41MC5CaWxsYm9hcmQNCmBgYCAgDQoNClN1bW1hcml6ZSB0aGUgcmVzdWx0cyBzbyBmYXI6ICANCmBgYHtyfQ0Kc3VtbWFyeSh0aGUuYmVhdGxlcy5zb25ncy5udW0pDQpgYGAgIA0KDQojIyMgUGF0dGVybnMgaW4gdGhlIGRhdGENCg0KU2VlIGlmIHRoZXJlIGFyZSBzb21lIHBhdHRlcm5zIGluIHRoZSBkYXRhLCBwYWlyd2lzZSwgdG8gcG9zc2libHkgaW5kaWNhdGUgY2x1c3RlcnM6ICANCmBwYWlycyh+IDxjb2x1bW4gMSBuYW1lPiArIDxjb2x1bW4gMiBuYW1lPiArIC4uLixgICANCmArICAgICBkYXRhID0gPGRhdGFmcmFtZT4pYCAgDQpgYGB7cn0NCnBhaXJzKH4gRHVyYXRpb24gKyBPdGhlci5yZWxlYXNlcyArIENvdmVyZWQuYnkgKyBUb3AuNTAuQmlsbGJvYXJkLCAgIyBubyBhbnkgc3RyaWtpbmcgcGF0dGVybiwgaS5lLiANCiAgICAgIHRoZS5iZWF0bGVzLnNvbmdzLm51bSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBubyB2aXN1YWwgaW5kaWNhdGlvbiBvZiBjbHVzdGVycw0KYGBgICANCg0KIyMjIEstTWVhbnMgd2l0aCAyIHZhcmlhYmxlcw0KDQpUcnkgSy1NZWFucyB3aXRoIDIgdmFyaWFibGVzLiAgDQoNClBsb3QgdGhlIGRhdGEgZmlyc3Q6ICANCmA8c2NhdHRlcnBsb3Q+IDwtYA0KYCsgZ2dwbG90KDxkYXRhc2V0PiwgYWVzKHggPSA8bnVtLnZhci4xPiwgeSA9IDxudW0udmFyLjI+KSkgK2AgIA0KYCsgZ2VvbV9wb2ludChzaGFwZSA9IDxuPiwgICAgICAgICAjIDxuPiA9IDE6IGhvbGxvdyBjaXJjbGUsIG5vIGZpbGw7YCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgPG4+ID0gMjE6IGNpcmNsZSB0aGF0IGNhbiBiZSBmaWxsZWRgICANCmArICAgICAgICAgICAgZmlsbCA9IDxjb2xvciAxPiwgICAgIyBjb2xvciBvZiBwb2ludCBmaWxsIChvcHRpb25hbClgICANCmArICAgICAgICAgICAgY29sb3IgPSA8Y29sb3IgMj4sICAgIyBjb2xvciBvZiBwb2ludCBsaW5lIChvcHRpb25hbClgICANCmArICAgICAgICAgICAgc2l6ZSA9IDxzPikgICAgICAgICAgIyBzaXplICBvZiBwb2ludCBsaW5lIChvcHRpb25hbClgICANCmA8c2NhdHRlcnBsb3Q+IDwtIDxzY2F0dGVycGxvdD4gKyB4bGFiKCI8eCBsYWJlbD4iKSAgICAgICAgICAgICAgICAgICAgIyBsYWJlbC9jYXB0aW9uIG9uIHgtYXhpc2AgIA0KYDxzY2F0dGVycGxvdD4gPC0gPHNjYXR0ZXJwbG90PiArIHlsYWIoIjx5IGxhYmVsPiIpICAgICAgICAgICAgICAgICAgICAjIGxhYmVsL2NhcHRpb24gb24geS1heGlzYCAgDQpgPHNjYXR0ZXJwbG90PiA8LSA8c2NhdHRlcnBsb3Q+ICsgZ2d0aXRsZSgiPHNjYXR0ZXJwbG90IHRpdGxlPiIpICAgICAgICMgc2NhdHRlcnBsb3QgdGl0bGVgICANCmA8c2NhdHRlcnBsb3Q+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBwbG90IGl0YCAgDQoNCkFsdGVybmF0aXZlbHk6ICANCmA8c2NhdHRlcnBsb3Q+IDwtYCAgDQpgKyAgZ2dwbG90KDxkYXRhc2V0PiwgYWVzKHggPSA8bnVtLnZhci4xPiwgeSA9IDxudW0udmFyLjI+KSkgK2AgIA0KYCsgIGdlb21fcG9pbnQoc2hhcGUgPSA8bj4sICAgICAgICAgIyA8bj4gPSAxOiBob2xsb3cgY2lyY2xlLCBubyBmaWxsO2AgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyA8bj4gPSAyMTogY2lyY2xlIHRoYXQgY2FuIGJlIGZpbGxlZGAgIA0KYCsgICAgICAgICAgICAgZmlsbCA9IDxjb2xvciAxPiwgICAgIyBjb2xvciBvZiBwb2ludCBmaWxsIChvcHRpb25hbClgICANCmArICAgICAgICAgICAgIGNvbG9yID0gPGNvbG9yIDI+LCAgICMgY29sb3Igb2YgcG9pbnQgbGluZSAob3B0aW9uYWwpYCAgDQpgKyAgICAgICAgICAgICBzaXplID0gPHM+KSAgICAgICAgICAjIHNpemUgIG9mIHBvaW50IGxpbmUgKG9wdGlvbmFsKWAgIA0KYDxzY2F0dGVycGxvdD4gPC0gPHNjYXR0ZXJwbG90PiArYCAgDQpgKyBsYWJzKHggPSAiPHggbGFiZWw+IiwgICAgICAgICAgICAgICAgICAgICAjIGxhYmVsL2NhcHRpb24gb24geC1heGlzYCAgDQpgKyAgICAgIHkgPSAiPHkgbGFiZWw+IiwgICAgICAgICAgICAgICAgICAgICAjIGxhYmVsL2NhcHRpb24gb24geS1heGlzYCAgDQpgKyAgICAgIHRpdGxlID0gIjxzY2F0dGVycGxvdCB0aXRsZT4iKSArICAgICAjIHNjYXR0ZXJwbG90IHRpdGxlYCAgDQpgPHNjYXR0ZXJwbG90PiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcGxvdCBpdGAgIA0KYGBge3J9DQpzY2F0dGVycGxvdC5PdGhlci5yZWxlYXNlcy52cy5Db3ZlcmVkLmJ5IDwtIA0KICBnZ3Bsb3QodGhlLmJlYXRsZXMuc29uZ3MubnVtLCBhZXMoeCA9IE90aGVyLnJlbGVhc2VzLCB5ID0gQ292ZXJlZC5ieSkpICsNCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxLCBmaWxsID0gInllbGxvdyIsIHNpemUgPSAyKSArIA0KICBsYWJzKHggPSAiT3RoZXIucmVsZWFzZXMiLCB5ID0gIkNvdmVyZWQuYnkiLCB0aXRsZSA9ICJDb3ZlcmVkLmJ5IHZzLiBPdGhlci5yZWxlYXNlcyIpICsNCiAgdGhlbWVfYncoKQ0Kc2NhdHRlcnBsb3QuT3RoZXIucmVsZWFzZXMudnMuQ292ZXJlZC5ieQ0KYGBgICANCg0KU3Vic2V0IHRoZSBvcmlnaW5hbCBkYXRhIHRvIGluY2x1ZGUgb25seSB0aGUgdmFyaWFibGVzIHRvIGJlIHVzZWQgaW4gSy1NZWFuczogIA0KYDxuZXcgZGF0YWZyYW1lPiA8LSA8ZGF0YWZyYW1lPlssIGMoIjxjb2wxIG5hbWU+IiwgIjxjb2wyIG5hbWU+IildYCAgDQpgPG5ldyBkYXRhZnJhbWU+IDwtIDxkYXRhZnJhbWU+WywgPGNvbDEgaW5kZXg+Ojxjb2wyIGluZGV4Pl1gICANCkFsdGVybmF0aXZlbHk6ICANCmA8bmV3IGRhdGFmcmFtZT4gPC0gc3Vic2V0KDxkYXRhZnJhbWU+LCBzZWxlY3QgPSBjKCI8Y29sMSBuYW1lPiIsICI8Y29sMiBuYW1lPiIpKWAgIA0KYDxuZXcgZGF0YWZyYW1lPiA8LSBzdWJzZXQoPGRhdGFmcmFtZT4sIHNlbGVjdCA9IGMoPGNvbDEgaW5kZXg+Ojxjb2wyIGluZGV4PikpYCAgDQpgYGB7cn0NCnRoZS5iZWF0bGVzLnNvbmdzLm51bS4xIDwtIHRoZS5iZWF0bGVzLnNvbmdzLm51bVssIGMoIk90aGVyLnJlbGVhc2VzIiwgIkNvdmVyZWQuYnkiKV0NCiMgdGhlLmJlYXRsZXMuc29uZ3MubnVtLjEgPC0gc3Vic2V0KHRoZS5iZWF0bGVzLnNvbmdzLm51bSwgc2VsZWN0ID0gYygiT3RoZXIucmVsZWFzZXMiLCAiQ292ZXJlZC5ieSIpKQ0Kc3VtbWFyeSh0aGUuYmVhdGxlcy5zb25ncy5udW0uMSkNCmhlYWQodGhlLmJlYXRsZXMuc29uZ3MubnVtLjEpDQpgYGAgIA0KDQojIyMgRGF0YSBub3JtYWxpemF0aW9uDQoNClJlcXVpcmVkIGJ5IEstTWVhbnMgd2hlbiB0aGUgdmFyaWFibGVzIGhhdmUgZGlmZmVyZW50IHJhbmdlcy4gIA0KYHJhbmdlKDxkYXRhZnJhbWUgd2l0aCBudW1lcmljIGNvbHVtbnM+JDxudW1lcmljIGNvbHVtbiAxPiAjIGNoZWNrIHRoZSByYW5nZSBvZiA8bnVtZXJpYyBjb2x1bW4gMT5gICANCmByYW5nZSg8ZGF0YWZyYW1lIHdpdGggbnVtZXJpYyBjb2x1bW5zPiQ8bnVtZXJpYyBjb2x1bW4gMj4gIyBjaGVjayB0aGUgcmFuZ2Ugb2YgPG51bWVyaWMgY29sdW1uIDI+YCAgDQpgLi4uYCAgDQpgIyBpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyU2ltIilgICANCmBsaWJyYXJ5KGNsdXN0ZXJTaW0pYCAgDQpgPGRhdGFmcmFtZSB3aXRoIG51bWVyaWMgY29sdW1ucz4gPC0gICAgICAgICAgICAgICAgICAgICAgICMgd29ya3Mgd2l0aCB2ZWN0b3JzIGFuZCBtYXRyaWNlcyBhcyB3ZWxsYCAgDQpgKyBkYXRhLk5vcm1hbGl6YXRpb24oPGRhdGFmcmFtZSB3aXRoIG51bWVyaWMgY29sdW1ucz4sYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJuNCIsICAgICAgICAgICAgICAgICAgICAgICAgICMgbm9ybWFsaXphdGlvbjogKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSlgICANCmArICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uID0gImNvbHVtbiIpICAgICAgICAgICAgIyBub3JtYWxpemF0aW9uIGJ5IGNvbHVtbnNgICANCmBgYHtyfQ0KcmFuZ2UodGhlLmJlYXRsZXMuc29uZ3MubnVtLjEkT3RoZXIucmVsZWFzZXMpDQpyYW5nZSh0aGUuYmVhdGxlcy5zb25ncy5udW0uMSRDb3ZlcmVkLmJ5KQ0KbGlicmFyeShjbHVzdGVyU2ltKQ0KdGhlLmJlYXRsZXMuc29uZ3MubnVtLjIgPC0gDQogIGRhdGEuTm9ybWFsaXphdGlvbih0aGUuYmVhdGxlcy5zb25ncy5udW0uMSwgDQogICAgICAgICAgICAgICAgICAgICB0eXBlID0gIm40IiwgDQogICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uID0gImNvbHVtbiIpDQp0YWlsKHRoZS5iZWF0bGVzLnNvbmdzLm51bS4yKQ0KYGBgICANCg0KIyMjIEstTWVhbnMgZm9yIEsgPSAzDQoNClJ1biBLLU1lYW5zIGZvciBLID0gMzogIA0KYHNldC5zZWVkKDxzZWVkPilgICANCmA8Y2x1c3RlcnM+IDwtIGttZWFucyh4ID0gPG5vcm1hbGl6ZWQgZGF0YWZyYW1lPixgICANCmArICAgICAgICAgICAgICAgICAgICBjZW50ZXJzID0gMywgICAgICAgICAgICAgICAgICAgICAgICAgIyBLID0gM2AgIA0KYCsgICAgICAgICAgICAgICAgICAgIGl0ZXIubWF4ID0gMjAsICAgICAgICAgICAgICAgICAgICAgICAjIG1heCBudW1iZXIgb2YgaXRlcmF0aW9ucyBhbGxvd2VkYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgbnN0YXJ0ID0gMTAwMCkgICAgICAgICAgICAgICAgICAgICAgICMgbm8uIG9mIGluaXRpYWwgY29uZmlndXJhdGlvbnNgICANCmArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAocmVwb3J0IGdlbmVyYXRlZCBiYXNlZCBvbiB0aGUgYmVzdCBvbmUpYCAgDQpgPGNsdXN0ZXJzPmAgIA0KYGBge3J9DQpzZXQuc2VlZCg4ODgpDQpjbHVzdGVycy5LMyA8LSBrbWVhbnMoeCA9IHRoZS5iZWF0bGVzLnNvbmdzLm51bS4yLCBjZW50ZXJzID0gMywgaXRlci5tYXggPSAyMCwgbnN0YXJ0ID0gMTAwMCkNCmNsdXN0ZXJzLkszDQpgYGAgIA0KDQpUaGUgbWVhbmluZyBvZiBwYXJhbWV0ZXJzIGluIHRoZSByZXBvcnQ6ICANCg0KKiB3aXRoaW5zcyAod2l0aGluX1NTKSAtIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzLCBpLmUuLCBzdW0gb2Ygc3F1YXJlZCBkaWZmZXJlbmNlcyBiZXR3ZWVuIGluZGl2aWR1YWwgZGF0YSBwb2ludHMgaW4gYSBjbHVzdGVyIGFuZCB0aGUgY2x1c3RlciBjZW50ZXI7IGl0IGlzIGNvbXB1dGVkIGZvciBlYWNoIGNsdXN0ZXIgIA0KKiB0b3RzcyAodG90YWxfU1MpIC0gdGhlIHN1bSBvZiBzcXVhcmVkIGRpZmZlcmVuY2VzIG9mIGVhY2ggZGF0YSBwb2ludCB0byB0aGUgZ2xvYmFsIHNhbXBsZSBtZWFuICANCiogYmV0d2VlbnNzIChiZXR3ZWVuX1NTKSAtIHRoZSBzdW0gb2Ygc3F1YXJlZCBkaWZmZXJlbmNlcyBvZiBlYWNoIGNsdXN0ZXIgY2VudGVyIHRvIHRoZSBnbG9iYWwgc2FtcGxlIG1lYW47IHRoZSBzcXVhcmVkIGRpZmZlcmVuY2Ugb2YgZWFjaCBjbHVzdGVyIGNlbnRlciB0byB0aGUgZ2xvYmFsIHNhbXBsZSBtZWFuIGlzIG11bHRpcGxpZWQgYnkgdGhlIG51bWJlciBvZiBkYXRhIHBvaW50cyBpbiB0aGF0IGNsdXN0ZXIgIA0KKiB0b3Qud2l0aGluc3MgLSB0aGUgc3VtIG9mIHNxdWFyZWQgZGlmZmVyZW5jZXMgYmV0d2VlbiBkYXRhIHBvaW50cyBhbmQgY2x1c3RlciBjZW50ZXJzICh0aGUgc3VtIG9mIHdpdGhpbl9TUyBmb3IgYWxsIHRoZSBjbHVzdGVycykgIA0KKiBiZXR3ZWVuX1NTIC8gdG90YXRfU1MgLSBpbmRpY2F0ZXMgaG93IHdlbGwgdGhlIHNhbXBsZSBzcGxpdHMgaW50byBjbHVzdGVyczsgdGhlIGhpZ2hlciB0aGUgcmF0aW8sIHRoZSBiZXR0ZXIgY2x1c3RlcmluZyAgDQoNCkFkZCB0aGUgdmVjdG9yIG9mIGNsdXN0ZXJzIHRvIHRoZSBkYXRhZnJhbWU6ICANCmA8bm9ybWFsaXplZCBkYXRhZnJhbWU+JDxuZXcgY29sdW1uPiA8LSBmYWN0b3IoPGNsdXN0ZXJzPiRjbHVzdGVyKSAgICMgPGNsdXN0ZXJzPjogZnJvbSB0aGUgcHJldmlvdXMgc3RlcGAgIA0KYGhlYWQoPG5vcm1hbGl6ZWQgZGF0YWZyYW1lPilgICANCmBgYHtyfQ0KdGhlLmJlYXRsZXMuc29uZ3MubnVtLjIkQ2x1c3RlciA8LSBmYWN0b3IoY2x1c3RlcnMuSzMkY2x1c3RlcikNCmhlYWQodGhlLmJlYXRsZXMuc29uZ3MubnVtLjIpDQpgYGAgIA0KDQpQbG90IHRoZSBjbHVzdGVycyBpbiBhIG5ldyBzY2F0dGVycGxvdCwgdXNpbmcgcGxvdENsdXN0ZXJzKCkgdXRpbGl0eSBmdW5jdGlvbjogIA0KYHNvdXJjZSgiUGxvdCBjbHVzdGVycy5SIilgICANCmBwbG90Q2x1c3RlcnMoKSA8LSBmdW5jdGlvbihkYXRhc2V0LCAgICAgICAgICAgIyBkYXRhc2V0IHdpdGggdGhlIGNsdXN0ZXIgY29sdW1uYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgeGNvbCwgICAgICAgICAgICAgICMgZGF0YXNldCBjb2x1bW4gZm9yIHRoZSB4LWF4aXMsIHBhc3NlZCBhcyBhIHN0cmluZ2AgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgICAgIHljb2wsICAgICAgICAgICAgICAjIGRhdGFzZXQgY29sdW1uIGZvciB0aGUgeS1heGlzLCBwYXNzZWQgYXMgYSBzdHJpbmdgICANCmArICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyY29sLCAgICAgICAgIyBkYXRhc2V0IGNvbHVtbiBzaG93aW5nIHRoZSBjbHVzdGVycywgcGFzc2VkIGFzIGEgc3RyaW5nYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUsICAgICAgICAgICAgICMgcGxvdCB0aXRsZWAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgICAgIHgubGFiZWwsICAgICAgICAgICAjIHgtYXhpcyBsYWJlbGAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgICAgIHkubGFiZWwsICAgICAgICAgICAjIHktYXhpcyBsYWJlbGAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5sYWJlbCwgICAgICAjIHBsb3QgbGVnZW5kIGxhYmVsYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvdy5jZW50ZXJzLmZsYWcsICMgcGxvdCBjbHVzdGVyIGNlbnRlcnMgaWYgVFJVRWAgIA0KYCsgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJzKSB7ICAgICAgICAjIGNsdXN0ZXJzIGNvbXB1dGVkIGJ5IGttZWFucygpIGluIGEgcHJldmlvdXMgc3RlcGAgIA0KYGBge3J9DQpzb3VyY2UoIlBsb3QgY2x1c3RlcnMuUiIpDQpwbG90Q2x1c3RlcnModGhlLmJlYXRsZXMuc29uZ3MubnVtLjIsDQogICAgICAgICAgICAgIk90aGVyLnJlbGVhc2VzIiwNCiAgICAgICAgICAgICAiQ292ZXJlZC5ieSIsDQogICAgICAgICAgICAgIkNsdXN0ZXIiLA0KICAgICAgICAgICAgICJDbHVzdGVyczogKE90aGVyLnJlbGVhc2VzLCBDb3ZlcmVkLmJ5KSwgbm9ybWFsaXplZCIsDQogICAgICAgICAgICAgIk90aGVyLnJlbGVhc2VzIiwNCiAgICAgICAgICAgICAiQ292ZXJlZC5ieSIsDQogICAgICAgICAgICAgIkNsdXN0ZXIiLA0KICAgICAgICAgICAgIFRSVUUsDQogICAgICAgICAgICAgY2x1c3RlcnMuSzMpDQpgYGAgIA0KDQojIyMgT3B0aW1hbCB2YWx1ZSBmb3IgSw0KRmluZCB0aGUgb3B0aW1hbCB2YWx1ZSBmb3IgSywgdXNpbmcgdGhlIEVsYm93IG1ldGhvZCAoYSBjYWxsIHRvIHRoZSBhcHByb3ByaWF0ZSB1dGlsaXR5IGZ1bmN0aW9uKTsgYW4gYXBwcm9wcmlhdGUgZGF0YWZyYW1lIHNob3VsZCBiZSBwYXNzZWQgYXMgdGhlIHBhcmFtZXRlciAoanVzdCBudW1lcmljIHZhcmlhYmxlcywgbm8gY2x1c3RlcnMpOiAgDQpgc291cmNlKCJFbGJvdyBtZXRob2QuUiIpYCAgDQpgPGVsYm93IHBhcmFtZXRlcnM+IDwtIGdldEVsYm93TWV0aG9kUGFyYW1ldGVycyg8ZGF0YWZyYW1lPlssIGMoPG4xPiwgPG4yPiwgLi4uKV0pICAgIyBsZWF2ZSBvdXQgdGhlIGNsdXN0ZXIgY29sdW1uYCAgDQpgPGVsYm93IHBhcmFtZXRlcnM+YCAgDQpgcGxvdEVsYm93KDxlbGJvdyBwYXJhbWV0ZXJzPilgICANCmBgYHtyfQ0Kc291cmNlKCJFbGJvdyBtZXRob2QuUiIpDQplbGJvdy4yIDwtIGdldEVsYm93TWV0aG9kUGFyYW1ldGVycyh0aGUuYmVhdGxlcy5zb25ncy5udW0uMlssIGMoMSwyKV0pICAgIyByZW1vdmUgdGhlIENsdXN0ZXIgY29sdW1uDQplbGJvdy4yDQpwbG90RWxib3coZWxib3cuMikNCmBgYCAgDQoNClNob3cgZGlmZmVyZW5jZXMgaW4gdG90LndpdGhpbnNzIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mIEsgbW9yZSBwcmVjaXNlbHksIHVzaW5nIHRoZSBnZXREaWZmZXJlbmNlcygpIHV0aWxpdHkgZnVuY3Rpb246ICANCmBzb3VyY2UoIkVsYm93IG1ldGhvZC5SIilgICANCmA8ZGlmZiBkYXRhZnJhbWU+IDwtYCAgDQpgKyBkYXRhLmZyYW1lKEsgPSA8bjE+OjxuMj4sYCAgDQpgKyAgICAgICAgICAgIGRpZmYudG90LndpdGhpbnNzID1gICANCmArICAgICAgICAgICAgICBnZXREaWZmZXJlbmNlcyg8ZWxib3cgc3RhdHM+JDx0b3Qud2l0aGluc3M+KSwgICAgICAgICAgICAgICAgICMgZnJvbSB0aGUgcHJldmlvdXMgc3RlcGAgIA0KYCsgICAgICAgICAgICBkaWZmLnJhdGlvID1gICANCmArICAgICAgICAgICAgICBnZXREaWZmZXJlbmNlcyg8ZWxib3cgc3RhdHM+JDxyYXRpbyBiZXR3ZWVuX1NTIC8gdG90YWxfU1M+KSkgICMgZnJvbSB0aGUgcHJldmlvdXMgc3RlcGAgIA0KYG5hbWVzKDxkaWZmIGRhdGFmcmFtZT4pIDwtIGMoIksiLCAiRGlmZmVyZW5jZSBpbiB0b3Qud2l0aGluc3MiLCAiRGlmZmVyZW5jZSBpbiBiZXR3ZWVuX1NTIC8gdG90YWxfU1MiKWAgIA0KYDxkaWZmIGRhdGFmcmFtZT5gICANCmBgYHtyfQ0KZGYuZGlmZmVyZW5jZXMgPC0gZGF0YS5mcmFtZShLID0gMjo4LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2V0RGlmZmVyZW5jZXMoZWxib3cuMlssIDJdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdldERpZmZlcmVuY2VzKGVsYm93LjJbLCAzXSkpDQpuYW1lcyhkZi5kaWZmZXJlbmNlcykgPC0gYygiSyIsICJEaWZmZXJlbmNlIGluIHRvdC53aXRoaW5zcyIsICJEaWZmZXJlbmNlIGluIGJldHdlZW5fU1MgLyB0b3RhbF9TUyIpDQpkZi5kaWZmZXJlbmNlcw0KYGBgICANCg0KIyMjIEstTWVhbnMgZm9yIEsgPSA0DQoNClJ1biBLLU1lYW5zIGFsc28gZm9yIEsgPSA0OiAgDQpgYGB7cn0NCnNldC5zZWVkKDgxOCkNCnRoZS5iZWF0bGVzLnNvbmdzLm51bS4yIDwtIHRoZS5iZWF0bGVzLnNvbmdzLm51bS4yWywgLTNdICAgICMgcmVtb3ZlIHRoZSBDbHVzdGVyIGNvbHVtbiBmb3IgdGhlIG5ldyBydW4NCmNsdXN0ZXJzLks0IDwtIGttZWFucyh4ID0gdGhlLmJlYXRsZXMuc29uZ3MubnVtLjIsIGNlbnRlcnMgPSA0LCBpdGVyLm1heCA9IDIwLCBuc3RhcnQgPSAxMDAwKQ0KY2x1c3RlcnMuSzQNCnRoZS5iZWF0bGVzLnNvbmdzLm51bS4yJENsdXN0ZXIgPC0gZmFjdG9yKGNsdXN0ZXJzLks0JGNsdXN0ZXIpDQpoZWFkKHRoZS5iZWF0bGVzLnNvbmdzLm51bS4yKQ0Kc291cmNlKCJQbG90IGNsdXN0ZXJzLlIiKQ0KcGxvdENsdXN0ZXJzKHRoZS5iZWF0bGVzLnNvbmdzLm51bS4yLA0KICAgICAgICAgICAgICJPdGhlci5yZWxlYXNlcyIsDQogICAgICAgICAgICAgIkNvdmVyZWQuYnkiLA0KICAgICAgICAgICAgICJDbHVzdGVyIiwNCiAgICAgICAgICAgICAiQ2x1c3RlcnM6IChPdGhlci5yZWxlYXNlcywgQ292ZXJlZC5ieSksIG5vcm1hbGl6ZWQiLA0KICAgICAgICAgICAgICJPdGhlci5yZWxlYXNlcyIsDQogICAgICAgICAgICAgIkNvdmVyZWQuYnkiLA0KICAgICAgICAgICAgICJDbHVzdGVyIiwNCiAgICAgICAgICAgICBUUlVFLA0KICAgICAgICAgICAgIGNsdXN0ZXJzLks0KQ0KYGBgICANCg0KRXhhbWluZSBjbHVzdGVycyBtb3JlIGNsb3NlbHkgYnkgbG9va2luZyBpbnRvIHRoZSBjbHVzdGVyIGNlbnRlcnMgKG1lYW5zKSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9ucyBmcm9tIHRoZSBjZW50ZXJzLiBJbiBkb2luZyBzbywgdXNlICdyZWd1bGFyJyAobm90IG5vcm1hbGl6ZWQpIGZlYXR1cmVzIGFuZCB0aGUgc3VtbWFyaXplQ2x1c3RlclN0YXRzKCkgdXRpbGl0eSBmdW5jdGlvbjogIA0KYHNvdXJjZSgiU3VtbWFyeSBzdGF0aXN0aWNzIGFib3V0IGNsdXN0ZXJzLlIiKWAgIA0KYDxzdGF0cyBkYXRhZnJhbWU+IDwtIGAgIA0KYCsgc3VtbWFyaXplQ2x1c3RlclN0YXRzKGZlYXR1cmUuc2V0ID0gPGRhdGFmcmFtZSB3aXRoICdyZWd1bGFyJyAobm90IG5vcm1hbGl6ZWQpIGZlYXR1cmVzPiwgYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcnMgPSA8Y2x1c3RlcnM+JGNsdXN0ZXIsICAgIyA8Y2x1c3RlcnM+OiByZXN1bHQgb2Yga21lYW5zKClgICANCmArICAgICAgICAgICAgICAgICAgICAgICBjbC5udW0gPSA8Sz4pICAgICAgICAgICAgICAgICAgICAjIDxLPjogbnVtYmVyIG9mIGNsdXN0ZXJzYCAgDQpDb21wYXJlIGRpc3BlcnNpb24gKHNkKSBpbiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhIGFuZCBpbiB0aGUgY29ycmVzcG9uZGluZyBwbG90cy5gICANCmBgYHtyfQ0Kc291cmNlKCJTdW1tYXJ5IHN0YXRpc3RpY3MgYWJvdXQgY2x1c3RlcnMuUiIpDQpjbHVzdGVycy5LMy5zdGF0cyA8LSBzdW1tYXJpemVDbHVzdGVyU3RhdHMoZmVhdHVyZS5zZXQgPSB0aGUuYmVhdGxlcy5zb25ncy5udW0uMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcnMgPSBjbHVzdGVycy5LMyRjbHVzdGVyLCBjbC5udW0gPSAzKQ0KY2x1c3RlcnMuSzQuc3RhdHMgPC0gc3VtbWFyaXplQ2x1c3RlclN0YXRzKGZlYXR1cmUuc2V0ID0gdGhlLmJlYXRsZXMuc29uZ3MubnVtLjEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJzID0gY2x1c3RlcnMuSzQkY2x1c3RlciwgY2wubnVtID0gNCkNCmBgYCAgDQoNCiMjIyBLLU1lYW5zIHdpdGggYWxsIHZhcmlhYmxlcyANCg0KRGF0YSBub3JtYWxpemF0aW9uOiAgDQpgYGB7cn0NCmxpYnJhcnkoY2x1c3RlclNpbSkNCnRoZS5iZWF0bGVzLnNvbmdzLm51bS40IDwtIA0KICBkYXRhLk5vcm1hbGl6YXRpb24odGhlLmJlYXRsZXMuc29uZ3MubnVtLCANCiAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibjQiLCANCiAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6YXRpb24gPSAiY29sdW1uIikNCnRhaWwodGhlLmJlYXRsZXMuc29uZ3MubnVtLjQpDQpgYGAgIA0KDQpGaW5kIHRoZSBvcHRpbWFsIHZhbHVlIGZvciBLLCB1c2luZyB0aGUgRWxib3cgbWV0aG9kIChhIGNhbGwgdG8gdGhlIGFwcHJvcHJpYXRlIHV0aWxpdHkgZnVuY3Rpb24pOiAgDQpgYGB7cn0NCnNvdXJjZSgiRWxib3cgbWV0aG9kLlIiKQ0KZWxib3cuNCA8LSBnZXRFbGJvd01ldGhvZFBhcmFtZXRlcnModGhlLmJlYXRsZXMuc29uZ3MubnVtLjQpDQplbGJvdy40DQpwbG90RWxib3coZWxib3cuNCkNCmBgYCAgDQoNClNob3cgZGlmZmVyZW5jZXMgaW4gdG90LndpdGhpbnNzIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mIEsgbW9yZSBwcmVjaXNlbHksIHVzaW5nIHRoZSBnZXREaWZmZXJlbmNlcygpIHV0aWxpdHkgZnVuY3Rpb246ICANCmBgYHtyfQ0KZGYuZGlmZmVyZW5jZXMgPC0gZGF0YS5mcmFtZShLID0gMjo4LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2V0RGlmZmVyZW5jZXMoZWxib3cuNFssIDJdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdldERpZmZlcmVuY2VzKGVsYm93LjRbLCAzXSkpDQpuYW1lcyhkZi5kaWZmZXJlbmNlcykgPC0gYygiSyIsICJEaWZmZXJlbmNlIGluIHRvdC53aXRoaW5zcyIsICJEaWZmZXJlbmNlIGluIGJldHdlZW5fU1MgLyB0b3RhbF9TUyIpDQpkZi5kaWZmZXJlbmNlcw0KYGBgICANCg0KUnVuIEstTWVhbnMgZm9yIEsgPSAzLCBzaW5jZSBLID0gMyBzZWVtcyB0byBiZSB0aGUgYmVzdCB2YWx1ZSBmb3IgSzogIA0KYGBge3J9DQpzZXQuc2VlZCg4ODgpDQpjbHVzdGVycy5LMy5hbGwudmFycyA8LSBrbWVhbnMoeCA9IHRoZS5iZWF0bGVzLnNvbmdzLm51bS40LCBjZW50ZXJzID0gMywgaXRlci5tYXggPSAyMCwgbnN0YXJ0ID0gMTAwMCkNCmNsdXN0ZXJzLkszLmFsbC52YXJzDQpgYGAgIA0KDQpSdW4gSy1NZWFucyBhbHNvIGZvciBLID0gNCwgc2luY2UgSyA9IDQgc2VlbXMgdG8gYmUgdGhlIG5leHQgYmVzdCB2YWx1ZSBmb3IgSzogIA0KYGBge3J9DQpzZXQuc2VlZCg4ODgpDQpjbHVzdGVycy5LNC5hbGwudmFycyA8LSBrbWVhbnMoeCA9IHRoZS5iZWF0bGVzLnNvbmdzLm51bS40LCBjZW50ZXJzID0gNCwgaXRlci5tYXggPSAyMCwgbnN0YXJ0ID0gMTAwMCkNCmNsdXN0ZXJzLks0LmFsbC52YXJzDQpgYGAgIA0KDQpFeGFtaW5lIGFuZCBjb21wYXJlIHRoZSBjbHVzdGVyIGNlbnRlcnMgZm9yIEsgPSAzIGFuZCBLID0gNDogIA0KYGBge3J9DQpjbHVzdGVycy5LMy5hbGwudmFycy5zdGF0cyA8LSBzdW1tYXJpemVDbHVzdGVyU3RhdHMoZmVhdHVyZS5zZXQgPSB0aGUuYmVhdGxlcy5zb25ncy5udW0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJzID0gY2x1c3RlcnMuSzMuYWxsLnZhcnMkY2x1c3RlciwgY2wubnVtID0gMykNCmNsdXN0ZXJzLkszLmFsbC52YXJzLnN0YXRzDQoNCmNsdXN0ZXJzLks0LmFsbC52YXJzLnN0YXRzIDwtIHN1bW1hcml6ZUNsdXN0ZXJTdGF0cyhmZWF0dXJlLnNldCA9IHRoZS5iZWF0bGVzLnNvbmdzLm51bSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcnMgPSBjbHVzdGVycy5LNC5hbGwudmFycyRjbHVzdGVyLCBjbC5udW0gPSA0KQ0KY2x1c3RlcnMuSzQuYWxsLnZhcnMuc3RhdHMNCmBgYCAgDQoNCiMjIyBDb21wYXJlIG11bHRpcGxlIGNsdXN0ZXJpbmcgcmVzdWx0cy9zY2hlbWVzDQoNCmAjIGluc3RhbGwucGFja2FnZXMoImZwYyIpYCAgDQpgbGlicmFyeShmcGMpYCAgDQpgP2NsdXN0ZXIuc3RhdHNgICANCmA8Y29tcGFyaXNvbiBjcml0ZXJpYT4gPC0gICAgICAgICAgICAgICMgc3BlY2lmeSBjcml0ZXJpYSAoZnJvbSBjbHVzdGVyLnN0YXRzKCkpIGZvciBjb21wYXJpbmdgICANCmArIGMoIjxjcml0ZXJpb24gMT4iLCAgICAgICAgICAgICAgICAgICMgZGlmZmVyZW50IGNsdXN0ZXJpbmdzIChlLmcuLCAibWF4LmRpYW1ldGVyIiwgIm1pbi5zZXBhcmF0aW9uIixgICANCmArICAgIjxjcml0ZXJpb24gMj4iLCAuLi4pICAgICAgICAgICAgICMgImF2ZXJhZ2UuYmV0d2VlbiIsICJhdmVyYWdlLndpdGhpbiIsICJ3aXRoaW4uY2x1c3Rlci5zcyIsIC4uLilgICANCmA8ZGlzdGFuY2UgbWF0cml4PiA8LWAgIA0KYCsgZGlzdCh4ID0gPG5vcm1hbGl6ZWQgZGF0YXNldD4pYCAgDQpgPGNvbXBhcmlzb24+IDwtIHNhcHBseShsaXN0KDxjbHVzdGVyaW5nIDEgbmFtZT4gPSA8Y2x1c3RlcmluZyAxPiRjbHVzdGVyLCAgICAjIDxjbHVzdGVyaW5nIDE+IGNvbXB1dGVkIGJ5IGttZWFucygpYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjbHVzdGVyaW5nIDIgbmFtZT4gPSA8Y2x1c3RlcmluZyAyPiRjbHVzdGVyLCAgICAjIDxjbHVzdGVyaW5nIDI+IGNvbXB1dGVkIGJ5IGttZWFucygpYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLilgICANCmArICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpYCAgDQpgKyAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXIuc3RhdHMoPGRpc3RhbmNlIG1hdHJpeD4sIHgpKVs8Y29tcGFyaXNvbiBjcml0ZXJpYT4sIF1gICANCkFsdGVybmF0aXZlIDEgLSBzaG93IG91dHB1dCBhcyBhIHRhYmxlIGluIHRoZSBjb25zb2xlOiAgDQpgIyBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpYCAgDQpgbGlicmFyeShrbml0cilgICANCmBrYWJsZSh4ID0gPGNvbXBhcmlzb24+LCBmb3JtYXQgPSAicnN0IilgICANCkFsdGVybmF0aXZlIDIgLSBzaG93IG91dHB1dCBhcyBhIGRhdGFmcmFtZSwgdXNpbmcgYW4gYXBwcm9wcmlhdGUgdXRpbGl0eSBmdW5jdGlvbjogIA0KYHNvdXJjZSgiU3VtbWFyeSBzdGF0aXN0aWNzIGFib3V0IGNsdXN0ZXJzLlIiKWAgIA0KYDxjb21wYXJpc29uLmRmPiA8LSBgICANCmArIGNvbXBhcmVNdWx0aXBsZUNsdXN0ZXJpbmdzKDxjb21wYXJpc29uPikgICAgICAjIHNob3cgY29tcGFyaXNvbiBhcyBhIGRhdGFmcmFtZSwgdXNpbmcgYSBjb3JyZXNwb25kaW5nIHV0aWxpdHkgZnVuY3Rpb25gICANCmA8Y29tcGFyaXNvbi5kZj5gICANCmBgYHtyfQ0KbGlicmFyeShmcGMpDQpjb21wYXJpc29uLmNyaXRlcmlhIDwtIGMoIm1heC5kaWFtZXRlciIsICJtaW4uc2VwYXJhdGlvbiIsICJhdmVyYWdlLmJldHdlZW4iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAiYXZlcmFnZS53aXRoaW4iLCAid2l0aGluLmNsdXN0ZXIuc3MiKSAgDQpkIDwtIGRpc3QoeCA9IHRoZS5iZWF0bGVzLnNvbmdzLm51bS40KQ0KY29tcGFyaXNvbiA8LSBzYXBwbHkobGlzdChjLkszLnZhcjIgPSBjbHVzdGVycy5LMyRjbHVzdGVyLCAjIGNsdXN0ZXJpbmc6IDMgY2x1c3RlcnMsIDIgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgIGMuSzQudmFyMiA9IGNsdXN0ZXJzLks0JGNsdXN0ZXIsICMgY2x1c3RlcmluZzogNCBjbHVzdGVycywgMiB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYy5LMy52YXI0ID0gY2x1c3RlcnMuSzMuYWxsLnZhcnMkY2x1c3RlciwgICMgMyBjbHVzdGVycywgNCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYy5LNC52YXI0ID0gY2x1c3RlcnMuSzQuYWxsLnZhcnMkY2x1c3RlciksICMgNCBjbHVzdGVycywgNCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIGNsdXN0ZXIuc3RhdHMoZCwgeCkpW2NvbXBhcmlzb24uY3JpdGVyaWEsIF0NCg0KIyBBbHRlcm5hdGl2ZSAxIC0gc2hvdyBvdXRwdXQgYXMgYSB0YWJsZSBpbiB0aGUgY29uc29sZTogDQojIGxpYnJhcnkoa25pdHIpDQojIGthYmxlKHggPSBjb21wYXJpc29uLCBmb3JtYXQgPSAicnN0IikNCg0KIyBBbHRlcm5hdGl2ZSAyIC0gc2hvdyBvdXRwdXQgYXMgYSBkYXRhZnJhbWUsIHVzaW5nIGFuIGFwcHJvcHJpYXRlIHV0aWxpdHkgZnVuY3Rpb246IA0KY29tcGFyaXNvbi5kZiA8LSBjb21wYXJlTXVsdGlwbGVDbHVzdGVyaW5ncyhjb21wYXJpc29uKQ0KY29tcGFyaXNvbi5kZg0KYGBgICANCg==