0. INTRODUCTION

Malcolm Gladwell first popped onto my radar with his book Blink in the early 2000s as I was studying military strategy and problem solving at an Army school on Kansas. The book fit nicely into the curriculum as it explores how we think about thinking. Gut instincts versus deep planning, how our brains work, and why some decisions are inexplicable are just a few of the topics that resonated with me from Blink. I also listen to a couple of the podcasts from his Pushkin production company as they dig into why we see the world the way we do. With so much positive interactions with his past works, I thought I’d be thrilled about his newest book, The Bomber Mafia, which was published in the Spring of 2021. I have yet to buy it. While it gets 4.5 out of 5 stars over its almost 6,000 reviews, many historians I follow and respect have been harsh critics of the book. While most of Gladwell’s work has focused on why people and businesses are successful, The Bomber Mafia is an historical account of the struggle between the concepts of precision bombing and scorched earth tactics for U.S. aviators in World War II. Historians by the bunches are writing that Gladwell missed the mark on much of his historic analysis.

This project will analyze Amazon book reviews to determine if topic modeling techniques can identify specific themes of why people may be unsatisfied with Gladwell’s latest work of nonfiction.

Project Focus

This week’s independent analysis will focus on identifying “topics” by examining how words cohere into different latent, or hidden, themes based on patterns of co-occurrence of words within online book reviews. Three questions will drive the analysis:

  1. Can distinct themes be identified through automated topic modeling?
  2. Will different topic modeling techniques (stemming, LDA vs STM) result in similar or dissimilar topics?
  3. Do the themes correlate to negative reactions to the book?

With respect to the actual R workflow of applying topic models to documents and text of interests, Silge & Robinson add a new bottom row to their flowchart detailing new data structures (i.e., a corpus object and document-term matrix) and and the LDA model:

Figure source: Silge, J., & Robinson, D. (2017). Text mining with R: A tidy approach. O’Reilly Media, Inc. Retrieved from: https://www.tidytextmining.com/topicmodeling.html

This project will explore multiple topic modeling techniques for identifying themes in a corpus of book reviews that received relatively low ratings. The project will be organized according to the basic data science analytic process:

  1. Prepare: Prior to analysis, I’ve spent quite a bit of time on the Amazon book review site ensuring I understand the structure and content of customer reviews. Since my main goal is to understand some of the negative feedback, the data for this project will taken exclusively from “critical” reviews, a mix of 1 to 3-star ratings that Amazon has labeled as negative. This should aid in the interpretation of our results and help guide some decisions as we tidy, model, and visualize the data.
  2. Wrangle: In section 2, the text is tidied and tokenized using the tidytext package and then stemmed through the stm package. This package makes use of the tm text mining package to preprocess text and prior to word stemming. Finally, the tidied text is transformed into a document term matrix (DTM) to describe the frequency of terms across the body of book reviews.
  3. Model: This analysis will apply two different approaches to topic modeling: Latent Dirichlet Allocation (LDA) and Structural Topic Modeling (STM), which is very similar to LDA but can use metadata about documents to improve the assignment of words to “topics” in a corpus and examine relationships between topics and covariates. While LDA relies on a bag of words grouping of terms to form independent clusters within topics, STM uses structual context to correlate topics to parent documents.
  4. Explore: To further explore the results of the topic models, several functions from the topicmodels and stm packages will be used including the findThoughts function for viewing documents assigned to a given topic and the toLDAvis function for exploring topic and word distributions.
  5. Communicate: The results of the multiple models will be consolidated to address the three questions guiding the project.

1. PREPARE

To assist in understanding the context behind the focus questions and data sources for this project, this section will focus on the following topics:

  1. Questions. I’ll review what insights topic modeling can provide to a series of questions on latent theme discovery.
  2. Project Setup. I’ll explain the set up of the R project to include data sources.

1a. Guiding Questions

Can distinct themes be identified through automated topic modeling?

In this case, I’m looking for themes common to multiple book reviews. Technically, each review can be thought of as “themed,” but I would like to see if topics emerge outside of the base documents.

Will different topic modeling techniques (stemming, LDA vs STM) result in similar or dissimilar topics?

We have a fairly small data set at 152 documents, so the value of stemming may be less than if we had a much larger set of terms to assess. Still, reducing redundancy may enable the emergence of topics masked by similar words that are repeated.

Do the themes correlate to negative reactions to the book?

This is the true purpose of the project. Can we discern why these reviews were critical of the book?

1b. Set Up

As highlighted in Chapter 6 of Data Science in Education Using R (DSIEUR), one of the first steps of every workflow should be to set up a “Project” within RStudio. This will be our “home” for any files and code used or created in the analysis.

The following packages were installed and/or loaded for this project:

library(tidyverse)
library(tidytext)
library(rvest)
library(purrr)
library(stringr)
library(XML)
library(RCurl)

library(SnowballC)
library(topicmodels)
library(stm)
library(ldatuning)
library(knitr)

2. WRANGLE

  1. Import Data. Once I pulled the data from Amazon, it was converted to a .csv file to mitigate the need to keep pulling data from the web.
  2. Cast a DTM. The tidytext package was used to “tidy” and tokenize book review data and the cast_dtm() function created the document term matrix (DTM) needed for topic modeling.
  3. Stemming. I experimented with stemming topic words through the textProcessor() function. I”ll compare this method with non-stemming techniques to gauge potential impact on consistency of theme identification.

2a. Import Amazon Book Reviews

My web scraping method was inspired by an article by Riki Saito posted to thbe R news and tutorials site R-BLOGGERS. The process began with establishing a connection to the site hosting public reviews of The Bomber Mafia:

asin <- "0316296619"
url <- paste0("https://www.amazon.com/dp/", asin)
doc <- read_html(url)

prod <- html_nodes(doc, "#productTitle") %>% 
  html_text() %>% 
  gsub("\n", "", .) %>% 
  trimws()

prod
## [1] "The Bomber Mafia: A Dream, a Temptation, and the Longest Night of the Second World War"

Now that I’m confident I can call the correct product review website, below is the code to ensure the html data is formatted appropriately for analysis. For this effort, I’m only interested in the review title, date, star ratings and comments:

scrape_amazon <- function(url, throttle = 0){

# Set throttle between URL calls
sec = 0
if(throttle < 0) warning("throttle was less than 0: set to 0")
if(throttle > 0) sec = max(0, throttle + runif(1, -1, 1))
  
# obtain HTML of URL
doc <- read_html(url)
  
# Parse relevant elements from HTML
title <- doc %>%
  html_nodes("#cm_cr-review_list .a-color-base") %>%
  html_text() %>% 
  gsub("^\\s+|\\s+$", "", .)
  
str_replace_all(title, "[\r\n]" , "")
  
date <- doc %>%
  html_nodes("#cm_cr-review_list .review-date") %>%
  html_text() %>% 
  gsub(".*on ", "", .)
  
stars <- doc %>%
  html_nodes("#cm_cr-review_list  .review-rating") %>%
  html_text() %>%
  str_extract("\\d") %>%
  as.numeric() 
  
comments <- doc %>%
  html_nodes("#cm_cr-review_list .review-text") %>%
  html_text() %>% 
  gsub("^\\s+|\\s+$", "", .)
  
  
# Combine attributes into a single data frame
df <- data.frame(title, date, stars, comments, stringsAsFactors = F)
  
return(df)
}

With the format set, I wanted to run a sample test to ensure the data arrived ready for analysis. I pulled the first page of reviews and sent it to a data frame:

url <- "http://www.amazon.com/product-reviews/0316296619/?pageNumber=1"
reviews <- scrape_amazon(url)

str(reviews)

Data frame for 1st ten reviews of The Bomber Mafia

My original intent was to download the first 100 pages which should yield approximately 1000 book reviews to begin exploratory analysis. However, it became problematic to get that data directly for a reason I didn’t anticipate. I tried multiple times and could only get 498 reviews, just shy of 50 full pages. Turns out that not every country submits book reviews in the same format or by using the same entry fields. My last book review entry was from Japan and the format did not complete the intended cells correctly in the data frame causing an error at entry #498. I researched a few solutions addressing this issue and they exceeded my skill level, so I took an alternate route.

If I were able to successfully scrape the first 1000 reviews, the results would be a mix of ratings from 1 through 5 stars. No telling how many I would get at each rating. Since my project is focused on the bad reviews, I decided to pull exclusively from the reviews Amazon classifies as “critical” as that’s where all the comments are contained that I’d like to analyze. With that in mind, I rebuilt my query to pull these reviews from that single category. There are only 16 pages of these “critical” reviews:

pages <- 16

reviews_all <- NULL

for(page_num in 1:pages){
  url <- paste0("https://www.amazon.com/product-reviews/0316296619/ref=cm_cr_getr_d_paging_btm_next_16?ie=UTF8&reviewerType=all_reviews&pageNumber=",page_num,"&filterByStar=critical")
  reviews <- scrape_amazon(url, throttle = 3)
  reviews_all <- rbind(reviews_all, cbind(prod, reviews))
}

This code returned a data frame with 153 perfectly formatted reviews ranging from 1 to 3 stars. There was one review not in English, so I pulled that from the data set to keep things simple, leaving 152 remaining entries. Finally, I added a column with a unique ID number to discriminate between book reviews. Since some readers title their review after the book, I wanted to ensure each review was treated as a separate document and not combined with others with the same title. To keep from having to continually run this query, I converted the data frame into a .csv file to continue the analysis:

reviews_all <- reviews_all[-nrow(reviews_all),]
reviews_all <- cbind(ID = 1:nrow(reviews_all), reviews_all)

write.csv(reviews_all, "data/reviews_all.csv")

2b. Cast a Document Term Matrix

In this section I employed some familiar tidytext functions to tidy and tokenize text while also introducing the stm package for processing text and transforming our data frames into new data structures required for topic modeling.

Functions Used

tidytext functions

  • unnest_tokens() splits a column into tokens
  • anti_join() returns all rows from x without a match in y and used to remove stop words from out data.
  • cast_dtm() takes a tidied data frame take and “casts” it into a document-term matrix (dtm)

dplyr functions

  • count() lets you quickly count the unique values of one or more variables
  • group_by() takes a data frame and one or more variables to group by
  • summarise() creates a summary of data using arguments like sum and mean

stm functions

  • textProcessor() takes in a vector or column of raw texts and performs text processing like removing punctuation and word stemming.
  • prepDocuments() performs several corpus manipulations including removing words and renumbering word indices

Tidying Text

  1. Transforming our text into “tokens”
  2. Removing unnecessary characters, punctuation, and white space
  3. Converting all text to lowercase
  4. Removing stop words such as “the”, “of”, and “to”
reviews_tidy <- reviews_all %>%
  unnest_tokens(output = word, input = comments) %>%
  anti_join(stop_words, by = "word")

Now let’s do a quick word count to see some of the most common words used throughout the customer reviews. This should give a sense of what we’re working with and later we’ll need these word counts for creating our document term matrix for topic modeling:

my_kable(reviews_tidy %>%
     count(word, sort = TRUE))
word n
bombing 187
book 182
war 157
gladwell 155
precision 72
lemay 70
bomber 65
air 62
japan 62
read 53

Terms like “bombing,” “war,” and “air” are about what we would have expected from a book on the WW II air campaign. The terms “written” and “subject” however, are not so intuitive and may be worth a quick look once we get into exploratory analysis. Additionally, many similar words appear that could skew analysis. “Gladwell” and “gladwell’s” do not carry significantly different meanings. Same could be said for “bomb” vs. “bombing.” This leads me to infer that stemming could reduce this redundancy and better highlight less common terms.

To reduce the influence of unnecessary terms, I’ll create a list of custom stop words to filter from the tidied data frame.

my_stopwords <- c("war", "bomb", "gladwell", "book", "bombing", "lemay", "read", "author", "u.s", "air", "bomber", "japan", "mafia", "books", "british", "wwii", "military", "malcolm", "hansell", "bombs", "world", "gladwell's", "cities", "curtis", "germany", "napalm", "casualties", "bombers", "american", "bombsight", "wars",  "army", "ii", "makes", "night", "29", "dropped", "lives", "japanese", "story", "people", "civilian", "gladwell’s")

reviews_tidy_2 <- reviews_tidy %>%
  filter(!word %in% my_stopwords)

my_kable(reviews_tidy_2 %>%
      count(word, sort = TRUE))
word n
precision 72
history 49
time 33
strategic 27
force 24
short 24
norden 22
tailwind 22
written 19
atomic 18

The updated term list looks to be much less a summary of the plot and highlights specific descriptors of writing style or value. To gain a little more context, I’ll do a search for one of the terms identified earlier as a possible indicator of criticism, “written”:

error_quotes <- reviews_all %>%
  select(comments) %>% 
  filter(grepl('written', comments))

sample_n(error_quotes,3) %>%
  kable()
comments
I found the story very interesting and well written but for $15.00 the book was much to short I read it in under 4 hours.
From this well-written book, we learn a third possible reason why Japan surrendered to end World War II. As kids, we all were told it was the atomic bomb. Then we learned it was also the addition of the Soviet Union after Germany was defeated with the potential to invade Japan. Now we hear Curtis LeMay’s napalm bombing of scores of Japanese cities did it, at least according to LeMay, and despite this, the theory of precision bombing is now the acceptable or at least morally superior way to wage war. The book is not convincing. Despite LeMay’s bombing, it is estimated that Japan had less than one million civilian casualties. The Soviet Union with maybe 2.5 times more people than Japan, and which was invaded, had five or ten million civilian casualties and it was a winner. It’s easy to argue the Soviet Union could have ultimately finished defeating Germany without any help from the Western Allies. In contrast, neither precision nor blanket bombing of civilians has been a proven success, and it’s still completely unclear which is better or even worthwhile. It’s also not clear why Japan really surrendered and this book is not helpful, but it’s important to know the answer. The US hasn’t been on the winning side of a significant war since, if the standard is, as it should be, that it wins the war, it feeds the defeated people, it takes over the government changing the constitution and it creates a friendly democratic country out of a completely undemocratic society. This has essentially been the result for Japan and Germany. We can’t say that about Korea, Vietnam, Iraq or Afghanistan or the so-called victory in the Cold War, If we’re not going to accomplish that, it greatly reduces the reason to fight at all except if directly attacked.
OK, I have never been a big fan of Gladwell, even before this. His “6 degrees of separation from a butterfly flapping its wings in Nairobi”. Imaginative, but verging on Sophist (anything can shown to be related to something else to show causality).This is a bit of a vanity project. “I noticed I have a lot of military history books - so I decided to do one myself!” Although, this was developed as a podcast, then an audiobook - and then a “real” book.It is obvious that the backbone of his book has been developed from secondary sources, but all he annotates and quotes are “original sources”. Which tend to be oral history interviews, from decades after the fact. And official US military histories. Questionable, at best.That he never mentions W G Sebald’s essays on the bombing of Germany in WWII, or Martin Caidin’s books, or any other of the valuable “secondary” sources that are out there is somewhat stunning. Caidin’s books, while written early on, and a bit dry, cover exactly the air campaigns Gladwell writes about here - the ballbearing plant raids in Germany, and the fire bombings of Japanese cities.What is also missing is politics and economics. Churchill is brought in briefly, but mostly for his friendship with Lindemann. Economics? How could he not have read A J P Taylor’s comments on how much of the British economy was invested in the bombings - so stopping them would have been a tough (economic) decision.I did enjoy the chapter on the development of napalm. And the Norden bombsight - but, was it ever successfully used? And if it worked, how often, why, where, and how well?Overall, a slight book. The other issue is in the end it seems he wants to have it both ways. He admires the “humanism” (military and humanism seems like an oxymoron to me) of the Bomber Mafia - but he obviously also admires LeMay for “getting it done”.A quick read, a quick history - that should lead you to other, more insightful and complete, books. For me, that meant going back to W G Sebald, where you can see a real brilliant mind at work.

