1 Foreword: About the Machine Learning in Medicine (MLM) project

The MLM project has been initialized in 2016 and aims to:

  1. Encourage using Machine Learning techniques in medical research in Vietnam and

  2. Promote the use of R statistical programming language, an open source and leading tool for practicing data science.

2 Introduction

How many features and which ones should be used to develop our model ? This question could be difficult, particularly when we have a very large dataset that contains hundred or thousands of variables. Feature selection can help us to identify the most relevant features for our problem.

Note that feature selection is different from Model selection, Model regularisation and dimensionality reduction. The feature selection procedure consists of selecting a subset of relevant features BEFORE fitting the models, whilst Model selection (Bayesian Model Averaging, Stepwise…) or Regularisation techniques such as boosting, Lasso or Elastic net imply modifications on the model itself. The dimensionality reduction techniques like PCA aims to generate new combinations from all features instead of excluding some of them.

3 Objective

In this case study X13, we will explore 12 simple filter methods of feature selection that are suported by the mlr package.

http://mlr-org.github.io/mlr-tutorial/devel/html/filter_methods/index.html

Filter methods consist of estimating a statistical measure to assign a scoring to each feature. The high ranked features will be considered more relevant and the ones with low rank could be excluded. Different statistical methods could be applied, including the simplest ones like Chi-square test, ANOVA or Kruskal-Wallis test or the scores that based on real machine learning algorithms like Information gain, gain ratio, Random Forest based variable’s importance. Those methods are often univariate and consider the feature independently, or with regard to the dependent variable.

We also introduce the Kuncheva stability index (KSI) for determining the best feature subset(s) for a binary classification problem.

4 Materials and method

Our experiment implies the SPECTF Heart Data Set, as described on the UCI ML database:

https://archive.ics.uci.edu/ml/datasets/SPECTF+Heart

The dataset represents a technique called “Cardiac Single Proton Emission Computed Tomography (SPECT)”. The train subset included 80 image sets and the test subset included 187 image sets. Each of the patients can be classified as normal and abnormal.

The data of these 267 SPECT image sets (patients) were processed to extract features that summarize the original SPECT images. As a result, 44 continuous feature pattern was created for each patient.Classification rule should be developed from these 44 patterns. Assuming that we already decided to apply Logistic model for constructing our classifier, and we would like to use only 15 features instead of 44. Our goal is to identify the most useful subset of 12 features from the original dataset.

5 Data preparation

First, we will prepare the ggplot theme for our experiment

library(tidyverse)

theme_bare <- function(base_size=8,base_family="sans"){theme_bw(base_size = base_size, base_family = base_family)+
  theme(
  axis.line = element_blank(), 
  axis.text.x = element_blank(), 
  axis.text.y = element_blank(),
  axis.ticks = element_blank(), 
  axis.title.x = element_blank(), 
  axis.title.y = element_blank(), 
  legend.position = "bottom", 
  panel.background = element_rect(fill = NA), 
  panel.border = element_blank(), 
  panel.grid.major = element_blank(), 
  panel.grid.minor = element_blank(), 
  plot.margin = unit(c(0,0,0,0), "lines")
)
}



my_theme <- function(base_size =5, base_family = "sans"){
  theme_bw(base_size = base_size, base_family = base_family) +
    theme(
      panel.grid.major = element_line(color = "gray"),
      panel.grid.minor = element_blank(),
      panel.background = element_rect(fill = NA),
      strip.background = element_rect(fill = "#001d60", color = "#00113a", size =0.5),
      strip.text = element_text(face = "bold", size = 5, color = "white"),
      legend.position = "bottom",
      legend.justification = "center",
      legend.background = element_blank(),
      legend.margin = margin(0.5,0.5,0.5,0.5)
    )
}

theme_set(my_theme())

myfillcolors=c("#ff003f","#0094ff", "#ae00ff" , "#94ff00", "#ffc700","#fc1814")

First, we load the data to R:

testset=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/spect/SPECTF.test",sep = ",")%>%as_tibble()
trainset=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/spect/SPECTF.train",sep = ",")%>%as_tibble()

testset$V1<-factor(testset$V1,levels = c(1,0), labels = c("Positive", "Negative") )
trainset$V1<-factor(trainset$V1,levels = c(1,0), labels = c("Positive", "Negative") )

names(testset)=c("Class",paste(rep("F",times=44),c(1:44),sep="") )
names(trainset)=c("Class",paste(rep("F",times=44),c(1:44),sep="") )

6 Data visualising

trainset%>%gather(F1:F44,key="Features",value="Value")%>%ggplot(aes(x=Value,fill=Class))+geom_density(alpha=0.5)+facet_wrap(~Features,scales="free",ncol=4)+scale_fill_manual(values=myfillcolors)
trainset%>%gather(F1:F44,key="Features",value="Value")%>%ggplot(aes(x=Class,y=Value,fill=Class))+geom_boxplot(alpha=0.7)+facet_wrap(~Features,scales="free",ncol=4)+scale_fill_manual(values=myfillcolors)+coord_flip()
library(corrplot)
library(RColorBrewer)

cor_mat=as.matrix(cor(as.matrix(trainset[,-1])))

cor_mat%>%corrplot(.,order="hclust",type="lower",method="ellipse",tl.col="black", tl.srt=45,tl.cex=0.5,col=rev(brewer.pal(n=11, name="RdBu")))

cor_mat=as.matrix(cor(as.matrix(trainset[,-1])))

library(igraph)

diag(cor_mat)<-0

library(ggraph)

m=cor_mat

df=data.frame(row=rownames(m)[row(m)[upper.tri(m)]], 
           col=colnames(m)[col(m)[upper.tri(m)]], 
           corr=m[upper.tri(m)])

cdf=subset(df,abs(corr)>0.5)

names(cdf)=c("from","to","corr")

graph<-graph_from_data_frame(cdf)

ggraph(graph, layout = 'linear',circular=T)+geom_edge_arc(aes(colour = corr,width=corr),alpha=0.3)+geom_node_label(aes(label = name))+coord_fixed()+scale_edge_color_gradient(high="#ff003f",low="#0094ff")+scale_edge_width_continuous()+theme_bare()

7 Feature selection by 12 Filter methods

We introduce a function called “vimplot”. This function will perform a filter method of interest then extract the subset of features and plot the result.

Then we apply the function with 12 different filter methods and save the output within the objects from impvar1 to impvar12

library(FSelector)
library(mlr)
library(randomForestSRC)
library(rJava)
library(MLmetrics)

vimplot=function(data,target,method){
  train.task=makeClassifTask(data=data, target=target)
  imp_var=generateFilterValuesData(train.task, method=method)
  fsdf=imp_var$data%>%.[,-2]
  names(fsdf)=c("Feature","Value")
  fsdf$Method=method
  fsdf$Feature
  vimp=fsdf%>%arrange(desc(Value))%>% select(Feature)
  plot=fsdf%>%ggplot(aes(x=reorder(fsdf$Feature,fsdf$Value),y=Value,fill=Value,color=Value))+geom_segment(aes(x=reorder(fsdf$Feature,fsdf$Value),xend=Feature,y=0,yend=Value),size=1,show.legend = F)+geom_point(size=2,show.legend = F)+scale_x_discrete("Features")+scale_y_continuous(method)+coord_flip()+scale_fill_gradient(low="blue",high="red")+scale_color_gradient(low="blue",high="red")+ggtitle(method)
  return(list(plot=plot,name=vimp))
  }

#ANOVA TEST

res=vimplot(data=trainset,target="Class",method="anova.test")

impvar1 <-res$name

res$plot

# cforest.importance

res=vimplot(data=trainset,target="Class",method="cforest.importance")

impvar2 <-res$name

res$plot

#chi.squared

res=vimplot(data=trainset,target="Class",method="chi.squared")

impvar3 <-res$name

res$plot

#gain.ratio

res=vimplot(data=trainset,target="Class",method="gain.ratio")

impvar4 <-res$name

res$plot

#information.gain

res=vimplot(data=trainset,target="Class",method="information.gain")

impvar5 <-res$name

res$plot

#OneR

