In this guided practice we will build two shiny apps. You can find the app.R files in the folders 2_gapminder and 2_Old_Faithful_Geyser.

Old Faithful Geyser

Our first shiny app is inspired on the example provided by https://shiny.posit.co/r/gallery/start-simple/faithful/. We will be displaying eruption data for the Old Faithful geyser.

Let’s start by building the basic structure of our app:

library(shiny)

# Define the ui object and it's basic layout, as well as the title
ui <- fluidPage(
  
  # Application title
  titlePanel("Old Faithful Geyser Data"),
  sidebarLayout(
    # Sidebar 
    sidebarPanel(
      
    ),
    # Main panel
    mainPanel(
      
    )
  )
)

# Define server object
server <- function(input, output) {
  
  
}

# Combine ui and server 
shinyApp(ui = ui, server = server)

You should be able to run the app and see the title, as well as the empty panels.

We can now proceed to design our ui by defining our input and output. Our app will display a histogram, allowing users to determine the number of bars (or bins) on the visualization. We therefore need to define a numeric input (where the user will tell us the desired number of bars) and a plot output (our histogram). If you are not familiar with histograms, you can use ?hist() to find out more about the function we will be using to draw our plot.

In this case, we will employ the sliderInput, a frequently used widget that enables users to choose a number within a defined interval (in this case, numbers between 1 and 50). The default value will be set to 30.

library(shiny)

# Develop the ui 
ui <- fluidPage(
  titlePanel("Old Faithful Geyser Data"),
  sidebarLayout(
    sidebarPanel(
      #our input
      sliderInput(inputId = "bins",
                  label ="Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)
    ),
    mainPanel(
      #our output
      plotOutput(outputId="distPlot")
    )
  )
)

server <- function(input, output) {
  
  
}

shinyApp(ui = ui, server = server)

Note that, even although the app may not yet fulfill its purpose, we can now interact with the input widget.

Let’s make the app actually do it’s job by designing the server. Remember we will need to refer to the ids we defined in the ui (distPlot fot the output and bins for the input).

Given our intention to generate a plot output, the suitable render function is renderPlot(). We can call this toy data set using faithful; in this case, we are interested on its second column, which contains information on eruptions. In order to generate a plot with the number of columns the user will decide, we will include this input in the function defining the histogram breaks.

library(shiny)

ui <- fluidPage(
  titlePanel("Old Faithful Geyser Data"),
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "bins",
                  label ="Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)
    ),
    mainPanel(
      plotOutput(outputId="distPlot")
    )
  )
)

# Define server logic required to draw a histogram
server <- function(input, output) {
  output$distPlot <- renderPlot({
    # generate bins based on input$bins from ui
    x    <- faithful[, 2]
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    
    # draw the histogram with the specified number of bins
    hist(x, breaks = bins, col = 'darkgray', border = 'white')
  })
}

shinyApp(ui = ui, server = server)

We are done! Let’s move on to a more complex app.

gapminder

Our second shiny app is inspired on the example provided by https://akshi8.shinyapps.io/Gapminder/

In this case, we will be exploring the population, life-expectancy and GDP per capita for different countries over the years. Users can select the indicator to compare, choose specific countries, and specify the years they wish to examine.

We will include plots and a table, and we will also organize the information in our app using tabs.

Let’s start by building the basic structure of our app:

library(tidyverse)
library(shiny)
library(gapminder)
library(countrycode)
library(plotly)
library(scales)
library(DT)

#our dataset, imported from the gapminder package:

gapminder <- gapminder

#some preprocessing: as Kuwuait will break the scale for some years, let's remove it from the database

gapminder <- gapminder %>% 
  filter(country!='Kuwait')

#### our ui

ui <- fluidPage(
  titlePanel("Gapminder Data"),
  tabsetPanel(
    tabPanel(title = 'time series',
             #a layout option: sidebar and main panel
             sidebarLayout(
               sidebarPanel(
                 
               ),
               mainPanel(
                 tabsetPanel(type = "tabs",
                             tabPanel("Plot"), 
                             tabPanel("Table")
                 )
                 
               )
             )
    ),
    tabPanel(title = 'distribution by year',
             #a different option: using a well panel
             wellPanel(
               
             )
    )
  )
)


#### Server

server <- function(input, output) {
  
}

shinyApp(ui = ui, server = server)

You should be able to run the app and view the app’s title, along with the two primary tabs and the two sub-tabs nested within the first main tab.

We can now proceed to design our ui by defining our input and output, as well as subtitles explaining more clearly the content of each tab (using h3()). In the first main tab, our app will display an option to choose the country (note that in this case the user can select multiple options; this becomes explicitly clear when defining multiple = TRUE) and the variable to display (in this case a single option; this becomes explicitly clear when stating multiple = FALSE).

On the other hand, in our second tab the user will have the option of selecting the year of the data. By setting animate = TRUE, we provide users with the option to view an animation that automatically cycles through all available years in succession by pressing on the play button at the bottom right corner of the widget.

While we will determine our outputs (and their ids!) at this stage, we won’t be able to visualize them until after designing the app’s server.

library(tidyverse)
library(shiny)
library(gapminder)
library(countrycode)
library(plotly)
library(scales)
library(DT)

gapminder <- gapminder

gapminder <- gapminder %>% 
  filter(country!='Kuwait')

