1 Supplementary Protocol 2 – create a gene list by analyzing gene expression data from Affymetrix microarrays with Limma

This protocol demonstrates the extraction of gene lists for pathway enrichment analysis using RMA-normalized gene expression data from Affymetrix microarrays for downstream pathway enrichment analysis with g:Profiler, GSEA and other similar tools. g:Profiler requires a ranked list of differentially expressed genes that are filtered according to a significance cut-off. GSEA requires a two-column tab-separated RNK file with a ranked list of all genes in the genome. In the RNK file, the first column specifies the gene name and the second column specifies a numeric score representing the level of differential expression. For both methods, the first step involves calculating a statistic for each gene that represents the difference in its expression levels between the two groups. This step is performed using the limma R package.

1.1 Process Microarray data

1.1.1 Load required packages

  1. Load required Bioconductor packages into R
#check to see if the library has already been installed.  If it hasn't then install it. 
tryCatch(expr = { library("Biobase")}, 
         error = function(e) { 
           source("https://bioconductor.org/biocLite.R")
           biocLite("Biobase")}, 
         finally = library("Biobase"))

tryCatch(expr = { library("limma")}, 
         error = function(e) { 
           source("https://bioconductor.org/biocLite.R")
           biocLite("limma")}, 
         finally = library("limma"))

Set the working directory to the location of where the Supplemental Tables 1-4 are stored. The function getwd() shows the working directory and dir() shows its files.

working_dir <- "./data"

1.1.2 Load Expression Data

  1. Load expression data into R. Minimally the expression set requires a gene name for each row and typically at least 6 expression values (3 values in each compared class). Our dataset consists of 216 patients with 107 Immunoreactive and 109 Mesenchymal samples. After loading, use the command head(expressionMatrix) to verify the loaded matrix.
expressionMatrix <- as.matrix(read.table(
  file.path(working_dir, "Supplementary_Table10_TCGA_Microarray_rmanormalized.txt"), 
  header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE))

1.1.3 Load subtype information

  1. Load subtype classification of samples. To calculate differential expression, we need to define at least 2 classes of samples. A common experimental design involves cases and controls but any two classes can be used. The current dataset is divided into Mesenchymal and Immunoreactive classes (Supplementary Table 3, third column). After loading the matrix, check that the column names of the expression matrix and class definitions are equal.
classDefinitions <- read.table( 
  file.path(working_dir,"Supplementary_Table11_Microarray_classdefinitions.txt"), 
  header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE)
identical(colnames(expressionMatrix), classDefinitions$patient)

1.1.4 Calculate Differential expression

  1. Format data and class definitions for limma. The expression data needs to be converted to an object of type ExpressionSet. The ExpressionSet needs to include a data matrix where rows are genes, columns are samples and each cell contains an expression value. Classes need to be defined as factors.
minimalSet <- ExpressionSet(assayData=expressionMatrix)
classes <- factor(classDefinitions[,"SUBTYPE"])
  1. Create a model matrix with the defined classes.
modelDesign <- model.matrix(~ 0 + classes)
  1. Fit the model to the expression matrix.
fit <- lmFit(minimalSet, modelDesign)
  1. Create the contrast matrix - By specifying Mesenchymal first and Immunoreactive second, positive logFC and t-values refer to higher expression levels (up-regulation) in the Mesenchenchymal versus Immunoreactive samples
contrastnm <- c("classesMesenchymal-classesImmunoreactive") 
contrast.matrix <- makeContrasts(
  original ="classesMesenchymal-classesImmunoreactive",
  mesenvsrest = "classesMesenchymal-(classesImmunoreactive + 
                classesProliferative +classesDifferentiated)/3",
  immunovsrest = "classesImmunoreactive-(classesMesenchymal + 
                classesProliferative +classesDifferentiated)/3",
  prolifvsrest = "classesProliferative-(classesMesenchymal + 
                classesImmunoreactive +classesDifferentiated)/3", 
  diffvsrest = "classesDifferentiated-(classesMesenchymal + 
                classesImmunoreactive +classesProliferative)/3",
  levels=modelDesign)
  1. Model contrasts of gene expression. The following command models gene expression differences of each gene between the two groups of samples using linear regression and computes coefficients and standard errors.
fit1 <- contrasts.fit(fit, contrast.matrix)
  1. Compute differential expression statistics. Given a fitted linear regression model, the command generates a table containing the log fold change, average expression, t statistic, p-value, adjusted p-value and B statistic for each entity in the expression matrix using empirical Bayes statistics. The B-statistic represents the log-odds that the gene is differentially expressed but it is based on a prior assumption of how many genes are differentially expressed in the dataset. Because of its reliance on this prior assumption, the adjusted p-value is preferentially used as an indicator of significant differential expression.
fit2 <- eBayes(fit1)
  1. Generate a table with differentially expressed genes and adjust for multiple hypothesis testing using Benjamini-Hochberg False Discovery Rate. The table contains all genes ranked by p-value and shown with log fold change, average expression, t-statistic, p-value, adjusted p-value and B-statistic.
topfit <- topTable(fit2, coef="immunovsrest",number=nrow(expressionMatrix), adjust="BH")

Top of resulting table:

head(topfit)

1.1.5 Create g:Profiler input list

11a. Create the gene list for use in g:Profiler or another thresholded enrichment tool. The list may comprise all genes that have a significant FDR-corrected p-value, all up-regulated genes with a significant FDR p-value, all down-regulated genes with a significant FDR p-value, or some other combination of thresholds. * To get all significant genes:

length(which(topfit$adj.P.Val<0.05))
topgenes_qvalue005 <- rownames(topfit)[which(topfit$adj.P.Val<0.05)]
head(topgenes_qvalue005)
write.table(topgenes_qvalue005, 
"ImmunovsRest_allsignificantgenes.txt", 
col.names=FALSE, sep="\t", row.names=FALSE, quote=FALSE)

1.1.6 Examine results

  • Significantly up-regulated genes in Mesenchymal samples have positive logFC and t-values.
length(which(topfit$adj.P.Val<0.05 & topfit$t >0))
topgenes_qvalue005_mesenchymal <-
    rownames(topfit)[which(topfit$adj.P.Val<0.05 & topfit$t >0)]
head(topgenes_qvalue005_mesenchymal)
write.table(topgenes_qvalue005_mesenchymal, 
            "ImmunovsRest_immuno_significantgenes.txt", 
            col.names=FALSE, sep="\t", row.names=FALSE, quote=FALSE)
  • Significantly up-regulated genes in Immunoreactive samples have negative logFC and t-values.
length(which(topfit$adj.P.Val<0.05 & topfit$t <0))
topgenes_qvalue005_immunoreactive<- 
  rownames(topfit)[which(topfit$adj.P.Val<0.05 & topfit$t <0)]
head(topgenes_qvalue005_immunoreactive)
write.table(topgenes_qvalue005_immunoreactive, 
  "ImmunovsRest_rest_significantgenes.txt", 
  col.names=FALSE, sep="\t", row.names=FALSE, quote=FALSE)

1.1.7 Create GSEA input list

11b. Create a rank file for GSEA. To run GSEA in pre-ranked mode, you need a two column RNK file with gene/protein/probe name (column 1) and the associated score (column 2). The first column should contain the same type of gene IDs used in the pathway gene-set (GMT) file. GSEA looks for enrichment in the top and bottom parts of the list, ranking the file using the t-statistic. The t-statistic indicates the strength of differential expression and is used in the p-value calculation. Other scores indicating the strength of differential expression may be used as well. GSEA ranks the most up-regulated genes at the top of the list and the most down-regulated at the bottom of the list. Genes at the top of the list are more highly expressed in class A compared to class B, while genes at the bottom of the list are higher in class B. In this workflow, a positive t-value means a higher expression of a gene in the Mesenchymal samples compared to the Immunoreactive samples (variable constrastnm). The following commands create a data frame with gene IDs and t-statistics, remove lines with missing gene IDs, and store the result as a RNK file. An additional step is usually required in analysis of Affymetrix microarray data as genes are represented with multiple probesets. The most significant probeset or average probeset score may be considered for every gene.

ranks <- data.frame(geneID=rownames(topfit),t_stat=topfit[,"t"], 
                    stringsAsFactors=F)
ranks <- ranks[which(ranks[,"geneID"] != ""),]
write.table(ranks,"ImmunovsRest_limma_ranks.rnk",
col.name=TRUE,sep="\t",row.names=FALSE, quote=FALSE)

head(ranks)

1.1.8 Create expression file

  1. Create an expression file for the enrichment map and save files to the home folder of the analysis. The expression file contains the gene IDs as the first column gene description as the second column and the expression values for each sample as the additional columns. Gene IDs should correspond to the first column of the rank file. The text files will be saved on your computer in the directory specified at the beginning of the script using setwd(). The .rnk, .cls and .txt are all tab delimited files that can be viewed in spreadsheet or in a text editor.
library(biomaRt)
mart = useMart(biomart="ensembl", dataset="hsapiens_gene_ensembl")

genes = getBM(attributes = c( 'hgnc_symbol', 'description'), filters='hgnc_symbol', 
              values=row.names(expressionMatrix), mart=mart);
genes$description = gsub("\\[Source.*", "", genes$description);

EM_expressionFile <- merge(genes,expressionMatrix,  all.y=TRUE,by.x=1, by.y=0)
colnames(EM_expressionFile)[1] <- "Name"
colnames(EM_expressionFile)[2] <- "Description"
write.table(EM_expressionFile, "TCGA_OV_expression.txt", 
    col.name=TRUE, sep="\t", row.names=FALSE, quote=FALSE)
