package test.log
import java.io.* ;
import java.nio.charset.CharsetEncoder ;
import java.nio.charset.StandardCharsets ;
import java.time.Instant ;
import java.time.ZoneId ;
import java.time.format.DateTimeFormatter ;
import java.util.Locale ;
import java.util.Random ;
import java.util.concurrent.Semaphore ;
public class Logger {
public static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern ( "MMM dd, yyyy hh:mm:ss a" ) .withZone ( ZoneId.systemDefault ( ) ) ;
private final String fileName
= "log4j.log" ; private final long maxFileSizeBytes;
private Level minLevel;
private final int maxBackups;
private CountingWriter w;
private boolean stdoutMode;
private static Logger logger;
public static void setLogger( Logger logger) {
Logger.logger = logger;
}
public static Logger getLogger
( String name
) { if ( logger== null ) logger= new Logger( ) ;
return logger;
}
public enum Level{ debug( ) ,info( ) ,warn( ) ,error( ) ,fatal( ) ;
private final String nameUcase
; Level( ) {
this .
nameUcase = this .
name ( ) .
toUpperCase ( Locale .
ROOT ) ; }
}
public Logger
( long maxFileSizeBytes, Level minLevel,
String dirName,
boolean stdoutMode,
int maxBackups
) { this .maxFileSizeBytes = maxFileSizeBytes;
this .minLevel = minLevel;
this .dirName = dirName;
this .stdoutMode = stdoutMode;
this .
file = new File ( dirName,fileName
) ; this .maxBackups = maxBackups;
System .
out .
println ( "file= " + this .
file ) ; this .w = initFileWriter( ) ;
}
private Logger( ) {
this ( 1024 * 1024 * 20L,Level.info ,"/var/log" ,false , 10 ) ;
}
private CountingWriter initFileWriter( ) {
try {
final long length ;
final boolean exists = file.exists ( ) ;
if ( exists) {
length = file.length ( ) ;
}
else {
final File parentFile
= file.
getParentFile ( ) ; parentFile.mkdirs ( ) ;
length= 0 ;
}
CharsetEncoder encoder = StandardCharsets.UTF_8 .newEncoder ( ) ;
stdoutMode= false ;
return countingWriter;
System .
err .
println ( "error initialising logging! Setting 'stdout' mode" ) ; e.printStackTrace ( ) ;
stdoutMode= true ;
}
return null ;
}
public void flush( ) {
try {
w.flush ( ) ;
e.printStackTrace ( ) ;
}
}
if ( message== null && e== null ) return ;
if ( level.ordinal ( ) < minLevel.ordinal ( ) ) return ;
try {
if ( stdoutMode) {
writeToStdErr( message,e,null ,null ) ;
return ;
}
if ( rollLock.availablePermits ( ) < 1 ) {
writeToStdErr( message, e, "rolling" , null ) ;
return ;
}
w.write ( dateFormatter.format ( Instant.now ( ) ) ) ;
w.write ( ' ' ) ;
w.write ( level.nameUcase ) ;
w.write ( ' ' ) ;
w.write ( '[' ) ;
w.
write ( Thread .
currentThread ( ) .
getName ( ) ) ; w.write ( ']' ) ;
w.write ( ' ' ) ;
final boolean hasMessage = message != null ;
if ( hasMessage) {
w.write ( message) ;
}
if ( e!= null ) {
if ( hasMessage) w.write ( ' ' ) ;
e.printStackTrace ( pw) ;
}
w.write ( '\n ' ) ;
if ( level.ordinal ( ) >= Level.warn .ordinal ( ) || isDebugEnabled( ) ) w.flush ( ) ;
//flush immediately (e.g. in example of fatal error
//also flush immediately if debugging: since we don't care so much about perf if we're debugging
if ( w.getCountBytes ( ) >= maxFileSizeBytes) {
if ( rollLock.tryAcquire ( ) ) { //if someone's already on it, just give up
try {
roll( ) ;
} finally {
rollLock.release ( ) ;
}
}
}
} catch ( Exception e1
) { //last thing we want is logging causing issues writeToStdErr( message, e, "exception encountered" , e1) ;
}
}
if ( reasonCannotWriteToFile!= null ) {
System .
err .
print ( reasonCannotWriteToFile
) ; }
if ( reasonError!= null ) {
reasonError.printStackTrace ( ) ;
}
if ( message
!= null ) System .
err .
println ( message
) ; if ( e!= null ) e.printStackTrace ( ) ;
}
private final Semaphore rollLock = new Semaphore( 1 ) ;
private void roll( ) {
System .
out .
println ( "LOGGER::: roll thread=" + Thread .
currentThread ( ) .
getName ( ) + " t=" + System .
currentTimeMillis ( ) ) ; renameOldBackupFiles( ) ;
final File newestBackup
= backupFile
( 1 ) ; try {
w.close ( ) ;
e.printStackTrace ( ) ;
}
//rename the main log file to newest backup (which we just renamed)
boolean success = file.renameTo ( newestBackup) ;
System .
out .
println ( "LOGGER::: rename " + file
+ " to=" + newestBackup
+ " success=" + success
) ; this .w = initFileWriter( ) ; //should now be writing to file (which is empty)
}
private void renameOldBackupFiles( ) {
File oldestBackup
= backupFile
( maxBackups
) ; if ( oldestBackup.exists ( ) ) {
boolean deleted = oldestBackup.delete ( ) ;
System .
out .
println ( "LOGGER::: delete " + oldestBackup
+ " success=" + deleted
) ; }
// - rename all other backup files <5 in desc order (so log.4 -> log.5 and so on)
for ( int backup = maxBackups- 1 ; backup > 0 ; backup-- ) {
final File backupFile
= backupFile
( backup
) ; if ( ! backupFile.exists ( ) ) continue ;
final File destFile
= backupFile
( backup
+ 1 ) ; boolean success = backupFile.renameTo ( destFile) ;
System .
out .
println ( "LOGGER::: rename " + backupFile
+ " to=" + destFile
+ " success=" + success
) ; }
}
private File backupFile
( int backupNumber
) { return new File ( dirName,
String .
format ( "%s.%s" ,fileName, backupNumber
) ) ; }
private static class CountingWriter
extends Writer { private long countBytes;
public CountingWriter
( Writer w,
long initialCount
) { this .writer = w;
countBytes= initialCount;
}
public void write
( char [ ] cbuf,
int off,
int len
) throws IOException { writer.write ( cbuf,off,len) ;
final long wrote = ( len - off) ;
countBytes += wrote; //this will likely be wrong if we use extended chars which take two bytes? ascii-type chars take 1 byte, but some take 2. But probably doesn't matter, unless we start logging korean strings or something?
}
public void flush
( ) throws IOException { writer.
flush ( ) ; } public void close
( ) throws IOException { writer.
close ( ) ; }
public long getCountBytes( ) { return countBytes; }
}
//log4j api
public void debug
( String format
) { debug
( format,
null ) ; } public void debug
( Throwable e
) { debug
( null ,e
) ; } public void debug
( String format,
Throwable e
) { log
( Level.
debug ,format,e
) ; }
public void info
( String format
) { info
( format,
null ) ; } public void info
( Throwable e
) { info
( null ,e
) ; }
public void warn
( String format
) { warn
( format,
null ) ; } public void warn
( Throwable e
) { warn
( null ,e
) ; }
public void error
( String format
) { error
( format,
null ) ; } public void error
( Throwable e
) { error
( null ,e
) ; } public void error
( String format,
Throwable e
) { log
( Level.
error ,format,e
) ; }
public void fatal
( String format
) { fatal
( format,
null ) ; } public void fatal
( Throwable e
) { fatal
( null ,e
) ; } log( Level.fatal ,format,e) ;
}
//new api meths
public boolean isDebugEnabled( ) {
return minLevel.ordinal ( ) <= Level.debug .ordinal ( ) ;
}
}
cGFja2FnZSB0ZXN0LmxvZwppbXBvcnQgamF2YS5pby4qOwppbXBvcnQgamF2YS5uaW8uY2hhcnNldC5DaGFyc2V0RW5jb2RlcjsKaW1wb3J0IGphdmEubmlvLmNoYXJzZXQuU3RhbmRhcmRDaGFyc2V0czsKaW1wb3J0IGphdmEudGltZS5JbnN0YW50OwppbXBvcnQgamF2YS50aW1lLlpvbmVJZDsKaW1wb3J0IGphdmEudGltZS5mb3JtYXQuRGF0ZVRpbWVGb3JtYXR0ZXI7CmltcG9ydCBqYXZhLnV0aWwuTG9jYWxlOwppbXBvcnQgamF2YS51dGlsLlJhbmRvbTsKaW1wb3J0IGphdmEudXRpbC5jb25jdXJyZW50LlNlbWFwaG9yZTsKCnB1YmxpYyBjbGFzcyBMb2dnZXIgewogICAgcHVibGljIHN0YXRpYyBmaW5hbCBEYXRlVGltZUZvcm1hdHRlciBkYXRlRm9ybWF0dGVyID0gRGF0ZVRpbWVGb3JtYXR0ZXIub2ZQYXR0ZXJuKCJNTU0gZGQsIHl5eXkgaGg6bW06c3MgYSIpLndpdGhab25lKFpvbmVJZC5zeXN0ZW1EZWZhdWx0KCkpOwogICAgcHJpdmF0ZSBmaW5hbCBTdHJpbmcgZmlsZU5hbWUgPSAibG9nNGoubG9nIjsKICAgIHB1YmxpYyBmaW5hbCBTdHJpbmcgZGlyTmFtZTsKICAgIHByaXZhdGUgZmluYWwgbG9uZyBtYXhGaWxlU2l6ZUJ5dGVzOwogICAgcHJpdmF0ZSBMZXZlbCBtaW5MZXZlbDsKICAgIHByaXZhdGUgZmluYWwgaW50IG1heEJhY2t1cHM7CiAgICBwcml2YXRlIGZpbmFsIEZpbGUgZmlsZTsKICAgIHByaXZhdGUgQ291bnRpbmdXcml0ZXIgdzsKICAgIHByaXZhdGUgYm9vbGVhbiBzdGRvdXRNb2RlOwoKICAgIHByaXZhdGUgc3RhdGljIExvZ2dlciBsb2dnZXI7CiAgICBwdWJsaWMgc3RhdGljIHZvaWQgc2V0TG9nZ2VyKExvZ2dlciBsb2dnZXIpewogICAgICAgIExvZ2dlci5sb2dnZXI9bG9nZ2VyOwogICAgfQogICAgcHVibGljIHN0YXRpYyBMb2dnZXIgZ2V0TG9nZ2VyKFN0cmluZyBuYW1lKSB7CiAgICAgICAgaWYobG9nZ2VyPT1udWxsKSBsb2dnZXI9bmV3IExvZ2dlcigpOwogICAgICAgIHJldHVybiBsb2dnZXI7CiAgICB9CgogICAgcHVibGljIGVudW0gTGV2ZWx7ZGVidWcoKSxpbmZvKCksd2FybigpLGVycm9yKCksZmF0YWwoKTsKICAgICAgICBwcml2YXRlIGZpbmFsIFN0cmluZyBuYW1lVWNhc2U7CiAgICAgICAgTGV2ZWwoKSB7CiAgICAgICAgICAgIHRoaXMubmFtZVVjYXNlID0gdGhpcy5uYW1lKCkudG9VcHBlckNhc2UoTG9jYWxlLlJPT1QpOwogICAgICAgIH0KICAgIH0KCiAgICBwdWJsaWMgTG9nZ2VyKGxvbmcgbWF4RmlsZVNpemVCeXRlcywgTGV2ZWwgbWluTGV2ZWwsIFN0cmluZyBkaXJOYW1lLCBib29sZWFuIHN0ZG91dE1vZGUsIGludCBtYXhCYWNrdXBzKSB7CiAgICAgICAgdGhpcy5tYXhGaWxlU2l6ZUJ5dGVzID0gbWF4RmlsZVNpemVCeXRlczsKICAgICAgICB0aGlzLm1pbkxldmVsID0gbWluTGV2ZWw7CiAgICAgICAgdGhpcy5kaXJOYW1lPWRpck5hbWU7CiAgICAgICAgdGhpcy5zdGRvdXRNb2RlID0gc3Rkb3V0TW9kZTsKICAgICAgICB0aGlzLmZpbGU9bmV3IEZpbGUoZGlyTmFtZSxmaWxlTmFtZSk7CiAgICAgICAgdGhpcy5tYXhCYWNrdXBzID0gbWF4QmFja3VwczsKICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oImZpbGU9ICIgKyB0aGlzLmZpbGUpOwogICAgICAgIHRoaXMudz1pbml0RmlsZVdyaXRlcigpOwogICAgfQoKICAgIHByaXZhdGUgTG9nZ2VyKCkgewogICAgICAgIHRoaXMoMTAyNCoxMDI0KjIwTCxMZXZlbC5pbmZvLCIvdmFyL2xvZyIsZmFsc2UsIDEwKTsKICAgIH0KCgogICAgcHJpdmF0ZSBDb3VudGluZ1dyaXRlciBpbml0RmlsZVdyaXRlcigpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICBmaW5hbCBsb25nIGxlbmd0aCA7CiAgICAgICAgICAgIGZpbmFsIGJvb2xlYW4gZXhpc3RzID0gZmlsZS5leGlzdHMoKTsKICAgICAgICAgICAgaWYgKGV4aXN0cykgewogICAgICAgICAgICAgICAgbGVuZ3RoID0gZmlsZS5sZW5ndGgoKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgIGZpbmFsIEZpbGUgcGFyZW50RmlsZSA9IGZpbGUuZ2V0UGFyZW50RmlsZSgpOwogICAgICAgICAgICAgICAgcGFyZW50RmlsZS5ta2RpcnMoKTsKICAgICAgICAgICAgICAgIGxlbmd0aD0wOwogICAgICAgICAgICB9CiAgICAgICAgICAgIENoYXJzZXRFbmNvZGVyIGVuY29kZXIgPSBTdGFuZGFyZENoYXJzZXRzLlVURl84Lm5ld0VuY29kZXIoKTsKICAgICAgICAgICAgRmlsZU91dHB1dFN0cmVhbSBvdXRwdXRTdHJlYW09bmV3IEZpbGVPdXRwdXRTdHJlYW0oZmlsZSxleGlzdHMpOwogICAgICAgICAgICBmaW5hbCBDb3VudGluZ1dyaXRlciBjb3VudGluZ1dyaXRlciA9IG5ldyBDb3VudGluZ1dyaXRlcihuZXcgQnVmZmVyZWRXcml0ZXIobmV3IE91dHB1dFN0cmVhbVdyaXRlcihvdXRwdXRTdHJlYW0sIGVuY29kZXIpKSwgbGVuZ3RoKTsKICAgICAgICAgICAgc3Rkb3V0TW9kZT1mYWxzZTsKICAgICAgICAgICAgcmV0dXJuIGNvdW50aW5nV3JpdGVyOwogICAgICAgIH0gY2F0Y2ggKEV4Y2VwdGlvbiBlKSB7CiAgICAgICAgICAgIFN5c3RlbS5lcnIucHJpbnRsbigiZXJyb3IgaW5pdGlhbGlzaW5nIGxvZ2dpbmchIFNldHRpbmcgJ3N0ZG91dCcgbW9kZSIpOwogICAgICAgICAgICBlLnByaW50U3RhY2tUcmFjZSgpOwogICAgICAgICAgICBzdGRvdXRNb2RlPXRydWU7CiAgICAgICAgfQogICAgICAgIHJldHVybiBudWxsOwogICAgfQogICAgcHVibGljIHZvaWQgZmx1c2goKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgdy5mbHVzaCgpOwogICAgICAgIH0gY2F0Y2ggKElPRXhjZXB0aW9uIGUpIHsKICAgICAgICAgICAgZS5wcmludFN0YWNrVHJhY2UoKTsKICAgICAgICB9CiAgICB9CgogICAgcHJpdmF0ZSB2b2lkIGxvZyhMZXZlbCBsZXZlbCwgU3RyaW5nIG1lc3NhZ2UsIFRocm93YWJsZSBlKXsKICAgICAgICBpZihtZXNzYWdlPT1udWxsICYmIGU9PW51bGwpIHJldHVybjsKICAgICAgICBpZihsZXZlbC5vcmRpbmFsKCk8bWluTGV2ZWwub3JkaW5hbCgpKSByZXR1cm47CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgaWYoc3Rkb3V0TW9kZSl7CiAgICAgICAgICAgICAgICB3cml0ZVRvU3RkRXJyKG1lc3NhZ2UsZSxudWxsLG51bGwpOwogICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmKHJvbGxMb2NrLmF2YWlsYWJsZVBlcm1pdHMoKTwxKXsKICAgICAgICAgICAgICAgIHdyaXRlVG9TdGRFcnIobWVzc2FnZSwgZSwgInJvbGxpbmciLCBudWxsKTsKICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgfQogICAgICAgICAgICB3LndyaXRlKGRhdGVGb3JtYXR0ZXIuZm9ybWF0KEluc3RhbnQubm93KCkpKTsKICAgICAgICAgICAgdy53cml0ZSgnICcpOwogICAgICAgICAgICB3LndyaXRlKGxldmVsLm5hbWVVY2FzZSk7CiAgICAgICAgICAgIHcud3JpdGUoJyAnKTsKICAgICAgICAgICAgdy53cml0ZSgnWycpOwogICAgICAgICAgICB3LndyaXRlKFRocmVhZC5jdXJyZW50VGhyZWFkKCkuZ2V0TmFtZSgpKTsKICAgICAgICAgICAgdy53cml0ZSgnXScpOwogICAgICAgICAgICB3LndyaXRlKCcgJyk7CiAgICAgICAgICAgIGZpbmFsIGJvb2xlYW4gaGFzTWVzc2FnZSA9IG1lc3NhZ2UgIT0gbnVsbDsKICAgICAgICAgICAgaWYoaGFzTWVzc2FnZSl7CiAgICAgICAgICAgICAgICB3LndyaXRlKG1lc3NhZ2UpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmKGUhPW51bGwpewogICAgICAgICAgICAgICAgaWYoaGFzTWVzc2FnZSkgdy53cml0ZSgnICcpOwogICAgICAgICAgICAgICAgZmluYWwgUHJpbnRXcml0ZXIgcHcgPSBuZXcgUHJpbnRXcml0ZXIodywgZmFsc2UpOwogICAgICAgICAgICAgICAgZS5wcmludFN0YWNrVHJhY2UocHcpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHcud3JpdGUoJ1xuJyk7CiAgICAgICAgICAgIGlmKGxldmVsLm9yZGluYWwoKT49IExldmVsLndhcm4ub3JkaW5hbCgpIHx8IGlzRGVidWdFbmFibGVkKCkpIHcuZmx1c2goKTsKICAgICAgICAgICAgLy9mbHVzaCBpbW1lZGlhdGVseSAoZS5nLiBpbiBleGFtcGxlIG9mIGZhdGFsIGVycm9yCiAgICAgICAgICAgIC8vYWxzbyBmbHVzaCBpbW1lZGlhdGVseSBpZiBkZWJ1Z2dpbmc6IHNpbmNlIHdlIGRvbid0IGNhcmUgc28gbXVjaCBhYm91dCBwZXJmIGlmIHdlJ3JlIGRlYnVnZ2luZwogICAgICAgICAgICBpZih3LmdldENvdW50Qnl0ZXMoKT49bWF4RmlsZVNpemVCeXRlcyl7CiAgICAgICAgICAgICAgICBpZihyb2xsTG9jay50cnlBY3F1aXJlKCkpey8vaWYgc29tZW9uZSdzIGFscmVhZHkgb24gaXQsIGp1c3QgZ2l2ZSB1cAogICAgICAgICAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHJvbGwoKTsKICAgICAgICAgICAgICAgICAgICB9IGZpbmFsbHkgewogICAgICAgICAgICAgICAgICAgICAgICByb2xsTG9jay5yZWxlYXNlKCk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgIH0gY2F0Y2ggKEV4Y2VwdGlvbiBlMSkgeyAvL2xhc3QgdGhpbmcgd2Ugd2FudCBpcyBsb2dnaW5nIGNhdXNpbmcgaXNzdWVzCiAgICAgICAgICAgIHdyaXRlVG9TdGRFcnIobWVzc2FnZSwgZSwgImV4Y2VwdGlvbiBlbmNvdW50ZXJlZCIsIGUxKTsKICAgICAgICB9CgogICAgfQoKICAgIHByaXZhdGUgdm9pZCB3cml0ZVRvU3RkRXJyKFN0cmluZyBtZXNzYWdlLCBUaHJvd2FibGUgZSwgU3RyaW5nIHJlYXNvbkNhbm5vdFdyaXRlVG9GaWxlLCBFeGNlcHRpb24gcmVhc29uRXJyb3IpIHsKICAgICAgICBpZiAocmVhc29uQ2Fubm90V3JpdGVUb0ZpbGUhPW51bGwpewogICAgICAgICAgICBTeXN0ZW0uZXJyLnByaW50KCJbIik7CiAgICAgICAgICAgIFN5c3RlbS5lcnIucHJpbnQocmVhc29uQ2Fubm90V3JpdGVUb0ZpbGUpOwogICAgICAgICAgICBTeXN0ZW0uZXJyLnByaW50KCJdICIpOwogICAgICAgIH0KICAgICAgICBpZiAocmVhc29uRXJyb3IhPW51bGwpewogICAgICAgICAgICBTeXN0ZW0uZXJyLnByaW50KCJbIik7CiAgICAgICAgICAgIHJlYXNvbkVycm9yLnByaW50U3RhY2tUcmFjZSgpOwogICAgICAgICAgICBTeXN0ZW0uZXJyLnByaW50KCJdICIpOwogICAgICAgIH0KICAgICAgICBpZiAobWVzc2FnZSE9bnVsbCkgU3lzdGVtLmVyci5wcmludGxuKG1lc3NhZ2UpOwogICAgICAgIGlmIChlIT1udWxsKSBlLnByaW50U3RhY2tUcmFjZSgpOwogICAgfQoKICAgIHByaXZhdGUgZmluYWwgU2VtYXBob3JlIHJvbGxMb2NrID0gbmV3IFNlbWFwaG9yZSgxKTsKICAgIHByaXZhdGUgdm9pZCByb2xsKCkgewogICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTE9HR0VSOjo6IHJvbGwgdGhyZWFkPSIgKyBUaHJlYWQuY3VycmVudFRocmVhZCgpLmdldE5hbWUoKSArICIgdD0iICsgU3lzdGVtLmN1cnJlbnRUaW1lTWlsbGlzKCkpOwogICAgICAgIHJlbmFtZU9sZEJhY2t1cEZpbGVzKCk7CiAgICAgICAgZmluYWwgRmlsZSBuZXdlc3RCYWNrdXAgPSBiYWNrdXBGaWxlKDEpOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIHcuY2xvc2UoKTsKICAgICAgICB9IGNhdGNoIChJT0V4Y2VwdGlvbiBlKSB7CiAgICAgICAgICAgIGUucHJpbnRTdGFja1RyYWNlKCk7CiAgICAgICAgfQogICAgICAgIC8vcmVuYW1lIHRoZSBtYWluIGxvZyBmaWxlIHRvIG5ld2VzdCBiYWNrdXAgKHdoaWNoIHdlIGp1c3QgcmVuYW1lZCkKICAgICAgICBib29sZWFuIHN1Y2Nlc3MgPSBmaWxlLnJlbmFtZVRvKG5ld2VzdEJhY2t1cCk7CiAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJMT0dHRVI6OjogcmVuYW1lICIgKyBmaWxlICsgIiB0bz0iICsgbmV3ZXN0QmFja3VwICsgIiBzdWNjZXNzPSIgKyBzdWNjZXNzKTsKICAgICAgICB0aGlzLnc9aW5pdEZpbGVXcml0ZXIoKTsvL3Nob3VsZCBub3cgYmUgd3JpdGluZyB0byBmaWxlICh3aGljaCBpcyBlbXB0eSkKICAgIH0KCiAgICBwcml2YXRlIHZvaWQgcmVuYW1lT2xkQmFja3VwRmlsZXMoKSB7CiAgICAgICAgRmlsZSBvbGRlc3RCYWNrdXAgPSBiYWNrdXBGaWxlKG1heEJhY2t1cHMpOwogICAgICAgIGlmIChvbGRlc3RCYWNrdXAuZXhpc3RzKCkpIHsKICAgICAgICAgICAgYm9vbGVhbiBkZWxldGVkID0gb2xkZXN0QmFja3VwLmRlbGV0ZSgpOwogICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIkxPR0dFUjo6OiBkZWxldGUgIiArIG9sZGVzdEJhY2t1cCArICIgc3VjY2Vzcz0iICsgZGVsZXRlZCk7CiAgICAgICAgfQovLyAgICAgICAgICAgICAgIC0gcmVuYW1lIGFsbCBvdGhlciBiYWNrdXAgZmlsZXMgPDUgaW4gZGVzYyBvcmRlciAoc28gbG9nLjQgLT4gbG9nLjUgYW5kIHNvIG9uKQogICAgICAgIGZvciAoaW50IGJhY2t1cCA9IG1heEJhY2t1cHMtMTsgYmFja3VwID4gMDsgYmFja3VwLS0pIHsKICAgICAgICAgICAgZmluYWwgRmlsZSBiYWNrdXBGaWxlID0gYmFja3VwRmlsZShiYWNrdXApOwogICAgICAgICAgICBpZiAoIWJhY2t1cEZpbGUuZXhpc3RzKCkpIGNvbnRpbnVlOwogICAgICAgICAgICBmaW5hbCBGaWxlIGRlc3RGaWxlID0gYmFja3VwRmlsZShiYWNrdXAgKyAxKTsKICAgICAgICAgICAgYm9vbGVhbiBzdWNjZXNzID0gYmFja3VwRmlsZS5yZW5hbWVUbyhkZXN0RmlsZSk7CiAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiTE9HR0VSOjo6IHJlbmFtZSAiICsgYmFja3VwRmlsZSArICIgdG89IiArIGRlc3RGaWxlICsgIiBzdWNjZXNzPSIgKyBzdWNjZXNzKTsKICAgICAgICB9CiAgICB9CgogICAgcHJpdmF0ZSBGaWxlIGJhY2t1cEZpbGUoaW50IGJhY2t1cE51bWJlcikgewogICAgICAgIHJldHVybiBuZXcgRmlsZShkaXJOYW1lLFN0cmluZy5mb3JtYXQoIiVzLiVzIixmaWxlTmFtZSwgYmFja3VwTnVtYmVyKSk7CiAgICB9CgoKICAgIHByaXZhdGUgc3RhdGljIGNsYXNzIENvdW50aW5nV3JpdGVyIGV4dGVuZHMgV3JpdGVyewogICAgICAgIHByaXZhdGUgZmluYWwgV3JpdGVyIHdyaXRlcjsKICAgICAgICBwcml2YXRlIGxvbmcgY291bnRCeXRlczsKICAgICAgICBwdWJsaWMgQ291bnRpbmdXcml0ZXIoV3JpdGVyIHcsIGxvbmcgaW5pdGlhbENvdW50KSB7CiAgICAgICAgICAgIHRoaXMud3JpdGVyID0gdzsKICAgICAgICAgICAgY291bnRCeXRlcz1pbml0aWFsQ291bnQ7CiAgICAgICAgfQogICAgICAgIHB1YmxpYyB2b2lkIHdyaXRlKGNoYXJbXSBjYnVmLCBpbnQgb2ZmLCBpbnQgbGVuKSB0aHJvd3MgSU9FeGNlcHRpb24gewogICAgICAgICAgICB3cml0ZXIud3JpdGUoY2J1ZixvZmYsbGVuKTsKICAgICAgICAgICAgZmluYWwgbG9uZyB3cm90ZSA9IChsZW4gLSBvZmYpOwogICAgICAgICAgICBjb3VudEJ5dGVzICs9IHdyb3RlOy8vdGhpcyB3aWxsIGxpa2VseSBiZSB3cm9uZyBpZiB3ZSB1c2UgZXh0ZW5kZWQgY2hhcnMgd2hpY2ggdGFrZSB0d28gYnl0ZXM/IGFzY2lpLXR5cGUgY2hhcnMgdGFrZSAxIGJ5dGUsIGJ1dCBzb21lIHRha2UgMi4gQnV0IHByb2JhYmx5IGRvZXNuJ3QgbWF0dGVyLCB1bmxlc3Mgd2Ugc3RhcnQgbG9nZ2luZyBrb3JlYW4gc3RyaW5ncyBvciBzb21ldGhpbmc/CiAgICAgICAgfQogICAgICAgIHB1YmxpYyB2b2lkIGZsdXNoKCkgdGhyb3dzIElPRXhjZXB0aW9uIHsgd3JpdGVyLmZsdXNoKCk7IH0KICAgICAgICBwdWJsaWMgdm9pZCBjbG9zZSgpIHRocm93cyBJT0V4Y2VwdGlvbiB7IHdyaXRlci5jbG9zZSgpOyB9CgogICAgICAgIHB1YmxpYyBsb25nIGdldENvdW50Qnl0ZXMoKSB7IHJldHVybiBjb3VudEJ5dGVzOyB9CiAgICB9CgogICAgLy9sb2c0aiBhcGkKICAgIHB1YmxpYyB2b2lkIGRlYnVnKFN0cmluZyBmb3JtYXQpIHtkZWJ1Zyhmb3JtYXQsbnVsbCk7IH0KICAgIHB1YmxpYyB2b2lkIGRlYnVnKFRocm93YWJsZSBlKSB7ZGVidWcobnVsbCxlKTsgfQogICAgcHVibGljIHZvaWQgZGVidWcoU3RyaW5nIGZvcm1hdCwgVGhyb3dhYmxlIGUpIHtsb2coTGV2ZWwuZGVidWcsZm9ybWF0LGUpOyB9CgogICAgcHVibGljIHZvaWQgaW5mbyhTdHJpbmcgZm9ybWF0KSB7aW5mbyhmb3JtYXQsbnVsbCk7IH0KICAgIHB1YmxpYyB2b2lkIGluZm8oVGhyb3dhYmxlIGUpIHtpbmZvKG51bGwsZSk7IH0KICAgIHB1YmxpYyB2b2lkIGluZm8oU3RyaW5nIGZvcm1hdCwgVGhyb3dhYmxlIGUpIHtsb2coTGV2ZWwuaW5mbyxmb3JtYXQsZSk7IH0KCiAgICBwdWJsaWMgdm9pZCB3YXJuKFN0cmluZyBmb3JtYXQpIHt3YXJuKGZvcm1hdCxudWxsKTt9CiAgICBwdWJsaWMgdm9pZCB3YXJuKFRocm93YWJsZSBlKSB7d2FybihudWxsLGUpOyB9CiAgICBwdWJsaWMgdm9pZCB3YXJuKFN0cmluZyBmb3JtYXQsIFRocm93YWJsZSBlKSB7bG9nKExldmVsLndhcm4sZm9ybWF0LGUpOyB9CgogICAgcHVibGljIHZvaWQgZXJyb3IoU3RyaW5nIGZvcm1hdCkge2Vycm9yKGZvcm1hdCxudWxsKTsgfQogICAgcHVibGljIHZvaWQgZXJyb3IoVGhyb3dhYmxlIGUpIHsgZXJyb3IobnVsbCxlKTt9CiAgICBwdWJsaWMgdm9pZCBlcnJvcihTdHJpbmcgZm9ybWF0LCBUaHJvd2FibGUgZSkge2xvZyhMZXZlbC5lcnJvcixmb3JtYXQsZSk7IH0KCiAgICBwdWJsaWMgdm9pZCBmYXRhbChTdHJpbmcgZm9ybWF0KSB7ZmF0YWwoZm9ybWF0LG51bGwpOyB9CiAgICBwdWJsaWMgdm9pZCBmYXRhbChUaHJvd2FibGUgZSkge2ZhdGFsKG51bGwsZSk7IH0KICAgIHB1YmxpYyB2b2lkIGZhdGFsKFN0cmluZyBmb3JtYXQsIFRocm93YWJsZSBlKSB7CiAgICAgICAgbG9nKExldmVsLmZhdGFsLGZvcm1hdCxlKTsKICAgIH0KCiAgICAvL25ldyBhcGkgbWV0aHMKICAgIHB1YmxpYyB2b2lkIHdhcm5mKFN0cmluZyBmb3JtYXQsIFRocm93YWJsZSBlLCBPYmplY3QuLi4gcGFyYW10ZXJzKSB7d2FybihTdHJpbmcuZm9ybWF0KGZvcm1hdCwgKE9iamVjdFtdKSBwYXJhbXRlcnMpLGUpO30KICAgIHB1YmxpYyB2b2lkIGluZm9mKFN0cmluZyBmb3JtYXQsIFRocm93YWJsZSBlLCBPYmplY3QuLi4gcGFyYW10ZXJzKSB7aW5mbyhTdHJpbmcuZm9ybWF0KGZvcm1hdCwgKE9iamVjdFtdKSBwYXJhbXRlcnMpLGUpO30KICAgIHB1YmxpYyB2b2lkIGRlYnVnZihTdHJpbmcgZm9ybWF0LCBUaHJvd2FibGUgZSwgT2JqZWN0Li4uIHBhcmFtdGVycykge2RlYnVnKFN0cmluZy5mb3JtYXQoZm9ybWF0LCAoT2JqZWN0W10pIHBhcmFtdGVycyksZSk7fQoKCiAgICBwdWJsaWMgYm9vbGVhbiBpc0RlYnVnRW5hYmxlZCgpIHsKICAgICAgICByZXR1cm4gbWluTGV2ZWwub3JkaW5hbCgpPD1MZXZlbC5kZWJ1Zy5vcmRpbmFsKCk7CiAgICB9Cgp9