#### ui

ui <- fluidPage(
  titlePanel("Gapminder Data"),
  tabsetPanel(
    tabPanel(title = 'time series',
             sidebarLayout(
               sidebarPanel(
                 selectizeInput(inputId ='countries', 
                                label='Choose the countries', 
                                choices = unique(gapminder$country), 
                                multiple = TRUE, 
                                selected = "United States") ,  
                 
                 selectizeInput(inputId ='inputvariable', 
                                label='Choose the variable to display', 
                                choices =  c("lifeExp","pop","gdpPercap"),
                                multiple = FALSE,
                                selected = "lifeExp")
               ),
               mainPanel(
                 tabsetPanel(type = "tabs",
                             tabPanel("Plot", 
                                      h3("Time series plot"),
                                      plotlyOutput("plot_1")), 
                             tabPanel("Table", 
                                      h3("Time series table"),
                                      dataTableOutput("table"))
                 )
                 
               )
             )
    ),
    tabPanel(title = 'distribution by year',
             wellPanel(
               sliderInput(inputId ='yr', 
                           label='Choose the year',
                           min = min(gapminder$year),
                           max = max(gapminder$year),
                           value = min(gapminder$year),
                           step = 5,
                           animate = TRUE)
             ),
             h3("Distribution by year of life-expectancy and GDP per capita"),
             plotlyOutput("plot_2") 
             
    )
  )
)


#### Server

server <- function(input, output) {
  
}

shinyApp(ui = ui, server = server)

Let’s make the app actually do it’s job by designing the server:

library(tidyverse)
library(shiny)
library(gapminder)
library(countrycode)
library(plotly)
library(scales)
library(DT)

gapminder <- gapminder

gapminder <- gapminder %>% 
  filter(country!='Kuwait')

#one of the options when designing a server is to predefine functions our server will draw on when producing our outputs:

plot_1 <- function(countries, var){
  
  plt <- gapminder %>%
    pivot_longer(lifeExp:gdpPercap) %>% 
    filter(country %in% countries, name==var) %>%  
    ggplot(aes(year, value, color = continent,group = country))+ 
    geom_line()+
    geom_point()
  
  ggplotly(plt)
}

plot_2 <- function(yr){
  
  plt <- gapminder %>%
    filter(year==yr) %>% 
    ggplot(aes(gdpPercap, lifeExp, color=continent,size=pop, 
               text = paste(country,
                            '<br>GDP per capita: ',number(gdpPercap, 1),
                            '<br>Population: ', number(pop/10000,1),'M'))) +
    geom_point()+
    theme_minimal()
  
  ggplotly(plt, tooltip = 'text')
}

#### ui

ui <- fluidPage(
  titlePanel("Gapminder Data"),
  tabsetPanel(
    tabPanel(title = 'time series',
             sidebarLayout(
               sidebarPanel(
                 selectizeInput(inputId ='countries', 
                                label='Choose the countries', 
                                choices = unique(gapminder$country), 
                                multiple = TRUE, 
                                selected = "United States") ,  
                 
                 selectizeInput(inputId ='inputvariable', 
                                label='Choose the variable to display', 
                                choices =  c("lifeExp","pop","gdpPercap"),
                                multiple = FALSE,
                                selected = "lifeExp")
               ),
               mainPanel(
                 tabsetPanel(type = "tabs",
                             tabPanel("Plot", 
                                      h3("Time series plot"),
                                      plotlyOutput("plot_1")), 
                             tabPanel("Table", 
                                      h3("Time series table"),
                                      dataTableOutput("table"))
                 )
                 
               )
             )
    ),
    tabPanel(title = 'distribution by year',
             wellPanel(
               sliderInput(inputId ='yr', 
                           label='Choose the year',
                           min = min(gapminder$year),
                           max = max(gapminder$year),
                           value = min(gapminder$year),
                           step = 5,
                           animate = TRUE)
             ),
             h3("Distribution by year of life-expectancy and GDP per capita"),
             plotlyOutput("plot_2") 
             
    )
  )
)


#### Server

server <- function(input, output) {
  ## first plot
  output$plot_1 <- renderPlotly({ 
    plot_1(countries = input$countries, var = input$inputvariable)
  })
  
  ## table
  output$table <- renderDataTable({
    gapminder %>%
      filter(country %in% input$countries) %>% 
      select(country,continent, year, input$inputvariable )
  }, 
  extensions = 'Buttons',
  options = list(
    paging = TRUE,
    searching = TRUE,
    fixedColumns = TRUE,
    autoWidth = TRUE,
    ordering = TRUE,
    dom = 'tB',
    buttons = c('copy', 'csv', 'excel')
  ),
  class = "display")
  
  ## second plot
  output$plot_2 <- renderPlotly({ plot_2(yr = input$yr)})
}

shinyApp(ui = ui, server = server)

Note that there should be a correspondence between render functions and the type of output (plotlyOutput and renderPlotly, dataTableOutput and renderDataTable). Each output will be defined using its unique ID, and a render function will be applied, invoking the relevant inputs (of course, also through their ids).

Note that plotly visualizations offer users even more freedom to explore our data as they come equipped with a wide range of functionalities. These include the ability to zoom in and examine specific portions of the visualization, or filter a continent of interest by simply clicking on the legends.