res=vimplot(data=trainset,target="Class",method="oneR")
## Class ~ .
## <environment: 0x00000000704520a0>
impvar6 <-res$name

res$plot

#randomForest.importance

res=vimplot(data=trainset,target="Class",method="randomForest.importance")

impvar7 <-res$name

res$plot

#relief

res=vimplot(data=trainset,target="Class",method="relief")

impvar8 <-res$name

res$plot

#symmetrical.uncertainty

res=vimplot(data=trainset,target="Class",method="symmetrical.uncertainty")

impvar9 <-res$name

res$plot

# randomForestSRC.rfsrc

res=vimplot(data=trainset,target="Class",method="randomForestSRC.rfsrc")

impvar10 <-res$name

res$plot

# variance

res=vimplot(data=trainset,target="Class",method="variance")

impvar11<-res$name

res$plot

#univariate.model.score (rpart)

res=vimplot(data=trainset,target="Class",method="univariate.model.score")

impvar12<-res$name

res$plot

8 Kuncheva stability index

The Kuncheva stability index is supported by OmicsMarkeR pacakge that could be installed from Bioconductor platform:

#source("https://bioconductor.org/biocLite.R")
#biocLite("OmicsMarkeR")

First, we will load the package, then apply a loop on our 12 output objects of filter methods (V1 to V12). As mentioned above, we would like to reduce the number of features from 44 to only 15 (thus excluding 29 features). Note that the subsets of 15 features might be different, depending on the filtering method in use.

The Kuncheva stability index aims to measure the consistency level between those 12 feature subsets. The result looks similar to a correlation matrix.

library("OmicsMarkeR")

v1=impvar1$Feature[1:15]
v2=impvar2$Feature[1:15]
v3=impvar3$Feature[1:15]
v4=impvar4$Feature[1:15]
v5=impvar5$Feature[1:15]
v6=impvar6$Feature[1:15]
v7=impvar7$Feature[1:15]
v8=impvar8$Feature[1:15]
v9=impvar9$Feature[1:15]
v10=impvar10$Feature[1:15]
v11=impvar11$Feature[1:15]
v12=impvar12$Feature[1:15]

mat <- matrix(NA,12,12)
varcol <- paste0("v",seq(1,12,by = 1))
varrow <- paste0("v",seq(1,12,by = 1))

for (row in 1:12){
  for(col in 1:12){
    command <- paste0(" mat[row,col] <- kuncheva(",
                      varrow[row],",",
                      varcol[col],",num.features = dim(trainset[,-1])[[2]])")
    
    eval(parse(text = command))
  }
}

This is the KSI matrix

colnames(mat)=c("ANOV","CFRI","CHISQ","GRT","INFG","ONER","RFI","REL","SYMU","SRC","VAR","UMS")
rownames(mat)=c("ANOV","CFRI","CHISQ","GRT","INFG","ONER","RFI","REL","SYMU","SRC","VAR","UMS")

print(mat)
##            ANOV      CFRI     CHISQ       GRT      INFG      ONER
## ANOV  1.0000000 0.9494253 0.7471264 0.7471264 0.7471264 0.5448276
## CFRI  0.9494253 1.0000000 0.7471264 0.7471264 0.7471264 0.5448276
## CHISQ 0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## GRT   0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## INFG  0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## ONER  0.5448276 0.5448276 0.7977011 0.7977011 0.7977011 1.0000000
## RFI   0.7471264 0.7471264 0.7471264 0.7471264 0.7471264 0.5448276
## REL   0.7977011 0.7977011 0.6965517 0.6965517 0.6965517 0.5954023
## SYMU  0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## SRC   0.7977011 0.8482759 0.7977011 0.7977011 0.7977011 0.6459770
## VAR   0.6965517 0.6965517 0.6459770 0.6459770 0.6459770 0.5448276
## UMS   0.6459770 0.6965517 0.6459770 0.6459770 0.6459770 0.5448276
##             RFI       REL      SYMU       SRC       VAR       UMS
## ANOV  0.7471264 0.7977011 0.7471264 0.7977011 0.6965517 0.6459770
## CFRI  0.7471264 0.7977011 0.7471264 0.8482759 0.6965517 0.6965517
## CHISQ 0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## GRT   0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## INFG  0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## ONER  0.5448276 0.5954023 0.7977011 0.6459770 0.5448276 0.5448276
## RFI   1.0000000 0.6459770 0.7471264 0.7977011 0.7471264 0.7471264
## REL   0.6459770 1.0000000 0.6965517 0.7471264 0.6459770 0.6459770
## SYMU  0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## SRC   0.7977011 0.7471264 0.7977011 1.0000000 0.6459770 0.7471264
## VAR   0.7471264 0.6459770 0.6459770 0.6459770 1.0000000 0.5448276
## UMS   0.7471264 0.6459770 0.6459770 0.7471264 0.5448276 1.0000000

Based on this consistency matrix, we can develop some graphs to visualise the coherence level between 12 feaure filtering methods:

mat%>%corrplot(.,order="hclust",type="lower",method="pie",tl.col="black", tl.srt=45,tl.cex=0.5,col=(brewer.pal(n=9, name="Reds")))

diag(mat)<-0

m=mat

df=data.frame(row=rownames(m)[row(m)[upper.tri(m)]], 
              col=colnames(m)[col(m)[upper.tri(m)]], 
              corr=m[upper.tri(m)])

df$Coherent=df$corr>=0.8
names(df)=c("from","to","corr","coherent")

cdf=subset(df,abs(corr)>=0.8)

graph1<-graph_from_data_frame(df)
graph2<-graph_from_data_frame(cdf)

ggraph(graph2, layout = 'linear',circular=T)+geom_edge_arc2(aes(colour = corr,width=corr),alpha=0.3)+geom_node_label(aes(label = name))+coord_fixed()+scale_edge_color_gradient(high="#ff003f",low="#0094ff")+scale_edge_width_continuous()+theme_bare()

ggraph(graph1, layout = 'fr')+geom_edge_density(aes(fill = coherent))+geom_edge_link(alpha=0.6)+geom_node_label(aes(label = name))+theme_bare()+coord_fixed()+scale_edge_fill_manual(values=c("#0094ff","#ff003f"))

The network revealed that the coherence level was highest among methods that based on Decision tree or Random Forest algorithms; consistency level was lower between OneR or ANOVA. The methods that shared a high value of KSI could be considered equivalent, as they would result the same features subset. Note that such result could change for each iteration, as the Random forest and permutation algorithms are random.

9 EVALUATING THE PERFORMANCE OF 12 LOGISTIC MODELS

The last step consists of fitting 12 logistic models that based on 12 filtered feature subsets, then evaluating the performance of each model. A good feature subset would results accurate model, as evaluated by AUC, Balanced accuracy, F1 score and balanced error rate

set.seed(123)

res <- NULL

for (i in 1:12){
  
  train=trainset[,c(eval(parse(text = varrow[i])),"Class")]
  test=testset[,c(eval(parse(text = varrow[i])),"Class")]
  
  #Logistic regression
  train.task=makeClassifTask(data=train, target="Class")
  test.task =makeClassifTask(data=test, target="Class")
  
  # Build Model
  learner_logis=makeLearner("classif.logreg", predict.type="prob")
  model=mlr::train(learner_logis, train.task)
  
  # Prediction and Evaluation
  pred=predict(model, test.task)
  mes=list(mlr::auc,mlr::bac,mlr::ber,mlr::f1)
  perf=performance(pred,measures=mes)
  auc=perf[[1]]
  bac=perf[[2]]
  ber=perf[[3]]
  f1=perf[[4]]
  line=data.frame(FSmethod = i,AUC=auc,BAC=bac,BER=ber,F1=f1)
  res[[i]] <- line
}

Here is the verdict:

res <- do.call(rbind,res)
res$FSmethod <- c("ANOV","CFRI","CHISQ","GRT","INFG","ONER","RFI","REL","SYMU","SRC","VAR","UMS")

res
res%>%gather(AUC:F1,key="Metric",value="Value")%>%ggplot()+geom_tile(aes(x=reorder(Metric,Value),y=reorder(FSmethod,Value),fill=Value),color="black")+geom_text(aes(x=reorder(Metric,Value),y=reorder(FSmethod,Value),label=round(Value,4)),color="black")+scale_fill_distiller(palette = "Spectral")+theme_bw()+scale_x_discrete("Metrics")+scale_y_discrete("FS methods")