LS0tCnRpdGxlOiAiU3VwcGxlbWVudGFyeSBQcm90b2NvbCAyIOKAkyBjcmVhdGUgYSBnZW5lIGxpc3QgYnkgYW5hbHl6aW5nIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGZyb20gQWZmeW1ldHJpeCBtaWNyb2FycmF5cyB3aXRoIExpbW1hIgphdXRob3I6ICJSdXRoIElzc2VybGluIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCkpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBoaWdobGlnaDogaGFkZG9jawogICAga2VlcF9tZDogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHBhcGVyCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogbm8KICAgICAgc21vb3RoX3Njcm9sbDogbm8KICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnMycKICBodG1sX25vdGVib29rOgogICAgaGlnaGxpZ2g6IGhhZGRvY2sKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogcGFwZXIKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBubwogICAgICBzbW9vdGhfc2Nyb2xsOiBubwpiaWJsaW9ncmFwaHk6IHN1cF9wcm90b2NvbDFfcmVmZXJlbmNlcy5iaWIKY3NsOiBuYXR1cmUtcHJvdG9jb2xzLmNzbAotLS0KIyBTdXBwbGVtZW50YXJ5IFByb3RvY29sIDIg4oCTIGNyZWF0ZSBhIGdlbmUgbGlzdCBieSBhbmFseXppbmcgZ2VuZSBleHByZXNzaW9uIGRhdGEgZnJvbSBBZmZ5bWV0cml4IG1pY3JvYXJyYXlzIHdpdGggTGltbWEKVGhpcyBwcm90b2NvbCBkZW1vbnN0cmF0ZXMgdGhlIGV4dHJhY3Rpb24gb2YgZ2VuZSBsaXN0cyBmb3IgcGF0aHdheSBlbnJpY2htZW50IGFuYWx5c2lzIHVzaW5nIFJNQS1ub3JtYWxpemVkIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGZyb20gQWZmeW1ldHJpeCBtaWNyb2FycmF5cyBmb3IgZG93bnN0cmVhbSBwYXRod2F5IGVucmljaG1lbnQgYW5hbHlzaXMgd2l0aCBnOlByb2ZpbGVyLCBHU0VBIGFuZCBvdGhlciBzaW1pbGFyIHRvb2xzLiBnOlByb2ZpbGVyIHJlcXVpcmVzIGEgcmFua2VkIGxpc3Qgb2YgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIHRoYXQgYXJlIGZpbHRlcmVkIGFjY29yZGluZyB0byBhIHNpZ25pZmljYW5jZSBjdXQtb2ZmLiBHU0VBIHJlcXVpcmVzIGEgdHdvLWNvbHVtbiB0YWItc2VwYXJhdGVkIFJOSyBmaWxlIHdpdGggYSByYW5rZWQgbGlzdCBvZiBhbGwgZ2VuZXMgaW4gdGhlIGdlbm9tZS4gSW4gdGhlIFJOSyBmaWxlLCB0aGUgZmlyc3QgY29sdW1uIHNwZWNpZmllcyB0aGUgZ2VuZSBuYW1lIGFuZCB0aGUgc2Vjb25kIGNvbHVtbiBzcGVjaWZpZXMgYSBudW1lcmljIHNjb3JlIHJlcHJlc2VudGluZyB0aGUgbGV2ZWwgb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIEZvciBib3RoIG1ldGhvZHMsIHRoZSBmaXJzdCBzdGVwIGludm9sdmVzIGNhbGN1bGF0aW5nIGEgc3RhdGlzdGljIGZvciBlYWNoIGdlbmUgdGhhdCByZXByZXNlbnRzIHRoZSBkaWZmZXJlbmNlIGluIGl0cyBleHByZXNzaW9uIGxldmVscyBiZXR3ZWVuIHRoZSB0d28gZ3JvdXBzLiBUaGlzIHN0ZXAgaXMgcGVyZm9ybWVkIHVzaW5nIHRoZSBsaW1tYSBSIHBhY2thZ2UuIAoKYGBge3IgaW5jbHVkZT1GQUxTRX0KY2hlY2s9ZnVuY3Rpb24oeCkgdHJ5Q2F0Y2goaWYoY2xhc3MoeCkgPT0gJ2xvZ2ljYWwnKSAxIGVsc2UgMSwgZXJyb3I9ZnVuY3Rpb24oZSkgMCkgCmlmKGNoZWNrKGFkZF9zZXR1cCkgPT0gMCl7CiAgYWRkX3NldHVwID0gVFJVRQp9CmBgYAoKCmBgYHtyIGF1dG9kb2MsIGNoaWxkPSdzdXBwbGVtZW50YXJ5X3Byb3RvY29sczEyM19zZXR1cC5SbWQnLCBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQpgYGAgCgojIyBQcm9jZXNzIE1pY3JvYXJyYXkgZGF0YQoKIyMjIExvYWQgcmVxdWlyZWQgcGFja2FnZXMKMS4gTG9hZCByZXF1aXJlZCBCaW9jb25kdWN0b3IgcGFja2FnZXMgaW50byBSCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojY2hlY2sgdG8gc2VlIGlmIHRoZSBsaWJyYXJ5IGhhcyBhbHJlYWR5IGJlZW4gaW5zdGFsbGVkLiAgSWYgaXQgaGFzbid0IHRoZW4gaW5zdGFsbCBpdC4gCnRyeUNhdGNoKGV4cHIgPSB7IGxpYnJhcnkoIkJpb2Jhc2UiKX0sIAogICAgICAgICBlcnJvciA9IGZ1bmN0aW9uKGUpIHsgCiAgICAgICAgICAgc291cmNlKCJodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYmlvY0xpdGUuUiIpCiAgICAgICAgICAgYmlvY0xpdGUoIkJpb2Jhc2UiKX0sIAogICAgICAgICBmaW5hbGx5ID0gbGlicmFyeSgiQmlvYmFzZSIpKQoKdHJ5Q2F0Y2goZXhwciA9IHsgbGlicmFyeSgibGltbWEiKX0sIAogICAgICAgICBlcnJvciA9IGZ1bmN0aW9uKGUpIHsgCiAgICAgICAgICAgc291cmNlKCJodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYmlvY0xpdGUuUiIpCiAgICAgICAgICAgYmlvY0xpdGUoImxpbW1hIil9LCAKICAgICAgICAgZmluYWxseSA9IGxpYnJhcnkoImxpbW1hIikpCgpgYGAKClNldCB0aGUgd29ya2luZyBkaXJlY3RvcnkgdG8gdGhlIGxvY2F0aW9uIG9mIHdoZXJlIHRoZSBTdXBwbGVtZW50YWwgVGFibGVzIDEtNCBhcmUgc3RvcmVkLiBUaGUgZnVuY3Rpb24gZ2V0d2QoKSBzaG93cyB0aGUgd29ya2luZyBkaXJlY3RvcnkgYW5kIGRpcigpIHNob3dzIGl0cyBmaWxlcy4KYGBge3J9CndvcmtpbmdfZGlyIDwtICIuL2RhdGEiCmBgYAoKIyMjIExvYWQgRXhwcmVzc2lvbiBEYXRhCjIuIExvYWQgZXhwcmVzc2lvbiBkYXRhIGludG8gUi4gTWluaW1hbGx5IHRoZSBleHByZXNzaW9uIHNldCByZXF1aXJlcyBhIGdlbmUgbmFtZSBmb3IgZWFjaCByb3cgYW5kIHR5cGljYWxseSBhdCBsZWFzdCA2IGV4cHJlc3Npb24gdmFsdWVzICgzIHZhbHVlcyBpbiBlYWNoIGNvbXBhcmVkIGNsYXNzKS4gT3VyIGRhdGFzZXQgY29uc2lzdHMgb2YgMjE2IHBhdGllbnRzIHdpdGggMTA3IEltbXVub3JlYWN0aXZlIGFuZCAxMDkgTWVzZW5jaHltYWwgc2FtcGxlcy4gQWZ0ZXIgbG9hZGluZywgdXNlIHRoZSBjb21tYW5kIGhlYWQoZXhwcmVzc2lvbk1hdHJpeCkgdG8gdmVyaWZ5IHRoZSBsb2FkZWQgbWF0cml4LgoKYGBge3J9CmV4cHJlc3Npb25NYXRyaXggPC0gYXMubWF0cml4KHJlYWQudGFibGUoCiAgZmlsZS5wYXRoKHdvcmtpbmdfZGlyLCAiU3VwcGxlbWVudGFyeV9UYWJsZTEwX1RDR0FfTWljcm9hcnJheV9ybWFub3JtYWxpemVkLnR4dCIpLCAKICBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiXHQiLCBxdW90ZT0iXCIiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpKQoKYGBgCgojIyMgTG9hZCBzdWJ0eXBlIGluZm9ybWF0aW9uCjMuIExvYWQgc3VidHlwZSBjbGFzc2lmaWNhdGlvbiBvZiBzYW1wbGVzLiBUbyBjYWxjdWxhdGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24sIHdlIG5lZWQgdG8gZGVmaW5lIGF0IGxlYXN0IDIgY2xhc3NlcyBvZiBzYW1wbGVzLiBBIGNvbW1vbiBleHBlcmltZW50YWwgZGVzaWduIGludm9sdmVzIGNhc2VzIGFuZCBjb250cm9scyBidXQgYW55IHR3byBjbGFzc2VzIGNhbiBiZSB1c2VkLiBUaGUgY3VycmVudCBkYXRhc2V0IGlzIGRpdmlkZWQgaW50byBNZXNlbmNoeW1hbCBhbmQgSW1tdW5vcmVhY3RpdmUgY2xhc3NlcyAoU3VwcGxlbWVudGFyeSBUYWJsZSAzLCB0aGlyZCBjb2x1bW4pLiBBZnRlciBsb2FkaW5nIHRoZSBtYXRyaXgsIGNoZWNrIHRoYXQgdGhlIGNvbHVtbiBuYW1lcyBvZiB0aGUgZXhwcmVzc2lvbiBtYXRyaXggYW5kIGNsYXNzIGRlZmluaXRpb25zIGFyZSBlcXVhbC4gCmBgYHtyfQpjbGFzc0RlZmluaXRpb25zIDwtIHJlYWQudGFibGUoIAogIGZpbGUucGF0aCh3b3JraW5nX2RpciwiU3VwcGxlbWVudGFyeV9UYWJsZTExX01pY3JvYXJyYXlfY2xhc3NkZWZpbml0aW9ucy50eHQiKSwgCiAgaGVhZGVyID0gVFJVRSwgc2VwID0gIlx0IiwgcXVvdGU9IlwiIiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQppZGVudGljYWwoY29sbmFtZXMoZXhwcmVzc2lvbk1hdHJpeCksIGNsYXNzRGVmaW5pdGlvbnMkcGF0aWVudCkKCmBgYAoKIyMjIENhbGN1bGF0ZSBEaWZmZXJlbnRpYWwgZXhwcmVzc2lvbgo0LiBGb3JtYXQgZGF0YSBhbmQgY2xhc3MgZGVmaW5pdGlvbnMgZm9yIGxpbW1hLiBUaGUgZXhwcmVzc2lvbiBkYXRhIG5lZWRzIHRvIGJlIGNvbnZlcnRlZCB0byBhbiBvYmplY3Qgb2YgdHlwZSBFeHByZXNzaW9uU2V0LiBUaGUgRXhwcmVzc2lvblNldCBuZWVkcyB0byBpbmNsdWRlIGEgZGF0YSBtYXRyaXggd2hlcmUgcm93cyBhcmUgZ2VuZXMsIGNvbHVtbnMgYXJlIHNhbXBsZXMgYW5kIGVhY2ggY2VsbCBjb250YWlucyBhbiBleHByZXNzaW9uIHZhbHVlLiBDbGFzc2VzIG5lZWQgdG8gYmUgZGVmaW5lZCBhcyBmYWN0b3JzLgpgYGB7cn0KbWluaW1hbFNldCA8LSBFeHByZXNzaW9uU2V0KGFzc2F5RGF0YT1leHByZXNzaW9uTWF0cml4KQpjbGFzc2VzIDwtIGZhY3RvcihjbGFzc0RlZmluaXRpb25zWywiU1VCVFlQRSJdKQoKYGBgCgo1LiBDcmVhdGUgYSBtb2RlbCBtYXRyaXggd2l0aCB0aGUgZGVmaW5lZCBjbGFzc2VzLgpgYGB7cn0KbW9kZWxEZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gMCArIGNsYXNzZXMpCmBgYAoKNi4gRml0IHRoZSBtb2RlbCB0byB0aGUgZXhwcmVzc2lvbiBtYXRyaXguCmBgYHtyfQpmaXQgPC0gbG1GaXQobWluaW1hbFNldCwgbW9kZWxEZXNpZ24pCmBgYAoKNy4gQ3JlYXRlIHRoZSBjb250cmFzdCBtYXRyaXggLSBCeSBzcGVjaWZ5aW5nIE1lc2VuY2h5bWFsIGZpcnN0IGFuZCBJbW11bm9yZWFjdGl2ZSBzZWNvbmQsIHBvc2l0aXZlIGxvZ0ZDIGFuZCB0LXZhbHVlcyByZWZlciB0byBoaWdoZXIgZXhwcmVzc2lvbiBsZXZlbHMgKHVwLXJlZ3VsYXRpb24pIGluIHRoZSBNZXNlbmNoZW5jaHltYWwgdmVyc3VzIEltbXVub3JlYWN0aXZlIHNhbXBsZXMgCmBgYHtyfQpjb250cmFzdG5tIDwtIGMoImNsYXNzZXNNZXNlbmNoeW1hbC1jbGFzc2VzSW1tdW5vcmVhY3RpdmUiKSAKY29udHJhc3QubWF0cml4IDwtIG1ha2VDb250cmFzdHMoCiAgb3JpZ2luYWwgPSJjbGFzc2VzTWVzZW5jaHltYWwtY2xhc3Nlc0ltbXVub3JlYWN0aXZlIiwKICBtZXNlbnZzcmVzdCA9ICJjbGFzc2VzTWVzZW5jaHltYWwtKGNsYXNzZXNJbW11bm9yZWFjdGl2ZSArIAogICAgICAgICAgICAgICAgY2xhc3Nlc1Byb2xpZmVyYXRpdmUgK2NsYXNzZXNEaWZmZXJlbnRpYXRlZCkvMyIsCiAgaW1tdW5vdnNyZXN0ID0gImNsYXNzZXNJbW11bm9yZWFjdGl2ZS0oY2xhc3Nlc01lc2VuY2h5bWFsICsgCiAgICAgICAgICAgICAgICBjbGFzc2VzUHJvbGlmZXJhdGl2ZSArY2xhc3Nlc0RpZmZlcmVudGlhdGVkKS8zIiwKICBwcm9saWZ2c3Jlc3QgPSAiY2xhc3Nlc1Byb2xpZmVyYXRpdmUtKGNsYXNzZXNNZXNlbmNoeW1hbCArIAogICAgICAgICAgICAgICAgY2xhc3Nlc0ltbXVub3JlYWN0aXZlICtjbGFzc2VzRGlmZmVyZW50aWF0ZWQpLzMiLCAKICBkaWZmdnNyZXN0ID0gImNsYXNzZXNEaWZmZXJlbnRpYXRlZC0oY2xhc3Nlc01lc2VuY2h5bWFsICsgCiAgICAgICAgICAgICAgICBjbGFzc2VzSW1tdW5vcmVhY3RpdmUgK2NsYXNzZXNQcm9saWZlcmF0aXZlKS8zIiwKICBsZXZlbHM9bW9kZWxEZXNpZ24pCgpgYGAKCjguIE1vZGVsIGNvbnRyYXN0cyBvZiBnZW5lIGV4cHJlc3Npb24uIFRoZSBmb2xsb3dpbmcgY29tbWFuZCBtb2RlbHMgZ2VuZSBleHByZXNzaW9uIGRpZmZlcmVuY2VzIG9mIGVhY2ggZ2VuZSBiZXR3ZWVuIHRoZSB0d28gZ3JvdXBzIG9mIHNhbXBsZXMgdXNpbmcgbGluZWFyIHJlZ3Jlc3Npb24gYW5kIGNvbXB1dGVzIGNvZWZmaWNpZW50cyBhbmQgc3RhbmRhcmQgZXJyb3JzLgpgYGB7cn0KZml0MSA8LSBjb250cmFzdHMuZml0KGZpdCwgY29udHJhc3QubWF0cml4KQpgYGAKCjkuIENvbXB1dGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gc3RhdGlzdGljcy4gR2l2ZW4gYSBmaXR0ZWQgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIHRoZSBjb21tYW5kIGdlbmVyYXRlcyBhIHRhYmxlIGNvbnRhaW5pbmcgdGhlIGxvZyBmb2xkIGNoYW5nZSwgYXZlcmFnZSBleHByZXNzaW9uLCB0IHN0YXRpc3RpYywgcC12YWx1ZSwgYWRqdXN0ZWQgcC12YWx1ZSBhbmQgQiBzdGF0aXN0aWMgZm9yIGVhY2ggZW50aXR5IGluIHRoZSBleHByZXNzaW9uIG1hdHJpeCB1c2luZyBlbXBpcmljYWwgQmF5ZXMgc3RhdGlzdGljcy4gVGhlIEItc3RhdGlzdGljIHJlcHJlc2VudHMgdGhlIGxvZy1vZGRzIHRoYXQgdGhlIGdlbmUgaXMgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGJ1dCBpdCBpcyBiYXNlZCBvbiBhIHByaW9yIGFzc3VtcHRpb24gb2YgaG93IG1hbnkgZ2VuZXMgYXJlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBpbiB0aGUgZGF0YXNldC4gQmVjYXVzZSBvZiBpdHMgcmVsaWFuY2Ugb24gdGhpcyBwcmlvciBhc3N1bXB0aW9uLCB0aGUgYWRqdXN0ZWQgcC12YWx1ZSBpcyBwcmVmZXJlbnRpYWxseSB1c2VkIGFzIGFuIGluZGljYXRvciBvZiBzaWduaWZpY2FudCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbi4KYGBge3J9CmZpdDIgPC0gZUJheWVzKGZpdDEpCmBgYAoKMTAuIEdlbmVyYXRlIGEgdGFibGUgd2l0aCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgYW5kIGFkanVzdCBmb3IgbXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nIHVzaW5nIEJlbmphbWluaS1Ib2NoYmVyZyBGYWxzZSBEaXNjb3ZlcnkgUmF0ZS4gVGhlIHRhYmxlIGNvbnRhaW5zIGFsbCBnZW5lcyByYW5rZWQgYnkgcC12YWx1ZSBhbmQgc2hvd24gd2l0aCBsb2cgZm9sZCBjaGFuZ2UsIGF2ZXJhZ2UgZXhwcmVzc2lvbiwgdC1zdGF0aXN0aWMsIHAtdmFsdWUsIGFkanVzdGVkIHAtdmFsdWUgYW5kIEItc3RhdGlzdGljLiAKYGBge3J9CnRvcGZpdCA8LSB0b3BUYWJsZShmaXQyLCBjb2VmPSJpbW11bm92c3Jlc3QiLG51bWJlcj1ucm93KGV4cHJlc3Npb25NYXRyaXgpLCBhZGp1c3Q9IkJIIikKYGBgCgpUb3Agb2YgcmVzdWx0aW5nIHRhYmxlOgpgYGB7cn0KaGVhZCh0b3BmaXQpCmBgYAoKCiMjIyBDcmVhdGUgZzpQcm9maWxlciBpbnB1dCBsaXN0CjExYS4gQ3JlYXRlIHRoZSBnZW5lIGxpc3QgZm9yIHVzZSBpbiBnOlByb2ZpbGVyIG9yIGFub3RoZXIgdGhyZXNob2xkZWQgZW5yaWNobWVudCB0b29sLiBUaGUgbGlzdCBtYXkgY29tcHJpc2UgYWxsIGdlbmVzIHRoYXQgaGF2ZSBhIHNpZ25pZmljYW50IEZEUi1jb3JyZWN0ZWQgcC12YWx1ZSwgYWxsIHVwLXJlZ3VsYXRlZCBnZW5lcyB3aXRoIGEgc2lnbmlmaWNhbnQgRkRSIHAtdmFsdWUsIGFsbCBkb3duLXJlZ3VsYXRlZCBnZW5lcyB3aXRoIGEgc2lnbmlmaWNhbnQgRkRSIHAtdmFsdWUsIG9yIHNvbWUgb3RoZXIgY29tYmluYXRpb24gb2YgdGhyZXNob2xkcy4KICogVG8gZ2V0IGFsbCBzaWduaWZpY2FudCBnZW5lczoKYGBge3J9Cmxlbmd0aCh3aGljaCh0b3BmaXQkYWRqLlAuVmFsPDAuMDUpKQp0b3BnZW5lc19xdmFsdWUwMDUgPC0gcm93bmFtZXModG9wZml0KVt3aGljaCh0b3BmaXQkYWRqLlAuVmFsPDAuMDUpXQpoZWFkKHRvcGdlbmVzX3F2YWx1ZTAwNSkKd3JpdGUudGFibGUodG9wZ2VuZXNfcXZhbHVlMDA1LCAKIkltbXVub3ZzUmVzdF9hbGxzaWduaWZpY2FudGdlbmVzLnR4dCIsIApjb2wubmFtZXM9RkFMU0UsIHNlcD0iXHQiLCByb3cubmFtZXM9RkFMU0UsIHF1b3RlPUZBTFNFKQpgYGAKCiMjIyBFeGFtaW5lIHJlc3VsdHMKICogU2lnbmlmaWNhbnRseSB1cC1yZWd1bGF0ZWQgZ2VuZXMgaW4gTWVzZW5jaHltYWwgc2FtcGxlcyBoYXZlIHBvc2l0aXZlIGxvZ0ZDIGFuZCB0LXZhbHVlcy4KYGBge3J9Cmxlbmd0aCh3aGljaCh0b3BmaXQkYWRqLlAuVmFsPDAuMDUgJiB0b3BmaXQkdCA+MCkpCnRvcGdlbmVzX3F2YWx1ZTAwNV9tZXNlbmNoeW1hbCA8LQoJcm93bmFtZXModG9wZml0KVt3aGljaCh0b3BmaXQkYWRqLlAuVmFsPDAuMDUgJiB0b3BmaXQkdCA+MCldCmhlYWQodG9wZ2VuZXNfcXZhbHVlMDA1X21lc2VuY2h5bWFsKQp3cml0ZS50YWJsZSh0b3BnZW5lc19xdmFsdWUwMDVfbWVzZW5jaHltYWwsIAogICAgICAgICAgICAiSW1tdW5vdnNSZXN0X2ltbXVub19zaWduaWZpY2FudGdlbmVzLnR4dCIsIAogICAgICAgICAgICBjb2wubmFtZXM9RkFMU0UsIHNlcD0iXHQiLCByb3cubmFtZXM9RkFMU0UsIHF1b3RlPUZBTFNFKQpgYGAKCiAqIFNpZ25pZmljYW50bHkgdXAtcmVndWxhdGVkIGdlbmVzIGluIEltbXVub3JlYWN0aXZlIHNhbXBsZXMgaGF2ZSBuZWdhdGl2ZSBsb2dGQyBhbmQgdC12YWx1ZXMuCmBgYHtyfQpsZW5ndGgod2hpY2godG9wZml0JGFkai5QLlZhbDwwLjA1ICYgdG9wZml0JHQgPDApKQp0b3BnZW5lc19xdmFsdWUwMDVfaW1tdW5vcmVhY3RpdmU8LSAKICByb3duYW1lcyh0b3BmaXQpW3doaWNoKHRvcGZpdCRhZGouUC5WYWw8MC4wNSAmIHRvcGZpdCR0IDwwKV0KaGVhZCh0b3BnZW5lc19xdmFsdWUwMDVfaW1tdW5vcmVhY3RpdmUpCndyaXRlLnRhYmxlKHRvcGdlbmVzX3F2YWx1ZTAwNV9pbW11bm9yZWFjdGl2ZSwgCiAgIkltbXVub3ZzUmVzdF9yZXN0X3NpZ25pZmljYW50Z2VuZXMudHh0IiwgCiAgY29sLm5hbWVzPUZBTFNFLCBzZXA9Ilx0Iiwgcm93Lm5hbWVzPUZBTFNFLCBxdW90ZT1GQUxTRSkKYGBgCgojIyMgQ3JlYXRlIEdTRUEgaW5wdXQgbGlzdAoxMWIuIENyZWF0ZSBhIHJhbmsgZmlsZSBmb3IgR1NFQS4gVG8gcnVuIEdTRUEgaW4gcHJlLXJhbmtlZCBtb2RlLCB5b3UgbmVlZCBhIHR3byBjb2x1bW4gUk5LIGZpbGUgd2l0aCBnZW5lL3Byb3RlaW4vcHJvYmUgbmFtZSAoY29sdW1uIDEpIGFuZCB0aGUgYXNzb2NpYXRlZCBzY29yZSAoY29sdW1uIDIpLiBUaGUgZmlyc3QgY29sdW1uIHNob3VsZCBjb250YWluIHRoZSBzYW1lIHR5cGUgb2YgZ2VuZSBJRHMgdXNlZCBpbiB0aGUgcGF0aHdheSBnZW5lLXNldCAoR01UKSBmaWxlLiBHU0VBIGxvb2tzIGZvciBlbnJpY2htZW50IGluIHRoZSB0b3AgYW5kIGJvdHRvbSBwYXJ0cyBvZiB0aGUgbGlzdCwgcmFua2luZyB0aGUgZmlsZSB1c2luZyB0aGUgdC1zdGF0aXN0aWMuIFRoZSB0LXN0YXRpc3RpYyBpbmRpY2F0ZXMgdGhlIHN0cmVuZ3RoIG9mIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuZCBpcyB1c2VkIGluIHRoZSBwLXZhbHVlIGNhbGN1bGF0aW9uLiBPdGhlciBzY29yZXMgaW5kaWNhdGluZyB0aGUgc3RyZW5ndGggb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gbWF5IGJlIHVzZWQgYXMgd2VsbC4gR1NFQSByYW5rcyB0aGUgbW9zdCB1cC1yZWd1bGF0ZWQgZ2VuZXMgYXQgdGhlIHRvcCBvZiB0aGUgbGlzdCBhbmQgdGhlIG1vc3QgZG93bi1yZWd1bGF0ZWQgYXQgdGhlIGJvdHRvbSBvZiB0aGUgbGlzdC4gR2VuZXMgYXQgdGhlIHRvcCBvZiB0aGUgbGlzdCBhcmUgbW9yZSBoaWdobHkgZXhwcmVzc2VkIGluIGNsYXNzIEEgY29tcGFyZWQgdG8gY2xhc3MgQiwgd2hpbGUgZ2VuZXMgYXQgdGhlIGJvdHRvbSBvZiB0aGUgbGlzdCBhcmUgaGlnaGVyIGluIGNsYXNzIEIuIEluIHRoaXMgd29ya2Zsb3csIGEgcG9zaXRpdmUgdC12YWx1ZSBtZWFucyBhIGhpZ2hlciBleHByZXNzaW9uIG9mIGEgZ2VuZSBpbiB0aGUgTWVzZW5jaHltYWwgc2FtcGxlcyBjb21wYXJlZCB0byB0aGUgSW1tdW5vcmVhY3RpdmUgc2FtcGxlcyAodmFyaWFibGUgY29uc3RyYXN0bm0pLiBUaGUgZm9sbG93aW5nIGNvbW1hbmRzIGNyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCBnZW5lIElEcyBhbmQgdC1zdGF0aXN0aWNzLCByZW1vdmUgbGluZXMgd2l0aCBtaXNzaW5nIGdlbmUgSURzLCBhbmQgc3RvcmUgdGhlIHJlc3VsdCBhcyBhIFJOSyBmaWxlLiBBbiBhZGRpdGlvbmFsIHN0ZXAgaXMgdXN1YWxseSByZXF1aXJlZCBpbiBhbmFseXNpcyBvZiBBZmZ5bWV0cml4IG1pY3JvYXJyYXkgZGF0YSBhcyBnZW5lcyBhcmUgcmVwcmVzZW50ZWQgd2l0aCBtdWx0aXBsZSBwcm9iZXNldHMuIFRoZSBtb3N0IHNpZ25pZmljYW50IHByb2Jlc2V0IG9yIGF2ZXJhZ2UgcHJvYmVzZXQgc2NvcmUgbWF5IGJlIGNvbnNpZGVyZWQgZm9yIGV2ZXJ5IGdlbmUuCmBgYHtyfQpyYW5rcyA8LSBkYXRhLmZyYW1lKGdlbmVJRD1yb3duYW1lcyh0b3BmaXQpLHRfc3RhdD10b3BmaXRbLCJ0Il0sIAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnM9RikKcmFua3MgPC0gcmFua3Nbd2hpY2gocmFua3NbLCJnZW5lSUQiXSAhPSAiIiksXQp3cml0ZS50YWJsZShyYW5rcywiSW1tdW5vdnNSZXN0X2xpbW1hX3JhbmtzLnJuayIsCmNvbC5uYW1lPVRSVUUsc2VwPSJcdCIscm93Lm5hbWVzPUZBTFNFLCBxdW90ZT1GQUxTRSkKCmhlYWQocmFua3MpCmBgYAoKIyMjIENyZWF0ZSBleHByZXNzaW9uIGZpbGUKMTIuIENyZWF0ZSBhbiBleHByZXNzaW9uIGZpbGUgZm9yIHRoZSBlbnJpY2htZW50IG1hcCBhbmQgc2F2ZSBmaWxlcyB0byB0aGUgaG9tZSBmb2xkZXIgb2YgdGhlIGFuYWx5c2lzLiBUaGUgZXhwcmVzc2lvbiBmaWxlIGNvbnRhaW5zIHRoZSBnZW5lIElEcyBhcyB0aGUgZmlyc3QgY29sdW1uIGdlbmUgZGVzY3JpcHRpb24gYXMgdGhlIHNlY29uZCBjb2x1bW4gYW5kIHRoZSBleHByZXNzaW9uIHZhbHVlcyBmb3IgZWFjaCBzYW1wbGUgYXMgdGhlIGFkZGl0aW9uYWwgY29sdW1ucy4gR2VuZSBJRHMgc2hvdWxkIGNvcnJlc3BvbmQgdG8gdGhlIGZpcnN0IGNvbHVtbiBvZiB0aGUgcmFuayBmaWxlLiBUaGUgdGV4dCBmaWxlcyB3aWxsIGJlIHNhdmVkIG9uIHlvdXIgY29tcHV0ZXIgaW4gdGhlIGRpcmVjdG9yeSBzcGVjaWZpZWQgYXQgdGhlIGJlZ2lubmluZyBvZiB0aGUgc2NyaXB0IHVzaW5nIHNldHdkKCkuIFRoZSAucm5rLCAuY2xzIGFuZCAudHh0IGFyZSBhbGwgdGFiIGRlbGltaXRlZCBmaWxlcyB0aGF0IGNhbiBiZSB2aWV3ZWQgaW4gc3ByZWFkc2hlZXQgb3IgaW4gYSB0ZXh0IGVkaXRvci4KCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmxpYnJhcnkoYmlvbWFSdCkKbWFydCA9IHVzZU1hcnQoYmlvbWFydD0iZW5zZW1ibCIsIGRhdGFzZXQ9ImhzYXBpZW5zX2dlbmVfZW5zZW1ibCIpCgpnZW5lcyA9IGdldEJNKGF0dHJpYnV0ZXMgPSBjKCAnaGduY19zeW1ib2wnLCAnZGVzY3JpcHRpb24nKSwgZmlsdGVycz0naGduY19zeW1ib2wnLCAKICAgICAgICAgICAgICB2YWx1ZXM9cm93Lm5hbWVzKGV4cHJlc3Npb25NYXRyaXgpLCBtYXJ0PW1hcnQpOwpnZW5lcyRkZXNjcmlwdGlvbiA9IGdzdWIoIlxcW1NvdXJjZS4qIiwgIiIsIGdlbmVzJGRlc2NyaXB0aW9uKTsKCkVNX2V4cHJlc3Npb25GaWxlIDwtIG1lcmdlKGdlbmVzLGV4cHJlc3Npb25NYXRyaXgsICBhbGwueT1UUlVFLGJ5Lng9MSwgYnkueT0wKQpjb2xuYW1lcyhFTV9leHByZXNzaW9uRmlsZSlbMV0gPC0gIk5hbWUiCmNvbG5hbWVzKEVNX2V4cHJlc3Npb25GaWxlKVsyXSA8LSAiRGVzY3JpcHRpb24iCndyaXRlLnRhYmxlKEVNX2V4cHJlc3Npb25GaWxlLCAiVENHQV9PVl9leHByZXNzaW9uLnR4dCIsIAoJY29sLm5hbWU9VFJVRSwgc2VwPSJcdCIsIHJvdy5uYW1lcz1GQUxTRSwgcXVvdGU9RkFMU0UpCgoKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFLGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoa25pdHIpCmFzaXNfb3V0cHV0KCIjIyBSZWZlcmVuY2VzXFxuIikgIyBIZWFkZXIgdGhhdCBpcyBvbmx5IHNob3duIGlmIGFkZF9zZXR1cCA9PSBUUlVFCmBgYAoK