In this notebook, we will do some exploratory data analysis and visualization on player of the weeks in the NBA. Player of the week awards are awarded to two individual players on a weekly basis. They are awared to the top performer in the Western and Eastern conferences. This dataset can be found on Kaggle. First, let’s get our libraries.

library(dplyr)
library(ggplot2)
library(sqldf)

Next, we will read in our data and take a peak at the first 6 rows.

df = read.csv("NBA_player_of_the_week.csv")
head(df)

Just by quick glance, we see that Ben Simmons and Kawhi Leonard were the last ones to win this award (on January 20, 2020). Next, let’s see a list of all the column names that are available in the dataset.

names(df)
 [1] "Player"            "Team"              "Conference"        "Date"             
 [5] "Position"          "Height"            "Weight"            "Age"              
 [9] "Draft.Year"        "Seasons.in.league" "Season"            "Season.short"     
[13] "Pre.draft.Team"    "Real_value"        "Height.CM"         "Weight.KG"        
[17] "Last.Season"      

Data Preparation

Before we start with our analysis, we need to first prepare our data for analysis. We will do this by checking for missing data, handling the missing data, editing columns to be easily digestible and potentially making new columns that will be useful in our analysis.

Missing Data

Let’s check how many nulls we have in each column of the dataset. First let’s convert any blank values, " ", to NA’s and then we will check for nulls.

df[df==""] <- NA
colSums(is.na(df))
           Player              Team        Conference              Date          Position 
                0                 0               501                 0                 0 
           Height            Weight               Age        Draft.Year Seasons.in.league 
                0                 0                 0                 0                 0 
           Season      Season.short    Pre.draft.Team        Real_value         Height.CM 
                0                 0                 0                 0                 0 
        Weight.KG       Last.Season 
                0                 0 

It looks like we only have null values in one column of the dataset. Let’s take a closer look at what is causing these null values in the conference column.

subset(df, is.na(df$Conference))

Just by looking at this dataframe, we can see that most of the null values in the conference column are before the April 15th, 2001 date. Most of these teams are current NBA teams so mapping the correct conference should not be too difficult. Let’s search our datafarame and store the teams into factors that represent their respective conference.

east_teams <- (df %>% filter(Conference =='East') %>% distinct(Team))$Team
west_teams <- (df %>% filter(Conference =='West') %>% distinct(Team))$Team

Now that we have our teams in each conference, let’s map in the missing conferences using the two different lists.

# First let's create a lookup data frame
east_df <- data.frame(matrix(unlist(east_teams), nrow=19, byrow=T),stringsAsFactors=FALSE)
east_df$New_Conference <- "Eastern"
colnames(east_df)[1] <- "Team"

west_df <- data.frame(matrix(unlist(west_teams), nrow=17, byrow=T),stringsAsFactors=FALSE)
west_df$New_Conference <- "Western"
colnames(west_df)[1] <- "Team"

east_west_df <- rbind(east_df, west_df)

# Now let's pull in the updated conference
df <- merge.data.frame(df, east_west_df, by.x = "Team", by.y = "Team")

Let’s check how many nulls we have now using our new conference column.

subset(df, is.na(df$New_Conference))

No nulls! Now that we have eliminated nulls from this column, we can move on. ### Re-mapping Columns Later in this analysis, we are going to take a look at positions. Before we get into the analysis, it is important to make sure this column is ready for analysis. Let’s take a look at the different positions that are in our dataset and make adjustments as needed.

unique(df['Position'])

Now that we see all the unique positions, let’s map this into a simpler position system. For example, we are going to group G-F into GF and F-C into FC. We are also going to roll up guards into the guard bucket and forwards to the forward bucket.

og_pos <- list("PG","SG","F","C" ,"SF","PF","G","FC","GF","F-C","G-F")
new_pos <- list("Guard", "Guard", "Forward", "Center", "Forward", "Forward", "Guard", "Forward-Center", "Guard-Forward", "Forward-Center", "Guard-Forward")

# Create Lookup DataFrame
pos_mapping <- do.call(rbind, Map(data.frame, Position=og_pos, New_Position=new_pos))
head(pos_mapping, 10)

Now that we have our lookup table of the new position mapping, we are going to join this with our original data frame. We will do this using a package called sqldf that will allow us to use some sql syntax to do merging in R.