res%>%gather(AUC:F1,key="Metric",value="Value")%>%ggplot()+geom_point(shape=21,color="black",size=3,aes(x=reorder(FSmethod,Value),y=Value,fill=Metric),color="black")+scale_fill_manual(values=myfillcolors)+theme_bw()+scale_x_discrete("FSmethod")+scale_y_continuous("Values")+facet_wrap(~Metric,scales="free")+coord_flip()

Based on those result, Random Forest based importance (RFI) based method were the most powerful methods for feature selection.

V(graph1)$AUC=res$AUC

ggraph(graph1, layout = 'linear',circular=T)+geom_edge_fan(alpha=0.1,aes(colour = corr,width=corr))+geom_node_point(shape=21,aes(size=AUC,fill=AUC))+geom_node_text(aes(label = name),nudge_x=,nudge_y=0.1)+theme_bare()+coord_fixed()+scale_edge_color_gradient(low="#0094ff",high="#ff003f")+scale_fill_gradient(high="red",low="gold")

V(graph1)$BAC=res$BAC

ggraph(graph1, layout = 'linear',circular=T)+geom_edge_fan(alpha=0.1,aes(colour = corr,width=corr))+geom_node_point(shape=21,aes(size=BAC,fill=BAC))+geom_node_text(aes(label = name),nudge_x=,nudge_y=0.1)+theme_bare()+coord_fixed()+scale_edge_color_gradient(low="#0094ff",high="#ff003f")+scale_fill_gradient(high="red",low="gold")

The network analysis that combines KSI and AUC/Accuracy values showed that the coherence level as measured by KSI is NOT associated to the performance as measured by AUC or BAC. This means the methods that have the same KSI value could have an equivalent performance (as they generate the same feature subset), but the KSI is not a measure of goodness of filter method, as a method with low consistency in regard to other methods could become a better one.

10 Conclusion

In thiscase study, we introduced different methods for filtering the features. The consistency level among features subsets could be evaluated by the Kuncheva stability index. However, this index is not a measure of the performance for each method. The Random Forest based criteria, including the variable importance, information gain and Gain ratio are very useful for selecting features.

In a next tutorial, we will discuss about the methods that modify our models instead of input data, i.e Model selection.

Reference: https://pdfs.semanticscholar.org/9c4f/006d5547cddecef588e66ab6b33e6845b33a.pdf

11 Contribution:

This joint tutorial was created by Viet Tran and Nam Le Dong as a part of MLM project

Viet Tran designed the data experiment. He is also the author of most functions and loops for Feature selection and Model performance evaluation.

Nam Le Dong handled the graphs, network analysis and Rmarkdown writing.

See you in the next tutorial and thank for joining us !

END

