/
area/
classes/net/sourceforge/pain/logic/
classes/net/sourceforge/pain/logic/event/
classes/net/sourceforge/pain/logic/fn/util/
classes/net/sourceforge/pain/network/console/
classes/net/sourceforge/pain/plugin/
classes/net/sourceforge/pain/plugin/reset/
classes/net/sourceforge/pain/plugin/shutdown/
classes/net/sourceforge/pain/plugin/social/
classest/net/sourceforge/pain/db/data/
doc/
doc/paindb/resources/
src/net/sourceforge/pain/logic/
src/net/sourceforge/pain/logic/event/
src/net/sourceforge/pain/logic/fn/util/
src/net/sourceforge/pain/network/console/
src/net/sourceforge/pain/network/console/telnet/
src/net/sourceforge/pain/plugin/
src/net/sourceforge/pain/plugin/command/
src/net/sourceforge/pain/plugin/reset/
src/net/sourceforge/pain/plugin/shutdown/
src/net/sourceforge/pain/plugin/social/
src/net/sourceforge/pain/util/
tests/
tests/net/sourceforge/pain/db/data/
package net.sourceforge.pain.util;

import java.io.*;
import java.text.*;
import java.util.*;

/**
 * PAiN  Date: 02.03.2003  Time: 22:19:31
 * this class could be changed to Log4j as soon as any reasons will be found
 * today I just do not want any additional jar files in distribution
 */
public final class Log {

	private static long lastTime;
	private static final char[] lastTimeString = new char[23];
	private static final GregorianCalendar calendar = new GregorianCalendar();
	private static SimpleDateFormat fileFormat = new SimpleDateFormat("dd_MM_yyyy_HH'h'_mm'm'");

	public static int rollPeriod = 60 * 60 * 1000;
	public static long nextRollTime = -1;
	public static String dirPath = "";
	public static final String activeLogFileName = "pain.log";
	public static Writer writer;

	private static final int flushPeriod = 1 * 1000;
	private static long nextFlushTime = -1;
	private static Flusher flusher;
	private static ShutdownFlusher shutdownFlusher;

	private Log() {
	}

	public static void debug(final String message) {
		roll();
		log(" [DEBUG] ", message);
	}

	public static void info(final String message) {
		roll();
		log(" [INFO] ", message);
	}

	public static void warn(final String message) {
		roll();
		log(" [WARN] ", message);
	}

	public static void error(final String message) {
		roll();
		log(" [ERROR] ", message);
	}

	public static void error(final Throwable e) {
		error("", e);
	}

	public static void error(final String message, final Throwable e) {
		roll();
		log(" [ERROR] ", message + " / " + e.getClass().getName() + " : " + e.getMessage());
		log("\r\n");
		final StackTraceElement[] stack = e.getStackTrace();
		final int catchDeep = stack.length - (new Throwable().getStackTrace().length - 1);
		for (int i = 0; i < stack.length; i++) {
			if (i == catchDeep) {
				log("[log]\t");
			} else {
				log("\t");
			}
			log(stack[i].toString());
			log("\r\n");
		}
		log(".\r\n");
	}

	private static void log(final String message) {
		try {
			out(message);
		} catch (Exception e) {
			handleException(message, e);
		}
	}

	private static void log(final char[] message) {
		try {
			out(message);
		} catch (Exception e) {
			handleException(new String(message), e);
		}
	}

	private static void handleException(final String message, final Exception e) {
		if (writer == null || writer instanceof FileWriter) {
			switchToSystemOut(e, message);
		}
	}


	private static void renameFile(final File file, final String name) {
		File toFile = new File(name);
		for (int i = 1; toFile.exists(); i++) {
			toFile = new File(name + i);
		}
		file.renameTo(toFile);
	}

	private static void log(final String prefix, final String message) {
		log(getTime());
		log(prefix);
		log(message);
		log("\r\n");
	}

	private static void switchToSystemOut(final Exception e, final String message) {
		writer = new OutputStreamWriter(System.out);
		error(e);
		info("Switching output to system out");
		log(message);
	}

	private static void flush() {
		if (nextFlushTime > System.currentTimeMillis()) {
			return;
		}
		synchronized (Log.class) {
			try {
				writer.flush();
			} catch (Exception e) {
				handleException(e.getMessage(), e);
			}
		}
		nextFlushTime = System.currentTimeMillis() + flushPeriod;
	}

	private static void out(String message) throws IOException {
		message = message == null ? "null" : message;
		writer.write(message, 0, message.length());
	}

	private static void out(final char[] message) throws IOException {
		writer.write(message, 0, message.length);
	}