Moreover, within renderDataTable, we’ve incorporated additional arguments that enable users to copy the table information or export it in CSV or Excel formats. This allows users to utilize the data for creating their own visualizations.

Feel free to add new elements or improve those already in the app to practice!

LS0tCnRpdGxlOiAiU2hpbnkgQXBwcyIKc3VidGl0bGU6ICJHdWlkZWQgcHJhY3RpY2UiCmF1dGhvcjogIiIKZGF0ZTogIiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSW4gdGhpcyBndWlkZWQgcHJhY3RpY2Ugd2Ugd2lsbCBidWlsZCB0d28gc2hpbnkgYXBwcy4gWW91IGNhbiBmaW5kIHRoZSBhcHAuUiBmaWxlcyBpbiB0aGUgZm9sZGVycyAyX2dhcG1pbmRlciBhbmQgMl9PbGRfRmFpdGhmdWxfR2V5c2VyLgoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRkFMU0Usd2FybmluZyA9IEZBTFNFKQpgYGAKCiMjIE9sZCBGYWl0aGZ1bCBHZXlzZXIKCk91ciBmaXJzdCBzaGlueSBhcHAgaXMgaW5zcGlyZWQgb24gdGhlIGV4YW1wbGUgcHJvdmlkZWQgYnkgaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dhbGxlcnkvc3RhcnQtc2ltcGxlL2ZhaXRoZnVsLy4gV2Ugd2lsbCBiZSBkaXNwbGF5aW5nIGVydXB0aW9uIGRhdGEgZm9yIHRoZSBPbGQgRmFpdGhmdWwgZ2V5c2VyLgoKTGV0J3Mgc3RhcnQgYnkgYnVpbGRpbmcgdGhlIGJhc2ljIHN0cnVjdHVyZSBvZiBvdXIgYXBwOgoKYGBge3IsZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpsaWJyYXJ5KHNoaW55KQoKIyBEZWZpbmUgdGhlIHVpIG9iamVjdCBhbmQgaXQncyBiYXNpYyBsYXlvdXQsIGFzIHdlbGwgYXMgdGhlIHRpdGxlCnVpIDwtIGZsdWlkUGFnZSgKICAKICAjIEFwcGxpY2F0aW9uIHRpdGxlCiAgdGl0bGVQYW5lbCgiT2xkIEZhaXRoZnVsIEdleXNlciBEYXRhIiksCiAgc2lkZWJhckxheW91dCgKICAgICMgU2lkZWJhciAKICAgIHNpZGViYXJQYW5lbCgKICAgICAgCiAgICApLAogICAgIyBNYWluIHBhbmVsCiAgICBtYWluUGFuZWwoCiAgICAgIAogICAgKQogICkKKQoKIyBEZWZpbmUgc2VydmVyIG9iamVjdApzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIAogIAp9CgojIENvbWJpbmUgdWkgYW5kIHNlcnZlciAKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCllvdSBzaG91bGQgYmUgYWJsZSB0byBydW4gdGhlIGFwcCBhbmQgc2VlIHRoZSB0aXRsZSwgYXMgd2VsbCBhcyB0aGUgZW1wdHkgcGFuZWxzLgoKV2UgY2FuIG5vdyBwcm9jZWVkIHRvIGRlc2lnbiBvdXIgKip1aSoqIGJ5IGRlZmluaW5nIG91ciBpbnB1dCBhbmQgb3V0cHV0LiAKT3VyIGFwcCB3aWxsIGRpc3BsYXkgYSBoaXN0b2dyYW0sIGFsbG93aW5nIHVzZXJzIHRvIGRldGVybWluZSB0aGUgbnVtYmVyIG9mIGJhcnMgKG9yIGJpbnMpIG9uIHRoZSB2aXN1YWxpemF0aW9uLiBXZSB0aGVyZWZvcmUgbmVlZCB0byBkZWZpbmUgYSBudW1lcmljIGlucHV0ICh3aGVyZSB0aGUgdXNlciB3aWxsIHRlbGwgdXMgdGhlIGRlc2lyZWQgbnVtYmVyIG9mIGJhcnMpIGFuZCBhIHBsb3Qgb3V0cHV0IChvdXIgaGlzdG9ncmFtKS4gSWYgeW91IGFyZSBub3QgZmFtaWxpYXIgd2l0aCBoaXN0b2dyYW1zLCB5b3UgY2FuIHVzZSBgP2hpc3QoKWAgdG8gZmluZCBvdXQgbW9yZSBhYm91dCB0aGUgZnVuY3Rpb24gd2Ugd2lsbCBiZSB1c2luZyB0byBkcmF3IG91ciBwbG90LgoKSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIGVtcGxveSB0aGUgc2xpZGVySW5wdXQsIGEgZnJlcXVlbnRseSB1c2VkIHdpZGdldCB0aGF0IGVuYWJsZXMgdXNlcnMgdG8gY2hvb3NlIGEgbnVtYmVyIHdpdGhpbiBhIGRlZmluZWQgaW50ZXJ2YWwgKGluIHRoaXMgY2FzZSwgbnVtYmVycyBiZXR3ZWVuIDEgYW5kIDUwKS4gVGhlIGRlZmF1bHQgdmFsdWUgd2lsbCBiZSBzZXQgdG8gMzAuCgpgYGB7cixldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmxpYnJhcnkoc2hpbnkpCgojIERldmVsb3AgdGhlIHVpIAp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiT2xkIEZhaXRoZnVsIEdleXNlciBEYXRhIiksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgI291ciBpbnB1dAogICAgICBzbGlkZXJJbnB1dChpbnB1dElkID0gImJpbnMiLAogICAgICAgICAgICAgICAgICBsYWJlbCA9Ik51bWJlciBvZiBiaW5zOiIsCiAgICAgICAgICAgICAgICAgIG1pbiA9IDEsCiAgICAgICAgICAgICAgICAgIG1heCA9IDUwLAogICAgICAgICAgICAgICAgICB2YWx1ZSA9IDMwKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgI291ciBvdXRwdXQKICAgICAgcGxvdE91dHB1dChvdXRwdXRJZD0iZGlzdFBsb3QiKQogICAgKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICAKICAKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCk5vdGUgdGhhdCwgZXZlbiBhbHRob3VnaCB0aGUgYXBwIG1heSBub3QgeWV0IGZ1bGZpbGwgaXRzIHB1cnBvc2UsIHdlIGNhbiBub3cgaW50ZXJhY3Qgd2l0aCB0aGUgaW5wdXQgd2lkZ2V0LgoKTGV0J3MgbWFrZSB0aGUgYXBwIGFjdHVhbGx5IGRvIGl0J3Mgam9iIGJ5IGRlc2lnbmluZyB0aGUgc2VydmVyLiBSZW1lbWJlciB3ZSB3aWxsIG5lZWQgdG8gcmVmZXIgdG8gdGhlIGlkcyB3ZSBkZWZpbmVkIGluIHRoZSB1aSAoYGRpc3RQbG90YCBmb3QgdGhlIG91dHB1dCBhbmQgYGJpbnNgIGZvciB0aGUgaW5wdXQpLgoKR2l2ZW4gb3VyIGludGVudGlvbiB0byBnZW5lcmF0ZSBhIHBsb3Qgb3V0cHV0LCB0aGUgc3VpdGFibGUgcmVuZGVyIGZ1bmN0aW9uIGlzIGByZW5kZXJQbG90KClgLiBXZSBjYW4gY2FsbCB0aGlzIHRveSBkYXRhIHNldCB1c2luZyAqZmFpdGhmdWwqOyBpbiB0aGlzIGNhc2UsIHdlIGFyZSBpbnRlcmVzdGVkIG9uIGl0cyBzZWNvbmQgY29sdW1uLCB3aGljaCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiBlcnVwdGlvbnMuIEluIG9yZGVyIHRvIGdlbmVyYXRlIGEgcGxvdCB3aXRoIHRoZSBudW1iZXIgb2YgY29sdW1ucyB0aGUgdXNlciB3aWxsIGRlY2lkZSwgd2Ugd2lsbCBpbmNsdWRlIHRoaXMgaW5wdXQgaW4gdGhlIGZ1bmN0aW9uIGRlZmluaW5nIHRoZSBoaXN0b2dyYW0gYnJlYWtzLgoKYGBge3IsZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpsaWJyYXJ5KHNoaW55KQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIk9sZCBGYWl0aGZ1bCBHZXlzZXIgRGF0YSIpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIHNsaWRlcklucHV0KGlucHV0SWQgPSAiYmlucyIsCiAgICAgICAgICAgICAgICAgIGxhYmVsID0iTnVtYmVyIG9mIGJpbnM6IiwKICAgICAgICAgICAgICAgICAgbWluID0gMSwKICAgICAgICAgICAgICAgICAgbWF4ID0gNTAsCiAgICAgICAgICAgICAgICAgIHZhbHVlID0gMzApCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICBwbG90T3V0cHV0KG91dHB1dElkPSJkaXN0UGxvdCIpCiAgICApCiAgKQopCgojIERlZmluZSBzZXJ2ZXIgbG9naWMgcmVxdWlyZWQgdG8gZHJhdyBhIGhpc3RvZ3JhbQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIG91dHB1dCRkaXN0UGxvdCA8LSByZW5kZXJQbG90KHsKICAgICMgZ2VuZXJhdGUgYmlucyBiYXNlZCBvbiBpbnB1dCRiaW5zIGZyb20gdWkKICAgIHggICAgPC0gZmFpdGhmdWxbLCAyXQogICAgYmlucyA8LSBzZXEobWluKHgpLCBtYXgoeCksIGxlbmd0aC5vdXQgPSBpbnB1dCRiaW5zICsgMSkKICAgIAogICAgIyBkcmF3IHRoZSBoaXN0b2dyYW0gd2l0aCB0aGUgc3BlY2lmaWVkIG51bWJlciBvZiBiaW5zCiAgICBoaXN0KHgsIGJyZWFrcyA9IGJpbnMsIGNvbCA9ICdkYXJrZ3JheScsIGJvcmRlciA9ICd3aGl0ZScpCiAgfSkKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCldlIGFyZSBkb25lISBMZXQncyBtb3ZlIG9uIHRvIGEgbW9yZSBjb21wbGV4IGFwcC4KCiMjIGdhcG1pbmRlcgoKT3VyIHNlY29uZCBzaGlueSBhcHAgaXMgaW5zcGlyZWQgb24gdGhlIGV4YW1wbGUgcHJvdmlkZWQgYnkgaHR0cHM6Ly9ha3NoaTguc2hpbnlhcHBzLmlvL0dhcG1pbmRlci8KCkluIHRoaXMgY2FzZSwgd2Ugd2lsbCBiZSBleHBsb3JpbmcgdGhlIHBvcHVsYXRpb24sIGxpZmUtZXhwZWN0YW5jeSBhbmQgR0RQIHBlciBjYXBpdGEgZm9yIGRpZmZlcmVudCBjb3VudHJpZXMgb3ZlciB0aGUgeWVhcnMuIFVzZXJzIGNhbiBzZWxlY3QgdGhlIGluZGljYXRvciB0byBjb21wYXJlLCBjaG9vc2Ugc3BlY2lmaWMgY291bnRyaWVzLCBhbmQgc3BlY2lmeSB0aGUgeWVhcnMgdGhleSB3aXNoIHRvIGV4YW1pbmUuCgpXZSB3aWxsIGluY2x1ZGUgcGxvdHMgYW5kIGEgdGFibGUsIGFuZCB3ZSB3aWxsIGFsc28gb3JnYW5pemUgdGhlIGluZm9ybWF0aW9uIGluIG91ciBhcHAgdXNpbmcgdGFicy4KCkxldCdzIHN0YXJ0IGJ5IGJ1aWxkaW5nIHRoZSBiYXNpYyBzdHJ1Y3R1cmUgb2Ygb3VyIGFwcDoKCmBgYHtyLGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZ2FwbWluZGVyKQpsaWJyYXJ5KGNvdW50cnljb2RlKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoRFQpCgojb3VyIGRhdGFzZXQsIGltcG9ydGVkIGZyb20gdGhlIGdhcG1pbmRlciBwYWNrYWdlOgoKZ2FwbWluZGVyIDwtIGdhcG1pbmRlcgoKI3NvbWUgcHJlcHJvY2Vzc2luZzogYXMgS3V3dWFpdCB3aWxsIGJyZWFrIHRoZSBzY2FsZSBmb3Igc29tZSB5ZWFycywgbGV0J3MgcmVtb3ZlIGl0IGZyb20gdGhlIGRhdGFiYXNlCgpnYXBtaW5kZXIgPC0gZ2FwbWluZGVyICU+JSAKICBmaWx0ZXIoY291bnRyeSE9J0t1d2FpdCcpCgojIyMjIG91ciB1aQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIkdhcG1pbmRlciBEYXRhIiksCiAgdGFic2V0UGFuZWwoCiAgICB0YWJQYW5lbCh0aXRsZSA9ICd0aW1lIHNlcmllcycsCiAgICAgICAgICAgICAjYSBsYXlvdXQgb3B0aW9uOiBzaWRlYmFyIGFuZCBtYWluIHBhbmVsCiAgICAgICAgICAgICBzaWRlYmFyTGF5b3V0KAogICAgICAgICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgIG1haW5QYW5lbCgKICAgICAgICAgICAgICAgICB0YWJzZXRQYW5lbCh0eXBlID0gInRhYnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJQbG90IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJUYWJsZSIpCiAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICApCiAgICAgICAgICAgICApCiAgICApLAogICAgdGFiUGFuZWwodGl0bGUgPSAnZGlzdHJpYnV0aW9uIGJ5IHllYXInLAogICAgICAgICAgICAgI2EgZGlmZmVyZW50IG9wdGlvbjogdXNpbmcgYSB3ZWxsIHBhbmVsCiAgICAgICAgICAgICB3ZWxsUGFuZWwoCiAgICAgICAgICAgICAgIAogICAgICAgICAgICAgKQogICAgKQogICkKKQoKCiMjIyMgU2VydmVyCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIAp9CgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAoKWW91IHNob3VsZCBiZSBhYmxlIHRvIHJ1biB0aGUgYXBwIGFuZCB2aWV3IHRoZSBhcHAncyB0aXRsZSwgYWxvbmcgd2l0aCB0aGUgdHdvIHByaW1hcnkgdGFicyBhbmQgdGhlIHR3byBzdWItdGFicyBuZXN0ZWQgd2l0aGluIHRoZSBmaXJzdCBtYWluIHRhYi4KCldlIGNhbiBub3cgcHJvY2VlZCB0byBkZXNpZ24gb3VyICoqdWkqKiBieSBkZWZpbmluZyBvdXIgaW5wdXQgYW5kIG91dHB1dCwgYXMgd2VsbCBhcyBzdWJ0aXRsZXMgZXhwbGFpbmluZyBtb3JlIGNsZWFybHkgdGhlIGNvbnRlbnQgb2YgZWFjaCB0YWIgKHVzaW5nIGBoMygpYCkuIEluIHRoZSBmaXJzdCBtYWluIHRhYiwgb3VyIGFwcCB3aWxsIGRpc3BsYXkgYW4gb3B0aW9uIHRvIGNob29zZSB0aGUgY291bnRyeSAobm90ZSB0aGF0IGluIHRoaXMgY2FzZSB0aGUgdXNlciBjYW4gc2VsZWN0IG11bHRpcGxlIG9wdGlvbnM7IHRoaXMgYmVjb21lcyBleHBsaWNpdGx5IGNsZWFyIHdoZW4gZGVmaW5pbmcgYG11bHRpcGxlID0gVFJVRWApIGFuZCB0aGUgdmFyaWFibGUgdG8gZGlzcGxheSAoaW4gdGhpcyBjYXNlIGEgc2luZ2xlIG9wdGlvbjsgdGhpcyBiZWNvbWVzIGV4cGxpY2l0bHkgY2xlYXIgd2hlbiBzdGF0aW5nIGBtdWx0aXBsZSA9IEZBTFNFYCkuIAoKT24gdGhlIG90aGVyIGhhbmQsIGluIG91ciBzZWNvbmQgdGFiIHRoZSB1c2VyIHdpbGwgaGF2ZSB0aGUgb3B0aW9uIG9mIHNlbGVjdGluZyB0aGUgeWVhciBvZiB0aGUgZGF0YS4gQnkgc2V0dGluZyBgYW5pbWF0ZSA9IFRSVUVgLCAgd2UgcHJvdmlkZSB1c2VycyB3aXRoIHRoZSBvcHRpb24gdG8gdmlldyBhbiBhbmltYXRpb24gdGhhdCBhdXRvbWF0aWNhbGx5IGN5Y2xlcyB0aHJvdWdoIGFsbCBhdmFpbGFibGUgeWVhcnMgaW4gc3VjY2Vzc2lvbiBieSBwcmVzc2luZyBvbiB0aGUgcGxheSBidXR0b24gYXQgdGhlIGJvdHRvbSByaWdodCBjb3JuZXIgb2YgdGhlIHdpZGdldC4gCgpXaGlsZSB3ZSB3aWxsIGRldGVybWluZSBvdXIgb3V0cHV0cyAoYW5kIHRoZWlyIGlkcyEpIGF0IHRoaXMgc3RhZ2UsIHdlIHdvbid0IGJlIGFibGUgdG8gdmlzdWFsaXplIHRoZW0gdW50aWwgYWZ0ZXIgZGVzaWduaW5nIHRoZSBhcHAncyBzZXJ2ZXIuCgpgYGB7cixldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KGdhcG1pbmRlcikKbGlicmFyeShjb3VudHJ5Y29kZSkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KERUKQoKZ2FwbWluZGVyIDwtIGdhcG1pbmRlcgoKZ2FwbWluZGVyIDwtIGdhcG1pbmRlciAlPiUgCiAgZmlsdGVyKGNvdW50cnkhPSdLdXdhaXQnKQoKIyMjIyB1aQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIkdhcG1pbmRlciBEYXRhIiksCiAgdGFic2V0UGFuZWwoCiAgICB0YWJQYW5lbCh0aXRsZSA9ICd0aW1lIHNlcmllcycsCiAgICAgICAgICAgICBzaWRlYmFyTGF5b3V0KAogICAgICAgICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICAgICAgICAgc2VsZWN0aXplSW5wdXQoaW5wdXRJZCA9J2NvdW50cmllcycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsPSdDaG9vc2UgdGhlIGNvdW50cmllcycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNob2ljZXMgPSB1bmlxdWUoZ2FwbWluZGVyJGNvdW50cnkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdWx0aXBsZSA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gIlVuaXRlZCBTdGF0ZXMiKSAsICAKICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICBzZWxlY3RpemVJbnB1dChpbnB1dElkID0naW5wdXR2YXJpYWJsZScsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsPSdDaG9vc2UgdGhlIHZhcmlhYmxlIHRvIGRpc3BsYXknLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gIGMoImxpZmVFeHAiLCJwb3AiLCJnZHBQZXJjYXAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdWx0aXBsZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gImxpZmVFeHAiKQogICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICBtYWluUGFuZWwoCiAgICAgICAgICAgICAgICAgdGFic2V0UGFuZWwodHlwZSA9ICJ0YWJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJQYW5lbCgiUGxvdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGgzKCJUaW1lIHNlcmllcyBwbG90IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdGx5T3V0cHV0KCJwbG90XzEiKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJUYWJsZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGgzKCJUaW1lIHNlcmllcyB0YWJsZSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFUYWJsZU91dHB1dCgidGFibGUiKSkKICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICkKICAgICksCiAgICB0YWJQYW5lbCh0aXRsZSA9ICdkaXN0cmlidXRpb24gYnkgeWVhcicsCiAgICAgICAgICAgICB3ZWxsUGFuZWwoCiAgICAgICAgICAgICAgIHNsaWRlcklucHV0KGlucHV0SWQgPSd5cicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD0nQ2hvb3NlIHRoZSB5ZWFyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluID0gbWluKGdhcG1pbmRlciR5ZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KGdhcG1pbmRlciR5ZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBtaW4oZ2FwbWluZGVyJHllYXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGVwID0gNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5pbWF0ZSA9IFRSVUUpCiAgICAgICAgICAgICApLAogICAgICAgICAgICAgaDMoIkRpc3RyaWJ1dGlvbiBieSB5ZWFyIG9mIGxpZmUtZXhwZWN0YW5jeSBhbmQgR0RQIHBlciBjYXBpdGEiKSwKICAgICAgICAgICAgIHBsb3RseU91dHB1dCgicGxvdF8yIikgCiAgICAgICAgICAgICAKICAgICkKICApCikKCgojIyMjIFNlcnZlcgoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICAKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCkxldCdzIG1ha2UgdGhlIGFwcCBhY3R1YWxseSBkbyBpdCdzIGpvYiBieSBkZXNpZ25pbmcgdGhlIHNlcnZlcjoKCmBgYHtyLGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZ2FwbWluZGVyKQpsaWJyYXJ5KGNvdW50cnljb2RlKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoRFQpCgpnYXBtaW5kZXIgPC0gZ2FwbWluZGVyCgpnYXBtaW5kZXIgPC0gZ2FwbWluZGVyICU+JSAKICBmaWx0ZXIoY291bnRyeSE9J0t1d2FpdCcpCgojb25lIG9mIHRoZSBvcHRpb25zIHdoZW4gZGVzaWduaW5nIGEgc2VydmVyIGlzIHRvIHByZWRlZmluZSBmdW5jdGlvbnMgb3VyIHNlcnZlciB3aWxsIGRyYXcgb24gd2hlbiBwcm9kdWNpbmcgb3VyIG91dHB1dHM6CgpwbG90XzEgPC0gZnVuY3Rpb24oY291bnRyaWVzLCB2YXIpewogIAogIHBsdCA8LSBnYXBtaW5kZXIgJT4lCiAgICBwaXZvdF9sb25nZXIobGlmZUV4cDpnZHBQZXJjYXApICU+JSAKICAgIGZpbHRlcihjb3VudHJ5ICVpbiUgY291bnRyaWVzLCBuYW1lPT12YXIpICU+JSAgCiAgICBnZ3Bsb3QoYWVzKHllYXIsIHZhbHVlLCBjb2xvciA9IGNvbnRpbmVudCxncm91cCA9IGNvdW50cnkpKSsgCiAgICBnZW9tX2xpbmUoKSsKICAgIGdlb21fcG9pbnQoKQogIAogIGdncGxvdGx5KHBsdCkKfQoKcGxvdF8yIDwtIGZ1bmN0aW9uKHlyKXsKICAKICBwbHQgPC0gZ2FwbWluZGVyICU+JQogICAgZmlsdGVyKHllYXI9PXlyKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKGdkcFBlcmNhcCwgbGlmZUV4cCwgY29sb3I9Y29udGluZW50LHNpemU9cG9wLCAKICAgICAgICAgICAgICAgdGV4dCA9IHBhc3RlKGNvdW50cnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPkdEUCBwZXIgY2FwaXRhOiAnLG51bWJlcihnZHBQZXJjYXAsIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5Qb3B1bGF0aW9uOiAnLCBudW1iZXIocG9wLzEwMDAwLDEpLCdNJykpKSArCiAgICBnZW9tX3BvaW50KCkrCiAgICB0aGVtZV9taW5pbWFsKCkKICAKICBnZ3Bsb3RseShwbHQsIHRvb2x0aXAgPSAndGV4dCcpCn0KCiMjIyMgdWkKCnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJHYXBtaW5kZXIgRGF0YSIpLAogIHRhYnNldFBhbmVsKAogICAgdGFiUGFuZWwodGl0bGUgPSAndGltZSBzZXJpZXMnLAogICAgICAgICAgICAgc2lkZWJhckxheW91dCgKICAgICAgICAgICAgICAgc2lkZWJhclBhbmVsKAogICAgICAgICAgICAgICAgIHNlbGVjdGl6ZUlucHV0KGlucHV0SWQgPSdjb3VudHJpZXMnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD0nQ2hvb3NlIHRoZSBjb3VudHJpZXMnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gdW5pcXVlKGdhcG1pbmRlciRjb3VudHJ5KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXVsdGlwbGUgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZCA9ICJVbml0ZWQgU3RhdGVzIikgLCAgCiAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgc2VsZWN0aXplSW5wdXQoaW5wdXRJZCA9J2lucHV0dmFyaWFibGUnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD0nQ2hvb3NlIHRoZSB2YXJpYWJsZSB0byBkaXNwbGF5JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9ICBjKCJsaWZlRXhwIiwicG9wIiwiZ2RwUGVyY2FwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXVsdGlwbGUgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZCA9ICJsaWZlRXhwIikKICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgbWFpblBhbmVsKAogICAgICAgICAgICAgICAgIHRhYnNldFBhbmVsKHR5cGUgPSAidGFicyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFiUGFuZWwoIlBsb3QiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoMygiVGltZSBzZXJpZXMgcGxvdCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RseU91dHB1dCgicGxvdF8xIikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJQYW5lbCgiVGFibGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoMygiVGltZSBzZXJpZXMgdGFibGUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhVGFibGVPdXRwdXQoInRhYmxlIikpCiAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICApCiAgICAgICAgICAgICApCiAgICApLAogICAgdGFiUGFuZWwodGl0bGUgPSAnZGlzdHJpYnV0aW9uIGJ5IHllYXInLAogICAgICAgICAgICAgd2VsbFBhbmVsKAogICAgICAgICAgICAgICBzbGlkZXJJbnB1dChpbnB1dElkID0neXInLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWw9J0Nob29zZSB0aGUgeWVhcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbiA9IG1pbihnYXBtaW5kZXIkeWVhciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heCA9IG1heChnYXBtaW5kZXIkeWVhciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gbWluKGdhcG1pbmRlciR5ZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlcCA9IDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuaW1hdGUgPSBUUlVFKQogICAgICAgICAgICAgKSwKICAgICAgICAgICAgIGgzKCJEaXN0cmlidXRpb24gYnkgeWVhciBvZiBsaWZlLWV4cGVjdGFuY3kgYW5kIEdEUCBwZXIgY2FwaXRhIiksCiAgICAgICAgICAgICBwbG90bHlPdXRwdXQoInBsb3RfMiIpIAogICAgICAgICAgICAgCiAgICApCiAgKQopCgoKIyMjIyBTZXJ2ZXIKCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7CiAgIyMgZmlyc3QgcGxvdAogIG91dHB1dCRwbG90XzEgPC0gcmVuZGVyUGxvdGx5KHsgCiAgICBwbG90XzEoY291bnRyaWVzID0gaW5wdXQkY291bnRyaWVzLCB2YXIgPSBpbnB1dCRpbnB1dHZhcmlhYmxlKQogIH0pCiAgCiAgIyMgdGFibGUKICBvdXRwdXQkdGFibGUgPC0gcmVuZGVyRGF0YVRhYmxlKHsKICAgIGdhcG1pbmRlciAlPiUKICAgICAgZmlsdGVyKGNvdW50cnkgJWluJSBpbnB1dCRjb3VudHJpZXMpICU+JSAKICAgICAgc2VsZWN0KGNvdW50cnksY29udGluZW50LCB5ZWFyLCBpbnB1dCRpbnB1dHZhcmlhYmxlICkKICB9LCAKICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLAogIG9wdGlvbnMgPSBsaXN0KAogICAgcGFnaW5nID0gVFJVRSwKICAgIHNlYXJjaGluZyA9IFRSVUUsCiAgICBmaXhlZENvbHVtbnMgPSBUUlVFLAogICAgYXV0b1dpZHRoID0gVFJVRSwKICAgIG9yZGVyaW5nID0gVFJVRSwKICAgIGRvbSA9ICd0QicsCiAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnKQogICksCiAgY2xhc3MgPSAiZGlzcGxheSIpCiAgCiAgIyMgc2Vjb25kIHBsb3QKICBvdXRwdXQkcGxvdF8yIDwtIHJlbmRlclBsb3RseSh7IHBsb3RfMih5ciA9IGlucHV0JHlyKX0pCn0KCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCgoKTm90ZSB0aGF0IHRoZXJlIHNob3VsZCBiZSBhIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gcmVuZGVyIGZ1bmN0aW9ucyBhbmQgdGhlIHR5cGUgb2Ygb3V0cHV0IChgcGxvdGx5T3V0cHV0YCBhbmQgYHJlbmRlclBsb3RseWAsIGBkYXRhVGFibGVPdXRwdXRgIGFuZCBgcmVuZGVyRGF0YVRhYmxlYCkuIEVhY2ggb3V0cHV0IHdpbGwgYmUgZGVmaW5lZCB1c2luZyBpdHMgdW5pcXVlIElELCBhbmQgYSByZW5kZXIgZnVuY3Rpb24gd2lsbCBiZSBhcHBsaWVkLCBpbnZva2luZyB0aGUgcmVsZXZhbnQgaW5wdXRzIChvZiBjb3Vyc2UsIGFsc28gdGhyb3VnaCB0aGVpciBpZHMpLgoKTm90ZSB0aGF0IGBwbG90bHlgIHZpc3VhbGl6YXRpb25zIG9mZmVyIHVzZXJzIGV2ZW4gbW9yZSBmcmVlZG9tIHRvIGV4cGxvcmUgb3VyIGRhdGEgYXMgdGhleSBjb21lIGVxdWlwcGVkIHdpdGggYSB3aWRlIHJhbmdlIG9mIGZ1bmN0aW9uYWxpdGllcy4gVGhlc2UgaW5jbHVkZSB0aGUgYWJpbGl0eSB0byB6b29tIGluIGFuZCBleGFtaW5lIHNwZWNpZmljIHBvcnRpb25zIG9mIHRoZSB2aXN1YWxpemF0aW9uLCBvciBmaWx0ZXIgYSBjb250aW5lbnQgb2YgaW50ZXJlc3QgYnkgc2ltcGx5IGNsaWNraW5nIG9uIHRoZSBsZWdlbmRzLgoKTW9yZW92ZXIsIHdpdGhpbiBgcmVuZGVyRGF0YVRhYmxlYCwgd2UndmUgaW5jb3Jwb3JhdGVkIGFkZGl0aW9uYWwgYXJndW1lbnRzIHRoYXQgZW5hYmxlIHVzZXJzIHRvIGNvcHkgdGhlIHRhYmxlIGluZm9ybWF0aW9uIG9yIGV4cG9ydCBpdCBpbiBDU1Ygb3IgRXhjZWwgZm9ybWF0cy4gVGhpcyBhbGxvd3MgdXNlcnMgdG8gdXRpbGl6ZSB0aGUgZGF0YSBmb3IgY3JlYXRpbmcgdGhlaXIgb3duIHZpc3VhbGl6YXRpb25zLgoKRmVlbCBmcmVlIHRvIGFkZCBuZXcgZWxlbWVudHMgb3IgaW1wcm92ZSB0aG9zZSBhbHJlYWR5IGluIHRoZSBhcHAgdG8gcHJhY3RpY2Uh