This request returned 10 reviews describing the author’s writing style. They include references to both Gladwell’s talent as a story teller, but also show that many were unhappy with this particular history manuscript.

Creating a Document Term Matrix

For this analysis, each individual book review will be treated as a unique “document.” To create the DTM, we’ll need to first count() how many times each word occurs in each document, or ID in our case, and create a matrix that contains one row per post as the original data frame did, but now contains a column for each word in the entire corpus and a value of n for how many times that word occurs in each review.

To create this document term matrix from our post counts, we’ll use the cast_dtm() function like so and assign it to the variable reviews_dtm:

reviews_dtm <- reviews_tidy_2 %>%
  count(ID, word) %>%
  cast_dtm(ID, word, n)

The result of this function is a simple_triplet_matrix object, called reviews_dtm. This DTM contains 152 documents and 3,686 terms.

3. MODEL

3a. Finding K

The ldatuning package has functions for both calculating and plotting different metrics that can be used to estimate the most preferable number of topics for LDA modeling. It also conveniently takes the standard document term matrix object created from the tidy text.

k_metrics <- FindTopicsNumber(
  reviews_dtm,
  topics = seq(10, 75, by = 5),
  metrics = "Griffiths2004",
  method = "Gibbs",
  control = list(),
  mc.cores = NA,
  return_models = FALSE,
  verbose = FALSE,
  libpath = NULL
)
FindTopicsNumber_plot(k_metrics)

Using the Griffiths2004 metrics included in the default example produced the results as show in the figure below:

As a general rule of thumb and overly simplistic heuristic, an inflection point in the plot indicates an optimal number of topics to select for a value of K (15 in this case).

3b. Fitting a Topic Modeling with LDA

reviews_lda <- LDA(reviews_dtm, 
                  k = 15, 
                  control = list(seed = 588)
                  )
reviews_lda
## A LDA_VEM topic model with 15 topics.

The control = argument was used to pass a random number (588) to seed the assignment of topics to each word in the corpus. Since LDA is a stochastic algorithm that could have different results depending on where the algorithm starts, a seed was specified for reproducibility. The model will now produce the same results every time the same number of topics is specified.

3c. Fitting a Structural Topic Model

Processing for STM

temp <- textProcessor(reviews_all$comments, 
                    metadata = reviews_all,  
                    lowercase=TRUE, 
                    removestopwords=TRUE, 
                    removenumbers=TRUE,  
                    removepunctuation=TRUE, 
                    wordLengths=c(3,Inf),
                    stem=FALSE,
                    onlycharacter= FALSE, 
                    striphtml=TRUE, 
                    customstopwords=my_stopwords)
## Building corpus... 
## Converting to Lower Case... 
## Removing punctuation... 
## Removing stopwords... 
## Remove Custom Stopwords...
## Removing numbers... 
## Creating Output...

Unlike the unnest_tokens() function, the output is not a nice tidy data frame. Topic modeling using the stm package requires a very unique set of inputs that are specific to the package. One change I made to the default values was to change stem to “FALSE.” The relatively small data set mixed with custom stop words should mitigate against redundant terms and potentially preserve some context.

The stm Package

As shown above, STM produced an unusual temp textProcessor output that is unique to the stm package. The stm() function for fitting a structural topic model does not take a fairly standard document term matrix like the LDA() function.

Before an STM model can be fitted, elements must be extracted from the temp object created after processing the review text. Specifically, the stm() function expects the following arguments:

  • documents = the document term matrix to be modeled in the native STM format
  • data = an optional data frame containing meta data for the prevalence and/or content covariates to include in the model
  • vocab = a character vector specifying the words in the corpus in the order of the vocab indices in documents.

Let’s go ahead and extract these elements:

docs <- temp$documents 
meta <- temp$meta 
vocab <- temp$vocab 

And now use these elements to fit the model using the same number of topics for K that we specified for our LDA topic model. Let’s also take advantage of the fact that we can include the ID and title covariates in the prevealence = argument to help improve, in theory, model fit:

reviews_stm <- stm(documents=docs, 
         data=meta,
         vocab=vocab, 
         prevalence =~ ID + title,
         K=10,
         max.em.its=25,
         verbose = FALSE,
         gamma.prior  = 'L1')
reviews_stm
## A topic model with 10 topics, 152 documents and a 3994 word dictionary.

As noted earlier, the stm package has a number of handy features. One of these is the plot.STM() function for viewing the most probable words assigned to each topic.

By default, it only shows the first 3 terms so let’s change that to 5 to help with interpretation:

plot.STM(reviews_stm, n = 5)

I chose two different K values for each of the topic models. I used K=15 for LDA as that’s what the Gibbs method recommended, but I reduced K to 10 for the STM model. That shift was made as I saw multiple overlapping topics in STM when K was set to 15. Screen shots below capture the STM differences for each of those K values using the LDAvis topic browser:

toLDAvis(mod = reviews_stm, docs = docs)

As you can see from the browser screen shot below, our current STM model of 15 topics is resulting in a lot of overlap among topics and suggest that 15 may not be an optimal number of topics:

Changing K to 10 significantly reduced the overlap in topics:

4. EXPLORE

4a. Exploring Beta Values

Hidden within this forums_lda topic model object we created are per-topic-per-word probabilities, called β (“beta”). It is the probability of a term (word) belonging to a topic.

Let’s take a look at the 5 most likely terms assigned to each topic, i.e. those with the largest β values using the terms() function from the topicmodels package:

terms(reviews_lda, 5)
##      Topic 1     Topic 2    Topic 3     Topic 4     Topic 5     Topic 6    
## [1,] "words"     "tactics"  "strategic" "tailwind"  "5"         "history"  
## [2,] "precision" "history"  "precision" "ferocious" "precision" "blah"     
## [3,] "burning"   "podcasts" "short"     "takes"     "narrative" "surrender"
## [4,] "claiming"  "feels"    "2"         "force"     "subject"   "precision"
## [5,] "doesn’t"   "combat"   "force"     "precision" "strategic" "bad"      
##      Topic 7     Topic 8     Topic 9     Topic 10  Topic 11        Topic 12   
## [1,] "precision" "history"   "precision" "time"    "precision"     "precision"
## [2,] "results"   "argument"  "reader"    "history" "effectiveness" "fighter"  
## [3,] "corps"     "precision" "harris"    "errors"  "morally"       "strategic"
## [4,] "raids"     "topic"     "history"   "short"   "means"         "targets"  
## [5,] "daylight"  "found"     "raids"     "podcast" "fighter"       "111"      
##      Topic 13      Topic 14    Topic 15   
## [1,] "idea"        "history"   "time"     
## [2,] "effective"   "tailwind"  "worth"    
## [3,] "stars"       "time"      "moral"    
## [4,] "note"        "effective" "treatment"
## [5,] "segregation" "lamaye"    "based"

Based on our selected number of topics for our corpus, some themes are fairly intuitive to interpret. For example:

  • Topic 2 (tactics, history, podcasts, feels, combat) seems to be about relating the authors la takes on military history to his podcast (which is about revisionist history);

  • Topic 6 (history, blah, surrender, precision, bad) indicates dissatisfaction with the author’s analysis of a specific event; and

  • Topic 4 (tailwind, ferocious, takes, force, precision) relates to a specific claim the author makes regarding the wind required for a plane to take off (debunked by many).

To get a more intuitive description of the relationships above, the tidytext package can covert the LDA model to a tidy data frame containing these beta values for each term:

tidy_lda <- tidy(reviews_lda)
tidy_lda
## # A tibble: 54,645 × 3
##    topic term         beta
##    <int> <chr>       <dbl>
##  1     1 100,000 2.68e-271
##  2     2 100,000 6.62e-270
##  3     3 100,000 2.69e-  3
##  4     4 100,000 2.13e-270
##  5     5 100,000 1.35e-268
##  6     6 100,000 1.24e-270
##  7     7 100,000 3.75e-  3
##  8     8 100,000 6.83e-270
##  9     9 100,000 1.61e-269
## 10    10 100,000 1.81e-270
## # … with 54,635 more rows

Then we can plot this information visually:

top_terms <- tidy_lda %>%
  group_by(topic) %>%
  slice_max(beta, n = 5, with_ties = FALSE) %>%
  ungroup() %>%
  arrange(topic, -beta)
top_terms %>%
  mutate(term = reorder_within(term, beta, topic)) %>%
  group_by(topic, term) %>%    
  arrange(desc(beta)) %>%  
  ungroup() %>%
  ggplot(aes(beta, term, fill = as.factor(topic))) +
  geom_col(show.legend = FALSE) +
  scale_y_reordered() +
  labs(title = "Top 5 terms in each LDA topic",
       x = expression(beta), y = NULL) +
  facet_wrap(~ topic, ncol = 4, scales = "free")

4b. Exploring Gamma Values

Now that we have a sense of the most common words associated with each topic, let’s take a look at the topic prevalence in the book review corpus, including the words that contribute to each topic we examined above.

Also, hidden within our forums_lda topic model object we created are per-document-per-topic probabilities, called γ (“gamma”). This provides the probabilities that each document is generated from each topic, the gamma matrix. Beta and gamma values can be combined to understand the topic prevalence in our corpus, and which words contribute to each topic.

First, let’s create two tidy data frames for our beta and gamma values

td_beta <- tidy(reviews_lda)
td_gamma <- tidy(reviews_lda, matrix = "gamma")
td_beta
## # A tibble: 54,645 × 3
##    topic term         beta
##    <int> <chr>       <dbl>
##  1     1 100,000 2.68e-271
##  2     2 100,000 6.62e-270
##  3     3 100,000 2.69e-  3
##  4     4 100,000 2.13e-270
##  5     5 100,000 1.35e-268
##  6     6 100,000 1.24e-270
##  7     7 100,000 3.75e-  3
##  8     8 100,000 6.83e-270
##  9     9 100,000 1.61e-269
## 10    10 100,000 1.81e-270
## # … with 54,635 more rows
td_gamma
## # A tibble: 2,280 × 3
##    document topic     gamma
##    <chr>    <int>     <dbl>
##  1 1            1 0.0000427
##  2 2            1 0.000108 
##  3 3            1 0.000423 
##  4 4            1 0.00238  
##  5 5            1 0.000372 
##  6 6            1 0.0000478
##  7 7            1 0.0000614
##  8 8            1 0.0000494
##  9 9            1 0.000228 
## 10 10           1 1.00     
## # … with 2,270 more rows

Next, we’ll create a filtered data frame of our top_terms, join this to a new data frame for gamma-terms and create a nice clean table using the kabel() function knitr package:

top_terms <- td_beta %>%
  arrange(beta) %>%
  group_by(topic) %>%
  top_n(7, beta) %>%
  arrange(-beta) %>%
  select(topic, term) %>%
  summarise(terms = list(term)) %>%
  mutate(terms = map(terms, paste, collapse = ", ")) %>% 
  unnest()
## Warning: `cols` is now required when using unnest().
## Please use `cols = c(terms)`
gamma_terms <- td_gamma %>%
  group_by(topic) %>%
  summarise(gamma = mean(gamma)) %>%
  arrange(desc(gamma)) %>%
  left_join(top_terms, by = "topic") %>%
  mutate(topic = paste0("Topic ", topic),
         topic = reorder(topic, gamma))

gamma_terms %>%
  select(topic, gamma, terms) %>%
  kable(digits = 3, 
        col.names = c("Topic", "Expected topic proportion", "Top 7 terms"))
Topic Expected topic proportion Top 7 terms
Topic 6 0.135 history, blah, surrender, precision, bad, content, time, future, evil, humans
Topic 15 0.128 time, worth, moral, treatment, based, biased, reading
Topic 10 0.117 time, history, errors, short, podcast, basic, precision, hard, wrong
Topic 8 0.117 history, argument, precision, topic, found, pacific, podcast
Topic 4 0.104 tailwind, ferocious, takes, force, precision, enola, error, gay, hiroshima, nagasaki
Topic 14 0.098 history, tailwind, time, effective, lamaye, force, navy, naval, personal, writing
Topic 2 0.098 tactics, history, podcasts, feels, combat, subject, thinking, ww, technology, strategies, practices, theme
Topic 3 0.040 strategic, precision, short, 2, force, historians, incendiary, message, morally, quotes, tokyo, authors
Topic 13 0.034 idea, effective, stars, note, segregation, timeline, je
Topic 5 0.027 5, precision, narrative, subject, strategic, raf, hard, lot
Topic 11 0.027 precision, effectiveness, morally, means, fighter, fighters, targets, combat, developed, prior, warfare, accuracy, aircraft, failure, results, flying, proved, provide, daylight, target, defensive, allied, saturation, anti, stealth, uncertain
Topic 12 0.021 precision, fighter, strategic, targets, 111, enemy, weapons, soviet, union, fan
Topic 9 0.021 precision, reader, harris, history, raids, left, force, arthur, time, fails, it’s, atomic, firebombing, imperial, invasion, raf, technology
Topic 1 0.018 words, precision, burning, claiming, doesn’t, satan, word
Topic 7 0.016 precision, results, corps, raids, daylight, civilians, atomic, deaths

And let’s also compare this to the most prevalent topics and terms from our reviews_stm model that we created using the plot() function:

plot(reviews_stm, n = 7)

4c. Reading the Tea Leaves

Recognizing that topic modeling is best used as a “tool for reading” and provides only an incomplete answer to our overarching, “How do we quantify what a corpus is about?”, the results do suggest some potential topics that have emerges as well as some areas worth following up on.

Specifically, looking at some of the common clusters of words for the more prevalent topics suggest that some key topics or “latent themes” (renamed in bold) might include:

  • Historical Inaccuracies: Unsurprising, given that most of those who I heard had issues with the book were historians. Prevalent in both the reviews_stm and reviews_lda models contains the terms “errors”, “wrong”, “history”. This could represent the general consensus across all the critical reviews. Some specific examples concern how planes work (tailwind) and which planes dropped specific ordinance during WW II (enola, gay, hiroshima, nagasaki).
  • Misunderstanding of Air Power and Strategy in WW II: Topics 8 & 2 from the LDA model focused in on how some thought the author may have been out of his depth in doing a history text on WW II. Words like “argument” and “podcast” and “theme” hint at the fact he produces a podcast on revisionist history and his arguments on strategy and tactics were seen as weak.
  • Lack of References and Citing only a few known historians: Topic 3 (STM) and topic 15 (LDA) reference “research” and “biased” as indicators of multiple reviews claiming the author cited the minimum historical references in the book. One review even referred to the book as an overly long blog post.

5. Communicate