	/** i just had nothing to do :) */
	private static char[] getTime() {
		// code from log4j package (ISO8601DateFormat with a minimal changes)
		final long now = System.currentTimeMillis();
		final int millis = (int) (now % 1000);
		if ((now - millis) != lastTime) {
			// We reach this point at most once per second
			// across all threads instead of each time format()
			// is called. This saves considerable CPU time.
			calendar.setTimeInMillis(now);
			final int year = calendar.get(Calendar.YEAR);
			lastTimeString[0] = (char) ('1' + (year / 1000) - 1);
			lastTimeString[1] = (char) ('1' + (year % 1000) / 100 - 1);
			lastTimeString[2] = (char) ('1' + (year % 100) / 10 - 1);
			lastTimeString[3] = (char) ('1' + (year % 10) - 1);
			lastTimeString[4] = '-';
			final char m1;
			final char m2;
			switch (calendar.get(Calendar.MONTH)) {
				case Calendar.JANUARY:
					m1 = '0';
					m2 = '1';
					break;
				case Calendar.FEBRUARY:
					m1 = '0';
					m2 = '2';
					break;
				case Calendar.MARCH:
					m1 = '0';
					m2 = '3';
					break;
				case Calendar.APRIL:
					m1 = '0';
					m2 = '4';
					break;
				case Calendar.MAY:
					m1 = '0';
					m2 = '5';
					break;
				case Calendar.JUNE:
					m1 = '0';
					m2 = '6';
					break;
				case Calendar.JULY:
					m1 = '0';
					m2 = '7';
					break;
				case Calendar.AUGUST:
					m1 = '0';
					m2 = '8';
					break;
				case Calendar.SEPTEMBER:
					m1 = '0';
					m2 = '9';
					break;
				case Calendar.OCTOBER:
					m1 = '1';
					m2 = '0';
					break;
				case Calendar.NOVEMBER:
					m1 = '1';
					m2 = '1';
					break;
				case Calendar.DECEMBER:
					m1 = '1';
					m2 = '2';
					break;
				default:
					m1 = 'N';
					m2 = 'A';
					break;
			}
			lastTimeString[5] = m1;
			lastTimeString[6] = m2;
			lastTimeString[7] = '-';

			final int day = calendar.get(Calendar.DAY_OF_MONTH);
			lastTimeString[8] = (char) ('1' + day / 10 - 1);
			lastTimeString[9] = (char) ('1' + day % 10 - 1);
			lastTimeString[10] = ' ';

			final int hour = calendar.get(Calendar.HOUR_OF_DAY);
			lastTimeString[11] = (char) ('1' + hour / 10 - 1);
			lastTimeString[12] = (char) ('1' + hour % 10 - 1);
			lastTimeString[13] = ':';

			final int mins = calendar.get(Calendar.MINUTE);
			lastTimeString[14] = (char) ('1' + mins / 10 - 1);
			lastTimeString[15] = (char) ('1' + mins % 10 - 1);
			lastTimeString[16] = ':';

			final int secs = calendar.get(Calendar.SECOND);
			lastTimeString[17] = (char) ('1' + secs / 10 - 1);
			lastTimeString[18] = (char) ('1' + secs % 10 - 1);
			lastTimeString[19] = ',';
			lastTime = now - millis;
		}
		lastTimeString[20] = (char) ('1' + (millis / 100) - 1);
		lastTimeString[21] = (char) ('1' + (millis % 100) / 10 - 1);
		lastTimeString[22] = (char) ('1' + (millis % 10) - 1);
		return lastTimeString;
	}

	private static void roll() {
		if (nextRollTime > System.currentTimeMillis()) {
			return;
		}
		synchronized (Log.class) {
			if (flusher == null) {
				flusher = new Flusher();
				Thread worker = new Thread(flusher);
				worker.setDaemon(true);
				worker.start();
			}
			if (shutdownFlusher == null) {
				shutdownFlusher = new ShutdownFlusher();
			}

			if (nextRollTime > System.currentTimeMillis()) {
				return;//DCL
			}
			try {
				nextRollTime = System.currentTimeMillis() + rollPeriod;
				if (writer != null && !(writer instanceof FileWriter)) {
					return;
				}
				if (writer != null) {
					writer.close();
				}
				createNewLogFile();
			} catch (Exception e) {
				handleException("roll error", e);
			}
		}

	}

	private static void createNewLogFile() throws Exception {
		final File file = new File(dirPath + activeLogFileName);
		if (file.exists()) {
			// check first line for name:
			String line = null;
			final BufferedReader r = new BufferedReader(new FileReader(file));
			try {
				line = r.readLine();
			} finally {
				r.close();
			}
			if (line == null) {
				line = fileFormat.format(new Date(file.lastModified())) + "_before.log";
			}
			renameFile(file, dirPath + line);
		}
		writer = new FileWriter(file);
		writer.write(fileFormat.format(new Date())); //first line in file is its creation date, and future name during roll
		writer.write(".log");
		writer.write("\r\n");
	}


	private static final class ShutdownFlusher extends Thread {
		public ShutdownFlusher() {
			this.setDaemon(true);
			Runtime.getRuntime().addShutdownHook(this);
		}

		public void run() {
			try {
				if (writer != null) {
					writer.flush();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}


		}
	}

	private static final class Flusher implements Runnable {
		public void run() {
			while (true) {
				synchronized (this) {
					try {
						this.wait(flushPeriod);
						flush();
					} catch (InterruptedException e) {
						Log.error(e);
					}
				}
			}
		}
	}

	public static boolean isDebugEnabled() {
		return true;
	}

	public static boolean isInfoEnabled() {
		return true;
	}

	public static boolean isWarnEnabled() {
		return true;
	}
}