# Perform Inner Join
df <- sqldf("select *
             from df
             join pos_mapping using(Position)")
head(df)

Exploratory Data Analysis

Since we spent some time cleaning the Conference column, let’s take a look at the distribution of the award by conference and average age.

Average Age of Player of the Week by Conference

First let’s group our dataframe by Conference, Year and Average Age.

avg_age <- df %>%
  group_by(New_Conference, Season.short) %>%
  summarise(avg_age=mean(Age))

head(avg_age)

Now that we have our data grouped, let’s plot this so we can compare the trend of average age. Since we want to do a population pyramid type chart, we need to turn all of the values in one conference to negative values.

# Transform Dataframe to turn West values into negative
avg_age <- transform(avg_age, avg_age_2=ifelse(New_Conference=="Western", avg_age*-1, avg_age))

ggplot(avg_age, aes(x = Season.short, y = avg_age_2, fill = New_Conference)) +   # Fill column
                              geom_bar(stat = "identity", width = .6) +   # draw the bars
                              scale_x_continuous(breaks = seq(1985,2020,by=5)) +
                              scale_y_continuous(breaks=seq(-35,35,5),labels=abs) +
                              coord_flip()

It looks like the only time average age exceeded 30 was in 1997 in the Western Conference. Additionally, the average age for a majority of the seasons on both the West and East was between 25 and 30. It looks like we have team data available - this might be interesting to see which teams have had the most amount of players win this award.

Player of the Week Counts by Team

First, let’s see a dataframe view of the top 5 teams.

by_team <- df %>% count(Team) %>% # Group by Team using dplyr
           arrange(desc(n))
by_team <- rename(by_team, 'Number of Players' = n) # Rename column
head(by_team, 5)  # Show Top 5

It looks like the Lakers have the most players to have won a Player of the Week award. It may be interesting to see which players from the Lakers compose that sum and see if the gap between them and the rest of the team can be attributed to one or two players. Before we move forward, let’s turn this dataframe into a bar chart.

by_team_10 <- head(by_team, 5)
by_team_10_lst <- by_team_10[['Team']]


by_team_plt <- ggplot(filter(df, Team %in% by_team_10_lst), aes(Team))
by_team_plt + geom_bar() +
  geom_text(stat='count',aes(label=..count..),vjust=-.5)

Player of the Week Counts - Los Angeles Lakers

laker_total = 87

lakers = df %>% select(everything()) %>% filter(Team == "Los Angeles Lakers")
lakers <- lakers %>% count(Player) %>% # Group by Player using dplyr
           arrange(desc(n))
lakers <- rename(lakers, 'Number_of_POTW_Awards' = n) # Rename column
lakers <- transform(lakers, Percent_of_Total = Number_of_POTW_Awards / laker_total)
lakers[, "Percent_of_Total"] <- round(lakers[, "Percent_of_Total"], digits = 2) # round to 2 decimals
head(lakers, 5)

Here we can see that Kobe Bryant makes up for most of the Lakers Player of the Week awards, with about 38% of the total being attributed to him. This makes sense since Kobe Bryant played 20 seasons with the Los Angeles Lakers. Let’s graph this, but using a horizontal bar chart instead.

lakers_5 <- head(lakers, 5)
lakers_5_lst <- lakers_5[['Player']]
  
lakers_plt <- ggplot(filter(df, Player %in% lakers_5_lst, Team == "Los Angeles Lakers"), aes(Player))
lakers_plt + geom_bar() + coord_flip() +
  geom_text(stat='count',aes(label=..count..),hjust=-.5)

Now that we have plotted some bar charts for counts, let’s take a look at the trend of player of the week awards by position over time.

Player of the Week Trend by Position

Before we jump into this analysis, let’s take a look at overall awards won by position.

by_position <- df %>% count(New_Position) %>% # Group by Position using dplyr
           arrange(desc(n))
by_position <- rename(by_position, 'Number of Players' = n) # Rename column
head(by_position, 10)  # Show Top 10

It looks like Guards and Forwards make up a majority of this award. This makes sense since we are rolling Point Guards/Shooting Guards to the guard position and Small Forwards/Power Forwards to the Forward Position. Let’s use the sql library to do a basic group by.

by_position_year <- sqldf("select New_Position, [Season.short], count(Player) as NumberAwards
             from df
             group by New_Position, [Season.short]
             order by [Season.short] asc, NumberAwards desc")

head(by_position_year)

Now let’s plot this trend over time with a line chart!

ggplot(by_position_year, aes(x=Season.short, y=NumberAwards, col=New_Position)) + geom_line()

Here we can see that the guard position is actually trending down with this award and reached it’s peak in the mid 2010’s. This is a surprising finding with so many good guards in the NBA currently. In their place, we are seeing a gradual increase in the amount of awards that Forwards are winning.

Impact of Height & Weight

Now that we have looked at all teams, a specific team and done some trend analysis on positions - the last piece of our analysis will be taking a look at the impact of height and weight on winning this award. First, we need to query our dataframe to get a list of players, their height/weight and also the amount of times they have won the award.

player_height_weight = sqldf("select Player, [Height.CM], Weight, New_Position, count(Player) as NumAwards
                              from df
                              group by Player, [Height.CM], Weight, New_Position
                              order by NumAwards desc")

head(player_height_weight)

Here we can see that LeBron James has won the most awards from any other player. Almost doubling Kobe Bryant’s number. Allen Iverson is also another interesting player to appear on the top of this list and is also the shortest and lightest player in the top 6. Another interesting observation is that there is no Center that appears in the top 6 list. Now that we have this data frame prepared, let’s try to get some correlation insight by plotting.

ggplot(player_height_weight, aes(x=Weight, y=Height.CM)) + 
  geom_point(aes(col=New_Position, size=NumAwards)) + 
  geom_smooth(method="loess", se=F) +
  geom_text(data=head(player_height_weight),
            aes(Weight,Height.CM,label=Player)) +
  labs(subtitle="By Height & Weight", 
       y="Height (cm)", 
       x="Weight (lb)", 
       title="Player of the Week Awards Distribution")

It does look like there is an upward trend with the amount of player of the week awards a player wins and an increased height/weight. However this trend is not as drastic and begins to taper off around the 230 pound and 205 cm coordinate. Allen Iverson is definitely an anomaly with the amount of player of the week awards he has won, considering his height and weight. However, many NBA players proclaim that Iverson is “pound for pound” one of the best to ever play the game - and this could make that argument stronger. The most interesting observation is the overlap between Michael Jordan and Kobe Bryant. These two players are always packaged together in basketball conversations because of their similarity in play style. It is interesting to see that their Height/Weight and Player of the Week awards distribution is almost identical.

Summary

In this notebook, we were able to do some Exploratory Data Analysis and Visualization on NBA Player of the Week statistics. We were able to find that players from the Los Angeles Lakers have won the award the most. From this subset, Kobe Bryant has won the award the most out of any Laker player. However, Kobe is not the player that has received this award the most - this accolade goes to LeBron James.

Additionally, we performed trend analysis by position and found that guards are trending down when it comes to this award. Conversely, we are seeing a trend upwards for forwards with players like Giannis Antetokounmpo, Kawhi Leonard, and LeBron James in the league. Lastly, we were able to visualize a scatterplot with density measures based on height and weight. We saw that there is a positive relationship between number of awards and height/weight, but the intensity of this relationship begins to flatten at a certain point.

LS0tCnRpdGxlOiAiTkJBIFBsYXllciBvZiB0aGUgV2VlayAtIEVEQSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQotLS0KSW4gdGhpcyBub3RlYm9vaywgd2Ugd2lsbCBkbyBzb21lIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgYW5kIHZpc3VhbGl6YXRpb24gb24gcGxheWVyIG9mIHRoZSB3ZWVrcyBpbiB0aGUgTkJBLiBQbGF5ZXIgb2YgdGhlIHdlZWsgYXdhcmRzIGFyZSBhd2FyZGVkIHRvIHR3byBpbmRpdmlkdWFsIHBsYXllcnMgb24gYSB3ZWVrbHkgYmFzaXMuIFRoZXkgYXJlIGF3YXJlZCB0byB0aGUgdG9wIHBlcmZvcm1lciBpbiB0aGUgV2VzdGVybiBhbmQgRWFzdGVybiBjb25mZXJlbmNlcy4gVGhpcyBkYXRhc2V0IGNhbiBiZSBmb3VuZCBvbiBbS2FnZ2xlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2phY29iYmFydWNoL25iYS1wbGF5ZXItb2YtdGhlLXdlZWspLiBGaXJzdCwgbGV0J3MgZ2V0IG91ciBsaWJyYXJpZXMuCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzcWxkZikKYGBgCgpOZXh0LCB3ZSB3aWxsIHJlYWQgaW4gb3VyIGRhdGEgYW5kIHRha2UgYSBwZWFrIGF0IHRoZSBmaXJzdCA2IHJvd3MuCmBgYHtyIFJlYWQgaW4gRGF0YX0KZGYgPSByZWFkLmNzdigiTkJBX3BsYXllcl9vZl90aGVfd2Vlay5jc3YiKQpoZWFkKGRmKQpgYGAKCkp1c3QgYnkgcXVpY2sgZ2xhbmNlLCB3ZSBzZWUgdGhhdCBCZW4gU2ltbW9ucyBhbmQgS2F3aGkgTGVvbmFyZCB3ZXJlIHRoZSBsYXN0IG9uZXMgdG8gd2luIHRoaXMgYXdhcmQgKG9uIEphbnVhcnkgMjAsIDIwMjApLiBOZXh0LCBsZXQncyBzZWUgYSBsaXN0IG9mIGFsbCB0aGUgY29sdW1uIG5hbWVzIHRoYXQgYXJlIGF2YWlsYWJsZSBpbiB0aGUgZGF0YXNldC4KYGBge3IgU2hvdyBBbGwgQ29sdW1uIE5hbWVzfQpuYW1lcyhkZikKYGBgCiMjIERhdGEgUHJlcGFyYXRpb24KCkJlZm9yZSB3ZSBzdGFydCB3aXRoIG91ciBhbmFseXNpcywgd2UgbmVlZCB0byBmaXJzdCBwcmVwYXJlIG91ciBkYXRhIGZvciBhbmFseXNpcy4gV2Ugd2lsbCBkbyB0aGlzIGJ5IGNoZWNraW5nIGZvciBtaXNzaW5nIGRhdGEsIGhhbmRsaW5nIHRoZSBtaXNzaW5nIGRhdGEsIGVkaXRpbmcgY29sdW1ucyB0byBiZSBlYXNpbHkgZGlnZXN0aWJsZSBhbmQgcG90ZW50aWFsbHkgbWFraW5nIG5ldyBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VmdWwgaW4gb3VyIGFuYWx5c2lzLgoKIyMjIE1pc3NpbmcgRGF0YQpMZXQncyBjaGVjayBob3cgbWFueSBudWxscyB3ZSBoYXZlIGluIGVhY2ggY29sdW1uIG9mIHRoZSBkYXRhc2V0LiBGaXJzdCBsZXQncyBjb252ZXJ0IGFueSBibGFuayB2YWx1ZXMsICIgIiwgdG8gTkEncyBhbmQgdGhlbiB3ZSB3aWxsIGNoZWNrIGZvciBudWxscy4KCmBgYHtyfQpkZltkZj09IiJdIDwtIE5BCmNvbFN1bXMoaXMubmEoZGYpKQpgYGAKSXQgbG9va3MgbGlrZSB3ZSBvbmx5IGhhdmUgbnVsbCB2YWx1ZXMgaW4gb25lIGNvbHVtbiBvZiB0aGUgZGF0YXNldC4gTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IHdoYXQgaXMgY2F1c2luZyB0aGVzZSBudWxsIHZhbHVlcyBpbiB0aGUgY29uZmVyZW5jZSBjb2x1bW4uCmBgYHtyfQpzdWJzZXQoZGYsIGlzLm5hKGRmJENvbmZlcmVuY2UpKQpgYGAKSnVzdCBieSBsb29raW5nIGF0IHRoaXMgZGF0YWZyYW1lLCB3ZSBjYW4gc2VlIHRoYXQgbW9zdCBvZiB0aGUgbnVsbCB2YWx1ZXMgaW4gdGhlIGNvbmZlcmVuY2UgY29sdW1uIGFyZSBiZWZvcmUgdGhlIEFwcmlsIDE1dGgsIDIwMDEgZGF0ZS4gTW9zdCBvZiB0aGVzZSB0ZWFtcyBhcmUgY3VycmVudCBOQkEgdGVhbXMgc28gbWFwcGluZyB0aGUgY29ycmVjdCBjb25mZXJlbmNlIHNob3VsZCBub3QgYmUgdG9vIGRpZmZpY3VsdC4gTGV0J3Mgc2VhcmNoIG91ciBkYXRhZmFyYW1lIGFuZCBzdG9yZSB0aGUgdGVhbXMgaW50byBmYWN0b3JzIHRoYXQgcmVwcmVzZW50IHRoZWlyIHJlc3BlY3RpdmUgY29uZmVyZW5jZS4KYGBge3J9CmVhc3RfdGVhbXMgPC0gKGRmICU+JSBmaWx0ZXIoQ29uZmVyZW5jZSA9PSdFYXN0JykgJT4lIGRpc3RpbmN0KFRlYW0pKSRUZWFtCndlc3RfdGVhbXMgPC0gKGRmICU+JSBmaWx0ZXIoQ29uZmVyZW5jZSA9PSdXZXN0JykgJT4lIGRpc3RpbmN0KFRlYW0pKSRUZWFtCmBgYAoKTm93IHRoYXQgd2UgaGF2ZSBvdXIgdGVhbXMgaW4gZWFjaCBjb25mZXJlbmNlLCBsZXQncyBtYXAgaW4gdGhlIG1pc3NpbmcgY29uZmVyZW5jZXMgdXNpbmcgdGhlIHR3byBkaWZmZXJlbnQgbGlzdHMuCgpgYGB7cn0KIyBGaXJzdCBsZXQncyBjcmVhdGUgYSBsb29rdXAgZGF0YSBmcmFtZQplYXN0X2RmIDwtIGRhdGEuZnJhbWUobWF0cml4KHVubGlzdChlYXN0X3RlYW1zKSwgbnJvdz0xOSwgYnlyb3c9VCksc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKZWFzdF9kZiROZXdfQ29uZmVyZW5jZSA8LSAiRWFzdGVybiIKY29sbmFtZXMoZWFzdF9kZilbMV0gPC0gIlRlYW0iCgp3ZXN0X2RmIDwtIGRhdGEuZnJhbWUobWF0cml4KHVubGlzdCh3ZXN0X3RlYW1zKSwgbnJvdz0xNywgYnlyb3c9VCksc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKd2VzdF9kZiROZXdfQ29uZmVyZW5jZSA8LSAiV2VzdGVybiIKY29sbmFtZXMod2VzdF9kZilbMV0gPC0gIlRlYW0iCgplYXN0X3dlc3RfZGYgPC0gcmJpbmQoZWFzdF9kZiwgd2VzdF9kZikKCiMgTm93IGxldCdzIHB1bGwgaW4gdGhlIHVwZGF0ZWQgY29uZmVyZW5jZQpkZiA8LSBtZXJnZS5kYXRhLmZyYW1lKGRmLCBlYXN0X3dlc3RfZGYsIGJ5LnggPSAiVGVhbSIsIGJ5LnkgPSAiVGVhbSIpCmBgYApMZXQncyBjaGVjayBob3cgbWFueSBudWxscyB3ZSBoYXZlIG5vdyB1c2luZyBvdXIgbmV3IGNvbmZlcmVuY2UgY29sdW1uLgpgYGB7cn0Kc3Vic2V0KGRmLCBpcy5uYShkZiROZXdfQ29uZmVyZW5jZSkpCmBgYAoKTm8gbnVsbHMhIE5vdyB0aGF0IHdlIGhhdmUgZWxpbWluYXRlZCBudWxscyBmcm9tIHRoaXMgY29sdW1uLCB3ZSBjYW4gbW92ZSBvbi4gCiMjIyBSZS1tYXBwaW5nIENvbHVtbnMKTGF0ZXIgaW4gdGhpcyBhbmFseXNpcywgd2UgYXJlIGdvaW5nIHRvIHRha2UgYSBsb29rIGF0IHBvc2l0aW9ucy4gQmVmb3JlIHdlIGdldCBpbnRvIHRoZSBhbmFseXNpcywgaXQgaXMgaW1wb3J0YW50IHRvIG1ha2Ugc3VyZSB0aGlzIGNvbHVtbiBpcyByZWFkeSBmb3IgYW5hbHlzaXMuIExldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkaWZmZXJlbnQgcG9zaXRpb25zIHRoYXQgYXJlIGluIG91ciBkYXRhc2V0IGFuZCBtYWtlIGFkanVzdG1lbnRzIGFzIG5lZWRlZC4KCmBgYHtyfQp1bmlxdWUoZGZbJ1Bvc2l0aW9uJ10pCmBgYApOb3cgdGhhdCB3ZSBzZWUgYWxsIHRoZSB1bmlxdWUgcG9zaXRpb25zLCBsZXQncyBtYXAgdGhpcyBpbnRvIGEgc2ltcGxlciBwb3NpdGlvbiBzeXN0ZW0uIEZvciBleGFtcGxlLCB3ZSBhcmUgZ29pbmcgdG8gZ3JvdXAgRy1GIGludG8gR0YgYW5kIEYtQyBpbnRvIEZDLiBXZSBhcmUgYWxzbyBnb2luZyB0byByb2xsIHVwIGd1YXJkcyBpbnRvIHRoZSBndWFyZCBidWNrZXQgYW5kIGZvcndhcmRzIHRvIHRoZSBmb3J3YXJkIGJ1Y2tldC4KCmBgYHtyfQpvZ19wb3MgPC0gbGlzdCgiUEciLCJTRyIsIkYiLCJDIiAsIlNGIiwiUEYiLCJHIiwiRkMiLCJHRiIsIkYtQyIsIkctRiIpCm5ld19wb3MgPC0gbGlzdCgiR3VhcmQiLCAiR3VhcmQiLCAiRm9yd2FyZCIsICJDZW50ZXIiLCAiRm9yd2FyZCIsICJGb3J3YXJkIiwgIkd1YXJkIiwgIkZvcndhcmQtQ2VudGVyIiwgIkd1YXJkLUZvcndhcmQiLCAiRm9yd2FyZC1DZW50ZXIiLCAiR3VhcmQtRm9yd2FyZCIpCgojIENyZWF0ZSBMb29rdXAgRGF0YUZyYW1lCnBvc19tYXBwaW5nIDwtIGRvLmNhbGwocmJpbmQsIE1hcChkYXRhLmZyYW1lLCBQb3NpdGlvbj1vZ19wb3MsIE5ld19Qb3NpdGlvbj1uZXdfcG9zKSkKaGVhZChwb3NfbWFwcGluZywgMTApCmBgYAoKTm93IHRoYXQgd2UgaGF2ZSBvdXIgbG9va3VwIHRhYmxlIG9mIHRoZSBuZXcgcG9zaXRpb24gbWFwcGluZywgd2UgYXJlIGdvaW5nIHRvIGpvaW4gdGhpcyB3aXRoIG91ciBvcmlnaW5hbCBkYXRhIGZyYW1lLiBXZSB3aWxsIGRvIHRoaXMgdXNpbmcgYSBwYWNrYWdlIGNhbGxlZCBzcWxkZiB0aGF0IHdpbGwgYWxsb3cgdXMgdG8gdXNlIHNvbWUgc3FsIHN5bnRheCB0byBkbyBtZXJnaW5nIGluIFIuCgpgYGB7cn0KIyBQZXJmb3JtIElubmVyIEpvaW4KZGYgPC0gc3FsZGYoInNlbGVjdCAqCiAgICAgICAgICAgICBmcm9tIGRmCiAgICAgICAgICAgICBqb2luIHBvc19tYXBwaW5nIHVzaW5nKFBvc2l0aW9uKSIpCmhlYWQoZGYpCmBgYAoKCiMjIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMKU2luY2Ugd2Ugc3BlbnQgc29tZSB0aW1lIGNsZWFuaW5nIHRoZSBDb25mZXJlbmNlIGNvbHVtbiwgbGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgYXdhcmQgYnkgY29uZmVyZW5jZSBhbmQgYXZlcmFnZSBhZ2UuCgojIyMgQXZlcmFnZSBBZ2Ugb2YgUGxheWVyIG9mIHRoZSBXZWVrIGJ5IENvbmZlcmVuY2UKRmlyc3QgbGV0J3MgZ3JvdXAgb3VyIGRhdGFmcmFtZSBieSBDb25mZXJlbmNlLCBZZWFyIGFuZCBBdmVyYWdlIEFnZS4KYGBge3J9CmF2Z19hZ2UgPC0gZGYgJT4lCiAgZ3JvdXBfYnkoTmV3X0NvbmZlcmVuY2UsIFNlYXNvbi5zaG9ydCkgJT4lCiAgc3VtbWFyaXNlKGF2Z19hZ2U9bWVhbihBZ2UpKQoKaGVhZChhdmdfYWdlKQpgYGAKTm93IHRoYXQgd2UgaGF2ZSBvdXIgZGF0YSBncm91cGVkLCBsZXQncyBwbG90IHRoaXMgc28gd2UgY2FuIGNvbXBhcmUgdGhlIHRyZW5kIG9mIGF2ZXJhZ2UgYWdlLiBTaW5jZSB3ZSB3YW50IHRvIGRvIGEgcG9wdWxhdGlvbiBweXJhbWlkIHR5cGUgY2hhcnQsIHdlIG5lZWQgdG8gdHVybiBhbGwgb2YgdGhlIHZhbHVlcyBpbiBvbmUgY29uZmVyZW5jZSB0byBuZWdhdGl2ZSB2YWx1ZXMuCmBgYHtyfQojIFRyYW5zZm9ybSBEYXRhZnJhbWUgdG8gdHVybiBXZXN0IHZhbHVlcyBpbnRvIG5lZ2F0aXZlCmF2Z19hZ2UgPC0gdHJhbnNmb3JtKGF2Z19hZ2UsIGF2Z19hZ2VfMj1pZmVsc2UoTmV3X0NvbmZlcmVuY2U9PSJXZXN0ZXJuIiwgYXZnX2FnZSotMSwgYXZnX2FnZSkpCgpnZ3Bsb3QoYXZnX2FnZSwgYWVzKHggPSBTZWFzb24uc2hvcnQsIHkgPSBhdmdfYWdlXzIsIGZpbGwgPSBOZXdfQ29uZmVyZW5jZSkpICsgICAjIEZpbGwgY29sdW1uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IC42KSArICAgIyBkcmF3IHRoZSBiYXJzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTk4NSwyMDIwLGJ5PTUpKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKC0zNSwzNSw1KSxsYWJlbHM9YWJzKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvb3JkX2ZsaXAoKQpgYGAKCkl0IGxvb2tzIGxpa2UgdGhlIG9ubHkgdGltZSBhdmVyYWdlIGFnZSBleGNlZWRlZCAzMCB3YXMgaW4gMTk5NyBpbiB0aGUgV2VzdGVybiBDb25mZXJlbmNlLiBBZGRpdGlvbmFsbHksIHRoZSBhdmVyYWdlIGFnZSBmb3IgYSBtYWpvcml0eSBvZiB0aGUgc2Vhc29ucyBvbiBib3RoIHRoZSBXZXN0IGFuZCBFYXN0IHdhcyBiZXR3ZWVuIDI1IGFuZCAzMC4gSXQgbG9va3MgbGlrZSB3ZSBoYXZlIHRlYW0gZGF0YSBhdmFpbGFibGUgLSB0aGlzIG1pZ2h0IGJlIGludGVyZXN0aW5nIHRvIHNlZSB3aGljaCB0ZWFtcyBoYXZlIGhhZCB0aGUgbW9zdCBhbW91bnQgb2YgcGxheWVycyB3aW4gdGhpcyBhd2FyZC4KCiMjIyBQbGF5ZXIgb2YgdGhlIFdlZWsgQ291bnRzIGJ5IFRlYW0KRmlyc3QsIGxldCdzIHNlZSBhIGRhdGFmcmFtZSB2aWV3IG9mIHRoZSB0b3AgNSB0ZWFtcy4KYGBge3IgU2hvdyBQbGF5ZXJzIGJ5IFRlYW19CmJ5X3RlYW0gPC0gZGYgJT4lIGNvdW50KFRlYW0pICU+JSAjIEdyb3VwIGJ5IFRlYW0gdXNpbmcgZHBseXIKICAgICAgICAgICBhcnJhbmdlKGRlc2MobikpCmJ5X3RlYW0gPC0gcmVuYW1lKGJ5X3RlYW0sICdOdW1iZXIgb2YgUGxheWVycycgPSBuKSAjIFJlbmFtZSBjb2x1bW4KaGVhZChieV90ZWFtLCA1KSAgIyBTaG93IFRvcCA1CmBgYApJdCBsb29rcyBsaWtlIHRoZSBMYWtlcnMgaGF2ZSB0aGUgbW9zdCBwbGF5ZXJzIHRvIGhhdmUgd29uIGEgUGxheWVyIG9mIHRoZSBXZWVrIGF3YXJkLiBJdCBtYXkgYmUgaW50ZXJlc3RpbmcgdG8gc2VlIHdoaWNoIHBsYXllcnMgZnJvbSB0aGUgTGFrZXJzIGNvbXBvc2UgdGhhdCBzdW0gYW5kIHNlZSBpZiB0aGUgZ2FwIGJldHdlZW4gdGhlbSBhbmQgdGhlIHJlc3Qgb2YgdGhlIHRlYW0gY2FuIGJlIGF0dHJpYnV0ZWQgdG8gb25lIG9yIHR3byBwbGF5ZXJzLiBCZWZvcmUgd2UgbW92ZSBmb3J3YXJkLCBsZXQncyB0dXJuIHRoaXMgZGF0YWZyYW1lIGludG8gYSBiYXIgY2hhcnQuCgpgYGB7ciBQbG90IFRvcCA1IFRlYW1zfQpieV90ZWFtXzEwIDwtIGhlYWQoYnlfdGVhbSwgNSkKYnlfdGVhbV8xMF9sc3QgPC0gYnlfdGVhbV8xMFtbJ1RlYW0nXV0KCgpieV90ZWFtX3BsdCA8LSBnZ3Bsb3QoZmlsdGVyKGRmLCBUZWFtICVpbiUgYnlfdGVhbV8xMF9sc3QpLCBhZXMoVGVhbSkpCmJ5X3RlYW1fcGx0ICsgZ2VvbV9iYXIoKSArCiAgZ2VvbV90ZXh0KHN0YXQ9J2NvdW50JyxhZXMobGFiZWw9Li5jb3VudC4uKSx2anVzdD0tLjUpCmBgYAoKIyMjIFBsYXllciBvZiB0aGUgV2VlayBDb3VudHMgLSBMb3MgQW5nZWxlcyBMYWtlcnMKYGBge3J9Cmxha2VyX3RvdGFsID0gODcKCmxha2VycyA9IGRmICU+JSBzZWxlY3QoZXZlcnl0aGluZygpKSAlPiUgZmlsdGVyKFRlYW0gPT0gIkxvcyBBbmdlbGVzIExha2VycyIpCmxha2VycyA8LSBsYWtlcnMgJT4lIGNvdW50KFBsYXllcikgJT4lICMgR3JvdXAgYnkgUGxheWVyIHVzaW5nIGRwbHlyCiAgICAgICAgICAgYXJyYW5nZShkZXNjKG4pKQpsYWtlcnMgPC0gcmVuYW1lKGxha2VycywgJ051bWJlcl9vZl9QT1RXX0F3YXJkcycgPSBuKSAjIFJlbmFtZSBjb2x1bW4KbGFrZXJzIDwtIHRyYW5zZm9ybShsYWtlcnMsIFBlcmNlbnRfb2ZfVG90YWwgPSBOdW1iZXJfb2ZfUE9UV19Bd2FyZHMgLyBsYWtlcl90b3RhbCkKbGFrZXJzWywgIlBlcmNlbnRfb2ZfVG90YWwiXSA8LSByb3VuZChsYWtlcnNbLCAiUGVyY2VudF9vZl9Ub3RhbCJdLCBkaWdpdHMgPSAyKSAjIHJvdW5kIHRvIDIgZGVjaW1hbHMKaGVhZChsYWtlcnMsIDUpCmBgYApIZXJlIHdlIGNhbiBzZWUgdGhhdCBLb2JlIEJyeWFudCBtYWtlcyB1cCBmb3IgbW9zdCBvZiB0aGUgTGFrZXJzIFBsYXllciBvZiB0aGUgV2VlayBhd2FyZHMsIHdpdGggYWJvdXQgMzglIG9mIHRoZSB0b3RhbCBiZWluZyBhdHRyaWJ1dGVkIHRvIGhpbS4gVGhpcyBtYWtlcyBzZW5zZSBzaW5jZSBLb2JlIEJyeWFudCBwbGF5ZWQgMjAgc2Vhc29ucyB3aXRoIHRoZSBMb3MgQW5nZWxlcyBMYWtlcnMuIExldCdzIGdyYXBoIHRoaXMsIGJ1dCB1c2luZyBhIGhvcml6b250YWwgYmFyIGNoYXJ0IGluc3RlYWQuCmBgYHtyfQpsYWtlcnNfNSA8LSBoZWFkKGxha2VycywgNSkKbGFrZXJzXzVfbHN0IDwtIGxha2Vyc181W1snUGxheWVyJ11dCiAgCmxha2Vyc19wbHQgPC0gZ2dwbG90KGZpbHRlcihkZiwgUGxheWVyICVpbiUgbGFrZXJzXzVfbHN0LCBUZWFtID09ICJMb3MgQW5nZWxlcyBMYWtlcnMiKSwgYWVzKFBsYXllcikpCmxha2Vyc19wbHQgKyBnZW9tX2JhcigpICsgY29vcmRfZmxpcCgpICsKICBnZW9tX3RleHQoc3RhdD0nY291bnQnLGFlcyhsYWJlbD0uLmNvdW50Li4pLGhqdXN0PS0uNSkKYGBgCk5vdyB0aGF0IHdlIGhhdmUgcGxvdHRlZCBzb21lIGJhciBjaGFydHMgZm9yIGNvdW50cywgbGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIHRyZW5kIG9mIHBsYXllciBvZiB0aGUgd2VlayBhd2FyZHMgYnkgcG9zaXRpb24gb3ZlciB0aW1lLgoKIyMjIFBsYXllciBvZiB0aGUgV2VlayBUcmVuZCBieSBQb3NpdGlvbgpCZWZvcmUgd2UganVtcCBpbnRvIHRoaXMgYW5hbHlzaXMsIGxldCdzIHRha2UgYSBsb29rIGF0IG92ZXJhbGwgYXdhcmRzIHdvbiBieSBwb3NpdGlvbi4KYGBge3J9CmJ5X3Bvc2l0aW9uIDwtIGRmICU+JSBjb3VudChOZXdfUG9zaXRpb24pICU+JSAjIEdyb3VwIGJ5IFBvc2l0aW9uIHVzaW5nIGRwbHlyCiAgICAgICAgICAgYXJyYW5nZShkZXNjKG4pKQpieV9wb3NpdGlvbiA8LSByZW5hbWUoYnlfcG9zaXRpb24sICdOdW1iZXIgb2YgUGxheWVycycgPSBuKSAjIFJlbmFtZSBjb2x1bW4KaGVhZChieV9wb3NpdGlvbiwgMTApICAjIFNob3cgVG9wIDEwCmBgYApJdCBsb29rcyBsaWtlIEd1YXJkcyBhbmQgRm9yd2FyZHMgbWFrZSB1cCBhIG1ham9yaXR5IG9mIHRoaXMgYXdhcmQuIFRoaXMgbWFrZXMgc2Vuc2Ugc2luY2Ugd2UgYXJlIHJvbGxpbmcgUG9pbnQgR3VhcmRzL1Nob290aW5nIEd1YXJkcyB0byB0aGUgZ3VhcmQgcG9zaXRpb24gYW5kIFNtYWxsIEZvcndhcmRzL1Bvd2VyIEZvcndhcmRzIHRvIHRoZSBGb3J3YXJkIFBvc2l0aW9uLiBMZXQncyB1c2UgdGhlIHNxbCBsaWJyYXJ5IHRvIGRvIGEgYmFzaWMgZ3JvdXAgYnkuCgpgYGB7cn0KYnlfcG9zaXRpb25feWVhciA8LSBzcWxkZigic2VsZWN0IE5ld19Qb3NpdGlvbiwgW1NlYXNvbi5zaG9ydF0sIGNvdW50KFBsYXllcikgYXMgTnVtYmVyQXdhcmRzCiAgICAgICAgICAgICBmcm9tIGRmCiAgICAgICAgICAgICBncm91cCBieSBOZXdfUG9zaXRpb24sIFtTZWFzb24uc2hvcnRdCiAgICAgICAgICAgICBvcmRlciBieSBbU2Vhc29uLnNob3J0XSBhc2MsIE51bWJlckF3YXJkcyBkZXNjIikKCmhlYWQoYnlfcG9zaXRpb25feWVhcikKYGBgCk5vdyBsZXQncyBwbG90IHRoaXMgdHJlbmQgb3ZlciB0aW1lIHdpdGggYSBsaW5lIGNoYXJ0IQoKYGBge3J9CmdncGxvdChieV9wb3NpdGlvbl95ZWFyLCBhZXMoeD1TZWFzb24uc2hvcnQsIHk9TnVtYmVyQXdhcmRzLCBjb2w9TmV3X1Bvc2l0aW9uKSkgKyBnZW9tX2xpbmUoKQpgYGAKSGVyZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIGd1YXJkIHBvc2l0aW9uIGlzIGFjdHVhbGx5IHRyZW5kaW5nIGRvd24gd2l0aCB0aGlzIGF3YXJkIGFuZCByZWFjaGVkIGl0J3MgcGVhayBpbiB0aGUgbWlkIDIwMTAncy4gVGhpcyBpcyBhIHN1cnByaXNpbmcgZmluZGluZyB3aXRoIHNvIG1hbnkgZ29vZCBndWFyZHMgaW4gdGhlIE5CQSBjdXJyZW50bHkuIEluIHRoZWlyIHBsYWNlLCB3ZSBhcmUgc2VlaW5nIGEgZ3JhZHVhbCBpbmNyZWFzZSBpbiB0aGUgYW1vdW50IG9mIGF3YXJkcyB0aGF0IEZvcndhcmRzIGFyZSB3aW5uaW5nLgoKIyMjIEltcGFjdCBvZiBIZWlnaHQgJiBXZWlnaHQKTm93IHRoYXQgd2UgaGF2ZSBsb29rZWQgYXQgYWxsIHRlYW1zLCBhIHNwZWNpZmljIHRlYW0gYW5kIGRvbmUgc29tZSB0cmVuZCBhbmFseXNpcyBvbiBwb3NpdGlvbnMgLSB0aGUgbGFzdCBwaWVjZSBvZiBvdXIgYW5hbHlzaXMgd2lsbCBiZSB0YWtpbmcgYSBsb29rIGF0IHRoZSBpbXBhY3Qgb2YgaGVpZ2h0IGFuZCB3ZWlnaHQgb24gd2lubmluZyB0aGlzIGF3YXJkLiBGaXJzdCwgd2UgbmVlZCB0byBxdWVyeSBvdXIgZGF0YWZyYW1lIHRvIGdldCBhIGxpc3Qgb2YgcGxheWVycywgdGhlaXIgaGVpZ2h0L3dlaWdodCBhbmQgYWxzbyB0aGUgYW1vdW50IG9mIHRpbWVzIHRoZXkgaGF2ZSB3b24gdGhlIGF3YXJkLgoKYGBge3J9CnBsYXllcl9oZWlnaHRfd2VpZ2h0ID0gc3FsZGYoInNlbGVjdCBQbGF5ZXIsIFtIZWlnaHQuQ01dLCBXZWlnaHQsIE5ld19Qb3NpdGlvbiwgY291bnQoUGxheWVyKSBhcyBOdW1Bd2FyZHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSBkZgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCBieSBQbGF5ZXIsIFtIZWlnaHQuQ01dLCBXZWlnaHQsIE5ld19Qb3NpdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciBieSBOdW1Bd2FyZHMgZGVzYyIpCgpoZWFkKHBsYXllcl9oZWlnaHRfd2VpZ2h0KQpgYGAKSGVyZSB3ZSBjYW4gc2VlIHRoYXQgTGVCcm9uIEphbWVzIGhhcyB3b24gdGhlIG1vc3QgYXdhcmRzIGZyb20gYW55IG90aGVyIHBsYXllci4gQWxtb3N0IGRvdWJsaW5nIEtvYmUgQnJ5YW50J3MgbnVtYmVyLiBBbGxlbiBJdmVyc29uIGlzIGFsc28gYW5vdGhlciBpbnRlcmVzdGluZyBwbGF5ZXIgdG8gYXBwZWFyIG9uIHRoZSB0b3Agb2YgdGhpcyBsaXN0IGFuZCBpcyBhbHNvIHRoZSBzaG9ydGVzdCBhbmQgbGlnaHRlc3QgcGxheWVyIGluIHRoZSB0b3AgNi4gQW5vdGhlciBpbnRlcmVzdGluZyBvYnNlcnZhdGlvbiBpcyB0aGF0IHRoZXJlIGlzIG5vIENlbnRlciB0aGF0IGFwcGVhcnMgaW4gdGhlIHRvcCA2IGxpc3QuIE5vdyB0aGF0IHdlIGhhdmUgdGhpcyBkYXRhIGZyYW1lIHByZXBhcmVkLCBsZXQncyB0cnkgdG8gZ2V0IHNvbWUgY29ycmVsYXRpb24gaW5zaWdodCBieSBwbG90dGluZy4KCmBgYHtyfQpnZ3Bsb3QocGxheWVyX2hlaWdodF93ZWlnaHQsIGFlcyh4PVdlaWdodCwgeT1IZWlnaHQuQ00pKSArIAogIGdlb21fcG9pbnQoYWVzKGNvbD1OZXdfUG9zaXRpb24sIHNpemU9TnVtQXdhcmRzKSkgKyAKICBnZW9tX3Ntb290aChtZXRob2Q9ImxvZXNzIiwgc2U9RikgKwogIGdlb21fdGV4dChkYXRhPWhlYWQocGxheWVyX2hlaWdodF93ZWlnaHQpLAogICAgICAgICAgICBhZXMoV2VpZ2h0LEhlaWdodC5DTSxsYWJlbD1QbGF5ZXIpKSArCiAgbGFicyhzdWJ0aXRsZT0iQnkgSGVpZ2h0ICYgV2VpZ2h0IiwgCiAgICAgICB5PSJIZWlnaHQgKGNtKSIsIAogICAgICAgeD0iV2VpZ2h0IChsYikiLCAKICAgICAgIHRpdGxlPSJQbGF5ZXIgb2YgdGhlIFdlZWsgQXdhcmRzIERpc3RyaWJ1dGlvbiIpCmBgYApJdCBkb2VzIGxvb2sgbGlrZSB0aGVyZSBpcyBhbiB1cHdhcmQgdHJlbmQgd2l0aCB0aGUgYW1vdW50IG9mIHBsYXllciBvZiB0aGUgd2VlayBhd2FyZHMgYSBwbGF5ZXIgd2lucyBhbmQgYW4gaW5jcmVhc2VkIGhlaWdodC93ZWlnaHQuIEhvd2V2ZXIgdGhpcyB0cmVuZCBpcyBub3QgYXMgZHJhc3RpYyBhbmQgYmVnaW5zIHRvIHRhcGVyIG9mZiBhcm91bmQgdGhlIDIzMCBwb3VuZCBhbmQgMjA1IGNtIGNvb3JkaW5hdGUuIEFsbGVuIEl2ZXJzb24gaXMgZGVmaW5pdGVseSBhbiBhbm9tYWx5IHdpdGggdGhlIGFtb3VudCBvZiBwbGF5ZXIgb2YgdGhlIHdlZWsgYXdhcmRzIGhlIGhhcyB3b24sIGNvbnNpZGVyaW5nIGhpcyBoZWlnaHQgYW5kIHdlaWdodC4gSG93ZXZlciwgbWFueSBOQkEgcGxheWVycyBwcm9jbGFpbSB0aGF0IEl2ZXJzb24gaXMgInBvdW5kIGZvciBwb3VuZCIgb25lIG9mIHRoZSBiZXN0IHRvIGV2ZXIgcGxheSB0aGUgZ2FtZSAtIGFuZCB0aGlzIGNvdWxkIG1ha2UgdGhhdCBhcmd1bWVudCBzdHJvbmdlci4gVGhlIG1vc3QgaW50ZXJlc3Rpbmcgb2JzZXJ2YXRpb24gaXMgdGhlIG92ZXJsYXAgYmV0d2VlbiBNaWNoYWVsIEpvcmRhbiBhbmQgS29iZSBCcnlhbnQuIFRoZXNlIHR3byBwbGF5ZXJzIGFyZSBhbHdheXMgcGFja2FnZWQgdG9nZXRoZXIgaW4gYmFza2V0YmFsbCBjb252ZXJzYXRpb25zIGJlY2F1c2Ugb2YgdGhlaXIgc2ltaWxhcml0eSBpbiBwbGF5IHN0eWxlLiBJdCBpcyBpbnRlcmVzdGluZyB0byBzZWUgdGhhdCB0aGVpciBIZWlnaHQvV2VpZ2h0IGFuZCBQbGF5ZXIgb2YgdGhlIFdlZWsgYXdhcmRzIGRpc3RyaWJ1dGlvbiBpcyBhbG1vc3QgaWRlbnRpY2FsLgoKIyMgU3VtbWFyeQpJbiB0aGlzIG5vdGVib29rLCB3ZSB3ZXJlIGFibGUgdG8gZG8gc29tZSBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIGFuZCBWaXN1YWxpemF0aW9uIG9uIE5CQSBQbGF5ZXIgb2YgdGhlIFdlZWsgc3RhdGlzdGljcy4gV2Ugd2VyZSBhYmxlIHRvIGZpbmQgdGhhdCBwbGF5ZXJzIGZyb20gdGhlIExvcyBBbmdlbGVzIExha2VycyBoYXZlIHdvbiB0aGUgYXdhcmQgdGhlIG1vc3QuIEZyb20gdGhpcyBzdWJzZXQsIEtvYmUgQnJ5YW50IGhhcyB3b24gdGhlIGF3YXJkIHRoZSBtb3N0IG91dCBvZiBhbnkgTGFrZXIgcGxheWVyLiBIb3dldmVyLCBLb2JlIGlzIG5vdCB0aGUgcGxheWVyIHRoYXQgaGFzIHJlY2VpdmVkIHRoaXMgYXdhcmQgdGhlIG1vc3QgLSB0aGlzIGFjY29sYWRlIGdvZXMgdG8gTGVCcm9uIEphbWVzLiAKCkFkZGl0aW9uYWxseSwgd2UgcGVyZm9ybWVkIHRyZW5kIGFuYWx5c2lzIGJ5IHBvc2l0aW9uIGFuZCBmb3VuZCB0aGF0IGd1YXJkcyBhcmUgdHJlbmRpbmcgZG93biB3aGVuIGl0IGNvbWVzIHRvIHRoaXMgYXdhcmQuIENvbnZlcnNlbHksIHdlIGFyZSBzZWVpbmcgYSB0cmVuZCB1cHdhcmRzIGZvciBmb3J3YXJkcyB3aXRoIHBsYXllcnMgbGlrZSBHaWFubmlzIEFudGV0b2tvdW5tcG8sIEthd2hpIExlb25hcmQsIGFuZCBMZUJyb24gSmFtZXMgaW4gdGhlIGxlYWd1ZS4gTGFzdGx5LCB3ZSB3ZXJlIGFibGUgdG8gdmlzdWFsaXplIGEgc2NhdHRlcnBsb3Qgd2l0aCBkZW5zaXR5IG1lYXN1cmVzIGJhc2VkIG9uIGhlaWdodCBhbmQgd2VpZ2h0LiBXZSBzYXcgdGhhdCB0aGVyZSBpcyBhIHBvc2l0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG51bWJlciBvZiBhd2FyZHMgYW5kIGhlaWdodC93ZWlnaHQsIGJ1dCB0aGUgaW50ZW5zaXR5IG9mIHRoaXMgcmVsYXRpb25zaGlwIGJlZ2lucyB0byBmbGF0dGVuIGF0IGEgY2VydGFpbiBwb2ludC4K