This project was designed to answer three key questions about using topic modeling to identify latent themes in a body of text. Specifically:

  1. Can distinct themes be identified through automated topic modeling?

I believe the simple answer is that yes, multiple themes can be identified. Being the techniques are unsupervised, however, their is an art to determining how inclusive those themes may be. In this case, I had to weed out much of the information about the plot of the book as it was common to almost every review. Losing that context may be detrimental to identifying topics, but if you have some understanding of the overall corpus, you can mitigate that risk.

  1. Will different topic modeling techniques (stemming, LDA vs STM) result in similar or dissimilar topics?

In this case, both LDA and STM models returned relatively similar results. Many of the same key terms appeared in both models. However, the number of topics (K) returned different results in each model. LDA was more consistent with K = 15 while STM models returned fewer overlapping topics with K =10. Lastly, stemming was less of a factor in this project as I reduced redundant terms during wrangling. Adding some additional stop words ensured that stemming would be less impactful. When it was tried, the results were not consistent with the other models.

  1. Do the themes correlate to negative reactions to the book?

The topics returned by both models do correlate to the negative reactions I expected from the reviews. A major limitation in this study was the size of the data set. With only 152 reviews categorized as “critical” of the book, I was left to analyze a fairly small corpus of text. I would argue that a minimum of 500 documents are needed when the text is so short in each document. In this case, many of the reviews only differed in a couple of key words that failed to register very high on frequency count. This kept those terms towards the bottom of key word counts and harder to distinguish as drivers of topic development.