LS0tDQp0aXRsZTogIk1MTSBDYXNlIHN0dWR5IFgxMyINCnN1YnRpdGxlOiAiRmVhdHVyZSBzZWxlY3Rpb24iDQphdXRob3I6ICJWaWV0IFRyYW4sIE5hbSBMZSBEb25nIg0KZGF0ZTogIjE4IEp1bmUsIDIwMTciDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogam91cm5hbA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGVycm9yID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoaWdyYXBoKQ0KbGlicmFyeShnZ3JhcGgpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KEZTZWxlY3RvcikNCmxpYnJhcnkobWxyKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3RTUkMpDQpsaWJyYXJ5KHJKYXZhKQ0KbGlicmFyeShNTG1ldHJpY3MpDQpsaWJyYXJ5KCJPbWljc01hcmtlUiIpDQpgYGANCg0KIVtdKGZlYXR1cmVzZWxlY3Rpb24ucG5nKQ0KDQojIEZvcmV3b3JkOiBBYm91dCB0aGUgTWFjaGluZSBMZWFybmluZyBpbiBNZWRpY2luZSAoTUxNKSBwcm9qZWN0DQoNClRoZSBNTE0gcHJvamVjdCBoYXMgYmVlbiBpbml0aWFsaXplZCBpbiAyMDE2IGFuZCBhaW1zIHRvOiANCg0KMSkgRW5jb3VyYWdlIHVzaW5nIE1hY2hpbmUgTGVhcm5pbmcgdGVjaG5pcXVlcyBpbiBtZWRpY2FsIHJlc2VhcmNoIGluIFZpZXRuYW0gYW5kDQoNCjIpIFByb21vdGUgdGhlIHVzZSBvZiBSIHN0YXRpc3RpY2FsIHByb2dyYW1taW5nIGxhbmd1YWdlLCBhbiBvcGVuIHNvdXJjZSBhbmQgbGVhZGluZyB0b29sIGZvciBwcmFjdGljaW5nIGRhdGEgc2NpZW5jZS4NCg0KDQojIEludHJvZHVjdGlvbg0KDQpIb3cgbWFueSBmZWF0dXJlcyBhbmQgd2hpY2ggb25lcyBzaG91bGQgYmUgdXNlZCB0byBkZXZlbG9wIG91ciBtb2RlbCA/IFRoaXMgcXVlc3Rpb24gY291bGQgYmUgZGlmZmljdWx0LCBwYXJ0aWN1bGFybHkgd2hlbiB3ZSBoYXZlIGEgdmVyeSBsYXJnZSBkYXRhc2V0IHRoYXQgY29udGFpbnMgaHVuZHJlZCBvciB0aG91c2FuZHMgb2YgdmFyaWFibGVzLiBGZWF0dXJlIHNlbGVjdGlvbiBjYW4gaGVscCB1cyB0byBpZGVudGlmeSB0aGUgbW9zdCByZWxldmFudCBmZWF0dXJlcyBmb3Igb3VyIHByb2JsZW0uDQoNCk5vdGUgdGhhdCBmZWF0dXJlIHNlbGVjdGlvbiBpcyBkaWZmZXJlbnQgZnJvbSBNb2RlbCBzZWxlY3Rpb24sIE1vZGVsIHJlZ3VsYXJpc2F0aW9uIGFuZCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uIFRoZSBmZWF0dXJlIHNlbGVjdGlvbiBwcm9jZWR1cmUgY29uc2lzdHMgb2Ygc2VsZWN0aW5nIGEgc3Vic2V0IG9mIHJlbGV2YW50IGZlYXR1cmVzIEJFRk9SRSBmaXR0aW5nIHRoZSBtb2RlbHMsIHdoaWxzdCBNb2RlbCBzZWxlY3Rpb24gKEJheWVzaWFuIE1vZGVsIEF2ZXJhZ2luZywgU3RlcHdpc2UuLi4pIG9yIFJlZ3VsYXJpc2F0aW9uIHRlY2huaXF1ZXMgc3VjaCBhcyBib29zdGluZywgTGFzc28gb3IgRWxhc3RpYyBuZXQgaW1wbHkgbW9kaWZpY2F0aW9ucyBvbiB0aGUgbW9kZWwgaXRzZWxmLiBUaGUgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHRlY2huaXF1ZXMgbGlrZSBQQ0EgYWltcyB0byBnZW5lcmF0ZSBuZXcgY29tYmluYXRpb25zIGZyb20gYWxsIGZlYXR1cmVzIGluc3RlYWQgb2YgZXhjbHVkaW5nIHNvbWUgb2YgdGhlbS4NCg0KIyBPYmplY3RpdmUNCg0KSW4gdGhpcyBjYXNlIHN0dWR5IFgxMywgd2Ugd2lsbCBleHBsb3JlIDEyIHNpbXBsZSBmaWx0ZXIgbWV0aG9kcyBvZiBmZWF0dXJlIHNlbGVjdGlvbiB0aGF0IGFyZSBzdXBvcnRlZCBieSB0aGUgbWxyIHBhY2thZ2UuIA0KDQo8aHR0cDovL21sci1vcmcuZ2l0aHViLmlvL21sci10dXRvcmlhbC9kZXZlbC9odG1sL2ZpbHRlcl9tZXRob2RzL2luZGV4Lmh0bWw+DQoNCkZpbHRlciBtZXRob2RzIGNvbnNpc3Qgb2YgZXN0aW1hdGluZyBhIHN0YXRpc3RpY2FsIG1lYXN1cmUgdG8gYXNzaWduIGEgc2NvcmluZyB0byBlYWNoIGZlYXR1cmUuIFRoZSBoaWdoIHJhbmtlZCBmZWF0dXJlcyB3aWxsIGJlIGNvbnNpZGVyZWQgbW9yZSByZWxldmFudCBhbmQgdGhlIG9uZXMgd2l0aCBsb3cgcmFuayBjb3VsZCBiZSBleGNsdWRlZC4gRGlmZmVyZW50IHN0YXRpc3RpY2FsIG1ldGhvZHMgY291bGQgYmUgYXBwbGllZCwgaW5jbHVkaW5nIHRoZSBzaW1wbGVzdCBvbmVzIGxpa2UgQ2hpLXNxdWFyZSB0ZXN0LCBBTk9WQSBvciBLcnVza2FsLVdhbGxpcyB0ZXN0IG9yIHRoZSBzY29yZXMgdGhhdCBiYXNlZCBvbiByZWFsIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBsaWtlIEluZm9ybWF0aW9uIGdhaW4sIGdhaW4gcmF0aW8sIFJhbmRvbSBGb3Jlc3QgYmFzZWQgdmFyaWFibGUncyBpbXBvcnRhbmNlLiBUaG9zZSBtZXRob2RzIGFyZSBvZnRlbiB1bml2YXJpYXRlIGFuZCBjb25zaWRlciB0aGUgZmVhdHVyZSBpbmRlcGVuZGVudGx5LCBvciB3aXRoIHJlZ2FyZCB0byB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLg0KDQpXZSBhbHNvIGludHJvZHVjZSB0aGUgS3VuY2hldmEgc3RhYmlsaXR5IGluZGV4IChLU0kpIGZvciBkZXRlcm1pbmluZyB0aGUgYmVzdCBmZWF0dXJlIHN1YnNldChzKSBmb3IgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbS4NCg0KIyBNYXRlcmlhbHMgYW5kIG1ldGhvZA0KDQpPdXIgZXhwZXJpbWVudCBpbXBsaWVzIHRoZSBTUEVDVEYgSGVhcnQgRGF0YSBTZXQsIGFzIGRlc2NyaWJlZCBvbiB0aGUgVUNJIE1MIGRhdGFiYXNlOg0KDQo8aHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL1NQRUNURitIZWFydD4NCg0KVGhlIGRhdGFzZXQgcmVwcmVzZW50cyBhIHRlY2huaXF1ZSBjYWxsZWQgIkNhcmRpYWMgU2luZ2xlIFByb3RvbiBFbWlzc2lvbiBDb21wdXRlZCBUb21vZ3JhcGh5IChTUEVDVCkiLiBUaGUgdHJhaW4gc3Vic2V0IGluY2x1ZGVkIDgwIGltYWdlIHNldHMgYW5kIHRoZSB0ZXN0IHN1YnNldCBpbmNsdWRlZCAxODcgaW1hZ2Ugc2V0cy4gRWFjaCBvZiB0aGUgcGF0aWVudHMgY2FuIGJlIGNsYXNzaWZpZWQgYXMgbm9ybWFsIGFuZCBhYm5vcm1hbC4gDQoNClRoZSBkYXRhIG9mIHRoZXNlIDI2NyBTUEVDVCBpbWFnZSBzZXRzIChwYXRpZW50cykgd2VyZSBwcm9jZXNzZWQgdG8gZXh0cmFjdCBmZWF0dXJlcyB0aGF0IHN1bW1hcml6ZSB0aGUgb3JpZ2luYWwgU1BFQ1QgaW1hZ2VzLiBBcyBhIHJlc3VsdCwgNDQgY29udGludW91cyBmZWF0dXJlIHBhdHRlcm4gd2FzIGNyZWF0ZWQgZm9yIGVhY2ggcGF0aWVudC5DbGFzc2lmaWNhdGlvbiBydWxlIHNob3VsZCBiZSBkZXZlbG9wZWQgZnJvbSB0aGVzZSA0NCBwYXR0ZXJucy4gDQpBc3N1bWluZyB0aGF0IHdlIGFscmVhZHkgZGVjaWRlZCB0byBhcHBseSBMb2dpc3RpYyBtb2RlbCBmb3IgY29uc3RydWN0aW5nIG91ciBjbGFzc2lmaWVyLCBhbmQgd2Ugd291bGQgbGlrZSB0byB1c2Ugb25seSAxNSBmZWF0dXJlcyBpbnN0ZWFkIG9mIDQ0LiBPdXIgZ29hbCBpcyB0byBpZGVudGlmeSB0aGUgbW9zdCB1c2VmdWwgc3Vic2V0IG9mIDEyIGZlYXR1cmVzIGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQuDQoNCiMgRGF0YSBwcmVwYXJhdGlvbg0KDQpGaXJzdCwgd2Ugd2lsbCBwcmVwYXJlIHRoZSBnZ3Bsb3QgdGhlbWUgZm9yIG91ciBleHBlcmltZW50DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KdGhlbWVfYmFyZSA8LSBmdW5jdGlvbihiYXNlX3NpemU9OCxiYXNlX2ZhbWlseT0ic2FucyIpe3RoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkrDQogIHRoZW1lKA0KICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksIA0KICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgDQogIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLA0KICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCANCiAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCANCiAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLCANCiAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIA0KICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksIA0KICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksIA0KICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCANCiAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogIHBsb3QubWFyZ2luID0gdW5pdChjKDAsMCwwLDApLCAibGluZXMiKQ0KKQ0KfQ0KDQoNCg0KbXlfdGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID01LCBiYXNlX2ZhbWlseSA9ICJzYW5zIil7DQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkgKw0KICAgIHRoZW1lKA0KICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksDQogICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLA0KICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiMwMDFkNjAiLCBjb2xvciA9ICIjMDAxMTNhIiwgc2l6ZSA9MC41KSwNCiAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDUsIGNvbG9yID0gIndoaXRlIiksDQogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsDQogICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIGxlZ2VuZC5tYXJnaW4gPSBtYXJnaW4oMC41LDAuNSwwLjUsMC41KQ0KICAgICkNCn0NCg0KdGhlbWVfc2V0KG15X3RoZW1lKCkpDQoNCm15ZmlsbGNvbG9ycz1jKCIjZmYwMDNmIiwiIzAwOTRmZiIsICIjYWUwMGZmIiAsICIjOTRmZjAwIiwgIiNmZmM3MDAiLCIjZmMxODE0IikNCg0KDQpgYGANCg0KRmlyc3QsIHdlIGxvYWQgdGhlIGRhdGEgdG8gUjoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0ZXN0c2V0PXJlYWQudGFibGUoImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9zcGVjdC9TUEVDVEYudGVzdCIsc2VwID0gIiwiKSU+JWFzX3RpYmJsZSgpDQp0cmFpbnNldD1yZWFkLnRhYmxlKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvc3BlY3QvU1BFQ1RGLnRyYWluIixzZXAgPSAiLCIpJT4lYXNfdGliYmxlKCkNCg0KdGVzdHNldCRWMTwtZmFjdG9yKHRlc3RzZXQkVjEsbGV2ZWxzID0gYygxLDApLCBsYWJlbHMgPSBjKCJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpICkNCnRyYWluc2V0JFYxPC1mYWN0b3IodHJhaW5zZXQkVjEsbGV2ZWxzID0gYygxLDApLCBsYWJlbHMgPSBjKCJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpICkNCg0KbmFtZXModGVzdHNldCk9YygiQ2xhc3MiLHBhc3RlKHJlcCgiRiIsdGltZXM9NDQpLGMoMTo0NCksc2VwPSIiKSApDQpuYW1lcyh0cmFpbnNldCk9YygiQ2xhc3MiLHBhc3RlKHJlcCgiRiIsdGltZXM9NDQpLGMoMTo0NCksc2VwPSIiKSApDQoNCmBgYA0KDQojIERhdGEgdmlzdWFsaXNpbmcNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHJlc3VsdHM9ImhpZGUiLGZpZy5zaG93ID0iaGlkZSJ9DQoNCnRyYWluc2V0JT4lZ2F0aGVyKEYxOkY0NCxrZXk9IkZlYXR1cmVzIix2YWx1ZT0iVmFsdWUiKSU+JWdncGxvdChhZXMoeD1WYWx1ZSxmaWxsPUNsYXNzKSkrZ2VvbV9kZW5zaXR5KGFscGhhPTAuNSkrZmFjZXRfd3JhcCh+RmVhdHVyZXMsc2NhbGVzPSJmcmVlIixuY29sPTQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1teWZpbGxjb2xvcnMpDQoNCnRyYWluc2V0JT4lZ2F0aGVyKEYxOkY0NCxrZXk9IkZlYXR1cmVzIix2YWx1ZT0iVmFsdWUiKSU+JWdncGxvdChhZXMoeD1DbGFzcyx5PVZhbHVlLGZpbGw9Q2xhc3MpKStnZW9tX2JveHBsb3QoYWxwaGE9MC43KStmYWNldF93cmFwKH5GZWF0dXJlcyxzY2FsZXM9ImZyZWUiLG5jb2w9NCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW15ZmlsbGNvbG9ycykrY29vcmRfZmxpcCgpDQoNCmBgYA0KDQohW10oRlMxLnBuZykNCg0KIVtdKEZTMi5wbmcpDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KDQpjb3JfbWF0PWFzLm1hdHJpeChjb3IoYXMubWF0cml4KHRyYWluc2V0WywtMV0pKSkNCg0KY29yX21hdCU+JWNvcnJwbG90KC4sb3JkZXI9ImhjbHVzdCIsdHlwZT0ibG93ZXIiLG1ldGhvZD0iZWxsaXBzZSIsdGwuY29sPSJibGFjayIsIHRsLnNydD00NSx0bC5jZXg9MC41LGNvbD1yZXYoYnJld2VyLnBhbChuPTExLCBuYW1lPSJSZEJ1IikpKQ0KDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSIsZmlnLnNob3cgPSJoaWRlIn0NCmNvcl9tYXQ9YXMubWF0cml4KGNvcihhcy5tYXRyaXgodHJhaW5zZXRbLC0xXSkpKQ0KDQpsaWJyYXJ5KGlncmFwaCkNCg0KZGlhZyhjb3JfbWF0KTwtMA0KDQpsaWJyYXJ5KGdncmFwaCkNCg0KbT1jb3JfbWF0DQoNCmRmPWRhdGEuZnJhbWUocm93PXJvd25hbWVzKG0pW3JvdyhtKVt1cHBlci50cmkobSldXSwgDQogICAgICAgICAgIGNvbD1jb2xuYW1lcyhtKVtjb2wobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICBjb3JyPW1bdXBwZXIudHJpKG0pXSkNCg0KY2RmPXN1YnNldChkZixhYnMoY29ycik+MC41KQ0KDQpuYW1lcyhjZGYpPWMoImZyb20iLCJ0byIsImNvcnIiKQ0KDQpncmFwaDwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGNkZikNCg0KZ2dyYXBoKGdyYXBoLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfYXJjKGFlcyhjb2xvdXIgPSBjb3JyLHdpZHRoPWNvcnIpLGFscGhhPTAuMykrZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IG5hbWUpKStjb29yZF9maXhlZCgpK3NjYWxlX2VkZ2VfY29sb3JfZ3JhZGllbnQoaGlnaD0iI2ZmMDAzZiIsbG93PSIjMDA5NGZmIikrc2NhbGVfZWRnZV93aWR0aF9jb250aW51b3VzKCkrdGhlbWVfYmFyZSgpDQoNCmBgYA0KDQohW10oRlMzLnBuZykNCg0KIyBGZWF0dXJlIHNlbGVjdGlvbiBieSAxMiBGaWx0ZXIgbWV0aG9kcw0KDQpXZSBpbnRyb2R1Y2UgYSBmdW5jdGlvbiBjYWxsZWQgInZpbXBsb3QiLiBUaGlzIGZ1bmN0aW9uIHdpbGwgcGVyZm9ybSBhIGZpbHRlciBtZXRob2Qgb2YgaW50ZXJlc3QgdGhlbiBleHRyYWN0IHRoZSBzdWJzZXQgb2YgZmVhdHVyZXMgYW5kIHBsb3QgdGhlIHJlc3VsdC4NCg0KVGhlbiB3ZSBhcHBseSB0aGUgZnVuY3Rpb24gd2l0aCAxMiBkaWZmZXJlbnQgZmlsdGVyIG1ldGhvZHMgYW5kIHNhdmUgdGhlIG91dHB1dCB3aXRoaW4gdGhlIG9iamVjdHMgZnJvbSBpbXB2YXIxIHRvIGltcHZhcjEyDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpsaWJyYXJ5KEZTZWxlY3RvcikNCmxpYnJhcnkobWxyKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3RTUkMpDQpsaWJyYXJ5KHJKYXZhKQ0KbGlicmFyeShNTG1ldHJpY3MpDQoNCnZpbXBsb3Q9ZnVuY3Rpb24oZGF0YSx0YXJnZXQsbWV0aG9kKXsNCiAgdHJhaW4udGFzaz1tYWtlQ2xhc3NpZlRhc2soZGF0YT1kYXRhLCB0YXJnZXQ9dGFyZ2V0KQ0KICBpbXBfdmFyPWdlbmVyYXRlRmlsdGVyVmFsdWVzRGF0YSh0cmFpbi50YXNrLCBtZXRob2Q9bWV0aG9kKQ0KICBmc2RmPWltcF92YXIkZGF0YSU+JS5bLC0yXQ0KICBuYW1lcyhmc2RmKT1jKCJGZWF0dXJlIiwiVmFsdWUiKQ0KICBmc2RmJE1ldGhvZD1tZXRob2QNCiAgZnNkZiRGZWF0dXJlDQogIHZpbXA9ZnNkZiU+JWFycmFuZ2UoZGVzYyhWYWx1ZSkpJT4lIHNlbGVjdChGZWF0dXJlKQ0KICBwbG90PWZzZGYlPiVnZ3Bsb3QoYWVzKHg9cmVvcmRlcihmc2RmJEZlYXR1cmUsZnNkZiRWYWx1ZSkseT1WYWx1ZSxmaWxsPVZhbHVlLGNvbG9yPVZhbHVlKSkrZ2VvbV9zZWdtZW50KGFlcyh4PXJlb3JkZXIoZnNkZiRGZWF0dXJlLGZzZGYkVmFsdWUpLHhlbmQ9RmVhdHVyZSx5PTAseWVuZD1WYWx1ZSksc2l6ZT0xLHNob3cubGVnZW5kID0gRikrZ2VvbV9wb2ludChzaXplPTIsc2hvdy5sZWdlbmQgPSBGKStzY2FsZV94X2Rpc2NyZXRlKCJGZWF0dXJlcyIpK3NjYWxlX3lfY29udGludW91cyhtZXRob2QpK2Nvb3JkX2ZsaXAoKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iYmx1ZSIsaGlnaD0icmVkIikrc2NhbGVfY29sb3JfZ3JhZGllbnQobG93PSJibHVlIixoaWdoPSJyZWQiKStnZ3RpdGxlKG1ldGhvZCkNCiAgcmV0dXJuKGxpc3QocGxvdD1wbG90LG5hbWU9dmltcCkpDQogIH0NCg0KI0FOT1ZBIFRFU1QNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9ImFub3ZhLnRlc3QiKQ0KDQppbXB2YXIxIDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KIyBjZm9yZXN0LmltcG9ydGFuY2UNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9ImNmb3Jlc3QuaW1wb3J0YW5jZSIpDQoNCmltcHZhcjIgPC1yZXMkbmFtZQ0KDQpyZXMkcGxvdA0KDQojY2hpLnNxdWFyZWQNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9ImNoaS5zcXVhcmVkIikNCg0KaW1wdmFyMyA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNnYWluLnJhdGlvDQoNCnJlcz12aW1wbG90KGRhdGE9dHJhaW5zZXQsdGFyZ2V0PSJDbGFzcyIsbWV0aG9kPSJnYWluLnJhdGlvIikNCg0KaW1wdmFyNCA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNpbmZvcm1hdGlvbi5nYWluDQoNCnJlcz12aW1wbG90KGRhdGE9dHJhaW5zZXQsdGFyZ2V0PSJDbGFzcyIsbWV0aG9kPSJpbmZvcm1hdGlvbi5nYWluIikNCg0KaW1wdmFyNSA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNPbmVSDQoNCnJlcz12aW1wbG90KGRhdGE9dHJhaW5zZXQsdGFyZ2V0PSJDbGFzcyIsbWV0aG9kPSJvbmVSIikNCg0KaW1wdmFyNiA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNyYW5kb21Gb3Jlc3QuaW1wb3J0YW5jZQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0icmFuZG9tRm9yZXN0LmltcG9ydGFuY2UiKQ0KDQppbXB2YXI3IDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KI3JlbGllZg0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0icmVsaWVmIikNCg0KaW1wdmFyOCA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNzeW1tZXRyaWNhbC51bmNlcnRhaW50eQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0ic3ltbWV0cmljYWwudW5jZXJ0YWludHkiKQ0KDQppbXB2YXI5IDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KIyByYW5kb21Gb3Jlc3RTUkMucmZzcmMNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9InJhbmRvbUZvcmVzdFNSQy5yZnNyYyIpDQoNCmltcHZhcjEwIDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KIyB2YXJpYW5jZQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0idmFyaWFuY2UiKQ0KDQppbXB2YXIxMTwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KI3VuaXZhcmlhdGUubW9kZWwuc2NvcmUgKHJwYXJ0KQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0idW5pdmFyaWF0ZS5tb2RlbC5zY29yZSIpDQoNCmltcHZhcjEyPC1yZXMkbmFtZQ0KDQpyZXMkcGxvdA0KDQpgYGANCg0KIyBLdW5jaGV2YSBzdGFiaWxpdHkgaW5kZXgNCg0KVGhlIEt1bmNoZXZhIHN0YWJpbGl0eSBpbmRleCBpcyBzdXBwb3J0ZWQgYnkgT21pY3NNYXJrZVIgcGFjYWtnZSB0aGF0IGNvdWxkIGJlIGluc3RhbGxlZCBmcm9tIEJpb2NvbmR1Y3RvciBwbGF0Zm9ybToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCiNzb3VyY2UoImh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9iaW9jTGl0ZS5SIikNCiNiaW9jTGl0ZSgiT21pY3NNYXJrZVIiKQ0KYGBgDQoNCkZpcnN0LCB3ZSB3aWxsIGxvYWQgdGhlIHBhY2thZ2UsIHRoZW4gYXBwbHkgYSBsb29wIG9uIG91ciAxMiBvdXRwdXQgb2JqZWN0cyBvZiBmaWx0ZXIgbWV0aG9kcyAoVjEgdG8gVjEyKS4gQXMgbWVudGlvbmVkIGFib3ZlLCB3ZSB3b3VsZCBsaWtlIHRvIHJlZHVjZSB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIGZyb20gNDQgdG8gb25seSAxNSAodGh1cyBleGNsdWRpbmcgMjkgZmVhdHVyZXMpLiBOb3RlIHRoYXQgdGhlIHN1YnNldHMgb2YgMTUgZmVhdHVyZXMgbWlnaHQgYmUgZGlmZmVyZW50LCBkZXBlbmRpbmcgb24gdGhlIGZpbHRlcmluZyBtZXRob2QgaW4gdXNlLiANCg0KVGhlIEt1bmNoZXZhIHN0YWJpbGl0eSBpbmRleCBhaW1zIHRvIG1lYXN1cmUgdGhlIGNvbnNpc3RlbmN5IGxldmVsIGJldHdlZW4gdGhvc2UgMTIgZmVhdHVyZSBzdWJzZXRzLiBUaGUgcmVzdWx0IGxvb2tzIHNpbWlsYXIgdG8gYSBjb3JyZWxhdGlvbiBtYXRyaXguIA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoIk9taWNzTWFya2VSIikNCg0KdjE9aW1wdmFyMSRGZWF0dXJlWzE6MTVdDQp2Mj1pbXB2YXIyJEZlYXR1cmVbMToxNV0NCnYzPWltcHZhcjMkRmVhdHVyZVsxOjE1XQ0KdjQ9aW1wdmFyNCRGZWF0dXJlWzE6MTVdDQp2NT1pbXB2YXI1JEZlYXR1cmVbMToxNV0NCnY2PWltcHZhcjYkRmVhdHVyZVsxOjE1XQ0Kdjc9aW1wdmFyNyRGZWF0dXJlWzE6MTVdDQp2OD1pbXB2YXI4JEZlYXR1cmVbMToxNV0NCnY5PWltcHZhcjkkRmVhdHVyZVsxOjE1XQ0KdjEwPWltcHZhcjEwJEZlYXR1cmVbMToxNV0NCnYxMT1pbXB2YXIxMSRGZWF0dXJlWzE6MTVdDQp2MTI9aW1wdmFyMTIkRmVhdHVyZVsxOjE1XQ0KDQptYXQgPC0gbWF0cml4KE5BLDEyLDEyKQ0KdmFyY29sIDwtIHBhc3RlMCgidiIsc2VxKDEsMTIsYnkgPSAxKSkNCnZhcnJvdyA8LSBwYXN0ZTAoInYiLHNlcSgxLDEyLGJ5ID0gMSkpDQoNCmZvciAocm93IGluIDE6MTIpew0KICBmb3IoY29sIGluIDE6MTIpew0KICAgIGNvbW1hbmQgPC0gcGFzdGUwKCIgbWF0W3Jvdyxjb2xdIDwtIGt1bmNoZXZhKCIsDQogICAgICAgICAgICAgICAgICAgICAgdmFycm93W3Jvd10sIiwiLA0KICAgICAgICAgICAgICAgICAgICAgIHZhcmNvbFtjb2xdLCIsbnVtLmZlYXR1cmVzID0gZGltKHRyYWluc2V0WywtMV0pW1syXV0pIikNCiAgICANCiAgICBldmFsKHBhcnNlKHRleHQgPSBjb21tYW5kKSkNCiAgfQ0KfQ0KDQpgYGANCg0KVGhpcyBpcyB0aGUgS1NJIG1hdHJpeA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KY29sbmFtZXMobWF0KT1jKCJBTk9WIiwiQ0ZSSSIsIkNISVNRIiwiR1JUIiwiSU5GRyIsIk9ORVIiLCJSRkkiLCJSRUwiLCJTWU1VIiwiU1JDIiwiVkFSIiwiVU1TIikNCnJvd25hbWVzKG1hdCk9YygiQU5PViIsIkNGUkkiLCJDSElTUSIsIkdSVCIsIklORkciLCJPTkVSIiwiUkZJIiwiUkVMIiwiU1lNVSIsIlNSQyIsIlZBUiIsIlVNUyIpDQoNCnByaW50KG1hdCkNCg0KYGBgDQoNCkJhc2VkIG9uIHRoaXMgY29uc2lzdGVuY3kgbWF0cml4LCB3ZSBjYW4gZGV2ZWxvcCBzb21lIGdyYXBocyB0byB2aXN1YWxpc2UgdGhlIGNvaGVyZW5jZSBsZXZlbCBiZXR3ZWVuIDEyIGZlYXVyZSBmaWx0ZXJpbmcgbWV0aG9kczoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQptYXQlPiVjb3JycGxvdCguLG9yZGVyPSJoY2x1c3QiLHR5cGU9Imxvd2VyIixtZXRob2Q9InBpZSIsdGwuY29sPSJibGFjayIsIHRsLnNydD00NSx0bC5jZXg9MC41LGNvbD0oYnJld2VyLnBhbChuPTksIG5hbWU9IlJlZHMiKSkpDQoNCmRpYWcobWF0KTwtMA0KDQptPW1hdA0KDQpkZj1kYXRhLmZyYW1lKHJvdz1yb3duYW1lcyhtKVtyb3cobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICAgICBjb2w9Y29sbmFtZXMobSlbY29sKG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgICAgY29ycj1tW3VwcGVyLnRyaShtKV0pDQoNCmRmJENvaGVyZW50PWRmJGNvcnI+PTAuOA0KbmFtZXMoZGYpPWMoImZyb20iLCJ0byIsImNvcnIiLCJjb2hlcmVudCIpDQoNCmNkZj1zdWJzZXQoZGYsYWJzKGNvcnIpPj0wLjgpDQoNCmdyYXBoMTwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGRmKQ0KZ3JhcGgyPC1ncmFwaF9mcm9tX2RhdGFfZnJhbWUoY2RmKQ0KDQpnZ3JhcGgoZ3JhcGgyLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfYXJjMihhZXMoY29sb3VyID0gY29ycix3aWR0aD1jb3JyKSxhbHBoYT0wLjMpK2dlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBuYW1lKSkrY29vcmRfZml4ZWQoKStzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGhpZ2g9IiNmZjAwM2YiLGxvdz0iIzAwOTRmZiIpK3NjYWxlX2VkZ2Vfd2lkdGhfY29udGludW91cygpK3RoZW1lX2JhcmUoKQ0KDQpnZ3JhcGgoZ3JhcGgxLCBsYXlvdXQgPSAnZnInKStnZW9tX2VkZ2VfZGVuc2l0eShhZXMoZmlsbCA9IGNvaGVyZW50KSkrZ2VvbV9lZGdlX2xpbmsoYWxwaGE9MC42KStnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSkpK3RoZW1lX2JhcmUoKStjb29yZF9maXhlZCgpK3NjYWxlX2VkZ2VfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMwMDk0ZmYiLCIjZmYwMDNmIikpDQoNCmBgYA0KDQpUaGUgbmV0d29yayByZXZlYWxlZCB0aGF0IHRoZSBjb2hlcmVuY2UgbGV2ZWwgd2FzIGhpZ2hlc3QgYW1vbmcgbWV0aG9kcyB0aGF0IGJhc2VkIG9uIERlY2lzaW9uIHRyZWUgb3IgUmFuZG9tIEZvcmVzdCBhbGdvcml0aG1zOyBjb25zaXN0ZW5jeSBsZXZlbCB3YXMgbG93ZXIgYmV0d2VlbiBPbmVSIG9yIEFOT1ZBLiBUaGUgbWV0aG9kcyB0aGF0IHNoYXJlZCBhIGhpZ2ggdmFsdWUgb2YgS1NJIGNvdWxkIGJlIGNvbnNpZGVyZWQgZXF1aXZhbGVudCwgYXMgdGhleSB3b3VsZCByZXN1bHQgdGhlIHNhbWUgZmVhdHVyZXMgc3Vic2V0LiBOb3RlIHRoYXQgc3VjaCByZXN1bHQgY291bGQgY2hhbmdlIGZvciBlYWNoIGl0ZXJhdGlvbiwgYXMgdGhlIFJhbmRvbSBmb3Jlc3QgYW5kIHBlcm11dGF0aW9uIGFsZ29yaXRobXMgYXJlIHJhbmRvbS4NCg0KIyBFVkFMVUFUSU5HIFRIRSBQRVJGT1JNQU5DRSBPRiAxMiBMT0dJU1RJQyBNT0RFTFMNCg0KVGhlIGxhc3Qgc3RlcCBjb25zaXN0cyBvZiBmaXR0aW5nIDEyIGxvZ2lzdGljIG1vZGVscyB0aGF0IGJhc2VkIG9uIDEyIGZpbHRlcmVkIGZlYXR1cmUgc3Vic2V0cywgdGhlbiBldmFsdWF0aW5nIHRoZSBwZXJmb3JtYW5jZSBvZiBlYWNoIG1vZGVsLiBBIGdvb2QgZmVhdHVyZSBzdWJzZXQgd291bGQgcmVzdWx0cyBhY2N1cmF0ZSBtb2RlbCwgYXMgZXZhbHVhdGVkIGJ5IEFVQywgQmFsYW5jZWQgYWNjdXJhY3ksIEYxIHNjb3JlIGFuZCBiYWxhbmNlZCBlcnJvciByYXRlDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzKQ0KDQpyZXMgPC0gTlVMTA0KDQpmb3IgKGkgaW4gMToxMil7DQogIA0KICB0cmFpbj10cmFpbnNldFssYyhldmFsKHBhcnNlKHRleHQgPSB2YXJyb3dbaV0pKSwiQ2xhc3MiKV0NCiAgdGVzdD10ZXN0c2V0WyxjKGV2YWwocGFyc2UodGV4dCA9IHZhcnJvd1tpXSkpLCJDbGFzcyIpXQ0KICANCiAgI0xvZ2lzdGljIHJlZ3Jlc3Npb24NCiAgdHJhaW4udGFzaz1tYWtlQ2xhc3NpZlRhc2soZGF0YT10cmFpbiwgdGFyZ2V0PSJDbGFzcyIpDQogIHRlc3QudGFzayA9bWFrZUNsYXNzaWZUYXNrKGRhdGE9dGVzdCwgdGFyZ2V0PSJDbGFzcyIpDQogIA0KICAjIEJ1aWxkIE1vZGVsDQogIGxlYXJuZXJfbG9naXM9bWFrZUxlYXJuZXIoImNsYXNzaWYubG9ncmVnIiwgcHJlZGljdC50eXBlPSJwcm9iIikNCiAgbW9kZWw9bWxyOjp0cmFpbihsZWFybmVyX2xvZ2lzLCB0cmFpbi50YXNrKQ0KICANCiAgIyBQcmVkaWN0aW9uIGFuZCBFdmFsdWF0aW9uDQogIHByZWQ9cHJlZGljdChtb2RlbCwgdGVzdC50YXNrKQ0KICBtZXM9bGlzdChtbHI6OmF1YyxtbHI6OmJhYyxtbHI6OmJlcixtbHI6OmYxKQ0KICBwZXJmPXBlcmZvcm1hbmNlKHByZWQsbWVhc3VyZXM9bWVzKQ0KICBhdWM9cGVyZltbMV1dDQogIGJhYz1wZXJmW1syXV0NCiAgYmVyPXBlcmZbWzNdXQ0KICBmMT1wZXJmW1s0XV0NCiAgbGluZT1kYXRhLmZyYW1lKEZTbWV0aG9kID0gaSxBVUM9YXVjLEJBQz1iYWMsQkVSPWJlcixGMT1mMSkNCiAgcmVzW1tpXV0gPC0gbGluZQ0KfQ0KDQpgYGANCg0KSGVyZSBpcyB0aGUgdmVyZGljdDogDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmVzIDwtIGRvLmNhbGwocmJpbmQscmVzKQ0KcmVzJEZTbWV0aG9kIDwtIGMoIkFOT1YiLCJDRlJJIiwiQ0hJU1EiLCJHUlQiLCJJTkZHIiwiT05FUiIsIlJGSSIsIlJFTCIsIlNZTVUiLCJTUkMiLCJWQVIiLCJVTVMiKQ0KDQpyZXMNCg0KcmVzJT4lZ2F0aGVyKEFVQzpGMSxrZXk9Ik1ldHJpYyIsdmFsdWU9IlZhbHVlIiklPiVnZ3Bsb3QoKStnZW9tX3RpbGUoYWVzKHg9cmVvcmRlcihNZXRyaWMsVmFsdWUpLHk9cmVvcmRlcihGU21ldGhvZCxWYWx1ZSksZmlsbD1WYWx1ZSksY29sb3I9ImJsYWNrIikrZ2VvbV90ZXh0KGFlcyh4PXJlb3JkZXIoTWV0cmljLFZhbHVlKSx5PXJlb3JkZXIoRlNtZXRob2QsVmFsdWUpLGxhYmVsPXJvdW5kKFZhbHVlLDQpKSxjb2xvcj0iYmxhY2siKStzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlNwZWN0cmFsIikrdGhlbWVfYncoKStzY2FsZV94X2Rpc2NyZXRlKCJNZXRyaWNzIikrc2NhbGVfeV9kaXNjcmV0ZSgiRlMgbWV0aG9kcyIpDQoNCnJlcyU+JWdhdGhlcihBVUM6RjEsa2V5PSJNZXRyaWMiLHZhbHVlPSJWYWx1ZSIpJT4lZ2dwbG90KCkrZ2VvbV9wb2ludChzaGFwZT0yMSxjb2xvcj0iYmxhY2siLHNpemU9MyxhZXMoeD1yZW9yZGVyKEZTbWV0aG9kLFZhbHVlKSx5PVZhbHVlLGZpbGw9TWV0cmljKSxjb2xvcj0iYmxhY2siKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9bXlmaWxsY29sb3JzKSt0aGVtZV9idygpK3NjYWxlX3hfZGlzY3JldGUoIkZTbWV0aG9kIikrc2NhbGVfeV9jb250aW51b3VzKCJWYWx1ZXMiKStmYWNldF93cmFwKH5NZXRyaWMsc2NhbGVzPSJmcmVlIikrY29vcmRfZmxpcCgpDQoNCmBgYA0KDQpCYXNlZCBvbiB0aG9zZSByZXN1bHQsIFJhbmRvbSBGb3Jlc3QgYmFzZWQgaW1wb3J0YW5jZSAoUkZJKSBiYXNlZCBtZXRob2Qgd2VyZSB0aGUgbW9zdCBwb3dlcmZ1bCBtZXRob2RzIGZvciBmZWF0dXJlIHNlbGVjdGlvbi4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSJ9DQoNClYoZ3JhcGgxKSRBVUM9cmVzJEFVQw0KDQpnZ3JhcGgoZ3JhcGgxLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfZmFuKGFscGhhPTAuMSxhZXMoY29sb3VyID0gY29ycix3aWR0aD1jb3JyKSkrZ2VvbV9ub2RlX3BvaW50KHNoYXBlPTIxLGFlcyhzaXplPUFVQyxmaWxsPUFVQykpK2dlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLG51ZGdlX3g9LG51ZGdlX3k9MC4xKSt0aGVtZV9iYXJlKCkrY29vcmRfZml4ZWQoKStzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGxvdz0iIzAwOTRmZiIsaGlnaD0iI2ZmMDAzZiIpK3NjYWxlX2ZpbGxfZ3JhZGllbnQoaGlnaD0icmVkIixsb3c9ImdvbGQiKQ0KDQoNClYoZ3JhcGgxKSRCQUM9cmVzJEJBQw0KDQpnZ3JhcGgoZ3JhcGgxLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfZmFuKGFscGhhPTAuMSxhZXMoY29sb3VyID0gY29ycix3aWR0aD1jb3JyKSkrZ2VvbV9ub2RlX3BvaW50KHNoYXBlPTIxLGFlcyhzaXplPUJBQyxmaWxsPUJBQykpK2dlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLG51ZGdlX3g9LG51ZGdlX3k9MC4xKSt0aGVtZV9iYXJlKCkrY29vcmRfZml4ZWQoKStzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGxvdz0iIzAwOTRmZiIsaGlnaD0iI2ZmMDAzZiIpK3NjYWxlX2ZpbGxfZ3JhZGllbnQoaGlnaD0icmVkIixsb3c9ImdvbGQiKQ0KDQoNCmBgYA0KDQpUaGUgbmV0d29yayBhbmFseXNpcyB0aGF0IGNvbWJpbmVzIEtTSSBhbmQgQVVDL0FjY3VyYWN5IHZhbHVlcyBzaG93ZWQgdGhhdCB0aGUgY29oZXJlbmNlIGxldmVsIGFzIG1lYXN1cmVkIGJ5IEtTSSBpcyBOT1QgYXNzb2NpYXRlZCB0byB0aGUgcGVyZm9ybWFuY2UgYXMgbWVhc3VyZWQgYnkgQVVDIG9yIEJBQy4gVGhpcyBtZWFucyB0aGUgbWV0aG9kcyB0aGF0IGhhdmUgdGhlIHNhbWUgS1NJIHZhbHVlIGNvdWxkIGhhdmUgYW4gZXF1aXZhbGVudCBwZXJmb3JtYW5jZSAoYXMgdGhleSBnZW5lcmF0ZSB0aGUgc2FtZSBmZWF0dXJlIHN1YnNldCksIGJ1dCB0aGUgS1NJIGlzIG5vdCBhIG1lYXN1cmUgb2YgZ29vZG5lc3Mgb2YgZmlsdGVyIG1ldGhvZCwgYXMgYSBtZXRob2Qgd2l0aCBsb3cgY29uc2lzdGVuY3kgaW4gcmVnYXJkIHRvIG90aGVyIG1ldGhvZHMgY291bGQgYmVjb21lIGEgYmV0dGVyIG9uZS4NCg0KIyBDb25jbHVzaW9uDQoNCkluIHRoaXNjYXNlIHN0dWR5LCB3ZSBpbnRyb2R1Y2VkIGRpZmZlcmVudCBtZXRob2RzIGZvciBmaWx0ZXJpbmcgdGhlIGZlYXR1cmVzLiBUaGUgY29uc2lzdGVuY3kgbGV2ZWwgYW1vbmcgZmVhdHVyZXMgc3Vic2V0cyBjb3VsZCBiZSBldmFsdWF0ZWQgYnkgdGhlIEt1bmNoZXZhIHN0YWJpbGl0eSBpbmRleC4gSG93ZXZlciwgdGhpcyBpbmRleCBpcyBub3QgYSBtZWFzdXJlIG9mIHRoZSBwZXJmb3JtYW5jZSBmb3IgZWFjaCBtZXRob2QuIFRoZSBSYW5kb20gRm9yZXN0IGJhc2VkIGNyaXRlcmlhLCBpbmNsdWRpbmcgdGhlIHZhcmlhYmxlIGltcG9ydGFuY2UsIGluZm9ybWF0aW9uIGdhaW4gYW5kIEdhaW4gcmF0aW8gYXJlIHZlcnkgdXNlZnVsIGZvciBzZWxlY3RpbmcgZmVhdHVyZXMuDQoNCkluIGEgbmV4dCB0dXRvcmlhbCwgd2Ugd2lsbCBkaXNjdXNzIGFib3V0IHRoZSBtZXRob2RzIHRoYXQgbW9kaWZ5IG91ciBtb2RlbHMgaW5zdGVhZCBvZiBpbnB1dCBkYXRhLCBpLmUgTW9kZWwgc2VsZWN0aW9uLg0KDQpSZWZlcmVuY2U6IDxodHRwczovL3BkZnMuc2VtYW50aWNzY2hvbGFyLm9yZy85YzRmLzAwNmQ1NTQ3Y2RkZWNlZjU4OGU2NmFiNmIzM2U2ODQ1YjMzYS5wZGY+DQoNCg0KIyBDb250cmlidXRpb246DQoNCipUaGlzIGpvaW50IHR1dG9yaWFsIHdhcyBjcmVhdGVkIGJ5IFZpZXQgVHJhbiBhbmQgTmFtIExlIERvbmcgYXMgYSBwYXJ0IG9mIE1MTSBwcm9qZWN0Kg0KDQoqVmlldCBUcmFuKiBkZXNpZ25lZCB0aGUgZGF0YSBleHBlcmltZW50LiBIZSBpcyBhbHNvIHRoZSBhdXRob3Igb2YgbW9zdCBmdW5jdGlvbnMgYW5kIGxvb3BzIGZvciBGZWF0dXJlIHNlbGVjdGlvbiBhbmQgTW9kZWwgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbi4NCg0KKk5hbSBMZSBEb25nKiBoYW5kbGVkIHRoZSBncmFwaHMsIG5ldHdvcmsgYW5hbHlzaXMgYW5kIFJtYXJrZG93biB3cml0aW5nLg0KDQoqKlNlZSB5b3UgaW4gdGhlIG5leHQgdHV0b3JpYWwgYW5kIHRoYW5rIGZvciBqb2luaW5nIHVzICEqKg0KDQoqRU5EKg0K