package clearcut; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.IOException; import java.io.FilenameFilter; import java.util.Properties; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.Date; import java.text.SimpleDateFormat; import static clearcut.Injector.INJECTOR; /** Java i/o */ public class Logger { public enum Logging { NONE, FILE, CONSOLE, BOTH } private static String DEFAULT_LOG_FILE = "logger.log"; private static int MAX_LINES = 333; private static int MIN_FILES = 5; private static int MAX_FILES = 10000; private static String LOG_FORMAT = "%0" +(""+MAX_FILES).length()+"d"; private static String LONG_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z"; private static String SHORT_FORMAT = "HH:mm:ss.SS"; private static Map < String, Logger > loggers; private int rotation = MIN_FILES; private int rotator = 0; private int numLines = 0; private Logging loggin = Logging.CONSOLE; private int maxLines = MAX_LINES ; private Class caller; private String logPath; public static Logger LOGGER( Object caller ) { if( Logger.loggers == null ) Logger.loggers = new HashMap< String, Logger > (); String packageName = caller.getClass().getPackage().getName(); if( Logger.loggers.get( packageName ) == null ) Logger.loggers.put( packageName, new Logger(caller) ); return Logger.loggers.get( packageName ); } private Logger() { super(); if( Logger.loggers == null ) Logger.loggers = new HashMap< String, Logger > (); } private Logger (Object caller) { this(); load( caller ); } private synchronized void load( Object caller ) { OutputStream outStream = null; try { setCaller( caller ); setRotation( INJECTOR.property( "logging", "files" ) ); setLogPath ( INJECTOR.iniPath() + File.separator + "log" + File.separator ); if( getLogging() == Logging.FILE || getLogging() == Logging.BOTH ) outStream = Logger.test( getLogPath() ); File file = new File ( getLogPath() ); File [] files = file.listFiles( new LogFilter() ) ; for( File logFile : files ) if( logFile.getName().startsWith( getCaller().getPackage().getName() ) ) incRotator( ); } catch( IOException x ) { System.out.println( x.getMessage() ); this.loggin = Logging.CONSOLE; } catch( InjectionException j ) { j.printStackTrace(); } finally { try { outStream.close(); } catch ( Exception e ) { } } } private static synchronized OutputStream test(String logPath) throws IOException { File file = new File( logPath ); if( ! file.exists() ) if( ! file.mkdirs() ) throw new IOException("Unable to create "+logPath); String fileName = logPath + DEFAULT_LOG_FILE; file = new File( fileName ); if( file.exists() ) if( ! file.delete() ) throw new IOException("Unable to delete "+fileName); OutputStream outStream = new FileOutputStream( file, true ); byte [] timedate = new byte[ 32 ]; String datetime = new SimpleDateFormat( LONG_FORMAT ).format( new Date() ); for( int i = 0; i < timedate.length && i < datetime.length(); i ++ ) timedate[ i ] = (byte) datetime.charAt( i ); timedate[ 31 ] = (byte) '\n'; outStream.write( timedate ); return outStream; } /** Makes a file name from package name of calling class and a rotating number. Creates new file if max no. of lines written in old file. Deletes old files if their numbers have been used and has various other side-effects. */ private String rotate( ) throws IOException { int num = getLineNum() + 1; if( num == 1 || num > MAX_LINES ) { this.numLines = 1; incRotator(); int rotated = getRotator(); String fileName = fileName( rotated ++ ); if( rotated > getRotation() ) rotated = 1; String nextFile = fileName( rotated ); File oldFile = new File( fileName ); File newFile = new File( nextFile ); if( oldFile.exists() && (oldFile.length() > MAX_LINES * 50L || newFile.exists()) ) { if( ! oldFile.delete() ) { setLogging( Logging.CONSOLE ); throw new IOException("Unable to delete old log file "+fileName); } } } else this.numLines = num; return this.fileName( getRotator() ); } public void log( String msg ) { try { Logging logging = this.getLogging(); if( logging == Logging.CONSOLE || logging == Logging.BOTH ) System.out.println( new SimpleDateFormat( SHORT_FORMAT ).format( new Date() ) + " " + msg ); if( logging == Logging.FILE || logging == Logging.BOTH ) log( msg, getCaller().getPackage().getName() ); } catch( InjectionException x ) { System.out.println( "Logger error" ); x.printStackTrace(); } } private void log( String msg, String packageName ) { OutputStream outStream = null; String logFile = "log file"; try { logFile = rotate( ); outStream = this.open( logFile ); this.write( outStream, msg ); } catch( IOException o ) { System.out.println( "Unable to write "+logFile ); setLogging( Logging.CONSOLE ); } finally { try { outStream.close(); } catch ( Exception e ) { } } } private OutputStream open( String logFile ) throws IOException { File file = new File( logFile ); if( file.exists() ) return new FileOutputStream( file, true ); OutputStream outStream = new FileOutputStream( new File( logFile ), false ); byte [] buffer = new byte[ 32 ]; String datetime = new SimpleDateFormat( LONG_FORMAT ).format( new Date() ); for( int i = 0; i < buffer.length && i < datetime.length(); i ++ ) buffer[ i ] = (byte) datetime.charAt( i ); buffer[ 31 ] = (byte) '\n'; outStream.write( buffer ); return outStream; } private void write( OutputStream stream, String message ) throws IOException { byte [] timedate = new byte[ 11 ]; String datetime = new SimpleDateFormat( SHORT_FORMAT ).format( new Date() ); for( int i = 0; i < timedate.length && i < datetime.length(); i ++ ) timedate[ i ] = (byte) datetime.charAt( i ); stream.write( timedate ); timedate = new byte[1]; timedate[0] = (byte) ' '; stream.write( timedate ); timedate = (getCaller().getSimpleName() + " " + message).getBytes(); stream.write( timedate ); timedate = new byte[2]; timedate[0] = (byte) '\r'; // M$ timedate[1] = (byte) '\n'; stream.write( timedate ); } private void setLogging( Logging logging ) { this.loggin = logging; } private Logging getLogging() throws InjectionException { String level = INJECTOR.property( "logging", "level" ); if( level != null ) for (Logging l : Logging.values()) if( l.toString().equals( level )) this.loggin = l; return this.loggin; } private void setRotation( String numFiles ) { if( numFiles == null || numFiles.length() < 1 ) this.rotation = MIN_FILES; else { int numFiles2Rotate = Integer.parseInt( numFiles ); if( numFiles2Rotate < 1 ) this.rotation = 1; else if( numFiles2Rotate > MAX_FILES ) this.rotation = MAX_FILES; else this.rotation = numFiles2Rotate; } } private int getRotation() { return this.rotation; } private void setLogPath( String logPath ) { this.logPath = logPath; } public String getLogPath() { return this.logPath; } public Logger.LogFilter getLogFilter() { LogFilter filter = new LogFilter(); return filter; } private int getRotator() { return this.rotator; } private void incRotator() { int rotNum =1+ this.getRotator(); if( rotNum > getRotation() ) this.rotator = 1; else this.rotator = rotNum; } private int getLineNum() { return this.numLines; } private Class getCaller() { return this.caller; } private void setCaller( Object caller ) { this.caller = caller.getClass(); } private String fileName( int rotNum ) { return getLogPath() + getCaller().getPackage().getName() + "." + String.format( LOG_FORMAT, rotNum ) + ".log"; } public class LogFilter implements FilenameFilter { public boolean accept(File dir, String name) { return (name.endsWith(".log")); } } }