LS0tCnRpdGxlOiAnVW5pdCAzIEluZGVwZW5kZW50IEFuYWx5c2lzOiBUb3BpYyBNb2RlbGluZyBpbiBCb29rIFJldmlld3MnCmF1dGhvcjogIkphbWVzIEhhcmRhd2F5IgpkYXRlOiAiMi8yMy8yMDIyIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogVFJVRQplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpteV9rYWJsZSA9IGZ1bmN0aW9uKHgsIG1heC5yb3dzPTEwLCAuLi4pIHsKICBrYWJsZSh4WzE6bWF4LnJvd3MsIF0sIC4uLikKfQpgYGAKCiMjIDAuIElOVFJPRFVDVElPTgoKTWFsY29sbSBHbGFkd2VsbCBmaXJzdCBwb3BwZWQgb250byBteSByYWRhciB3aXRoIGhpcyBib29rClsqQmxpbmsqXShodHRwczovL3d3dy5nbGFkd2VsbGJvb2tzLmNvbS90aXRsZXMvbWFsY29sbS1nbGFkd2VsbC9ibGluay85NzgwMzE2MTcyMzI1LykKaW4gdGhlIGVhcmx5IDIwMDBzIGFzIEkgd2FzIHN0dWR5aW5nIG1pbGl0YXJ5IHN0cmF0ZWd5IGFuZCBwcm9ibGVtCnNvbHZpbmcgYXQgYW4gQXJteSBzY2hvb2wgb24gS2Fuc2FzLiBUaGUgYm9vayBmaXQgbmljZWx5IGludG8gdGhlCmN1cnJpY3VsdW0gYXMgaXQgZXhwbG9yZXMgaG93IHdlIHRoaW5rIGFib3V0IHRoaW5raW5nLiBHdXQgaW5zdGluY3RzCnZlcnN1cyBkZWVwIHBsYW5uaW5nLCBob3cgb3VyIGJyYWlucyB3b3JrLCBhbmQgd2h5IHNvbWUgZGVjaXNpb25zIGFyZQppbmV4cGxpY2FibGUgYXJlIGp1c3QgYSBmZXcgb2YgdGhlIHRvcGljcyB0aGF0IHJlc29uYXRlZCB3aXRoIG1lIGZyb20KKkJsaW5rKi4gSSBhbHNvIGxpc3RlbiB0byBhIGNvdXBsZSBvZiB0aGUgcG9kY2FzdHMgZnJvbSBoaXMKW1B1c2hraW5dKGh0dHBzOi8vd3d3LnB1c2hraW4uZm0vKSBwcm9kdWN0aW9uIGNvbXBhbnkgYXMgdGhleSBkaWcgaW50bwp3aHkgd2Ugc2VlIHRoZSB3b3JsZCB0aGUgd2F5IHdlIGRvLiBXaXRoIHNvIG11Y2ggcG9zaXRpdmUgaW50ZXJhY3Rpb25zCndpdGggaGlzIHBhc3Qgd29ya3MsIEkgdGhvdWdodCBJJ2QgYmUgdGhyaWxsZWQgYWJvdXQgaGlzIG5ld2VzdCBib29rLApbKlRoZSBCb21iZXIKTWFmaWEqXShodHRwczovL3d3dy5nbGFkd2VsbGJvb2tzLmNvbS90aXRsZXMvbWFsY29sbS1nbGFkd2VsbC90aGUtYm9tYmVyLW1hZmlhLzk3ODAzMTYyOTY2MTgvKSwKd2hpY2ggd2FzIHB1Ymxpc2hlZCBpbiB0aGUgU3ByaW5nIG9mIDIwMjEuIEkgaGF2ZSB5ZXQgdG8gYnV5IGl0LiBXaGlsZQppdCBnZXRzIDQuNSBvdXQgb2YgNSBzdGFycyBvdmVyIGl0cyBhbG1vc3QgNiwwMDAgcmV2aWV3cywgbWFueQpoaXN0b3JpYW5zIEkgZm9sbG93IGFuZCByZXNwZWN0IGhhdmUgYmVlbiBoYXJzaCBjcml0aWNzIG9mIHRoZSBib29rLgpXaGlsZSBtb3N0IG9mIEdsYWR3ZWxsJ3Mgd29yayBoYXMgZm9jdXNlZCBvbiB3aHkgcGVvcGxlIGFuZCBidXNpbmVzc2VzCmFyZSBzdWNjZXNzZnVsLCAqVGhlIEJvbWJlciBNYWZpYSogaXMgYW4gaGlzdG9yaWNhbCBhY2NvdW50IG9mIHRoZQpzdHJ1Z2dsZSBiZXR3ZWVuIHRoZSBjb25jZXB0cyBvZiBwcmVjaXNpb24gYm9tYmluZyBhbmQgc2NvcmNoZWQgZWFydGgKdGFjdGljcyBmb3IgVS5TLiBhdmlhdG9ycyBpbiBXb3JsZCBXYXIgSUkuIEhpc3RvcmlhbnMgYnkgdGhlIGJ1bmNoZXMgYXJlCndyaXRpbmcgdGhhdCBHbGFkd2VsbCBtaXNzZWQgdGhlIG1hcmsgb24gbXVjaCBvZiBoaXMgaGlzdG9yaWMgYW5hbHlzaXMuCgpUaGlzIHByb2plY3Qgd2lsbCBhbmFseXplIFtBbWF6b24gYm9vawpyZXZpZXdzXShodHRwczovL3d3dy5hbWF6b24uY29tL2RwLzAzMTYyOTY2MTkjY3VzdG9tZXJSZXZpZXdzKSB0bwpkZXRlcm1pbmUgaWYgdG9waWMgbW9kZWxpbmcgdGVjaG5pcXVlcyBjYW4gaWRlbnRpZnkgc3BlY2lmaWMgdGhlbWVzIG9mCndoeSBwZW9wbGUgbWF5IGJlIHVuc2F0aXNmaWVkIHdpdGggR2xhZHdlbGwncyBsYXRlc3Qgd29yayBvZiBub25maWN0aW9uLgoKIyMjIFByb2plY3QgRm9jdXMKClRoaXMgd2VlaydzIGluZGVwZW5kZW50IGFuYWx5c2lzIHdpbGwgZm9jdXMgb24gaWRlbnRpZnlpbmcgInRvcGljcyIgYnkKZXhhbWluaW5nIGhvdyB3b3JkcyBjb2hlcmUgaW50byBkaWZmZXJlbnQgbGF0ZW50LCBvciBoaWRkZW4sIHRoZW1lcwpiYXNlZCBvbiBwYXR0ZXJucyBvZiBjby1vY2N1cnJlbmNlIG9mIHdvcmRzIHdpdGhpbiBvbmxpbmUgYm9vayByZXZpZXdzLgpUaHJlZSBxdWVzdGlvbnMgd2lsbCBkcml2ZSB0aGUgYW5hbHlzaXM6CgoxLiAgQ2FuIGRpc3RpbmN0IHRoZW1lcyBiZSBpZGVudGlmaWVkIHRocm91Z2ggYXV0b21hdGVkIHRvcGljIG1vZGVsaW5nPwoyLiAgV2lsbCBkaWZmZXJlbnQgdG9waWMgbW9kZWxpbmcgdGVjaG5pcXVlcyAoc3RlbW1pbmcsIExEQSB2cyBTVE0pCiAgICByZXN1bHQgaW4gc2ltaWxhciBvciBkaXNzaW1pbGFyIHRvcGljcz8KMy4gIERvIHRoZSB0aGVtZXMgY29ycmVsYXRlIHRvIG5lZ2F0aXZlIHJlYWN0aW9ucyB0byB0aGUgYm9vaz8KCldpdGggcmVzcGVjdCB0byB0aGUgYWN0dWFsIFIgd29ya2Zsb3cgb2YgYXBwbHlpbmcgdG9waWMgbW9kZWxzIHRvCmRvY3VtZW50cyBhbmQgdGV4dCBvZiBpbnRlcmVzdHMsIFtTaWxnZSAmClJvYmluc29uXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vdG9waWNtb2RlbGluZy5odG1sKSBhZGQgYSBuZXcKYm90dG9tIHJvdyB0byB0aGVpciBmbG93Y2hhcnQgZGV0YWlsaW5nIG5ldyBkYXRhIHN0cnVjdHVyZXMgKGkuZS4sIGEKY29ycHVzIG9iamVjdCBhbmQgZG9jdW1lbnQtdGVybSBtYXRyaXgpIGFuZCBhbmQgdGhlIExEQSBtb2RlbDoKClshW0ZpZ3VyZSBzb3VyY2U6IFNpbGdlLCBKLiwgJiBSb2JpbnNvbiwgRC4gKDIwMTcpLiBUZXh0IG1pbmluZyB3aXRoIFI6CkEgdGlkeSBhcHByb2FjaC4gTydSZWlsbHkgTWVkaWEsIEluYy4gUmV0cmlldmVkIGZyb206Cmh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90b3BpY21vZGVsaW5nLmh0bWxdKGltYWdlcy90bV9mbG93LnBuZyAiQSBmbG93Y2hhcnQgb2YgYSB0ZXh0IGFuYWx5c2lzIHRoYXQgaW5jb3Jwb3JhdGVzIHRvcGljIG1vZGVsaW5nLiBUaGUgdG9waWNtb2RlbHMgcGFja2FnZSB0YWtlcyBhIERvY3VtZW50LVRlcm0gTWF0cml4IGFzIGlucHV0IGFuZCBwcm9kdWNlcyBhIG1vZGVsIHRoYXQgY2FuIGJlIHRpZGVkIGJ5IHRpZHl0ZXh0LCBzdWNoIHRoYXQgaXQgY2FuIGJlIG1hbmlwdWxhdGVkIGFuZCB2aXN1YWxpemVkIHdpdGggZHBseXIgYW5kIGdncGxvdDIuIildKGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90b3BpY21vZGVsaW5nLmh0bWwpCgpUaGlzIHByb2plY3Qgd2lsbCBleHBsb3JlIG11bHRpcGxlIHRvcGljIG1vZGVsaW5nIHRlY2huaXF1ZXMgZm9yCmlkZW50aWZ5aW5nIHRoZW1lcyBpbiBhIGNvcnB1cyBvZiBib29rIHJldmlld3MgdGhhdCByZWNlaXZlZCByZWxhdGl2ZWx5CmxvdyByYXRpbmdzLiBUaGUgcHJvamVjdCB3aWxsIGJlIG9yZ2FuaXplZCBhY2NvcmRpbmcgdG8gdGhlIGJhc2ljIGRhdGEKc2NpZW5jZSBhbmFseXRpYyBwcm9jZXNzOgoKMS4gICoqUHJlcGFyZSoqOiBQcmlvciB0byBhbmFseXNpcywgSSd2ZSBzcGVudCBxdWl0ZSBhIGJpdCBvZiB0aW1lIG9uCiAgICB0aGUgQW1hem9uIGJvb2sgcmV2aWV3IHNpdGUgZW5zdXJpbmcgSSB1bmRlcnN0YW5kIHRoZSBzdHJ1Y3R1cmUgYW5kCiAgICBjb250ZW50IG9mIGN1c3RvbWVyIHJldmlld3MuIFNpbmNlIG15IG1haW4gZ29hbCBpcyB0byB1bmRlcnN0YW5kCiAgICBzb21lIG9mIHRoZSBuZWdhdGl2ZSBmZWVkYmFjaywgdGhlIGRhdGEgZm9yIHRoaXMgcHJvamVjdCB3aWxsIHRha2VuCiAgICBleGNsdXNpdmVseSBmcm9tICJjcml0aWNhbCIgcmV2aWV3cywgYSBtaXggb2YgMSB0byAzLXN0YXIgcmF0aW5ncwogICAgdGhhdCBBbWF6b24gaGFzIGxhYmVsZWQgYXMgbmVnYXRpdmUuIFRoaXMgc2hvdWxkIGFpZCBpbiB0aGUKICAgIGludGVycHJldGF0aW9uIG9mIG91ciByZXN1bHRzIGFuZCBoZWxwIGd1aWRlIHNvbWUgZGVjaXNpb25zIGFzIHdlCiAgICB0aWR5LCBtb2RlbCwgYW5kIHZpc3VhbGl6ZSB0aGUgZGF0YS4KMi4gICoqV3JhbmdsZSoqOiBJbiBzZWN0aW9uIDIsIHRoZSB0ZXh0IGlzIHRpZGllZCBhbmQgdG9rZW5pemVkIHVzaW5nCiAgICB0aGUgYHRpZHl0ZXh0YCBwYWNrYWdlIGFuZCB0aGVuIHN0ZW1tZWQgdGhyb3VnaCB0aGUgYHN0bWAgcGFja2FnZS4KICAgIFRoaXMgcGFja2FnZSBtYWtlcyB1c2Ugb2YgdGhlIGB0bWAgdGV4dCBtaW5pbmcgcGFja2FnZSB0byBwcmVwcm9jZXNzCiAgICB0ZXh0IGFuZCBwcmlvciB0byB3b3JkIHN0ZW1taW5nLiBGaW5hbGx5LCB0aGUgdGlkaWVkIHRleHQgaXMKICAgIHRyYW5zZm9ybWVkIGludG8gYSBkb2N1bWVudCB0ZXJtIG1hdHJpeCAoRFRNKSB0byBkZXNjcmliZSB0aGUKICAgIGZyZXF1ZW5jeSBvZiB0ZXJtcyBhY3Jvc3MgdGhlIGJvZHkgb2YgYm9vayByZXZpZXdzLgozLiAgKipNb2RlbCoqOiBUaGlzIGFuYWx5c2lzIHdpbGwgYXBwbHkgdHdvIGRpZmZlcmVudCBhcHByb2FjaGVzIHRvCiAgICB0b3BpYyBtb2RlbGluZzogKipMYXRlbnQgRGlyaWNobGV0IEFsbG9jYXRpb24qKiAoTERBKSBhbmQKICAgICoqU3RydWN0dXJhbCBUb3BpYyBNb2RlbGluZyoqIChTVE0pLCB3aGljaCBpcyB2ZXJ5IHNpbWlsYXIgdG8gTERBCiAgICBidXQgY2FuIHVzZSBtZXRhZGF0YSBhYm91dCBkb2N1bWVudHMgdG8gaW1wcm92ZSB0aGUgYXNzaWdubWVudCBvZgogICAgd29yZHMgdG8gInRvcGljcyIgaW4gYSBjb3JwdXMgYW5kIGV4YW1pbmUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuCiAgICB0b3BpY3MgYW5kIGNvdmFyaWF0ZXMuIFdoaWxlIExEQSByZWxpZXMgb24gYSBiYWcgb2Ygd29yZHMgZ3JvdXBpbmcKICAgIG9mIHRlcm1zIHRvIGZvcm0gaW5kZXBlbmRlbnQgY2x1c3RlcnMgd2l0aGluIHRvcGljcywgU1RNIHVzZXMKICAgIHN0cnVjdHVhbCBjb250ZXh0IHRvIGNvcnJlbGF0ZSB0b3BpY3MgdG8gcGFyZW50IGRvY3VtZW50cy4KNC4gICoqRXhwbG9yZSoqOiBUbyBmdXJ0aGVyIGV4cGxvcmUgdGhlIHJlc3VsdHMgb2YgdGhlIHRvcGljIG1vZGVscywKICAgIHNldmVyYWwgZnVuY3Rpb25zIGZyb20gdGhlIGB0b3BpY21vZGVsc2AgYW5kIGBzdG1gIHBhY2thZ2VzIHdpbGwgYmUKICAgIHVzZWQgaW5jbHVkaW5nIHRoZSBgZmluZFRob3VnaHRzYCBmdW5jdGlvbiBmb3Igdmlld2luZyBkb2N1bWVudHMKICAgIGFzc2lnbmVkIHRvIGEgZ2l2ZW4gdG9waWMgYW5kIHRoZSBgdG9MREF2aXNgIGZ1bmN0aW9uIGZvciBleHBsb3JpbmcKICAgIHRvcGljIGFuZCB3b3JkIGRpc3RyaWJ1dGlvbnMuCjUuICAqKkNvbW11bmljYXRlKio6IFRoZSByZXN1bHRzIG9mIHRoZSBtdWx0aXBsZSBtb2RlbHMgd2lsbCBiZQogICAgY29uc29saWRhdGVkIHRvIGFkZHJlc3MgdGhlIHRocmVlIHF1ZXN0aW9ucyBndWlkaW5nIHRoZSBwcm9qZWN0LgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyAxLiBQUkVQQVJFCgpUbyBhc3Npc3QgaW4gdW5kZXJzdGFuZGluZyB0aGUgY29udGV4dCBiZWhpbmQgdGhlIGZvY3VzIHF1ZXN0aW9ucyBhbmQKZGF0YSBzb3VyY2VzIGZvciB0aGlzIHByb2plY3QsIHRoaXMgc2VjdGlvbiB3aWxsIGZvY3VzIG9uIHRoZSBmb2xsb3dpbmcKdG9waWNzOgoKYS4gICoqUXVlc3Rpb25zLioqIEknbGwgcmV2aWV3IHdoYXQgaW5zaWdodHMgdG9waWMgbW9kZWxpbmcgY2FuIHByb3ZpZGUKICAgIHRvIGEgc2VyaWVzIG9mIHF1ZXN0aW9ucyBvbiBsYXRlbnQgdGhlbWUgZGlzY292ZXJ5LgpiLiAgKipQcm9qZWN0IFNldHVwLioqIEknbGwgZXhwbGFpbiB0aGUgc2V0IHVwIG9mIHRoZSBSIHByb2plY3QgdG8KICAgIGluY2x1ZGUgZGF0YSBzb3VyY2VzLgoKIyMjIDFhLiBHdWlkaW5nIFF1ZXN0aW9ucwoKPiBDYW4gZGlzdGluY3QgdGhlbWVzIGJlIGlkZW50aWZpZWQgdGhyb3VnaCBhdXRvbWF0ZWQgdG9waWMgbW9kZWxpbmc/CgpJbiB0aGlzIGNhc2UsIEknbSBsb29raW5nIGZvciB0aGVtZXMgY29tbW9uIHRvIG11bHRpcGxlIGJvb2sgcmV2aWV3cy4KVGVjaG5pY2FsbHksIGVhY2ggcmV2aWV3IGNhbiBiZSB0aG91Z2h0IG9mIGFzICJ0aGVtZWQsIiBidXQgSSB3b3VsZCBsaWtlCnRvIHNlZSBpZiB0b3BpY3MgZW1lcmdlIG91dHNpZGUgb2YgdGhlIGJhc2UgZG9jdW1lbnRzLgoKPiBXaWxsIGRpZmZlcmVudCB0b3BpYyBtb2RlbGluZyB0ZWNobmlxdWVzIChzdGVtbWluZywgTERBIHZzIFNUTSkgcmVzdWx0Cj4gaW4gc2ltaWxhciBvciBkaXNzaW1pbGFyIHRvcGljcz8KCldlIGhhdmUgYSBmYWlybHkgc21hbGwgZGF0YSBzZXQgYXQgMTUyIGRvY3VtZW50cywgc28gdGhlIHZhbHVlIG9mCnN0ZW1taW5nIG1heSBiZSBsZXNzIHRoYW4gaWYgd2UgaGFkIGEgbXVjaCBsYXJnZXIgc2V0IG9mIHRlcm1zIHRvCmFzc2Vzcy4gU3RpbGwsIHJlZHVjaW5nIHJlZHVuZGFuY3kgbWF5IGVuYWJsZSB0aGUgZW1lcmdlbmNlIG9mIHRvcGljcwptYXNrZWQgYnkgc2ltaWxhciB3b3JkcyB0aGF0IGFyZSByZXBlYXRlZC4KCj4gRG8gdGhlIHRoZW1lcyBjb3JyZWxhdGUgdG8gbmVnYXRpdmUgcmVhY3Rpb25zIHRvIHRoZSBib29rPwoKVGhpcyBpcyB0aGUgdHJ1ZSBwdXJwb3NlIG9mIHRoZSBwcm9qZWN0LiBDYW4gd2UgZGlzY2VybiB3aHkgdGhlc2UKcmV2aWV3cyB3ZXJlIGNyaXRpY2FsIG9mIHRoZSBib29rPwoKIyMjIDFiLiBTZXQgVXAKCkFzIGhpZ2hsaWdodGVkIGluIFtDaGFwdGVyIDYgb2YgRGF0YSBTY2llbmNlIGluIEVkdWNhdGlvbiBVc2luZwpSXShodHRwczovL2RhdGFzY2llbmNlaW5lZHVjYXRpb24uY29tL2MwNi5odG1sKSAoRFNJRVVSKSwgb25lIG9mIHRoZQpmaXJzdCBzdGVwcyBvZiBldmVyeSB3b3JrZmxvdyBzaG91bGQgYmUgdG8gc2V0IHVwIGEgIlByb2plY3QiIHdpdGhpbgpSU3R1ZGlvLiBUaGlzIHdpbGwgYmUgb3VyICJob21lIiBmb3IgYW55IGZpbGVzIGFuZCBjb2RlIHVzZWQgb3IgY3JlYXRlZAppbiB0aGUgYW5hbHlzaXMuCgpUaGUgZm9sbG93aW5nIHBhY2thZ2VzIHdlcmUgaW5zdGFsbGVkIGFuZC9vciBsb2FkZWQgZm9yIHRoaXMgcHJvamVjdDoKCmBgYHtyIGxvYWQtcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHJ2ZXN0KQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoWE1MKQpsaWJyYXJ5KFJDdXJsKQoKbGlicmFyeShTbm93YmFsbEMpCmxpYnJhcnkodG9waWNtb2RlbHMpCmxpYnJhcnkoc3RtKQpsaWJyYXJ5KGxkYXR1bmluZykKbGlicmFyeShrbml0cikKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIDIuIFdSQU5HTEUKCmEuICAqKkltcG9ydCBEYXRhLioqIE9uY2UgSSBwdWxsZWQgdGhlIGRhdGEgZnJvbSBBbWF6b24sIGl0IHdhcwogICAgY29udmVydGVkIHRvIGEgLmNzdiBmaWxlIHRvIG1pdGlnYXRlIHRoZSBuZWVkIHRvIGtlZXAgcHVsbGluZyBkYXRhCiAgICBmcm9tIHRoZSB3ZWIuCmIuICAqKkNhc3QgYSBEVE0uKiogVGhlIGB0aWR5dGV4dGAgcGFja2FnZSB3YXMgdXNlZCB0byAidGlkeSIgYW5kCiAgICB0b2tlbml6ZSBib29rIHJldmlldyBkYXRhIGFuZCB0aGUgYGNhc3RfZHRtKClgIGZ1bmN0aW9uIGNyZWF0ZWQgdGhlCiAgICBkb2N1bWVudCB0ZXJtIG1hdHJpeCAoRFRNKSBuZWVkZWQgZm9yIHRvcGljIG1vZGVsaW5nLgpjLiAgKipTdGVtbWluZy4qKiBJIGV4cGVyaW1lbnRlZCB3aXRoIHN0ZW1taW5nIHRvcGljIHdvcmRzIHRocm91Z2ggdGhlCiAgICBgdGV4dFByb2Nlc3NvcigpYCBmdW5jdGlvbi4gSSJsbCBjb21wYXJlIHRoaXMgbWV0aG9kIHdpdGgKICAgIG5vbi1zdGVtbWluZyB0ZWNobmlxdWVzIHRvIGdhdWdlIHBvdGVudGlhbCBpbXBhY3Qgb24gY29uc2lzdGVuY3kgb2YKICAgIHRoZW1lIGlkZW50aWZpY2F0aW9uLgoKIyMjIDJhLiBJbXBvcnQgQW1hem9uIEJvb2sgUmV2aWV3cwoKTXkgd2ViIHNjcmFwaW5nIG1ldGhvZCB3YXMgaW5zcGlyZWQgYnkgYW4KW2FydGljbGVdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLzIwMTkvMDMvd2ViLXNjcmFwaW5nLWFtYXpvbi1yZXZpZXdzLW1hcmNoLTIwMTkvI2dvb2dsZV92aWduZXR0ZSkKYnkgUmlraSBTYWl0byBwb3N0ZWQgdG8gdGhiZSBSIG5ld3MgYW5kIHR1dG9yaWFscyBzaXRlCltSLUJMT0dHRVJTXShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8pLiBUaGUgcHJvY2VzcyBiZWdhbiB3aXRoCmVzdGFibGlzaGluZyBhIGNvbm5lY3Rpb24gdG8gdGhlIHNpdGUgaG9zdGluZyBwdWJsaWMgcmV2aWV3cyBvZiAqVGhlCkJvbWJlciBNYWZpYSo6CgpgYGB7ciBBbWF6b24gZGF0YSBjYWxsIHByZXB9CmFzaW4gPC0gIjAzMTYyOTY2MTkiCnVybCA8LSBwYXN0ZTAoImh0dHBzOi8vd3d3LmFtYXpvbi5jb20vZHAvIiwgYXNpbikKZG9jIDwtIHJlYWRfaHRtbCh1cmwpCgpwcm9kIDwtIGh0bWxfbm9kZXMoZG9jLCAiI3Byb2R1Y3RUaXRsZSIpICU+JSAKICBodG1sX3RleHQoKSAlPiUgCiAgZ3N1YigiXG4iLCAiIiwgLikgJT4lIAogIHRyaW13cygpCgpwcm9kCmBgYAoKTm93IHRoYXQgSSdtIGNvbmZpZGVudCBJIGNhbiBjYWxsIHRoZSBjb3JyZWN0IHByb2R1Y3QgcmV2aWV3IHdlYnNpdGUsCmJlbG93IGlzIHRoZSBjb2RlIHRvIGVuc3VyZSB0aGUgaHRtbCBkYXRhIGlzIGZvcm1hdHRlZCBhcHByb3ByaWF0ZWx5IGZvcgphbmFseXNpcy4gRm9yIHRoaXMgZWZmb3J0LCBJJ20gb25seSBpbnRlcmVzdGVkIGluIHRoZSByZXZpZXcgdGl0bGUsCmRhdGUsIHN0YXIgcmF0aW5ncyBhbmQgY29tbWVudHM6CgpgYGB7ciBkYXRhIGZvcm1hdH0Kc2NyYXBlX2FtYXpvbiA8LSBmdW5jdGlvbih1cmwsIHRocm90dGxlID0gMCl7CgojIFNldCB0aHJvdHRsZSBiZXR3ZWVuIFVSTCBjYWxscwpzZWMgPSAwCmlmKHRocm90dGxlIDwgMCkgd2FybmluZygidGhyb3R0bGUgd2FzIGxlc3MgdGhhbiAwOiBzZXQgdG8gMCIpCmlmKHRocm90dGxlID4gMCkgc2VjID0gbWF4KDAsIHRocm90dGxlICsgcnVuaWYoMSwgLTEsIDEpKQogIAojIG9idGFpbiBIVE1MIG9mIFVSTApkb2MgPC0gcmVhZF9odG1sKHVybCkKICAKIyBQYXJzZSByZWxldmFudCBlbGVtZW50cyBmcm9tIEhUTUwKdGl0bGUgPC0gZG9jICU+JQogIGh0bWxfbm9kZXMoIiNjbV9jci1yZXZpZXdfbGlzdCAuYS1jb2xvci1iYXNlIikgJT4lCiAgaHRtbF90ZXh0KCkgJT4lIAogIGdzdWIoIl5cXHMrfFxccyskIiwgIiIsIC4pCiAgCnN0cl9yZXBsYWNlX2FsbCh0aXRsZSwgIltcclxuXSIgLCAiIikKICAKZGF0ZSA8LSBkb2MgJT4lCiAgaHRtbF9ub2RlcygiI2NtX2NyLXJldmlld19saXN0IC5yZXZpZXctZGF0ZSIpICU+JQogIGh0bWxfdGV4dCgpICU+JSAKICBnc3ViKCIuKm9uICIsICIiLCAuKQogIApzdGFycyA8LSBkb2MgJT4lCiAgaHRtbF9ub2RlcygiI2NtX2NyLXJldmlld19saXN0ICAucmV2aWV3LXJhdGluZyIpICU+JQogIGh0bWxfdGV4dCgpICU+JQogIHN0cl9leHRyYWN0KCJcXGQiKSAlPiUKICBhcy5udW1lcmljKCkgCiAgCmNvbW1lbnRzIDwtIGRvYyAlPiUKICBodG1sX25vZGVzKCIjY21fY3ItcmV2aWV3X2xpc3QgLnJldmlldy10ZXh0IikgJT4lCiAgaHRtbF90ZXh0KCkgJT4lIAogIGdzdWIoIl5cXHMrfFxccyskIiwgIiIsIC4pCiAgCiAgCiMgQ29tYmluZSBhdHRyaWJ1dGVzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZQpkZiA8LSBkYXRhLmZyYW1lKHRpdGxlLCBkYXRlLCBzdGFycywgY29tbWVudHMsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQogIApyZXR1cm4oZGYpCn0KYGBgCgpXaXRoIHRoZSBmb3JtYXQgc2V0LCBJIHdhbnRlZCB0byBydW4gYSBzYW1wbGUgdGVzdCB0byBlbnN1cmUgdGhlIGRhdGEKYXJyaXZlZCByZWFkeSBmb3IgYW5hbHlzaXMuIEkgcHVsbGVkIHRoZSBmaXJzdCBwYWdlIG9mIHJldmlld3MgYW5kIHNlbnQKaXQgdG8gYSBkYXRhIGZyYW1lOgoKYGBge3IgdGVzdCBydW4sIGV2YWw9RkFMU0V9CnVybCA8LSAiaHR0cDovL3d3dy5hbWF6b24uY29tL3Byb2R1Y3QtcmV2aWV3cy8wMzE2Mjk2NjE5Lz9wYWdlTnVtYmVyPTEiCnJldmlld3MgPC0gc2NyYXBlX2FtYXpvbih1cmwpCgpzdHIocmV2aWV3cykKYGBgCgohW0RhdGEgZnJhbWUgZm9yIDFzdCB0ZW4gcmV2aWV3cyBvZiBUaGUgQm9tYmVyIE1hZmlhXShpbWFnZXMvdGVzdC5wbmcpCgpNeSBvcmlnaW5hbCBpbnRlbnQgd2FzIHRvIGRvd25sb2FkIHRoZSBmaXJzdCAxMDAgcGFnZXMgd2hpY2ggc2hvdWxkCnlpZWxkIGFwcHJveGltYXRlbHkgMTAwMCBib29rIHJldmlld3MgdG8gYmVnaW4gZXhwbG9yYXRvcnkgYW5hbHlzaXMuCkhvd2V2ZXIsIGl0IGJlY2FtZSBwcm9ibGVtYXRpYyB0byBnZXQgdGhhdCBkYXRhIGRpcmVjdGx5IGZvciBhIHJlYXNvbiBJCmRpZG4ndCBhbnRpY2lwYXRlLiBJIHRyaWVkIG11bHRpcGxlIHRpbWVzIGFuZCBjb3VsZCBvbmx5IGdldCA0OTgKcmV2aWV3cywganVzdCBzaHkgb2YgNTAgZnVsbCBwYWdlcy4gVHVybnMgb3V0IHRoYXQgbm90IGV2ZXJ5IGNvdW50cnkKc3VibWl0cyBib29rIHJldmlld3MgaW4gdGhlIHNhbWUgZm9ybWF0IG9yIGJ5IHVzaW5nIHRoZSBzYW1lIGVudHJ5CmZpZWxkcy4gTXkgbGFzdCBib29rIHJldmlldyBlbnRyeSB3YXMgZnJvbSBKYXBhbiBhbmQgdGhlIGZvcm1hdCBkaWQgbm90CmNvbXBsZXRlIHRoZSBpbnRlbmRlZCBjZWxscyBjb3JyZWN0bHkgaW4gdGhlIGRhdGEgZnJhbWUgY2F1c2luZyBhbiBlcnJvcgphdCBlbnRyeSAjNDk4LiBJIHJlc2VhcmNoZWQgYSBmZXcgc29sdXRpb25zIGFkZHJlc3NpbmcgdGhpcyBpc3N1ZSBhbmQKdGhleSBleGNlZWRlZCBteSBza2lsbCBsZXZlbCwgc28gSSB0b29rIGFuIGFsdGVybmF0ZSByb3V0ZS4KCklmIEkgd2VyZSBhYmxlIHRvIHN1Y2Nlc3NmdWxseSBzY3JhcGUgdGhlIGZpcnN0IDEwMDAgcmV2aWV3cywgdGhlCnJlc3VsdHMgd291bGQgYmUgYSBtaXggb2YgcmF0aW5ncyBmcm9tIDEgdGhyb3VnaCA1IHN0YXJzLiBObyB0ZWxsaW5nIGhvdwptYW55IEkgd291bGQgZ2V0IGF0IGVhY2ggcmF0aW5nLiBTaW5jZSBteSBwcm9qZWN0IGlzIGZvY3VzZWQgb24gdGhlIGJhZApyZXZpZXdzLCBJIGRlY2lkZWQgdG8gcHVsbCBleGNsdXNpdmVseSBmcm9tIHRoZSByZXZpZXdzIEFtYXpvbgpjbGFzc2lmaWVzIGFzICJjcml0aWNhbCIgYXMgdGhhdCdzIHdoZXJlIGFsbCB0aGUgY29tbWVudHMgYXJlIGNvbnRhaW5lZAp0aGF0IEknZCBsaWtlIHRvIGFuYWx5emUuIFdpdGggdGhhdCBpbiBtaW5kLCBJIHJlYnVpbHQgbXkgcXVlcnkgdG8gcHVsbAp0aGVzZSByZXZpZXdzIGZyb20gdGhhdCBzaW5nbGUgY2F0ZWdvcnkuIFRoZXJlIGFyZSBvbmx5IDE2IHBhZ2VzIG9mCnRoZXNlICJjcml0aWNhbCIgcmV2aWV3czoKCmBgYHtyIFNjcmFwZSAxIGFuZCAyLXN0YXIgcmV2aWV3c30KcGFnZXMgPC0gMTYKCnJldmlld3NfYWxsIDwtIE5VTEwKCmZvcihwYWdlX251bSBpbiAxOnBhZ2VzKXsKICB1cmwgPC0gcGFzdGUwKCJodHRwczovL3d3dy5hbWF6b24uY29tL3Byb2R1Y3QtcmV2aWV3cy8wMzE2Mjk2NjE5L3JlZj1jbV9jcl9nZXRyX2RfcGFnaW5nX2J0bV9uZXh0XzE2P2llPVVURjgmcmV2aWV3ZXJUeXBlPWFsbF9yZXZpZXdzJnBhZ2VOdW1iZXI9IixwYWdlX251bSwiJmZpbHRlckJ5U3Rhcj1jcml0aWNhbCIpCiAgcmV2aWV3cyA8LSBzY3JhcGVfYW1hem9uKHVybCwgdGhyb3R0bGUgPSAzKQogIHJldmlld3NfYWxsIDwtIHJiaW5kKHJldmlld3NfYWxsLCBjYmluZChwcm9kLCByZXZpZXdzKSkKfQpgYGAKClRoaXMgY29kZSByZXR1cm5lZCBhIGRhdGEgZnJhbWUgd2l0aCAxNTMgcGVyZmVjdGx5IGZvcm1hdHRlZCByZXZpZXdzCnJhbmdpbmcgZnJvbSAxIHRvIDMgc3RhcnMuIFRoZXJlIHdhcyBvbmUgcmV2aWV3IG5vdCBpbiBFbmdsaXNoLCBzbyBJCnB1bGxlZCB0aGF0IGZyb20gdGhlIGRhdGEgc2V0IHRvIGtlZXAgdGhpbmdzIHNpbXBsZSwgbGVhdmluZyAxNTIKcmVtYWluaW5nIGVudHJpZXMuIEZpbmFsbHksIEkgYWRkZWQgYSBjb2x1bW4gd2l0aCBhIHVuaXF1ZSBJRCBudW1iZXIgdG8KZGlzY3JpbWluYXRlIGJldHdlZW4gYm9vayByZXZpZXdzLiBTaW5jZSBzb21lIHJlYWRlcnMgdGl0bGUgdGhlaXIgcmV2aWV3CmFmdGVyIHRoZSBib29rLCBJIHdhbnRlZCB0byBlbnN1cmUgZWFjaCByZXZpZXcgd2FzIHRyZWF0ZWQgYXMgYSBzZXBhcmF0ZQpkb2N1bWVudCBhbmQgbm90IGNvbWJpbmVkIHdpdGggb3RoZXJzIHdpdGggdGhlIHNhbWUgdGl0bGUuIFRvIGtlZXAgZnJvbQpoYXZpbmcgdG8gY29udGludWFsbHkgcnVuIHRoaXMgcXVlcnksIEkgY29udmVydGVkIHRoZSBkYXRhIGZyYW1lIGludG8gYQouY3N2IGZpbGUgdG8gY29udGludWUgdGhlIGFuYWx5c2lzOgoKYGBge3IgUmVtb3ZlIHJvdyBhbmQgY3JlYXRlIGNzdn0KcmV2aWV3c19hbGwgPC0gcmV2aWV3c19hbGxbLW5yb3cocmV2aWV3c19hbGwpLF0KcmV2aWV3c19hbGwgPC0gY2JpbmQoSUQgPSAxOm5yb3cocmV2aWV3c19hbGwpLCByZXZpZXdzX2FsbCkKCndyaXRlLmNzdihyZXZpZXdzX2FsbCwgImRhdGEvcmV2aWV3c19hbGwuY3N2IikKYGBgCgojIyMgMmIuIENhc3QgYSBEb2N1bWVudCBUZXJtIE1hdHJpeAoKSW4gdGhpcyBzZWN0aW9uIEkgZW1wbG95ZWQgc29tZSBmYW1pbGlhciBgdGlkeXRleHRgIGZ1bmN0aW9ucyB0byB0aWR5CmFuZCB0b2tlbml6ZSB0ZXh0IHdoaWxlIGFsc28gaW50cm9kdWNpbmcgdGhlIGBzdG1gIHBhY2thZ2UgZm9yCnByb2Nlc3NpbmcgdGV4dCBhbmQgdHJhbnNmb3JtaW5nIG91ciBkYXRhIGZyYW1lcyBpbnRvIG5ldyBkYXRhCnN0cnVjdHVyZXMgcmVxdWlyZWQgZm9yIHRvcGljIG1vZGVsaW5nLgoKIyMjIyBGdW5jdGlvbnMgVXNlZAoKKipgdGlkeXRleHRgIGZ1bmN0aW9ucyoqCgotICAgYHVubmVzdF90b2tlbnMoKWAgc3BsaXRzIGEgY29sdW1uIGludG8gdG9rZW5zCi0gICBgYW50aV9qb2luKClgIHJldHVybnMgYWxsIHJvd3MgZnJvbSB4IHdpdGhvdXQgYSBtYXRjaCBpbiB5IGFuZCB1c2VkCiAgICB0byByZW1vdmUgYHN0b3Agd29yZHNgIGZyb20gb3V0IGRhdGEuCi0gICBgY2FzdF9kdG0oKWAgdGFrZXMgYSB0aWRpZWQgZGF0YSBmcmFtZSB0YWtlIGFuZCAiY2FzdHMiIGl0IGludG8gYQogICAgZG9jdW1lbnQtdGVybSBtYXRyaXggKGR0bSkKCioqYGRwbHlyYCoqICoqZnVuY3Rpb25zKioKCi0gICBgY291bnQoKWAgbGV0cyB5b3UgcXVpY2tseSBjb3VudCB0aGUgdW5pcXVlIHZhbHVlcyBvZiBvbmUgb3IgbW9yZQogICAgdmFyaWFibGVzCi0gICBgZ3JvdXBfYnkoKWAgdGFrZXMgYSBkYXRhIGZyYW1lIGFuZCBvbmUgb3IgbW9yZSB2YXJpYWJsZXMgdG8gZ3JvdXAKICAgIGJ5Ci0gICBgc3VtbWFyaXNlKClgIGNyZWF0ZXMgYSBzdW1tYXJ5IG9mIGRhdGEgdXNpbmcgYXJndW1lbnRzIGxpa2Ugc3VtIGFuZAogICAgbWVhbgoKKipgc3RtYCBmdW5jdGlvbnMqKgoKLSAgIGB0ZXh0UHJvY2Vzc29yKClgIHRha2VzIGluIGEgdmVjdG9yIG9yIGNvbHVtbiBvZiByYXcgdGV4dHMgYW5kCiAgICBwZXJmb3JtcyB0ZXh0IHByb2Nlc3NpbmcgbGlrZSByZW1vdmluZyBwdW5jdHVhdGlvbiBhbmQgd29yZAogICAgc3RlbW1pbmcuCi0gICBgcHJlcERvY3VtZW50cygpYCBwZXJmb3JtcyBzZXZlcmFsIGNvcnB1cyBtYW5pcHVsYXRpb25zIGluY2x1ZGluZwogICAgcmVtb3Zpbmcgd29yZHMgYW5kIHJlbnVtYmVyaW5nIHdvcmQgaW5kaWNlcwoKIyMjIyBUaWR5aW5nIFRleHQKCjEuICBUcmFuc2Zvcm1pbmcgb3VyIHRleHQgaW50byAidG9rZW5zIgoyLiAgUmVtb3ZpbmcgdW5uZWNlc3NhcnkgY2hhcmFjdGVycywgcHVuY3R1YXRpb24sIGFuZCB3aGl0ZSBzcGFjZQozLiAgQ29udmVydGluZyBhbGwgdGV4dCB0byBsb3dlcmNhc2UKNC4gIFJlbW92aW5nIHN0b3Agd29yZHMgc3VjaCBhcyAidGhlIiwgIm9mIiwgYW5kICJ0byIKCmBgYHtyIFRva2VuaXplIHJldmlld3N9CnJldmlld3NfdGlkeSA8LSByZXZpZXdzX2FsbCAlPiUKICB1bm5lc3RfdG9rZW5zKG91dHB1dCA9IHdvcmQsIGlucHV0ID0gY29tbWVudHMpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikKYGBgCgpOb3cgbGV0J3MgZG8gYSBxdWljayB3b3JkIGNvdW50IHRvIHNlZSBzb21lIG9mIHRoZSBtb3N0IGNvbW1vbiB3b3Jkcwp1c2VkIHRocm91Z2hvdXQgdGhlIGN1c3RvbWVyIHJldmlld3MuIFRoaXMgc2hvdWxkIGdpdmUgYSBzZW5zZSBvZiB3aGF0CndlJ3JlIHdvcmtpbmcgd2l0aCBhbmQgbGF0ZXIgd2UnbGwgbmVlZCB0aGVzZSB3b3JkIGNvdW50cyBmb3IgY3JlYXRpbmcKb3VyIGRvY3VtZW50IHRlcm0gbWF0cml4IGZvciB0b3BpYyBtb2RlbGluZzoKCmBgYHtyIENvdW50IHdvcmRzLCB9Cm15X2thYmxlKHJldmlld3NfdGlkeSAlPiUKICAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkpCmBgYAoKVGVybXMgbGlrZSAiYm9tYmluZywiICJ3YXIsIiBhbmQgImFpciIgYXJlIGFib3V0IHdoYXQgd2Ugd291bGQgaGF2ZQpleHBlY3RlZCBmcm9tIGEgYm9vayBvbiB0aGUgV1cgSUkgYWlyIGNhbXBhaWduLiBUaGUgdGVybXMgIndyaXR0ZW4iIGFuZAoic3ViamVjdCIgaG93ZXZlciwgYXJlIG5vdCBzbyBpbnR1aXRpdmUgYW5kIG1heSBiZSB3b3J0aCBhIHF1aWNrIGxvb2sKb25jZSB3ZSBnZXQgaW50byBleHBsb3JhdG9yeSBhbmFseXNpcy4gQWRkaXRpb25hbGx5LCBtYW55IHNpbWlsYXIgd29yZHMKYXBwZWFyIHRoYXQgY291bGQgc2tldyBhbmFseXNpcy4gIkdsYWR3ZWxsIiBhbmQgImdsYWR3ZWxsJ3MiIGRvIG5vdApjYXJyeSBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBtZWFuaW5ncy4gU2FtZSBjb3VsZCBiZSBzYWlkIGZvciAiYm9tYiIKdnMuICJib21iaW5nLiIgVGhpcyBsZWFkcyBtZSB0byBpbmZlciB0aGF0IHN0ZW1taW5nIGNvdWxkIHJlZHVjZSB0aGlzCnJlZHVuZGFuY3kgYW5kIGJldHRlciBoaWdobGlnaHQgbGVzcyBjb21tb24gdGVybXMuCgpUbyByZWR1Y2UgdGhlIGluZmx1ZW5jZSBvZiB1bm5lY2Vzc2FyeSB0ZXJtcywgSSdsbCBjcmVhdGUgYSBsaXN0IG9mCmN1c3RvbSBzdG9wIHdvcmRzIHRvIGZpbHRlciBmcm9tIHRoZSB0aWRpZWQgZGF0YSBmcmFtZS4KCmBgYHtyIEN1c3RvbSBzdG9wIHdvcmRzLCBjb2xsYXBzZT1UUlVFfQpteV9zdG9wd29yZHMgPC0gYygid2FyIiwgImJvbWIiLCAiZ2xhZHdlbGwiLCAiYm9vayIsICJib21iaW5nIiwgImxlbWF5IiwgInJlYWQiLCAiYXV0aG9yIiwgInUucyIsICJhaXIiLCAiYm9tYmVyIiwgImphcGFuIiwgIm1hZmlhIiwgImJvb2tzIiwgImJyaXRpc2giLCAid3dpaSIsICJtaWxpdGFyeSIsICJtYWxjb2xtIiwgImhhbnNlbGwiLCAiYm9tYnMiLCAid29ybGQiLCAiZ2xhZHdlbGwncyIsICJjaXRpZXMiLCAiY3VydGlzIiwgImdlcm1hbnkiLCAibmFwYWxtIiwgImNhc3VhbHRpZXMiLCAiYm9tYmVycyIsICJhbWVyaWNhbiIsICJib21ic2lnaHQiLCAid2FycyIsICAiYXJteSIsICJpaSIsICJtYWtlcyIsICJuaWdodCIsICIyOSIsICJkcm9wcGVkIiwgImxpdmVzIiwgImphcGFuZXNlIiwgInN0b3J5IiwgInBlb3BsZSIsICJjaXZpbGlhbiIsICJnbGFkd2VsbOKAmXMiKQoKcmV2aWV3c190aWR5XzIgPC0gcmV2aWV3c190aWR5ICU+JQogIGZpbHRlcighd29yZCAlaW4lIG15X3N0b3B3b3JkcykKCm15X2thYmxlKHJldmlld3NfdGlkeV8yICU+JQogICAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkpCmBgYAoKVGhlIHVwZGF0ZWQgdGVybSBsaXN0IGxvb2tzIHRvIGJlIG11Y2ggbGVzcyBhIHN1bW1hcnkgb2YgdGhlIHBsb3QgYW5kCmhpZ2hsaWdodHMgc3BlY2lmaWMgZGVzY3JpcHRvcnMgb2Ygd3JpdGluZyBzdHlsZSBvciB2YWx1ZS4gVG8gZ2FpbiBhCmxpdHRsZSBtb3JlIGNvbnRleHQsIEknbGwgZG8gYSBzZWFyY2ggZm9yIG9uZSBvZiB0aGUgdGVybXMgaWRlbnRpZmllZAplYXJsaWVyIGFzIGEgcG9zc2libGUgaW5kaWNhdG9yIG9mIGNyaXRpY2lzbSwgIndyaXR0ZW4iOgoKYGBge3IgZmluZC1xdW90ZXN9CmVycm9yX3F1b3RlcyA8LSByZXZpZXdzX2FsbCAlPiUKICBzZWxlY3QoY29tbWVudHMpICU+JSAKICBmaWx0ZXIoZ3JlcGwoJ3dyaXR0ZW4nLCBjb21tZW50cykpCgpzYW1wbGVfbihlcnJvcl9xdW90ZXMsMykgJT4lCiAga2FibGUoKQpgYGAKClRoaXMgcmVxdWVzdCByZXR1cm5lZCAxMCByZXZpZXdzIGRlc2NyaWJpbmcgdGhlIGF1dGhvcidzIHdyaXRpbmcgc3R5bGUuClRoZXkgaW5jbHVkZSByZWZlcmVuY2VzIHRvIGJvdGggR2xhZHdlbGwncyB0YWxlbnQgYXMgYSBzdG9yeSB0ZWxsZXIsIGJ1dAphbHNvIHNob3cgdGhhdCBtYW55IHdlcmUgdW5oYXBweSB3aXRoIHRoaXMgcGFydGljdWxhciBoaXN0b3J5Cm1hbnVzY3JpcHQuCgojIyMjIENyZWF0aW5nIGEgRG9jdW1lbnQgVGVybSBNYXRyaXgKCkZvciB0aGlzIGFuYWx5c2lzLCBlYWNoIGluZGl2aWR1YWwgYm9vayByZXZpZXcgd2lsbCBiZSB0cmVhdGVkIGFzIGEKdW5pcXVlICJkb2N1bWVudC4iIFRvIGNyZWF0ZSB0aGUgRFRNLCB3ZSdsbCBuZWVkIHRvIGZpcnN0IGBjb3VudCgpYCBob3cKbWFueSB0aW1lcyBlYWNoIGB3b3JkYCBvY2N1cnMgaW4gZWFjaCBkb2N1bWVudCwgb3IgYElEYCBpbiBvdXIgY2FzZSwgYW5kCmNyZWF0ZSBhIG1hdHJpeCB0aGF0IGNvbnRhaW5zIG9uZSByb3cgcGVyIHBvc3QgYXMgdGhlIG9yaWdpbmFsIGRhdGEKZnJhbWUgZGlkLCBidXQgbm93IGNvbnRhaW5zIGEgY29sdW1uIGZvciBlYWNoIGB3b3JkYCBpbiB0aGUgZW50aXJlCmNvcnB1cyBhbmQgYSB2YWx1ZSBvZiBgbmAgZm9yIGhvdyBtYW55IHRpbWVzIHRoYXQgd29yZCBvY2N1cnMgaW4gZWFjaApyZXZpZXcuCgpUbyBjcmVhdGUgdGhpcyBkb2N1bWVudCB0ZXJtIG1hdHJpeCBmcm9tIG91ciBwb3N0IGNvdW50cywgd2UnbGwgdXNlIHRoZQpgY2FzdF9kdG0oKWAgZnVuY3Rpb24gbGlrZSBzbyBhbmQgYXNzaWduIGl0IHRvIHRoZSB2YXJpYWJsZQpgcmV2aWV3c19kdG1gOgoKYGBge3IgQ2FzdCBEVE19CnJldmlld3NfZHRtIDwtIHJldmlld3NfdGlkeV8yICU+JQogIGNvdW50KElELCB3b3JkKSAlPiUKICBjYXN0X2R0bShJRCwgd29yZCwgbikKYGBgCgpUaGUgcmVzdWx0IG9mIHRoaXMgZnVuY3Rpb24gaXMgYSBzaW1wbGVfdHJpcGxldF9tYXRyaXggb2JqZWN0LCBjYWxsZWQKYHJldmlld3NfZHRtYC4gVGhpcyBEVE0gY29udGFpbnMgMTUyIGRvY3VtZW50cyBhbmQgMyw2ODYgdGVybXMuCgojIyAzLiBNT0RFTAoKIyMjIDNhLiBGaW5kaW5nICpLKgoKVGhlIGBsZGF0dW5pbmdgIHBhY2thZ2UgaGFzIGZ1bmN0aW9ucyBmb3IgYm90aCBjYWxjdWxhdGluZyBhbmQgcGxvdHRpbmcKZGlmZmVyZW50IG1ldHJpY3MgdGhhdCBjYW4gYmUgdXNlZCB0byBlc3RpbWF0ZSB0aGUgbW9zdCBwcmVmZXJhYmxlCm51bWJlciBvZiB0b3BpY3MgZm9yIExEQSBtb2RlbGluZy4gSXQgYWxzbyBjb252ZW5pZW50bHkgdGFrZXMgdGhlCnN0YW5kYXJkIGRvY3VtZW50IHRlcm0gbWF0cml4IG9iamVjdCBjcmVhdGVkIGZyb20gdGhlIHRpZHkgdGV4dC4KCmBgYHtyIGZpbmQtdG9waWMsIGV2YWw9RkFMU0V9CmtfbWV0cmljcyA8LSBGaW5kVG9waWNzTnVtYmVyKAogIHJldmlld3NfZHRtLAogIHRvcGljcyA9IHNlcSgxMCwgNzUsIGJ5ID0gNSksCiAgbWV0cmljcyA9ICJHcmlmZml0aHMyMDA0IiwKICBtZXRob2QgPSAiR2liYnMiLAogIGNvbnRyb2wgPSBsaXN0KCksCiAgbWMuY29yZXMgPSBOQSwKICByZXR1cm5fbW9kZWxzID0gRkFMU0UsCiAgdmVyYm9zZSA9IEZBTFNFLAogIGxpYnBhdGggPSBOVUxMCikKRmluZFRvcGljc051bWJlcl9wbG90KGtfbWV0cmljcykKYGBgCgpVc2luZyB0aGUgR3JpZmZpdGhzMjAwNCBtZXRyaWNzIGluY2x1ZGVkIGluIHRoZSBkZWZhdWx0IGV4YW1wbGUgcHJvZHVjZWQKdGhlIHJlc3VsdHMgYXMgc2hvdyBpbiB0aGUgZmlndXJlIGJlbG93OgoKIVtdKGltYWdlcy90b3BpY3MyLnBuZykKCkFzIGEgZ2VuZXJhbCBydWxlIG9mIHRodW1iIGFuZCBvdmVybHkgc2ltcGxpc3RpYyBoZXVyaXN0aWMsIGFuCmluZmxlY3Rpb24gcG9pbnQgaW4gdGhlIHBsb3QgaW5kaWNhdGVzIGFuIG9wdGltYWwgbnVtYmVyIG9mIHRvcGljcyB0bwpzZWxlY3QgZm9yIGEgdmFsdWUgb2YgSyAoMTUgaW4gdGhpcyBjYXNlKS4KCiMjIyAzYi4gRml0dGluZyBhIFRvcGljIE1vZGVsaW5nIHdpdGggTERBCgpgYGB7ciBMREF9CnJldmlld3NfbGRhIDwtIExEQShyZXZpZXdzX2R0bSwgCiAgICAgICAgICAgICAgICAgIGsgPSAxNSwgCiAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSA1ODgpCiAgICAgICAgICAgICAgICAgICkKcmV2aWV3c19sZGEKYGBgCgpUaGUgYGNvbnRyb2wgPWAgYXJndW1lbnQgd2FzIHVzZWQgdG8gcGFzcyBhIHJhbmRvbSBudW1iZXIgKGA1ODhgKSB0bwpzZWVkIHRoZSBhc3NpZ25tZW50IG9mIHRvcGljcyB0byBlYWNoIHdvcmQgaW4gdGhlIGNvcnB1cy4gU2luY2UgTERBIGlzIGEKW3N0b2NoYXN0aWMKYWxnb3JpdGhtXShodHRwczovL21hY2hpbmVsZWFybmluZ21hc3RlcnkuY29tL3N0b2NoYXN0aWMtaW4tbWFjaGluZS1sZWFybmluZy8pCnRoYXQgY291bGQgaGF2ZSBkaWZmZXJlbnQgcmVzdWx0cyBkZXBlbmRpbmcgb24gd2hlcmUgdGhlIGFsZ29yaXRobQpzdGFydHMsIGEgYHNlZWRgIHdhcyBzcGVjaWZpZWQgZm9yIHJlcHJvZHVjaWJpbGl0eS4gVGhlIG1vZGVsIHdpbGwgbm93CnByb2R1Y2UgdGhlIHNhbWUgcmVzdWx0cyBldmVyeSB0aW1lIHRoZSBzYW1lIG51bWJlciBvZiB0b3BpY3MgaXMKc3BlY2lmaWVkLgoKIyMjIDNjLiBGaXR0aW5nIGEgU3RydWN0dXJhbCBUb3BpYyBNb2RlbAoKIyMjIyBQcm9jZXNzaW5nIGZvciBTVE0KCmBgYHtyIHRleHRQcm9jZXNzb3J9CnRlbXAgPC0gdGV4dFByb2Nlc3NvcihyZXZpZXdzX2FsbCRjb21tZW50cywgCiAgICAgICAgICAgICAgICAgICAgbWV0YWRhdGEgPSByZXZpZXdzX2FsbCwgIAogICAgICAgICAgICAgICAgICAgIGxvd2VyY2FzZT1UUlVFLCAKICAgICAgICAgICAgICAgICAgICByZW1vdmVzdG9wd29yZHM9VFJVRSwgCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlbnVtYmVycz1UUlVFLCAgCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlcHVuY3R1YXRpb249VFJVRSwgCiAgICAgICAgICAgICAgICAgICAgd29yZExlbmd0aHM9YygzLEluZiksCiAgICAgICAgICAgICAgICAgICAgc3RlbT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICBvbmx5Y2hhcmFjdGVyPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgc3RyaXBodG1sPVRSVUUsIAogICAgICAgICAgICAgICAgICAgIGN1c3RvbXN0b3B3b3Jkcz1teV9zdG9wd29yZHMpCmBgYAoKVW5saWtlIHRoZSBgdW5uZXN0X3Rva2VucygpYCBmdW5jdGlvbiwgdGhlIG91dHB1dCBpcyBub3QgYSBuaWNlIHRpZHkKZGF0YSBmcmFtZS4gVG9waWMgbW9kZWxpbmcgdXNpbmcgdGhlIGBzdG1gIHBhY2thZ2UgcmVxdWlyZXMgYSB2ZXJ5CnVuaXF1ZSBzZXQgb2YgaW5wdXRzIHRoYXQgYXJlIHNwZWNpZmljIHRvIHRoZSBwYWNrYWdlLiBPbmUgY2hhbmdlIEkgbWFkZQp0byB0aGUgZGVmYXVsdCB2YWx1ZXMgd2FzIHRvIGNoYW5nZSBzdGVtIHRvICJGQUxTRS4iIFRoZSByZWxhdGl2ZWx5CnNtYWxsIGRhdGEgc2V0IG1peGVkIHdpdGggY3VzdG9tIHN0b3Agd29yZHMgc2hvdWxkIG1pdGlnYXRlIGFnYWluc3QKcmVkdW5kYW50IHRlcm1zIGFuZCBwb3RlbnRpYWxseSBwcmVzZXJ2ZSBzb21lIGNvbnRleHQuCgojIyMjIFRoZSBgc3RtYCBQYWNrYWdlCgpBcyBzaG93biBhYm92ZSwgU1RNIHByb2R1Y2VkIGFuIHVudXN1YWwgYHRlbXBgIHRleHRQcm9jZXNzb3Igb3V0cHV0IHRoYXQKaXMgdW5pcXVlIHRvIHRoZSBgc3RtYCBwYWNrYWdlLiBUaGUgYHN0bSgpYCBmdW5jdGlvbiBmb3IgZml0dGluZyBhCnN0cnVjdHVyYWwgdG9waWMgbW9kZWwgZG9lcyBub3QgdGFrZSBhIGZhaXJseSBzdGFuZGFyZCBkb2N1bWVudCB0ZXJtCm1hdHJpeCBsaWtlIHRoZSBgTERBKClgIGZ1bmN0aW9uLgoKQmVmb3JlIGFuIFNUTSBtb2RlbCBjYW4gYmUgZml0dGVkLCBlbGVtZW50cyBtdXN0IGJlIGV4dHJhY3RlZCBmcm9tIHRoZQpgdGVtcGAgb2JqZWN0IGNyZWF0ZWQgYWZ0ZXIgcHJvY2Vzc2luZyB0aGUgcmV2aWV3IHRleHQuIFNwZWNpZmljYWxseSwKdGhlIGBzdG0oKWAgZnVuY3Rpb24gZXhwZWN0cyB0aGUgZm9sbG93aW5nIGFyZ3VtZW50czoKCi0gICBgZG9jdW1lbnRzID1gIHRoZSBkb2N1bWVudCB0ZXJtIG1hdHJpeCB0byBiZSBtb2RlbGVkIGluIHRoZSBuYXRpdmUKICAgIFNUTSBmb3JtYXQKLSAgIGBkYXRhID1gIGFuIG9wdGlvbmFsIGRhdGEgZnJhbWUgY29udGFpbmluZyBtZXRhIGRhdGEgZm9yIHRoZQogICAgcHJldmFsZW5jZSBhbmQvb3IgY29udGVudCBjb3ZhcmlhdGVzIHRvIGluY2x1ZGUgaW4gdGhlIG1vZGVsCi0gICBgdm9jYWIgPWAgYSBjaGFyYWN0ZXIgdmVjdG9yIHNwZWNpZnlpbmcgdGhlIHdvcmRzIGluIHRoZSBjb3JwdXMgaW4KICAgIHRoZSBvcmRlciBvZiB0aGUgdm9jYWIgaW5kaWNlcyBpbiBkb2N1bWVudHMuCgpMZXQncyBnbyBhaGVhZCBhbmQgZXh0cmFjdCB0aGVzZSBlbGVtZW50czoKCmBgYHtyIHN0bSBkb2NzfQpkb2NzIDwtIHRlbXAkZG9jdW1lbnRzIAptZXRhIDwtIHRlbXAkbWV0YSAKdm9jYWIgPC0gdGVtcCR2b2NhYiAKYGBgCgpBbmQgbm93IHVzZSB0aGVzZSBlbGVtZW50cyB0byBmaXQgdGhlIG1vZGVsIHVzaW5nIHRoZSBzYW1lIG51bWJlciBvZgp0b3BpY3MgZm9yICpLKiB0aGF0IHdlIHNwZWNpZmllZCBmb3Igb3VyIExEQSB0b3BpYyBtb2RlbC4gTGV0J3MgYWxzbwp0YWtlIGFkdmFudGFnZSBvZiB0aGUgZmFjdCB0aGF0IHdlIGNhbiBpbmNsdWRlIHRoZSBgSURgIGFuZCBgdGl0bGVgCmNvdmFyaWF0ZXMgaW4gdGhlIGBwcmV2ZWFsZW5jZSA9YCBhcmd1bWVudCB0byBoZWxwIGltcHJvdmUsIGluIHRoZW9yeSwKbW9kZWwgZml0OgoKYGBge3Igc3RtfQpyZXZpZXdzX3N0bSA8LSBzdG0oZG9jdW1lbnRzPWRvY3MsIAogICAgICAgICBkYXRhPW1ldGEsCiAgICAgICAgIHZvY2FiPXZvY2FiLCAKICAgICAgICAgcHJldmFsZW5jZSA9fiBJRCArIHRpdGxlLAogICAgICAgICBLPTEwLAogICAgICAgICBtYXguZW0uaXRzPTI1LAogICAgICAgICB2ZXJib3NlID0gRkFMU0UsCiAgICAgICAgIGdhbW1hLnByaW9yICA9ICdMMScpCnJldmlld3Nfc3RtCmBgYAoKQXMgbm90ZWQgZWFybGllciwgdGhlIGBzdG1gIHBhY2thZ2UgaGFzIGEgbnVtYmVyIG9mIGhhbmR5IGZlYXR1cmVzLiBPbmUKb2YgdGhlc2UgaXMgdGhlIGBwbG90LlNUTSgpYCBmdW5jdGlvbiBmb3Igdmlld2luZyB0aGUgbW9zdCBwcm9iYWJsZQp3b3JkcyBhc3NpZ25lZCB0byBlYWNoIHRvcGljLgoKQnkgZGVmYXVsdCwgaXQgb25seSBzaG93cyB0aGUgZmlyc3QgMyB0ZXJtcyBzbyBsZXQncyBjaGFuZ2UgdGhhdCB0byA1IHRvCmhlbHAgd2l0aCBpbnRlcnByZXRhdGlvbjoKCmBgYHtyIHBsb3Qgc3RtfQpwbG90LlNUTShyZXZpZXdzX3N0bSwgbiA9IDUpCmBgYAoKSSBjaG9zZSB0d28gZGlmZmVyZW50IEsgdmFsdWVzIGZvciBlYWNoIG9mIHRoZSB0b3BpYyBtb2RlbHMuIEkgdXNlZCBLPTE1CmZvciBMREEgYXMgdGhhdCdzIHdoYXQgdGhlIEdpYmJzIG1ldGhvZCByZWNvbW1lbmRlZCwgYnV0IEkgcmVkdWNlZCBLIHRvCjEwIGZvciB0aGUgU1RNIG1vZGVsLiBUaGF0IHNoaWZ0IHdhcyBtYWRlIGFzIEkgc2F3IG11bHRpcGxlIG92ZXJsYXBwaW5nCnRvcGljcyBpbiBTVE0gd2hlbiBLIHdhcyBzZXQgdG8gMTUuIFNjcmVlbiBzaG90cyBiZWxvdyBjYXB0dXJlIHRoZSBTVE0KZGlmZmVyZW5jZXMgZm9yIGVhY2ggb2YgdGhvc2UgSyB2YWx1ZXMgdXNpbmcgdGhlIGBMREF2aXNgIHRvcGljIGJyb3dzZXI6CgpgYGB7ciBMREF2aXMsIGV2YWw9RkFMU0V9CnRvTERBdmlzKG1vZCA9IHJldmlld3Nfc3RtLCBkb2NzID0gZG9jcykKYGBgCgpBcyB5b3UgY2FuIHNlZSBmcm9tIHRoZSBicm93c2VyIHNjcmVlbiBzaG90IGJlbG93LCBvdXIgY3VycmVudCBTVE0gbW9kZWwKb2YgMTUgdG9waWNzIGlzIHJlc3VsdGluZyBpbiBhIGxvdCBvZiBvdmVybGFwIGFtb25nIHRvcGljcyBhbmQgc3VnZ2VzdAp0aGF0IDE1IG1heSBub3QgYmUgYW4gb3B0aW1hbCBudW1iZXIgb2YgdG9waWNzOgoKIVtdKGltYWdlcy9MREF2aXMyLTAxLnBuZykKCkNoYW5naW5nIEsgdG8gMTAgc2lnbmlmaWNhbnRseSByZWR1Y2VkIHRoZSBvdmVybGFwIGluIHRvcGljczoKCiFbXShpbWFnZXMvTERBdmlzMy5wbmcpCgojIyA0LiBFWFBMT1JFCgojIyMgNGEuIEV4cGxvcmluZyBCZXRhIFZhbHVlcwoKSGlkZGVuIHdpdGhpbiB0aGlzIGBmb3J1bXNfbGRhYCB0b3BpYyBtb2RlbCBvYmplY3Qgd2UgY3JlYXRlZCBhcmUKcGVyLXRvcGljLXBlci13b3JkIHByb2JhYmlsaXRpZXMsIGNhbGxlZCDOsiAoImJldGEiKS4gSXQgaXMgdGhlCnByb2JhYmlsaXR5IG9mIGEgdGVybSAod29yZCkgYmVsb25naW5nIHRvIGEgdG9waWMuCgpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgNSBtb3N0IGxpa2VseSB0ZXJtcyBhc3NpZ25lZCB0byBlYWNoIHRvcGljLAppLmUuIHRob3NlIHdpdGggdGhlIGxhcmdlc3QgzrIgdmFsdWVzIHVzaW5nIHRoZSBgdGVybXMoKWAgZnVuY3Rpb24gZnJvbQp0aGUgYHRvcGljbW9kZWxzYCBwYWNrYWdlOgoKYGBge3IgdGVybXN9CnRlcm1zKHJldmlld3NfbGRhLCA1KQpgYGAKCkJhc2VkIG9uIG91ciBzZWxlY3RlZCBudW1iZXIgb2YgdG9waWNzIGZvciBvdXIgY29ycHVzLCBzb21lIHRoZW1lcyBhcmUKZmFpcmx5IGludHVpdGl2ZSB0byBpbnRlcnByZXQuIEZvciBleGFtcGxlOgoKLSAgIFRvcGljIDIgKHRhY3RpY3MsIGhpc3RvcnksIHBvZGNhc3RzLCBmZWVscywgY29tYmF0KSBzZWVtcyB0byBiZQogICAgYWJvdXQgcmVsYXRpbmcgdGhlIGF1dGhvcnMgbGEgdGFrZXMgb24gbWlsaXRhcnkgaGlzdG9yeSB0byBoaXMKICAgIHBvZGNhc3QgKHdoaWNoIGlzIGFib3V0IHJldmlzaW9uaXN0IGhpc3RvcnkpOwoKLSAgIFRvcGljIDYgKGhpc3RvcnksIGJsYWgsIHN1cnJlbmRlciwgcHJlY2lzaW9uLCBiYWQpIGluZGljYXRlcwogICAgZGlzc2F0aXNmYWN0aW9uIHdpdGggdGhlIGF1dGhvcidzIGFuYWx5c2lzIG9mIGEgc3BlY2lmaWMgZXZlbnQ7IGFuZAoKLSAgIFRvcGljIDQgKHRhaWx3aW5kLCBmZXJvY2lvdXMsIHRha2VzLCBmb3JjZSwgcHJlY2lzaW9uKSByZWxhdGVzIHRvIGEKICAgIHNwZWNpZmljIGNsYWltIHRoZSBhdXRob3IgbWFrZXMgcmVnYXJkaW5nIHRoZSB3aW5kIHJlcXVpcmVkIGZvciBhCiAgICBwbGFuZSB0byB0YWtlIG9mZiAoZGVidW5rZWQgYnkgbWFueSkuCgpUbyBnZXQgYSBtb3JlIGludHVpdGl2ZSBkZXNjcmlwdGlvbiBvZiB0aGUgcmVsYXRpb25zaGlwcyBhYm92ZSwgdGhlCmB0aWR5dGV4dGAgcGFja2FnZSBjYW4gY292ZXJ0IHRoZSBMREEgbW9kZWwgdG8gYSB0aWR5IGRhdGEgZnJhbWUKY29udGFpbmluZyB0aGVzZSBiZXRhIHZhbHVlcyBmb3IgZWFjaCB0ZXJtOgoKYGBge3IgdGlkeV9sZGF9CnRpZHlfbGRhIDwtIHRpZHkocmV2aWV3c19sZGEpCnRpZHlfbGRhCmBgYAoKVGhlbiB3ZSBjYW4gcGxvdCB0aGlzIGluZm9ybWF0aW9uIHZpc3VhbGx5OgoKYGBge3IgdG9wX3Rlcm1zfQp0b3BfdGVybXMgPC0gdGlkeV9sZGEgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHNsaWNlX21heChiZXRhLCBuID0gNSwgd2l0aF90aWVzID0gRkFMU0UpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKdG9wX3Rlcm1zICU+JQogIG11dGF0ZSh0ZXJtID0gcmVvcmRlcl93aXRoaW4odGVybSwgYmV0YSwgdG9waWMpKSAlPiUKICBncm91cF9ieSh0b3BpYywgdGVybSkgJT4lICAgIAogIGFycmFuZ2UoZGVzYyhiZXRhKSkgJT4lICAKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KGFlcyhiZXRhLCB0ZXJtLCBmaWxsID0gYXMuZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfeV9yZW9yZGVyZWQoKSArCiAgbGFicyh0aXRsZSA9ICJUb3AgNSB0ZXJtcyBpbiBlYWNoIExEQSB0b3BpYyIsCiAgICAgICB4ID0gZXhwcmVzc2lvbihiZXRhKSwgeSA9IE5VTEwpICsKICBmYWNldF93cmFwKH4gdG9waWMsIG5jb2wgPSA0LCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKIyMjIDRiLiBFeHBsb3JpbmcgR2FtbWEgVmFsdWVzCgpOb3cgdGhhdCB3ZSBoYXZlIGEgc2Vuc2Ugb2YgdGhlIG1vc3QgY29tbW9uIHdvcmRzIGFzc29jaWF0ZWQgd2l0aCBlYWNoCnRvcGljLCBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgdG9waWMgcHJldmFsZW5jZSBpbiB0aGUgYm9vayByZXZpZXcKY29ycHVzLCBpbmNsdWRpbmcgdGhlIHdvcmRzIHRoYXQgY29udHJpYnV0ZSB0byBlYWNoIHRvcGljIHdlIGV4YW1pbmVkCmFib3ZlLgoKQWxzbywgaGlkZGVuIHdpdGhpbiBvdXIgYGZvcnVtc19sZGFgIHRvcGljIG1vZGVsIG9iamVjdCB3ZSBjcmVhdGVkIGFyZQpwZXItZG9jdW1lbnQtcGVyLXRvcGljIHByb2JhYmlsaXRpZXMsIGNhbGxlZCDOsyAoImdhbW1hIikuIFRoaXMgcHJvdmlkZXMKdGhlIHByb2JhYmlsaXRpZXMgdGhhdCBlYWNoIGRvY3VtZW50IGlzIGdlbmVyYXRlZCBmcm9tIGVhY2ggdG9waWMsIHRoZQpnYW1tYSBtYXRyaXguIEJldGEgYW5kIGdhbW1hIHZhbHVlcyBjYW4gYmUgY29tYmluZWQgdG8gdW5kZXJzdGFuZCB0aGUKdG9waWMgcHJldmFsZW5jZSBpbiBvdXIgY29ycHVzLCBhbmQgd2hpY2ggd29yZHMgY29udHJpYnV0ZSB0byBlYWNoCnRvcGljLgoKRmlyc3QsIGxldCdzIGNyZWF0ZSB0d28gdGlkeSBkYXRhIGZyYW1lcyBmb3Igb3VyIGJldGEgYW5kIGdhbW1hIHZhbHVlcwoKYGBge3IgYmV0YV9nYW1tYX0KdGRfYmV0YSA8LSB0aWR5KHJldmlld3NfbGRhKQp0ZF9nYW1tYSA8LSB0aWR5KHJldmlld3NfbGRhLCBtYXRyaXggPSAiZ2FtbWEiKQp0ZF9iZXRhCnRkX2dhbW1hCmBgYAoKTmV4dCwgd2UnbGwgY3JlYXRlIGEgZmlsdGVyZWQgZGF0YSBmcmFtZSBvZiBvdXIgYHRvcF90ZXJtc2AsIGpvaW4gdGhpcwp0byBhIG5ldyBkYXRhIGZyYW1lIGZvciBgZ2FtbWEtdGVybXNgIGFuZCBjcmVhdGUgYSBuaWNlIGNsZWFuIHRhYmxlCnVzaW5nIHRoZSBga2FiZWwoKWAgZnVuY3Rpb24gYGtuaXRyYCBwYWNrYWdlOgoKYGBge3IgcHJldmFsZW5jZV90YWJsZX0KdG9wX3Rlcm1zIDwtIHRkX2JldGEgJT4lCiAgYXJyYW5nZShiZXRhKSAlPiUKICBncm91cF9ieSh0b3BpYykgJT4lCiAgdG9wX24oNywgYmV0YSkgJT4lCiAgYXJyYW5nZSgtYmV0YSkgJT4lCiAgc2VsZWN0KHRvcGljLCB0ZXJtKSAlPiUKICBzdW1tYXJpc2UodGVybXMgPSBsaXN0KHRlcm0pKSAlPiUKICBtdXRhdGUodGVybXMgPSBtYXAodGVybXMsIHBhc3RlLCBjb2xsYXBzZSA9ICIsICIpKSAlPiUgCiAgdW5uZXN0KCkKCmdhbW1hX3Rlcm1zIDwtIHRkX2dhbW1hICU+JQogIGdyb3VwX2J5KHRvcGljKSAlPiUKICBzdW1tYXJpc2UoZ2FtbWEgPSBtZWFuKGdhbW1hKSkgJT4lCiAgYXJyYW5nZShkZXNjKGdhbW1hKSkgJT4lCiAgbGVmdF9qb2luKHRvcF90ZXJtcywgYnkgPSAidG9waWMiKSAlPiUKICBtdXRhdGUodG9waWMgPSBwYXN0ZTAoIlRvcGljICIsIHRvcGljKSwKICAgICAgICAgdG9waWMgPSByZW9yZGVyKHRvcGljLCBnYW1tYSkpCgpnYW1tYV90ZXJtcyAlPiUKICBzZWxlY3QodG9waWMsIGdhbW1hLCB0ZXJtcykgJT4lCiAga2FibGUoZGlnaXRzID0gMywgCiAgICAgICAgY29sLm5hbWVzID0gYygiVG9waWMiLCAiRXhwZWN0ZWQgdG9waWMgcHJvcG9ydGlvbiIsICJUb3AgNyB0ZXJtcyIpKQpgYGAKCkFuZCBsZXQncyBhbHNvIGNvbXBhcmUgdGhpcyB0byB0aGUgbW9zdCBwcmV2YWxlbnQgdG9waWNzIGFuZCB0ZXJtcyBmcm9tCm91ciBgcmV2aWV3c19zdG1gIG1vZGVsIHRoYXQgd2UgY3JlYXRlZCB1c2luZyB0aGUgYHBsb3QoKWAgZnVuY3Rpb246CgpgYGB7ciBwbG90X3N0bX0KcGxvdChyZXZpZXdzX3N0bSwgbiA9IDcpCmBgYAoKIyMjIDRjLiBSZWFkaW5nIHRoZSBUZWEgTGVhdmVzCgpSZWNvZ25pemluZyB0aGF0IHRvcGljIG1vZGVsaW5nIGlzIGJlc3QgdXNlZCBhcyBhICJ0b29sIGZvciByZWFkaW5nIiBhbmQKcHJvdmlkZXMgb25seSBhbiBpbmNvbXBsZXRlIGFuc3dlciB0byBvdXIgb3ZlcmFyY2hpbmcsICoqIkhvdyBkbyB3ZQpxdWFudGlmeSB3aGF0IGEgY29ycHVzIGlzIGFib3V0PyIqKiwgdGhlIHJlc3VsdHMgZG8gc3VnZ2VzdCBzb21lCnBvdGVudGlhbCB0b3BpY3MgdGhhdCBoYXZlIGVtZXJnZXMgYXMgd2VsbCBhcyBzb21lIGFyZWFzIHdvcnRoIGZvbGxvd2luZwp1cCBvbi4KClNwZWNpZmljYWxseSwgbG9va2luZyBhdCBzb21lIG9mIHRoZSBjb21tb24gY2x1c3RlcnMgb2Ygd29yZHMgZm9yIHRoZQptb3JlIHByZXZhbGVudCB0b3BpY3Mgc3VnZ2VzdCB0aGF0IHNvbWUga2V5IHRvcGljcyBvciAibGF0ZW50IHRoZW1lcyIKKHJlbmFtZWQgaW4gYm9sZCkgbWlnaHQgaW5jbHVkZToKCi0gICAqKkhpc3RvcmljYWwgSW5hY2N1cmFjaWVzOioqIFVuc3VycHJpc2luZywgZ2l2ZW4gdGhhdCBtb3N0IG9mIHRob3NlCiAgICB3aG8gSSBoZWFyZCBoYWQgaXNzdWVzIHdpdGggdGhlIGJvb2sgd2VyZSBoaXN0b3JpYW5zLiBQcmV2YWxlbnQgaW4KICAgIGJvdGggdGhlIGByZXZpZXdzX3N0bWAgYW5kIGByZXZpZXdzX2xkYWAgbW9kZWxzIGNvbnRhaW5zIHRoZSB0ZXJtcwogICAgImVycm9ycyIsICJ3cm9uZyIsICJoaXN0b3J5Ii4gVGhpcyBjb3VsZCByZXByZXNlbnQgdGhlIGdlbmVyYWwKICAgIGNvbnNlbnN1cyBhY3Jvc3MgYWxsIHRoZSBjcml0aWNhbCByZXZpZXdzLiBTb21lIHNwZWNpZmljIGV4YW1wbGVzCiAgICBjb25jZXJuIGhvdyBwbGFuZXMgd29yayAodGFpbHdpbmQpIGFuZCB3aGljaCBwbGFuZXMgZHJvcHBlZCBzcGVjaWZpYwogICAgb3JkaW5hbmNlIGR1cmluZyBXVyBJSSAoZW5vbGEsIGdheSwgaGlyb3NoaW1hLCBuYWdhc2FraSkuCi0gICAqKk1pc3VuZGVyc3RhbmRpbmcgb2YgQWlyIFBvd2VyIGFuZCBTdHJhdGVneSBpbiBXVyBJSToqKiBUb3BpY3MgOCAmCiAgICAyIGZyb20gdGhlIExEQSBtb2RlbCBmb2N1c2VkIGluIG9uIGhvdyBzb21lIHRob3VnaHQgdGhlIGF1dGhvciBtYXkKICAgIGhhdmUgYmVlbiBvdXQgb2YgaGlzIGRlcHRoIGluIGRvaW5nIGEgaGlzdG9yeSB0ZXh0IG9uIFdXIElJLiBXb3JkcwogICAgbGlrZSAiYXJndW1lbnQiIGFuZCAicG9kY2FzdCIgYW5kICJ0aGVtZSIgaGludCBhdCB0aGUgZmFjdCBoZQogICAgcHJvZHVjZXMgYSBwb2RjYXN0IG9uIHJldmlzaW9uaXN0IGhpc3RvcnkgYW5kIGhpcyBhcmd1bWVudHMgb24KICAgIHN0cmF0ZWd5IGFuZCB0YWN0aWNzIHdlcmUgc2VlbiBhcyB3ZWFrLgotICAgKipMYWNrIG9mIFJlZmVyZW5jZXMgYW5kIENpdGluZyBvbmx5IGEgZmV3IGtub3duIGhpc3RvcmlhbnM6KiogVG9waWMKICAgIDMgKFNUTSkgYW5kIHRvcGljIDE1IChMREEpIHJlZmVyZW5jZSAicmVzZWFyY2giIGFuZCAiYmlhc2VkIiBhcwogICAgaW5kaWNhdG9ycyBvZiBtdWx0aXBsZSByZXZpZXdzIGNsYWltaW5nIHRoZSBhdXRob3IgY2l0ZWQgdGhlIG1pbmltdW0KICAgIGhpc3RvcmljYWwgcmVmZXJlbmNlcyBpbiB0aGUgYm9vay4gT25lIHJldmlldyBldmVuIHJlZmVycmVkIHRvIHRoZQogICAgYm9vayBhcyBhbiBvdmVybHkgbG9uZyBibG9nIHBvc3QuCgojIyA1LiBDb21tdW5pY2F0ZQoKVGhpcyBwcm9qZWN0IHdhcyBkZXNpZ25lZCB0byBhbnN3ZXIgdGhyZWUga2V5IHF1ZXN0aW9ucyBhYm91dCB1c2luZwp0b3BpYyBtb2RlbGluZyB0byBpZGVudGlmeSBsYXRlbnQgdGhlbWVzIGluIGEgYm9keSBvZiB0ZXh0LgpTcGVjaWZpY2FsbHk6CgoxLiAgKipDYW4gZGlzdGluY3QgdGhlbWVzIGJlIGlkZW50aWZpZWQgdGhyb3VnaCBhdXRvbWF0ZWQgdG9waWMKICAgIG1vZGVsaW5nPyoqCgpJIGJlbGlldmUgdGhlIHNpbXBsZSBhbnN3ZXIgaXMgdGhhdCB5ZXMsIG11bHRpcGxlIHRoZW1lcyBjYW4gYmUKaWRlbnRpZmllZC4gQmVpbmcgdGhlIHRlY2huaXF1ZXMgYXJlIHVuc3VwZXJ2aXNlZCwgaG93ZXZlciwgdGhlaXIgaXMgYW4KYXJ0IHRvIGRldGVybWluaW5nIGhvdyBpbmNsdXNpdmUgdGhvc2UgdGhlbWVzIG1heSBiZS4gSW4gdGhpcyBjYXNlLCBJCmhhZCB0byB3ZWVkIG91dCBtdWNoIG9mIHRoZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcGxvdCBvZiB0aGUgYm9vayBhcyBpdAp3YXMgY29tbW9uIHRvIGFsbW9zdCBldmVyeSByZXZpZXcuIExvc2luZyB0aGF0IGNvbnRleHQgbWF5IGJlCmRldHJpbWVudGFsIHRvIGlkZW50aWZ5aW5nIHRvcGljcywgYnV0IGlmIHlvdSBoYXZlIHNvbWUgdW5kZXJzdGFuZGluZyBvZgp0aGUgb3ZlcmFsbCBjb3JwdXMsIHlvdSBjYW4gbWl0aWdhdGUgdGhhdCByaXNrLgoKMi4gICoqV2lsbCBkaWZmZXJlbnQgdG9waWMgbW9kZWxpbmcgdGVjaG5pcXVlcyAoc3RlbW1pbmcsIExEQSB2cyBTVE0pCiAgICByZXN1bHQgaW4gc2ltaWxhciBvciBkaXNzaW1pbGFyIHRvcGljcz8qKgoKSW4gdGhpcyBjYXNlLCBib3RoIExEQSBhbmQgU1RNIG1vZGVscyByZXR1cm5lZCByZWxhdGl2ZWx5IHNpbWlsYXIKcmVzdWx0cy4gTWFueSBvZiB0aGUgc2FtZSBrZXkgdGVybXMgYXBwZWFyZWQgaW4gYm90aCBtb2RlbHMuIEhvd2V2ZXIsCnRoZSBudW1iZXIgb2YgdG9waWNzIChLKSByZXR1cm5lZCBkaWZmZXJlbnQgcmVzdWx0cyBpbiBlYWNoIG1vZGVsLiBMREEKd2FzIG1vcmUgY29uc2lzdGVudCB3aXRoIEsgPSAxNSB3aGlsZSBTVE0gbW9kZWxzIHJldHVybmVkIGZld2VyCm92ZXJsYXBwaW5nIHRvcGljcyB3aXRoIEsgPTEwLiBMYXN0bHksIHN0ZW1taW5nIHdhcyBsZXNzIG9mIGEgZmFjdG9yIGluCnRoaXMgcHJvamVjdCBhcyBJIHJlZHVjZWQgcmVkdW5kYW50IHRlcm1zIGR1cmluZyB3cmFuZ2xpbmcuIEFkZGluZyBzb21lCmFkZGl0aW9uYWwgc3RvcCB3b3JkcyBlbnN1cmVkIHRoYXQgc3RlbW1pbmcgd291bGQgYmUgbGVzcyBpbXBhY3RmdWwuCldoZW4gaXQgd2FzIHRyaWVkLCB0aGUgcmVzdWx0cyB3ZXJlIG5vdCBjb25zaXN0ZW50IHdpdGggdGhlIG90aGVyCm1vZGVscy4KCjMuICAqKkRvIHRoZSB0aGVtZXMgY29ycmVsYXRlIHRvIG5lZ2F0aXZlIHJlYWN0aW9ucyB0byB0aGUgYm9vaz8qKgoKVGhlIHRvcGljcyByZXR1cm5lZCBieSBib3RoIG1vZGVscyBkbyBjb3JyZWxhdGUgdG8gdGhlIG5lZ2F0aXZlCnJlYWN0aW9ucyBJIGV4cGVjdGVkIGZyb20gdGhlIHJldmlld3MuIEEgbWFqb3IgbGltaXRhdGlvbiBpbiB0aGlzIHN0dWR5CndhcyB0aGUgc2l6ZSBvZiB0aGUgZGF0YSBzZXQuIFdpdGggb25seSAxNTIgcmV2aWV3cyBjYXRlZ29yaXplZCBhcwoiY3JpdGljYWwiIG9mIHRoZSBib29rLCBJIHdhcyBsZWZ0IHRvIGFuYWx5emUgYSBmYWlybHkgc21hbGwgY29ycHVzIG9mCnRleHQuIEkgd291bGQgYXJndWUgdGhhdCBhIG1pbmltdW0gb2YgNTAwIGRvY3VtZW50cyBhcmUgbmVlZGVkIHdoZW4gdGhlCnRleHQgaXMgc28gc2hvcnQgaW4gZWFjaCBkb2N1bWVudC4gSW4gdGhpcyBjYXNlLCBtYW55IG9mIHRoZSByZXZpZXdzCm9ubHkgZGlmZmVyZWQgaW4gYSBjb3VwbGUgb2Yga2V5IHdvcmRzIHRoYXQgZmFpbGVkIHRvIHJlZ2lzdGVyIHZlcnkgaGlnaApvbiBmcmVxdWVuY3kgY291bnQuIFRoaXMga2VwdCB0aG9zZSB0ZXJtcyB0b3dhcmRzIHRoZSBib3R0b20gb2Yga2V5IHdvcmQKY291bnRzIGFuZCBoYXJkZXIgdG8gZGlzdGluZ3Vpc2ggYXMgZHJpdmVycyBvZiB0b3BpYyBkZXZlbG9wbWVudC4K