import java.io.* ;
import java.text.SimpleDateFormat ;
import java.util.* ;
class Expense_8_puzzle implements Comparable< Expense_8_puzzle> {
int [ ] [ ] mat;
int [ ] movablePlaceHolder;
int cost;
int depth;
Expense_8_puzzle predecessor;
static final Map
< String ,
int [ ] > movableActions
= new HashMap
<> ( ) ;
static {
movableActions.put ( "DOWN" , new int [ ] { - 1 , 0 } ) ;
movableActions.put ( "UP" , new int [ ] { 1 , 0 } ) ;
movableActions.put ( "RIGHT" , new int [ ] { 0 , - 1 } ) ;
movableActions.put ( "LEFT" , new int [ ] { 0 , 1 } ) ;
}
Expense_8_puzzle
( int [ ] [ ] mat,
int [ ] movablePlaceHolder,
int cost,
int depth, Expense_8_puzzle predecessor,
String action
) { this .mat = new int [ 3 ] [ 3 ] ;
for ( int i = 0 ; i < 3 ; i++ ) {
this .
mat [ i
] = Arrays .
copyOf ( mat
[ i
] , mat
[ i
] .
length ) ; }
this .
movablePlaceHolder = Arrays .
copyOf ( movablePlaceHolder, movablePlaceHolder.
length ) ; this .cost = cost;
this .depth = depth;
this .predecessor = predecessor;
this .action = action;
}
@Override
public int compareTo( Expense_8_puzzle other) {
return Integer .
compare ( this .
cost , other.
cost ) ; }
// Method to parse input file and create the problem state
int [ ] [ ] mat = new int [ 3 ] [ 3 ] ;
int [ ] movablePlaceHolder = new int [ 2 ] ;
for ( int i = 0 ; i < 3 ; i++ ) {
line = br.readLine ( ) ;
String [ ] tokens
= line.
split ( " " ) ; for ( int j = 0 ; j < tokens.length ; j++ ) {
mat
[ i
] [ j
] = Integer .
parseInt ( tokens
[ j
] ) ; if ( mat[ i] [ j] == 0 ) {
movablePlaceHolder[ 0 ] = i;
movablePlaceHolder[ 1 ] = j;
}
}
}
br.close ( ) ;
return new Expense_8_puzzle( mat, movablePlaceHolder, 0 , 0 , null , null ) ;
}
// Expansions for the current state (children nodes)
List< Expense_8_puzzle> getExpansions( ) {
List< Expense_8_puzzle> expansions = new ArrayList<> ( ) ;
int x = movablePlaceHolder[ 0 ] , y = movablePlaceHolder[ 1 ] ;
for ( Map .
Entry < String ,
int [ ] > entry
: movableActions.
entrySet ( ) ) { String action
= entry.
getKey ( ) ; int [ ] dir = entry.getValue ( ) ;
int tempA = x + dir[ 0 ] , tempB = y + dir[ 1 ] ;
// Valid grid boundaries
if ( tempA >= 0 && tempA < 3 && tempB >= 0 && tempB < 3 ) {
int [ ] [ ] tempMat = new int [ 3 ] [ 3 ] ;
for ( int i = 0 ; i < 3 ; i++ ) {
tempMat
[ i
] = Arrays .
copyOf ( mat
[ i
] , mat
[ i
] .
length ) ; }
tempMat[ x] [ y] = tempMat[ tempA] [ tempB] ;
tempMat[ tempA] [ tempB] = 0 ;
int tileValue = tempMat[ x] [ y] ;
expansions.add ( new Expense_8_puzzle( tempMat, new int [ ] { tempA, tempB} , this .cost + tileValue, this .depth + 1 , this , action) ) ;
}
}
return expansions;
}
// Trace the path from current to root (predecessors)
List< String> parentChainGenerator( ) {
List< String> path = new ArrayList<> ( ) ;
Expense_8_puzzle state = this ;
while ( state.predecessor != null ) {
path.add ( "Move " + state.mat [ state.predecessor .movablePlaceHolder [ 0 ] ] [ state.predecessor .movablePlaceHolder [ 1 ] ] + " " + state.action ) ;
state = state.predecessor ;
}
return path; // No need to reverse. It prints from current to root.
}
// Heuristic function
static int estimate_heuristicValues( Expense_8_puzzle state, Expense_8_puzzle goal) {
int cost = 0 ;
for ( int i = 0 ; i < 3 ; i++ ) {
for ( int j = 0 ; j < 3 ; j++ ) {
int tile = state.mat [ i] [ j] ;
if ( tile != 0 ) {
int goalI = ( tile - 1 ) / 3 ;
int goalJ = ( tile - 1 ) % 3 ;
int moves
= Math .
abs ( i
- goalI
) + Math .
abs ( j
- goalJ
) ; cost += moves * tile;
}
}
}
return cost;
}
// Parent chain from current state to root (no reversal)
static List< Expense_8_puzzle> getParentChain( Expense_8_puzzle state) {
List< Expense_8_puzzle> chain = new ArrayList<> ( ) ;
while ( state != null ) {
chain.add ( state) ; // Start from current state
state = state.predecessor ;
}
return chain;
}
// Log state and its predecessor chain
static void dumpLogger
( Expense_8_puzzle presentPuzzleMat, List
< Expense_8_puzzle
> successors, Set
< String
> closedSet, Collection
< Expense_8_puzzle
> frontier,
int NPop,
String logFile, Expense_8_puzzle goalState,
String method
) throws IOException { if ( logFile != null ) {
if ( method.equals ( "a*" ) || method.equals ( "greedy" ) ) {
fileEditor.
write ( String .
format ( "Generating successors to < state = %s, action = {%s} g(n) = %d, d = %d, f(n) = %d, Parent = Pointer to %s >:\n " ,
Arrays .
deepToString ( presentPuzzleMat.
mat ) , presentPuzzleMat.
action == null ? "Start" : presentPuzzleMat.
action , presentPuzzleMat.
cost ,
presentPuzzleMat.depth , presentPuzzleMat.cost + estimate_heuristicValues( presentPuzzleMat, goalState) ,
presentPuzzleMat.
predecessor == null ? "None" : Arrays .
deepToString ( presentPuzzleMat.
predecessor .
mat ) ) ) ; } else {
fileEditor.
write ( String .
format ( "Generating successors to < state = %s, action = {%s} cost = %d, d = %d, Parent = Pointer to %s >:\n " ,
Arrays .
deepToString ( presentPuzzleMat.
mat ) , presentPuzzleMat.
action == null ? "Start" : presentPuzzleMat.
action , presentPuzzleMat.
cost ,
presentPuzzleMat.
depth , presentPuzzleMat.
predecessor == null ? "None" : Arrays .
deepToString ( presentPuzzleMat.
predecessor .
mat ) ) ) ; }
fileEditor.write ( "\t successors generated -> \n " ) ;
fileEditor.write ( "\t Closed: " + closedSet + "\n " ) ;
fileEditor.write ( "\t Fringe: [\n " ) ;
// Iterate over frontier and log the state and its predecessor chain
for ( Expense_8_puzzle state : frontier) {
List< Expense_8_puzzle> predecessorChain = getParentChain( state) ; // Correct predecessor chain
// Print chain from current state to root
StringBuilder predecessorChainStr = new StringBuilder( ) ;
for ( int i = 0 ; i < predecessorChain.size ( ) ; i++ ) { // Keep order as-is
Expense_8_puzzle p = predecessorChain.get ( i) ;
predecessorChainStr.
append ( String .
format ( "< state = %s, action = {Move %s}, g(n) = %d, d = %d, f(n) = %d >" ,
Arrays .
deepToString ( p.
mat ) , p.
action , p.
cost , p.
depth , method.
equals ( "a*" ) || method.
equals ( "greedy" ) ? ( p.
cost + estimate_heuristicValues
( p, goalState
) ) : p.
cost ) ) ; if ( i != predecessorChain.size ( ) - 1 ) { // Append arrow unless last state
predecessorChainStr.append ( " -> " ) ;
}
}
fileEditor.write ( "\t \t " + predecessorChainStr.toString ( ) + "\n " ) ;
}
fileEditor.write ( "\t ]\n " ) ;
fileEditor.close ( ) ;
}
}
// DLS (Depth-Limited Search)
static Expense_8_puzzle dls
( Expense_8_puzzle start, Expense_8_puzzle goal,
int DL,
boolean logBoolean,
String logFile
) throws IOException { Stack< Expense_8_puzzle> frontier = new Stack<> ( ) ;
Set< String> closedSet = new HashSet<> ( ) ;
frontier.push ( start) ;
int NPop = 0 , NGen = 1 , NExp = 0 , MFSize = 0 ;
while ( ! frontier.isEmpty ( ) ) {
MFSize
= Math .
max ( MFSize, frontier.
size ( ) ) ; Expense_8_puzzle presentPuzzleMat = frontier.pop ( ) ;
NPop++;
if ( presentPuzzleMat.depth > DL) {
continue ;
}
if ( Arrays .
deepEquals ( presentPuzzleMat.
mat , goal.
mat ) ) { System .
out .
println ( "Nodes Expanded: " + NExp
) ; System .
out .
println ( "Nodes Generated: " + NGen
) ; System .
out .
println ( "Nodes Popped: " + NPop
) ; System .
out .
println ( "Max Fringe Size: " + MFSize
) ; return presentPuzzleMat;
}
if ( closedSet.contains ( matStr) ) {
continue ;
}
closedSet.add ( matStr) ;
NExp++;
List< Expense_8_puzzle> successors = presentPuzzleMat.getExpansions ( ) ;
for ( Expense_8_puzzle neighbor : successors) {
if ( neighbor.depth <= DL) {
frontier.push ( neighbor) ;
NGen++;
}
}
if ( logBoolean) {
dumpLogger( presentPuzzleMat, successors, closedSet, frontier, NPop, logFile, goal, "dls" ) ;
}
}
return null ;
}
// BFS
static Expense_8_puzzle bfs
( Expense_8_puzzle start, Expense_8_puzzle goal,
boolean logBoolean,
String logFile
) throws IOException { Queue< Expense_8_puzzle> frontier = new LinkedList<> ( ) ;
Set< String> closedSet = new HashSet<> ( ) ;
frontier.add ( start) ;
int NPop = 0 , NGen = 1 , NExp = 0 , MFSize = 0 ;
while ( ! frontier.isEmpty ( ) ) {
MFSize
= Math .
max ( MFSize, frontier.
size ( ) ) ; Expense_8_puzzle presentPuzzleMat = frontier.poll ( ) ;
NPop++;
if ( Arrays .
deepEquals ( presentPuzzleMat.
mat , goal.
mat ) ) { System .
out .
println ( "Nodes Expanded: " + NExp
) ; System .
out .
println ( "Nodes Generated: " + NGen
) ; System .
out .
println ( "Nodes Popped: " + NPop
) ; System .
out .
println ( "Max Fringe Size: " + MFSize
) ; return presentPuzzleMat;
}
if ( closedSet.contains ( matStr) ) {
continue ;
}
closedSet.add ( matStr) ;
NExp++;
List< Expense_8_puzzle> successors = presentPuzzleMat.getExpansions ( ) ;
for ( Expense_8_puzzle neighbor : successors) {
frontier.add ( neighbor) ;
NGen++;
}
if ( logBoolean) {
dumpLogger( presentPuzzleMat, successors, closedSet, frontier, NPop, logFile, goal, "bfs" ) ;
}
}
return null ;
}
// IDS
static Expense_8_puzzle ids
( Expense_8_puzzle start, Expense_8_puzzle goal,
boolean logBoolean,
String logFile
) throws IOException { int DL = 0 ;
while ( true ) {
Expense_8_puzzle result = dls( start, goal, DL, logBoolean, logFile) ;
if ( result != null ) {
return result;
}
DL++;
}
}
// UCS
static Expense_8_puzzle ucs
( Expense_8_puzzle start, Expense_8_puzzle goal,
boolean logBoolean,
String logFile
) throws IOException { PriorityQueue< Expense_8_puzzle> frontier = new PriorityQueue<> ( ) ;
Set< String> closedSet = new HashSet<> ( ) ;
frontier.add ( start) ;
int NPop = 0 , NGen = 1 , NExp = 0 , MFSize = 0 ;
while ( ! frontier.isEmpty ( ) ) {
MFSize
= Math .
max ( MFSize, frontier.
size ( ) ) ; Expense_8_puzzle presentPuzzleMat = frontier.poll ( ) ;
NPop++;
if ( Arrays .
deepEquals ( presentPuzzleMat.
mat , goal.
mat ) ) { System .
out .
println ( "Nodes Expanded: " + NExp
) ; System .
out .
println ( "Nodes Generated: " + NGen
) ; System .
out .
println ( "Nodes Popped: " + NPop
) ; System .
out .
println ( "Max Fringe Size: " + MFSize
) ; return presentPuzzleMat;
}
if ( closedSet.contains ( matStr) ) {
continue ;
}
closedSet.add ( matStr) ;
NExp++;
List< Expense_8_puzzle> successors = presentPuzzleMat.getExpansions ( ) ;
for ( Expense_8_puzzle neighbor : successors) {
frontier.add ( neighbor) ;
NGen++;
}
if ( logBoolean) {
dumpLogger( presentPuzzleMat, successors, closedSet, frontier, NPop, logFile, goal, "ucs" ) ;
}
}
return null ;
}
// Greedy
static Expense_8_puzzle greedy
( Expense_8_puzzle start, Expense_8_puzzle goal,
boolean logBoolean,
String logFile
) throws IOException { PriorityQueue
< Expense_8_puzzle
> frontier
= new PriorityQueue
<> ( Comparator .
comparingInt ( s
-> estimate_heuristicValues
( s, goal
) ) ) ; Set< String> closedSet = new HashSet<> ( ) ;
frontier.add ( start) ;
int NPop = 0 , NGen = 1 , NExp = 0 , MFSize = 0 ;
while ( ! frontier.isEmpty ( ) ) {
MFSize
= Math .
max ( MFSize, frontier.
size ( ) ) ; Expense_8_puzzle presentPuzzleMat = frontier.poll ( ) ;
NPop++;
if ( Arrays .
deepEquals ( presentPuzzleMat.
mat , goal.
mat ) ) { System .
out .
println ( "Nodes Expanded: " + NExp
) ; System .
out .
println ( "Nodes Generated: " + NGen
) ; System .
out .
println ( "Nodes Popped: " + NPop
) ; System .
out .
println ( "Max Fringe Size: " + MFSize
) ; return presentPuzzleMat;
}
if ( closedSet.contains ( matStr) ) {
continue ;
}
closedSet.add ( matStr) ;
NExp++;
List< Expense_8_puzzle> successors = presentPuzzleMat.getExpansions ( ) ;
for ( Expense_8_puzzle neighbor : successors) {
frontier.add ( neighbor) ;
NGen++;
}
if ( logBoolean) {
dumpLogger( presentPuzzleMat, successors, closedSet, frontier, NPop, logFile, goal, "greedy" ) ;
}
}
return null ;
}
// A*
static Expense_8_puzzle aStar
( Expense_8_puzzle start, Expense_8_puzzle goal,
boolean logBoolean,
String logFile
) throws IOException { PriorityQueue
< Expense_8_puzzle
> frontier
= new PriorityQueue
<> ( Comparator .
comparingInt ( s
-> s.
cost + estimate_heuristicValues
( s, goal
) ) ) ; Set< String> closedSet = new HashSet<> ( ) ;
frontier.add ( start) ;
int NPop = 0 , NGen = 1 , NExp = 0 , MFSize = 0 ;
while ( ! frontier.isEmpty ( ) ) {
MFSize
= Math .
max ( MFSize, frontier.
size ( ) ) ; Expense_8_puzzle presentPuzzleMat = frontier.poll ( ) ;
NPop++;
if ( Arrays .
deepEquals ( presentPuzzleMat.
mat , goal.
mat ) ) { System .
out .
println ( "Nodes Expanded: " + NExp
) ; System .
out .
println ( "Nodes Generated: " + NGen
) ; System .
out .
println ( "Nodes Popped: " + NPop
) ; System .
out .
println ( "Max Fringe Size: " + MFSize
) ; return presentPuzzleMat;
}
if ( closedSet.contains ( matStr) ) {
continue ;
}
closedSet.add ( matStr) ;
NExp++;
List< Expense_8_puzzle> successors = presentPuzzleMat.getExpansions ( ) ;
for ( Expense_8_puzzle neighbor : successors) {
frontier.add ( neighbor) ;
NGen++;
}
if ( logBoolean) {
dumpLogger( presentPuzzleMat, successors, closedSet, frontier, NPop, logFile, goal, "a*" ) ;
}
}
return null ;
}
if ( args.length < 2 ) {
System .
out .
println ( "Usage: java Expense_8_puzzle <start-file> <goal-file> <method> <log-file>" ) ; return ;
}
boolean logBoolean = false ;
String logFile
= "traceDateTime.txt" ; // trace-Date-Time.txt
if ( args.length > 2 ) {
if ( args[ 2 ] .equals ( "true" ) || args[ 2 ] .equals ( "false" ) ) {
logBoolean
= Boolean .
parseBoolean ( args
[ 2 ] ) ; }
else {
method = args[ 2 ] ;
}
}
if ( args.length > 3 ) {
logBoolean
= Boolean .
parseBoolean ( args
[ 3 ] ) ; }
Expense_8_puzzle startState = Expense_8_puzzle.convertToMatFromFile ( startFile) ;
Expense_8_puzzle goalState = Expense_8_puzzle.convertToMatFromFile ( goalFile) ;
Expense_8_puzzle answer = null ;
switch ( method) {
case "bfs" :
answer = Expense_8_puzzle.bfs ( startState, goalState, logBoolean, logFile) ;
break ;
case "dfs" :
answer
= Expense_8_puzzle.
dls ( startState, goalState,
Integer .
MAX_VALUE , logBoolean, logFile
) ; break ;
case "dls" :
int DL = 10 ;
Scanner sc
= new Scanner
( System .
in ) ; System .
out .
println ( "Enter the depth limit: " ) ; String inputTaken
= sc.
nextLine ( ) ; if ( inputTaken.length ( ) > 0 && inputTaken.matches ( "[0-9]+" ) ) {
}
else {
System .
out .
println ( "Invalid input, using default depth limit of 10" ) ; }
answer = Expense_8_puzzle.dls ( startState, goalState, DL, logBoolean, logFile) ;
break ;
case "ids" :
answer = Expense_8_puzzle.ids ( startState, goalState, logBoolean, logFile) ;
break ;
case "ucs" :
answer = Expense_8_puzzle.ucs ( startState, goalState, logBoolean, logFile) ;
break ;
case "greedy" :
answer = Expense_8_puzzle.greedy ( startState, goalState, logBoolean, logFile) ;
break ;
case "a*" :
answer = Expense_8_puzzle.aStar ( startState, goalState, logBoolean, logFile) ;
break ;
default :
System .
out .
println ( "Unknown method!" ) ; return ;
}
if ( answer != null ) {
System .
out .
println ( "Solution Found at depth " + answer.
depth + " with cost of " + answer.
cost ) ; List< String> steps = answer.parentChainGenerator ( ) ;
System .
out .
println ( "\t " + step
) ; }
} else {
System .
out .
println ( "No answer found." ) ; }
}
}
aW1wb3J0IGphdmEuaW8uKjsKaW1wb3J0IGphdmEudGV4dC5TaW1wbGVEYXRlRm9ybWF0OwppbXBvcnQgamF2YS51dGlsLio7CgpjbGFzcyBFeHBlbnNlXzhfcHV6emxlIGltcGxlbWVudHMgQ29tcGFyYWJsZTxFeHBlbnNlXzhfcHV6emxlPiB7CiAgICBpbnRbXVtdIG1hdDsKICAgIGludFtdIG1vdmFibGVQbGFjZUhvbGRlcjsKICAgIGludCBjb3N0OwogICAgaW50IGRlcHRoOwogICAgRXhwZW5zZV84X3B1enpsZSBwcmVkZWNlc3NvcjsKICAgIFN0cmluZyBhY3Rpb247CgogICAgc3RhdGljIGZpbmFsIE1hcDxTdHJpbmcsIGludFtdPiBtb3ZhYmxlQWN0aW9ucyA9IG5ldyBIYXNoTWFwPD4oKTsKCiAgICBzdGF0aWMgewogICAgICAgIG1vdmFibGVBY3Rpb25zLnB1dCgiRE9XTiIsIG5ldyBpbnRbXXstMSwgMH0pOwogICAgICAgIG1vdmFibGVBY3Rpb25zLnB1dCgiVVAiLCBuZXcgaW50W117MSwgMH0pOwogICAgICAgIG1vdmFibGVBY3Rpb25zLnB1dCgiUklHSFQiLCBuZXcgaW50W117MCwgLTF9KTsKICAgICAgICBtb3ZhYmxlQWN0aW9ucy5wdXQoIkxFRlQiLCBuZXcgaW50W117MCwgMX0pOwogICAgfQoKICAgIEV4cGVuc2VfOF9wdXp6bGUoaW50W11bXSBtYXQsIGludFtdIG1vdmFibGVQbGFjZUhvbGRlciwgaW50IGNvc3QsIGludCBkZXB0aCwgRXhwZW5zZV84X3B1enpsZSBwcmVkZWNlc3NvciwgU3RyaW5nIGFjdGlvbikgewogICAgICAgIHRoaXMubWF0ID0gbmV3IGludFszXVszXTsKICAgICAgICBmb3IgKGludCBpID0gMDsgaSA8IDM7IGkrKykgewogICAgICAgICAgICB0aGlzLm1hdFtpXSA9IEFycmF5cy5jb3B5T2YobWF0W2ldLCBtYXRbaV0ubGVuZ3RoKTsKICAgICAgICB9CiAgICAgICAgdGhpcy5tb3ZhYmxlUGxhY2VIb2xkZXIgPSBBcnJheXMuY29weU9mKG1vdmFibGVQbGFjZUhvbGRlciwgbW92YWJsZVBsYWNlSG9sZGVyLmxlbmd0aCk7CiAgICAgICAgdGhpcy5jb3N0ID0gY29zdDsKICAgICAgICB0aGlzLmRlcHRoID0gZGVwdGg7CiAgICAgICAgdGhpcy5wcmVkZWNlc3NvciA9IHByZWRlY2Vzc29yOwogICAgICAgIHRoaXMuYWN0aW9uID0gYWN0aW9uOwogICAgfQoKICAgIEBPdmVycmlkZQogICAgcHVibGljIGludCBjb21wYXJlVG8oRXhwZW5zZV84X3B1enpsZSBvdGhlcikgewogICAgICAgIHJldHVybiBJbnRlZ2VyLmNvbXBhcmUodGhpcy5jb3N0LCBvdGhlci5jb3N0KTsKICAgIH0KCiAgICAvLyBNZXRob2QgdG8gcGFyc2UgaW5wdXQgZmlsZSBhbmQgY3JlYXRlIHRoZSBwcm9ibGVtIHN0YXRlCiAgICBzdGF0aWMgRXhwZW5zZV84X3B1enpsZSBjb252ZXJ0VG9NYXRGcm9tRmlsZShTdHJpbmcgZmlsZU5hbWUpIHRocm93cyBJT0V4Y2VwdGlvbiB7CiAgICAgICAgQnVmZmVyZWRSZWFkZXIgYnIgPSBuZXcgQnVmZmVyZWRSZWFkZXIobmV3IEZpbGVSZWFkZXIoZmlsZU5hbWUpKTsKICAgICAgICBTdHJpbmcgbGluZTsKICAgICAgICBpbnRbXVtdIG1hdCA9IG5ldyBpbnRbM11bM107CiAgICAgICAgaW50W10gbW92YWJsZVBsYWNlSG9sZGVyID0gbmV3IGludFsyXTsKCiAgICAgICAgZm9yIChpbnQgaSA9IDA7IGkgPCAzOyBpKyspIHsKICAgICAgICAgICAgbGluZSA9IGJyLnJlYWRMaW5lKCk7CiAgICAgICAgICAgIFN0cmluZ1tdIHRva2VucyA9IGxpbmUuc3BsaXQoIiAiKTsKICAgICAgICAgICAgZm9yIChpbnQgaiA9IDA7IGogPCB0b2tlbnMubGVuZ3RoOyBqKyspIHsKICAgICAgICAgICAgICAgIG1hdFtpXVtqXSA9IEludGVnZXIucGFyc2VJbnQodG9rZW5zW2pdKTsKICAgICAgICAgICAgICAgIGlmIChtYXRbaV1bal0gPT0gMCkgewogICAgICAgICAgICAgICAgICAgIG1vdmFibGVQbGFjZUhvbGRlclswXSA9IGk7CiAgICAgICAgICAgICAgICAgICAgbW92YWJsZVBsYWNlSG9sZGVyWzFdID0gajsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBici5jbG9zZSgpOwogICAgICAgIHJldHVybiBuZXcgRXhwZW5zZV84X3B1enpsZShtYXQsIG1vdmFibGVQbGFjZUhvbGRlciwgMCwgMCwgbnVsbCwgbnVsbCk7CiAgICB9CgogICAgLy8gRXhwYW5zaW9ucyBmb3IgdGhlIGN1cnJlbnQgc3RhdGUgKGNoaWxkcmVuIG5vZGVzKQogICAgTGlzdDxFeHBlbnNlXzhfcHV6emxlPiBnZXRFeHBhbnNpb25zKCkgewogICAgICAgIExpc3Q8RXhwZW5zZV84X3B1enpsZT4gZXhwYW5zaW9ucyA9IG5ldyBBcnJheUxpc3Q8PigpOwogICAgICAgIGludCB4ID0gbW92YWJsZVBsYWNlSG9sZGVyWzBdLCB5ID0gbW92YWJsZVBsYWNlSG9sZGVyWzFdOwoKICAgICAgICBmb3IgKE1hcC5FbnRyeTxTdHJpbmcsIGludFtdPiBlbnRyeSA6IG1vdmFibGVBY3Rpb25zLmVudHJ5U2V0KCkpIHsKICAgICAgICAgICAgU3RyaW5nIGFjdGlvbiA9IGVudHJ5LmdldEtleSgpOwogICAgICAgICAgICBpbnRbXSBkaXIgPSBlbnRyeS5nZXRWYWx1ZSgpOwogICAgICAgICAgICBpbnQgdGVtcEEgPSB4ICsgZGlyWzBdLCB0ZW1wQiA9IHkgKyBkaXJbMV07CgogICAgICAgICAgICAvLyBWYWxpZCBncmlkIGJvdW5kYXJpZXMKICAgICAgICAgICAgaWYgKHRlbXBBID49IDAgJiYgdGVtcEEgPCAzICYmIHRlbXBCID49IDAgJiYgdGVtcEIgPCAzKSB7CiAgICAgICAgICAgICAgICBpbnRbXVtdIHRlbXBNYXQgPSBuZXcgaW50WzNdWzNdOwogICAgICAgICAgICAgICAgZm9yIChpbnQgaSA9IDA7IGkgPCAzOyBpKyspIHsKICAgICAgICAgICAgICAgICAgICB0ZW1wTWF0W2ldID0gQXJyYXlzLmNvcHlPZihtYXRbaV0sIG1hdFtpXS5sZW5ndGgpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgdGVtcE1hdFt4XVt5XSA9IHRlbXBNYXRbdGVtcEFdW3RlbXBCXTsKICAgICAgICAgICAgICAgIHRlbXBNYXRbdGVtcEFdW3RlbXBCXSA9IDA7CiAgICAgICAgICAgICAgICBpbnQgdGlsZVZhbHVlID0gdGVtcE1hdFt4XVt5XTsKICAgICAgICAgICAgICAgIGV4cGFuc2lvbnMuYWRkKG5ldyBFeHBlbnNlXzhfcHV6emxlKHRlbXBNYXQsIG5ldyBpbnRbXXt0ZW1wQSwgdGVtcEJ9LCB0aGlzLmNvc3QgKyB0aWxlVmFsdWUsIHRoaXMuZGVwdGggKyAxLCB0aGlzLCBhY3Rpb24pKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gZXhwYW5zaW9uczsKICAgIH0KCiAgICAvLyBUcmFjZSB0aGUgcGF0aCBmcm9tIGN1cnJlbnQgdG8gcm9vdCAocHJlZGVjZXNzb3JzKQogICAgTGlzdDxTdHJpbmc+IHBhcmVudENoYWluR2VuZXJhdG9yKCkgewogICAgICAgIExpc3Q8U3RyaW5nPiBwYXRoID0gbmV3IEFycmF5TGlzdDw+KCk7CiAgICAgICAgRXhwZW5zZV84X3B1enpsZSBzdGF0ZSA9IHRoaXM7CgogICAgICAgIHdoaWxlIChzdGF0ZS5wcmVkZWNlc3NvciAhPSBudWxsKSB7CiAgICAgICAgICAgIHBhdGguYWRkKCJNb3ZlICIgKyBzdGF0ZS5tYXRbc3RhdGUucHJlZGVjZXNzb3IubW92YWJsZVBsYWNlSG9sZGVyWzBdXVtzdGF0ZS5wcmVkZWNlc3Nvci5tb3ZhYmxlUGxhY2VIb2xkZXJbMV1dICsgIiAiICsgc3RhdGUuYWN0aW9uKTsKICAgICAgICAgICAgc3RhdGUgPSBzdGF0ZS5wcmVkZWNlc3NvcjsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHBhdGg7ICAvLyBObyBuZWVkIHRvIHJldmVyc2UuIEl0IHByaW50cyBmcm9tIGN1cnJlbnQgdG8gcm9vdC4KICAgIH0KCiAgICAvLyBIZXVyaXN0aWMgZnVuY3Rpb24KICAgIHN0YXRpYyBpbnQgZXN0aW1hdGVfaGV1cmlzdGljVmFsdWVzKEV4cGVuc2VfOF9wdXp6bGUgc3RhdGUsIEV4cGVuc2VfOF9wdXp6bGUgZ29hbCkgewogICAgICAgIGludCBjb3N0ID0gMDsKICAgICAgICBmb3IgKGludCBpID0gMDsgaSA8IDM7IGkrKykgewogICAgICAgICAgICBmb3IgKGludCBqID0gMDsgaiA8IDM7IGorKykgewogICAgICAgICAgICAgICAgaW50IHRpbGUgPSBzdGF0ZS5tYXRbaV1bal07CiAgICAgICAgICAgICAgICBpZiAodGlsZSAhPSAwKSB7CiAgICAgICAgICAgICAgICAgICAgaW50IGdvYWxJID0gKHRpbGUgLSAxKSAvIDM7CiAgICAgICAgICAgICAgICAgICAgaW50IGdvYWxKID0gKHRpbGUgLSAxKSAlIDM7CiAgICAgICAgICAgICAgICAgICAgaW50IG1vdmVzID0gTWF0aC5hYnMoaSAtIGdvYWxJKSArIE1hdGguYWJzKGogLSBnb2FsSik7CiAgICAgICAgICAgICAgICAgICAgY29zdCArPSBtb3ZlcyAqIHRpbGU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIGNvc3Q7CiAgICB9CgogICAgLy8gUGFyZW50IGNoYWluIGZyb20gY3VycmVudCBzdGF0ZSB0byByb290IChubyByZXZlcnNhbCkKICAgIHN0YXRpYyBMaXN0PEV4cGVuc2VfOF9wdXp6bGU+IGdldFBhcmVudENoYWluKEV4cGVuc2VfOF9wdXp6bGUgc3RhdGUpIHsKICAgICAgICBMaXN0PEV4cGVuc2VfOF9wdXp6bGU+IGNoYWluID0gbmV3IEFycmF5TGlzdDw+KCk7CiAgICAgICAgd2hpbGUgKHN0YXRlICE9IG51bGwpIHsKICAgICAgICAgICAgY2hhaW4uYWRkKHN0YXRlKTsgIC8vIFN0YXJ0IGZyb20gY3VycmVudCBzdGF0ZQogICAgICAgICAgICBzdGF0ZSA9IHN0YXRlLnByZWRlY2Vzc29yOwogICAgICAgIH0KICAgICAgICByZXR1cm4gY2hhaW47CiAgICB9CgogICAgLy8gTG9nIHN0YXRlIGFuZCBpdHMgcHJlZGVjZXNzb3IgY2hhaW4KICAgIHN0YXRpYyB2b2lkIGR1bXBMb2dnZXIoRXhwZW5zZV84X3B1enpsZSBwcmVzZW50UHV6emxlTWF0LCBMaXN0PEV4cGVuc2VfOF9wdXp6bGU+IHN1Y2Nlc3NvcnMsIFNldDxTdHJpbmc+IGNsb3NlZFNldCwgQ29sbGVjdGlvbjxFeHBlbnNlXzhfcHV6emxlPiBmcm9udGllciwgaW50IE5Qb3AsIFN0cmluZyBsb2dGaWxlLCBFeHBlbnNlXzhfcHV6emxlIGdvYWxTdGF0ZSwgU3RyaW5nIG1ldGhvZCkgdGhyb3dzIElPRXhjZXB0aW9uIHsKICAgICAgICBpZiAobG9nRmlsZSAhPSBudWxsKSB7CiAgICAgICAgICAgIEJ1ZmZlcmVkV3JpdGVyIGZpbGVFZGl0b3IgPSBuZXcgQnVmZmVyZWRXcml0ZXIobmV3IEZpbGVXcml0ZXIobG9nRmlsZSwgdHJ1ZSkpOwoKICAgICAgICAgICAgaWYgKG1ldGhvZC5lcXVhbHMoImEqIikgfHwgbWV0aG9kLmVxdWFscygiZ3JlZWR5IikpIHsKICAgICAgICAgICAgICAgIGZpbGVFZGl0b3Iud3JpdGUoU3RyaW5nLmZvcm1hdCgiR2VuZXJhdGluZyBzdWNjZXNzb3JzIHRvIDwgc3RhdGUgPSAlcywgYWN0aW9uID0geyVzfSBnKG4pID0gJWQsIGQgPSAlZCwgZihuKSA9ICVkLCBQYXJlbnQgPSBQb2ludGVyIHRvICVzID46XG4iLAogICAgICAgICAgICAgICAgICAgICAgICBBcnJheXMuZGVlcFRvU3RyaW5nKHByZXNlbnRQdXp6bGVNYXQubWF0KSwgcHJlc2VudFB1enpsZU1hdC5hY3Rpb24gPT0gbnVsbCA/ICJTdGFydCIgOiBwcmVzZW50UHV6emxlTWF0LmFjdGlvbiwgcHJlc2VudFB1enpsZU1hdC5jb3N0LAogICAgICAgICAgICAgICAgICAgICAgICBwcmVzZW50UHV6emxlTWF0LmRlcHRoLCBwcmVzZW50UHV6emxlTWF0LmNvc3QgKyBlc3RpbWF0ZV9oZXVyaXN0aWNWYWx1ZXMocHJlc2VudFB1enpsZU1hdCwgZ29hbFN0YXRlKSwKICAgICAgICAgICAgICAgICAgICAgICAgcHJlc2VudFB1enpsZU1hdC5wcmVkZWNlc3NvciA9PSBudWxsID8gIk5vbmUiIDogQXJyYXlzLmRlZXBUb1N0cmluZyhwcmVzZW50UHV6emxlTWF0LnByZWRlY2Vzc29yLm1hdCkpKTsKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIGZpbGVFZGl0b3Iud3JpdGUoU3RyaW5nLmZvcm1hdCgiR2VuZXJhdGluZyBzdWNjZXNzb3JzIHRvIDwgc3RhdGUgPSAlcywgYWN0aW9uID0geyVzfSBjb3N0ID0gJWQsIGQgPSAlZCwgUGFyZW50ID0gUG9pbnRlciB0byAlcyA+OlxuIiwKICAgICAgICAgICAgICAgICAgICAgICAgQXJyYXlzLmRlZXBUb1N0cmluZyhwcmVzZW50UHV6emxlTWF0Lm1hdCksIHByZXNlbnRQdXp6bGVNYXQuYWN0aW9uID09IG51bGwgPyAiU3RhcnQiIDogcHJlc2VudFB1enpsZU1hdC5hY3Rpb24sIHByZXNlbnRQdXp6bGVNYXQuY29zdCwKICAgICAgICAgICAgICAgICAgICAgICAgcHJlc2VudFB1enpsZU1hdC5kZXB0aCwgcHJlc2VudFB1enpsZU1hdC5wcmVkZWNlc3NvciA9PSBudWxsID8gIk5vbmUiIDogQXJyYXlzLmRlZXBUb1N0cmluZyhwcmVzZW50UHV6emxlTWF0LnByZWRlY2Vzc29yLm1hdCkpKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgZmlsZUVkaXRvci53cml0ZSgiXHRzdWNjZXNzb3JzIGdlbmVyYXRlZCAtPiBcbiIpOwogICAgICAgICAgICBmaWxlRWRpdG9yLndyaXRlKCJcdENsb3NlZDogIiArIGNsb3NlZFNldCArICJcbiIpOwogICAgICAgICAgICBmaWxlRWRpdG9yLndyaXRlKCJcdEZyaW5nZTogW1xuIik7CgogICAgICAgICAgICAvLyBJdGVyYXRlIG92ZXIgZnJvbnRpZXIgYW5kIGxvZyB0aGUgc3RhdGUgYW5kIGl0cyBwcmVkZWNlc3NvciBjaGFpbgogICAgICAgICAgICBmb3IgKEV4cGVuc2VfOF9wdXp6bGUgc3RhdGUgOiBmcm9udGllcikgewogICAgICAgICAgICAgICAgTGlzdDxFeHBlbnNlXzhfcHV6emxlPiBwcmVkZWNlc3NvckNoYWluID0gZ2V0UGFyZW50Q2hhaW4oc3RhdGUpOyAgLy8gQ29ycmVjdCBwcmVkZWNlc3NvciBjaGFpbgoKICAgICAgICAgICAgICAgIC8vIFByaW50IGNoYWluIGZyb20gY3VycmVudCBzdGF0ZSB0byByb290CiAgICAgICAgICAgICAgICBTdHJpbmdCdWlsZGVyIHByZWRlY2Vzc29yQ2hhaW5TdHIgPSBuZXcgU3RyaW5nQnVpbGRlcigpOwogICAgICAgICAgICAgICAgZm9yIChpbnQgaSA9IDA7IGkgPCBwcmVkZWNlc3NvckNoYWluLnNpemUoKTsgaSsrKSB7ICAvLyBLZWVwIG9yZGVyIGFzLWlzCiAgICAgICAgICAgICAgICAgICAgRXhwZW5zZV84X3B1enpsZSBwID0gcHJlZGVjZXNzb3JDaGFpbi5nZXQoaSk7CiAgICAgICAgICAgICAgICAgICAgcHJlZGVjZXNzb3JDaGFpblN0ci5hcHBlbmQoU3RyaW5nLmZvcm1hdCgiPCBzdGF0ZSA9ICVzLCBhY3Rpb24gPSB7TW92ZSAlc30sIGcobikgPSAlZCwgZCA9ICVkLCBmKG4pID0gJWQgPiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBBcnJheXMuZGVlcFRvU3RyaW5nKHAubWF0KSwgcC5hY3Rpb24sIHAuY29zdCwgcC5kZXB0aCwgbWV0aG9kLmVxdWFscygiYSoiKSB8fCBtZXRob2QuZXF1YWxzKCJncmVlZHkiKSA/IChwLmNvc3QgKyBlc3RpbWF0ZV9oZXVyaXN0aWNWYWx1ZXMocCwgZ29hbFN0YXRlKSkgOiBwLmNvc3QpKTsKICAgICAgICAgICAgICAgICAgICBpZiAoaSAhPSBwcmVkZWNlc3NvckNoYWluLnNpemUoKSAtIDEpIHsgIC8vIEFwcGVuZCBhcnJvdyB1bmxlc3MgbGFzdCBzdGF0ZQogICAgICAgICAgICAgICAgICAgICAgICBwcmVkZWNlc3NvckNoYWluU3RyLmFwcGVuZCgiIC0+ICIpOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGZpbGVFZGl0b3Iud3JpdGUoIlx0XHQiICsgcHJlZGVjZXNzb3JDaGFpblN0ci50b1N0cmluZygpICsgIlxuIik7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGZpbGVFZGl0b3Iud3JpdGUoIlx0XVxuIik7CiAgICAgICAgICAgIGZpbGVFZGl0b3IuY2xvc2UoKTsKICAgICAgICB9CiAgICB9CgoKICAgIC8vIERMUyAoRGVwdGgtTGltaXRlZCBTZWFyY2gpCiAgICBzdGF0aWMgRXhwZW5zZV84X3B1enpsZSBkbHMoRXhwZW5zZV84X3B1enpsZSBzdGFydCwgRXhwZW5zZV84X3B1enpsZSBnb2FsLCBpbnQgREwsIGJvb2xlYW4gbG9nQm9vbGVhbiwgU3RyaW5nIGxvZ0ZpbGUpIHRocm93cyBJT0V4Y2VwdGlvbiB7CiAgICAgICAgU3RhY2s8RXhwZW5zZV84X3B1enpsZT4gZnJvbnRpZXIgPSBuZXcgU3RhY2s8PigpOwogICAgICAgIFNldDxTdHJpbmc+IGNsb3NlZFNldCA9IG5ldyBIYXNoU2V0PD4oKTsKICAgICAgICBmcm9udGllci5wdXNoKHN0YXJ0KTsKICAgICAgICBpbnQgTlBvcCA9IDAsIE5HZW4gPSAxLCBORXhwID0gMCwgTUZTaXplID0gMDsKCiAgICAgICAgd2hpbGUgKCFmcm9udGllci5pc0VtcHR5KCkpIHsKICAgICAgICAgICAgTUZTaXplID0gTWF0aC5tYXgoTUZTaXplLCBmcm9udGllci5zaXplKCkpOwogICAgICAgICAgICBFeHBlbnNlXzhfcHV6emxlIHByZXNlbnRQdXp6bGVNYXQgPSBmcm9udGllci5wb3AoKTsKICAgICAgICAgICAgTlBvcCsrOwoKICAgICAgICAgICAgaWYgKHByZXNlbnRQdXp6bGVNYXQuZGVwdGggPiBETCkgewogICAgICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGlmIChBcnJheXMuZGVlcEVxdWFscyhwcmVzZW50UHV6emxlTWF0Lm1hdCwgZ29hbC5tYXQpKSB7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk5vZGVzIEV4cGFuZGVkOiAiICsgTkV4cCk7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk5vZGVzIEdlbmVyYXRlZDogIiArIE5HZW4pOwogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJOb2RlcyBQb3BwZWQ6ICIgKyBOUG9wKTsKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTWF4IEZyaW5nZSBTaXplOiAiICsgTUZTaXplKTsKICAgICAgICAgICAgICAgIHJldHVybiBwcmVzZW50UHV6emxlTWF0OwogICAgICAgICAgICB9CgogICAgICAgICAgICBTdHJpbmcgbWF0U3RyID0gQXJyYXlzLmRlZXBUb1N0cmluZyhwcmVzZW50UHV6emxlTWF0Lm1hdCk7CiAgICAgICAgICAgIGlmIChjbG9zZWRTZXQuY29udGFpbnMobWF0U3RyKSkgewogICAgICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGNsb3NlZFNldC5hZGQobWF0U3RyKTsKICAgICAgICAgICAgTkV4cCsrOwogICAgICAgICAgICBMaXN0PEV4cGVuc2VfOF9wdXp6bGU+IHN1Y2Nlc3NvcnMgPSBwcmVzZW50UHV6emxlTWF0LmdldEV4cGFuc2lvbnMoKTsKICAgICAgICAgICAgZm9yIChFeHBlbnNlXzhfcHV6emxlIG5laWdoYm9yIDogc3VjY2Vzc29ycykgewogICAgICAgICAgICAgICAgaWYgKG5laWdoYm9yLmRlcHRoIDw9IERMKSB7CiAgICAgICAgICAgICAgICAgICAgZnJvbnRpZXIucHVzaChuZWlnaGJvcik7CiAgICAgICAgICAgICAgICAgICAgTkdlbisrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgICAgICBpZiAobG9nQm9vbGVhbikgewogICAgICAgICAgICAgICAgZHVtcExvZ2dlcihwcmVzZW50UHV6emxlTWF0LCBzdWNjZXNzb3JzLCBjbG9zZWRTZXQsIGZyb250aWVyLCBOUG9wLCBsb2dGaWxlLCBnb2FsLCAiZGxzIik7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CgoKICAgIC8vIEJGUwogICAgc3RhdGljIEV4cGVuc2VfOF9wdXp6bGUgYmZzKEV4cGVuc2VfOF9wdXp6bGUgc3RhcnQsIEV4cGVuc2VfOF9wdXp6bGUgZ29hbCwgYm9vbGVhbiBsb2dCb29sZWFuLCBTdHJpbmcgbG9nRmlsZSkgdGhyb3dzIElPRXhjZXB0aW9uIHsKICAgICAgICBRdWV1ZTxFeHBlbnNlXzhfcHV6emxlPiBmcm9udGllciA9IG5ldyBMaW5rZWRMaXN0PD4oKTsKICAgICAgICBTZXQ8U3RyaW5nPiBjbG9zZWRTZXQgPSBuZXcgSGFzaFNldDw+KCk7CiAgICAgICAgZnJvbnRpZXIuYWRkKHN0YXJ0KTsKICAgICAgICBpbnQgTlBvcCA9IDAsIE5HZW4gPSAxLCBORXhwID0gMCwgTUZTaXplID0gMDsKCiAgICAgICAgd2hpbGUgKCFmcm9udGllci5pc0VtcHR5KCkpIHsKICAgICAgICAgICAgTUZTaXplID0gTWF0aC5tYXgoTUZTaXplLCBmcm9udGllci5zaXplKCkpOwogICAgICAgICAgICBFeHBlbnNlXzhfcHV6emxlIHByZXNlbnRQdXp6bGVNYXQgPSBmcm9udGllci5wb2xsKCk7CiAgICAgICAgICAgIE5Qb3ArKzsKCiAgICAgICAgICAgIGlmIChBcnJheXMuZGVlcEVxdWFscyhwcmVzZW50UHV6emxlTWF0Lm1hdCwgZ29hbC5tYXQpKSB7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk5vZGVzIEV4cGFuZGVkOiAiICsgTkV4cCk7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk5vZGVzIEdlbmVyYXRlZDogIiArIE5HZW4pOwogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJOb2RlcyBQb3BwZWQ6ICIgKyBOUG9wKTsKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTWF4IEZyaW5nZSBTaXplOiAiICsgTUZTaXplKTsKICAgICAgICAgICAgICAgIHJldHVybiBwcmVzZW50UHV6emxlTWF0OwogICAgICAgICAgICB9CgogICAgICAgICAgICBTdHJpbmcgbWF0U3RyID0gQXJyYXlzLmRlZXBUb1N0cmluZyhwcmVzZW50UHV6emxlTWF0Lm1hdCk7CiAgICAgICAgICAgIGlmIChjbG9zZWRTZXQuY29udGFpbnMobWF0U3RyKSkgewogICAgICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGNsb3NlZFNldC5hZGQobWF0U3RyKTsKICAgICAgICAgICAgTkV4cCsrOwogICAgICAgICAgICBMaXN0PEV4cGVuc2VfOF9wdXp6bGU+IHN1Y2Nlc3NvcnMgPSBwcmVzZW50UHV6emxlTWF0LmdldEV4cGFuc2lvbnMoKTsKICAgICAgICAgICAgZm9yIChFeHBlbnNlXzhfcHV6emxlIG5laWdoYm9yIDogc3VjY2Vzc29ycykgewogICAgICAgICAgICAgICAgZnJvbnRpZXIuYWRkKG5laWdoYm9yKTsKICAgICAgICAgICAgICAgIE5HZW4rKzsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgaWYgKGxvZ0Jvb2xlYW4pIHsKICAgICAgICAgICAgICAgIGR1bXBMb2dnZXIocHJlc2VudFB1enpsZU1hdCwgc3VjY2Vzc29ycywgY2xvc2VkU2V0LCBmcm9udGllciwgTlBvcCwgbG9nRmlsZSwgZ29hbCwgImJmcyIpOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiBudWxsOwogICAgfQoKCgogICAgLy8gSURTCiAgICBzdGF0aWMgRXhwZW5zZV84X3B1enpsZSBpZHMoRXhwZW5zZV84X3B1enpsZSBzdGFydCwgRXhwZW5zZV84X3B1enpsZSBnb2FsLCBib29sZWFuIGxvZ0Jvb2xlYW4sIFN0cmluZyBsb2dGaWxlKSB0aHJvd3MgSU9FeGNlcHRpb24gewogICAgICAgIGludCBETCA9IDA7CiAgICAgICAgd2hpbGUgKHRydWUpIHsKICAgICAgICAgICAgRXhwZW5zZV84X3B1enpsZSByZXN1bHQgPSBkbHMoc3RhcnQsIGdvYWwsIERMLCBsb2dCb29sZWFuLCBsb2dGaWxlKTsKICAgICAgICAgICAgaWYgKHJlc3VsdCAhPSBudWxsKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgICAgICAgICB9CiAgICAgICAgICAgIERMKys7CiAgICAgICAgfQogICAgfQoKICAgIC8vIFVDUwogICAgc3RhdGljIEV4cGVuc2VfOF9wdXp6bGUgdWNzKEV4cGVuc2VfOF9wdXp6bGUgc3RhcnQsIEV4cGVuc2VfOF9wdXp6bGUgZ29hbCwgYm9vbGVhbiBsb2dCb29sZWFuLCBTdHJpbmcgbG9nRmlsZSkgdGhyb3dzIElPRXhjZXB0aW9uIHsKICAgICAgICBQcmlvcml0eVF1ZXVlPEV4cGVuc2VfOF9wdXp6bGU+IGZyb250aWVyID0gbmV3IFByaW9yaXR5UXVldWU8PigpOwogICAgICAgIFNldDxTdHJpbmc+IGNsb3NlZFNldCA9IG5ldyBIYXNoU2V0PD4oKTsKICAgICAgICBmcm9udGllci5hZGQoc3RhcnQpOwogICAgICAgIGludCBOUG9wID0gMCwgTkdlbiA9IDEsIE5FeHAgPSAwLCBNRlNpemUgPSAwOwoKICAgICAgICB3aGlsZSAoIWZyb250aWVyLmlzRW1wdHkoKSkgewogICAgICAgICAgICBNRlNpemUgPSBNYXRoLm1heChNRlNpemUsIGZyb250aWVyLnNpemUoKSk7CiAgICAgICAgICAgIEV4cGVuc2VfOF9wdXp6bGUgcHJlc2VudFB1enpsZU1hdCA9IGZyb250aWVyLnBvbGwoKTsKICAgICAgICAgICAgTlBvcCsrOwoKICAgICAgICAgICAgaWYgKEFycmF5cy5kZWVwRXF1YWxzKHByZXNlbnRQdXp6bGVNYXQubWF0LCBnb2FsLm1hdCkpIHsKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTm9kZXMgRXhwYW5kZWQ6ICIgKyBORXhwKTsKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTm9kZXMgR2VuZXJhdGVkOiAiICsgTkdlbik7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk5vZGVzIFBvcHBlZDogIiArIE5Qb3ApOwogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJNYXggRnJpbmdlIFNpemU6ICIgKyBNRlNpemUpOwogICAgICAgICAgICAgICAgcmV0dXJuIHByZXNlbnRQdXp6bGVNYXQ7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIFN0cmluZyBtYXRTdHIgPSBBcnJheXMuZGVlcFRvU3RyaW5nKHByZXNlbnRQdXp6bGVNYXQubWF0KTsKICAgICAgICAgICAgaWYgKGNsb3NlZFNldC5jb250YWlucyhtYXRTdHIpKSB7CiAgICAgICAgICAgICAgICBjb250aW51ZTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgY2xvc2VkU2V0LmFkZChtYXRTdHIpOwogICAgICAgICAgICBORXhwKys7CiAgICAgICAgICAgIExpc3Q8RXhwZW5zZV84X3B1enpsZT4gc3VjY2Vzc29ycyA9IHByZXNlbnRQdXp6bGVNYXQuZ2V0RXhwYW5zaW9ucygpOwogICAgICAgICAgICBmb3IgKEV4cGVuc2VfOF9wdXp6bGUgbmVpZ2hib3IgOiBzdWNjZXNzb3JzKSB7CiAgICAgICAgICAgICAgICBmcm9udGllci5hZGQobmVpZ2hib3IpOwogICAgICAgICAgICAgICAgTkdlbisrOwogICAgICAgICAgICB9CgogICAgICAgICAgICBpZiAobG9nQm9vbGVhbikgewogICAgICAgICAgICAgICAgZHVtcExvZ2dlcihwcmVzZW50UHV6emxlTWF0LCBzdWNjZXNzb3JzLCBjbG9zZWRTZXQsIGZyb250aWVyLCBOUG9wLCBsb2dGaWxlLCBnb2FsLCAidWNzIik7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIG51bGw7CiAgICB9CgogICAgLy8gR3JlZWR5CiAgICBzdGF0aWMgRXhwZW5zZV84X3B1enpsZSBncmVlZHkoRXhwZW5zZV84X3B1enpsZSBzdGFydCwgRXhwZW5zZV84X3B1enpsZSBnb2FsLCBib29sZWFuIGxvZ0Jvb2xlYW4sIFN0cmluZyBsb2dGaWxlKSB0aHJvd3MgSU9FeGNlcHRpb24gewogICAgICAgIFByaW9yaXR5UXVldWU8RXhwZW5zZV84X3B1enpsZT4gZnJvbnRpZXIgPSBuZXcgUHJpb3JpdHlRdWV1ZTw+KENvbXBhcmF0b3IuY29tcGFyaW5nSW50KHMgLT4gZXN0aW1hdGVfaGV1cmlzdGljVmFsdWVzKHMsIGdvYWwpKSk7CiAgICAgICAgU2V0PFN0cmluZz4gY2xvc2VkU2V0ID0gbmV3IEhhc2hTZXQ8PigpOwogICAgICAgIGZyb250aWVyLmFkZChzdGFydCk7CiAgICAgICAgaW50IE5Qb3AgPSAwLCBOR2VuID0gMSwgTkV4cCA9IDAsIE1GU2l6ZSA9IDA7CgogICAgICAgIHdoaWxlICghZnJvbnRpZXIuaXNFbXB0eSgpKSB7CiAgICAgICAgICAgIE1GU2l6ZSA9IE1hdGgubWF4KE1GU2l6ZSwgZnJvbnRpZXIuc2l6ZSgpKTsKICAgICAgICAgICAgRXhwZW5zZV84X3B1enpsZSBwcmVzZW50UHV6emxlTWF0ID0gZnJvbnRpZXIucG9sbCgpOwogICAgICAgICAgICBOUG9wKys7CgogICAgICAgICAgICBpZiAoQXJyYXlzLmRlZXBFcXVhbHMocHJlc2VudFB1enpsZU1hdC5tYXQsIGdvYWwubWF0KSkgewogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJOb2RlcyBFeHBhbmRlZDogIiArIE5FeHApOwogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJOb2RlcyBHZW5lcmF0ZWQ6ICIgKyBOR2VuKTsKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTm9kZXMgUG9wcGVkOiAiICsgTlBvcCk7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk1heCBGcmluZ2UgU2l6ZTogIiArIE1GU2l6ZSk7CiAgICAgICAgICAgICAgICByZXR1cm4gcHJlc2VudFB1enpsZU1hdDsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgU3RyaW5nIG1hdFN0ciA9IEFycmF5cy5kZWVwVG9TdHJpbmcocHJlc2VudFB1enpsZU1hdC5tYXQpOwogICAgICAgICAgICBpZiAoY2xvc2VkU2V0LmNvbnRhaW5zKG1hdFN0cikpIHsKICAgICAgICAgICAgICAgIGNvbnRpbnVlOwogICAgICAgICAgICB9CgogICAgICAgICAgICBjbG9zZWRTZXQuYWRkKG1hdFN0cik7CiAgICAgICAgICAgIE5FeHArKzsKICAgICAgICAgICAgTGlzdDxFeHBlbnNlXzhfcHV6emxlPiBzdWNjZXNzb3JzID0gcHJlc2VudFB1enpsZU1hdC5nZXRFeHBhbnNpb25zKCk7CiAgICAgICAgICAgIGZvciAoRXhwZW5zZV84X3B1enpsZSBuZWlnaGJvciA6IHN1Y2Nlc3NvcnMpIHsKICAgICAgICAgICAgICAgIGZyb250aWVyLmFkZChuZWlnaGJvcik7CiAgICAgICAgICAgICAgICBOR2VuKys7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGlmIChsb2dCb29sZWFuKSB7CiAgICAgICAgICAgICAgICBkdW1wTG9nZ2VyKHByZXNlbnRQdXp6bGVNYXQsIHN1Y2Nlc3NvcnMsIGNsb3NlZFNldCwgZnJvbnRpZXIsIE5Qb3AsIGxvZ0ZpbGUsIGdvYWwsICJncmVlZHkiKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KCiAgICAvLyBBKgogICAgc3RhdGljIEV4cGVuc2VfOF9wdXp6bGUgYVN0YXIoRXhwZW5zZV84X3B1enpsZSBzdGFydCwgRXhwZW5zZV84X3B1enpsZSBnb2FsLCBib29sZWFuIGxvZ0Jvb2xlYW4sIFN0cmluZyBsb2dGaWxlKSB0aHJvd3MgSU9FeGNlcHRpb24gewogICAgICAgIFByaW9yaXR5UXVldWU8RXhwZW5zZV84X3B1enpsZT4gZnJvbnRpZXIgPSBuZXcgUHJpb3JpdHlRdWV1ZTw+KENvbXBhcmF0b3IuY29tcGFyaW5nSW50KHMgLT4gcy5jb3N0ICsgZXN0aW1hdGVfaGV1cmlzdGljVmFsdWVzKHMsIGdvYWwpKSk7CiAgICAgICAgU2V0PFN0cmluZz4gY2xvc2VkU2V0ID0gbmV3IEhhc2hTZXQ8PigpOwogICAgICAgIGZyb250aWVyLmFkZChzdGFydCk7CiAgICAgICAgaW50IE5Qb3AgPSAwLCBOR2VuID0gMSwgTkV4cCA9IDAsIE1GU2l6ZSA9IDA7CgogICAgICAgIHdoaWxlICghZnJvbnRpZXIuaXNFbXB0eSgpKSB7CiAgICAgICAgICAgIE1GU2l6ZSA9IE1hdGgubWF4KE1GU2l6ZSwgZnJvbnRpZXIuc2l6ZSgpKTsKICAgICAgICAgICAgRXhwZW5zZV84X3B1enpsZSBwcmVzZW50UHV6emxlTWF0ID0gZnJvbnRpZXIucG9sbCgpOwogICAgICAgICAgICBOUG9wKys7CgogICAgICAgICAgICBpZiAoQXJyYXlzLmRlZXBFcXVhbHMocHJlc2VudFB1enpsZU1hdC5tYXQsIGdvYWwubWF0KSkgewogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJOb2RlcyBFeHBhbmRlZDogIiArIE5FeHApOwogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJOb2RlcyBHZW5lcmF0ZWQ6ICIgKyBOR2VuKTsKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTm9kZXMgUG9wcGVkOiAiICsgTlBvcCk7CiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk1heCBGcmluZ2UgU2l6ZTogIiArIE1GU2l6ZSk7CiAgICAgICAgICAgICAgICByZXR1cm4gcHJlc2VudFB1enpsZU1hdDsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgU3RyaW5nIG1hdFN0ciA9IEFycmF5cy5kZWVwVG9TdHJpbmcocHJlc2VudFB1enpsZU1hdC5tYXQpOwogICAgICAgICAgICBpZiAoY2xvc2VkU2V0LmNvbnRhaW5zKG1hdFN0cikpIHsKICAgICAgICAgICAgICAgIGNvbnRpbnVlOwogICAgICAgICAgICB9CgogICAgICAgICAgICBjbG9zZWRTZXQuYWRkKG1hdFN0cik7CiAgICAgICAgICAgIE5FeHArKzsKICAgICAgICAgICAgTGlzdDxFeHBlbnNlXzhfcHV6emxlPiBzdWNjZXNzb3JzID0gcHJlc2VudFB1enpsZU1hdC5nZXRFeHBhbnNpb25zKCk7CiAgICAgICAgICAgIGZvciAoRXhwZW5zZV84X3B1enpsZSBuZWlnaGJvciA6IHN1Y2Nlc3NvcnMpIHsKICAgICAgICAgICAgICAgIGZyb250aWVyLmFkZChuZWlnaGJvcik7CiAgICAgICAgICAgICAgICBOR2VuKys7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGlmIChsb2dCb29sZWFuKSB7CiAgICAgICAgICAgICAgICBkdW1wTG9nZ2VyKHByZXNlbnRQdXp6bGVNYXQsIHN1Y2Nlc3NvcnMsIGNsb3NlZFNldCwgZnJvbnRpZXIsIE5Qb3AsIGxvZ0ZpbGUsIGdvYWwsICJhKiIpOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybiBudWxsOwogICAgfQoKICAgIHB1YmxpYyBzdGF0aWMgdm9pZCBtYWluKFN0cmluZ1tdIGFyZ3MpIHRocm93cyBJT0V4Y2VwdGlvbiB7CiAgICAgICAgaWYgKGFyZ3MubGVuZ3RoIDwgMikgewogICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIlVzYWdlOiBqYXZhIEV4cGVuc2VfOF9wdXp6bGUgPHN0YXJ0LWZpbGU+IDxnb2FsLWZpbGU+IDxtZXRob2Q+IDxsb2ctZmlsZT4iKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgU3RyaW5nIHN0YXJ0RmlsZSA9IGFyZ3NbMF07CiAgICAgICAgU3RyaW5nIGdvYWxGaWxlID0gYXJnc1sxXTsKICAgICAgIAogICAgICAgIFN0cmluZyBtZXRob2QgPSAiYSoiOwogICAgICAgIGJvb2xlYW4gbG9nQm9vbGVhbiA9IGZhbHNlOwogICAgICAgIFN0cmluZyBsb2dGaWxlID0gInRyYWNlRGF0ZVRpbWUudHh0IiA7CiAgICAgICAgLy8gdHJhY2UtRGF0ZS1UaW1lLnR4dCAKICAgICAgICBsb2dGaWxlID0gInRyYWNlLSIgKyBuZXcgU2ltcGxlRGF0ZUZvcm1hdCgieXl5eS1NTS1kZC1ISC1tbS1zcyIpLmZvcm1hdChuZXcgRGF0ZSgpKSArICIudHh0IjsKICAgICAgICBpZihhcmdzLmxlbmd0aCA+IDIpewogICAgICAgICAgICBpZihhcmdzWzJdLmVxdWFscygidHJ1ZSIpIHx8IGFyZ3NbMl0uZXF1YWxzKCJmYWxzZSIpKXsKICAgICAgICAgICAgICAgIGxvZ0Jvb2xlYW4gPSBCb29sZWFuLnBhcnNlQm9vbGVhbihhcmdzWzJdKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlewogICAgICAgICAgICAgICAgbWV0aG9kID0gYXJnc1syXTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBpZihhcmdzLmxlbmd0aCA+IDMpewogICAgICAgICAgICBsb2dCb29sZWFuID0gQm9vbGVhbi5wYXJzZUJvb2xlYW4oYXJnc1szXSk7CiAgICAgICAgfQoKCiAgICAgICAgRXhwZW5zZV84X3B1enpsZSBzdGFydFN0YXRlID0gRXhwZW5zZV84X3B1enpsZS5jb252ZXJ0VG9NYXRGcm9tRmlsZShzdGFydEZpbGUpOwogICAgICAgIEV4cGVuc2VfOF9wdXp6bGUgZ29hbFN0YXRlID0gRXhwZW5zZV84X3B1enpsZS5jb252ZXJ0VG9NYXRGcm9tRmlsZShnb2FsRmlsZSk7CgogICAgICAgIEV4cGVuc2VfOF9wdXp6bGUgYW5zd2VyID0gbnVsbDsKCiAgICAgICAgc3dpdGNoIChtZXRob2QpIHsKICAgICAgICAgICAgY2FzZSAiYmZzIjoKICAgICAgICAgICAgICAgIGFuc3dlciA9IEV4cGVuc2VfOF9wdXp6bGUuYmZzKHN0YXJ0U3RhdGUsIGdvYWxTdGF0ZSwgbG9nQm9vbGVhbiwgbG9nRmlsZSk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiZGZzIjoKICAgICAgICAgICAgICAgIGFuc3dlciA9IEV4cGVuc2VfOF9wdXp6bGUuZGxzKHN0YXJ0U3RhdGUsIGdvYWxTdGF0ZSwgSW50ZWdlci5NQVhfVkFMVUUsIGxvZ0Jvb2xlYW4sIGxvZ0ZpbGUpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImRscyI6CiAgICAgICAgICAgICAgICBpbnQgREwgPSAxMDsKICAgICAgICAgICAgICAgIFNjYW5uZXIgc2MgPSBuZXcgU2Nhbm5lcihTeXN0ZW0uaW4pOwogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJFbnRlciB0aGUgZGVwdGggbGltaXQ6ICIpOwogICAgICAgICAgICAgICAgU3RyaW5nIGlucHV0VGFrZW4gPSBzYy5uZXh0TGluZSgpOwogICAgICAgICAgICAgICAgaWYoaW5wdXRUYWtlbi5sZW5ndGgoKSA+IDAgJiYgaW5wdXRUYWtlbi5tYXRjaGVzKCJbMC05XSsiKSl7CiAgICAgICAgICAgICAgICAgICAgREwgPSBJbnRlZ2VyLnBhcnNlSW50KGlucHV0VGFrZW4pOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgZWxzZXsKICAgICAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIkludmFsaWQgaW5wdXQsIHVzaW5nIGRlZmF1bHQgZGVwdGggbGltaXQgb2YgMTAiKTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBhbnN3ZXIgPSBFeHBlbnNlXzhfcHV6emxlLmRscyhzdGFydFN0YXRlLCBnb2FsU3RhdGUsIERMLCBsb2dCb29sZWFuLCBsb2dGaWxlKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJpZHMiOgogICAgICAgICAgICAgICAgYW5zd2VyID0gRXhwZW5zZV84X3B1enpsZS5pZHMoc3RhcnRTdGF0ZSwgZ29hbFN0YXRlLCBsb2dCb29sZWFuLCBsb2dGaWxlKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJ1Y3MiOgogICAgICAgICAgICAgICAgYW5zd2VyID0gRXhwZW5zZV84X3B1enpsZS51Y3Moc3RhcnRTdGF0ZSwgZ29hbFN0YXRlLCBsb2dCb29sZWFuLCBsb2dGaWxlKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJncmVlZHkiOgogICAgICAgICAgICAgICAgYW5zd2VyID0gRXhwZW5zZV84X3B1enpsZS5ncmVlZHkoc3RhcnRTdGF0ZSwgZ29hbFN0YXRlLCBsb2dCb29sZWFuLCBsb2dGaWxlKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJhKiI6CiAgICAgICAgICAgICAgICBhbnN3ZXIgPSBFeHBlbnNlXzhfcHV6emxlLmFTdGFyKHN0YXJ0U3RhdGUsIGdvYWxTdGF0ZSwgbG9nQm9vbGVhbiwgbG9nRmlsZSk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiVW5rbm93biBtZXRob2QhIik7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQoKICAgICAgICBpZiAoYW5zd2VyICE9IG51bGwpIHsKICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJTb2x1dGlvbiBGb3VuZCBhdCBkZXB0aCAiICsgYW5zd2VyLmRlcHRoICsgIiB3aXRoIGNvc3Qgb2YgIiArIGFuc3dlci5jb3N0KTsKICAgICAgICAgICAgTGlzdDxTdHJpbmc+IHN0ZXBzID0gYW5zd2VyLnBhcmVudENoYWluR2VuZXJhdG9yKCk7CiAgICAgICAgICAgIGZvciAoU3RyaW5nIHN0ZXAgOiBzdGVwcykgewogICAgICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJcdCIgKyBzdGVwKTsKICAgICAgICAgICAgfQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTm8gYW5zd2VyIGZvdW5kLiIpOwogICAgICAgIH0KICAgIH0KfQo=