package ecologylab.generic; import java.io.BufferedWriter; import java.io.IOException; import java.io.PrintStream; import java.util.Date; import java.util.HashMap; import java.util.NoSuchElementException; import java.util.StringTokenizer; import ecologylab.appframework.types.prefs.Pref; import ecologylab.io.Files; /** * A developer-friendly base class and toolset for logging debug messages. * * Supports a threshold, aka <code>level</code> with 2 levels of granularity: 1) global <br> * 2) on a per class basis <br> * * This levels are configured via runtime startup params ( via the JavaScript prefs mechanisms for * applet versions) * * in the form of 1) debug_global_level = 4; 2) debug_levels = "Parser 3; HTMLPage 2; CollageOp 37"; * * @author andruid */ public class Debug { private static final String SEPARATOR = ": "; /** * Global flag for printing "interactive debug" statements. See also {@link #debugI(String) * debugI()}. */ private static boolean interactive; private static boolean logToFile = false; private static final HashMap<Class<?>, String> classAbbrevNames = new HashMap<Class<?>, String>(); private static final HashMap<Class<?>, String> packageNames = new HashMap<Class<?>, String>(); static final int FLUSH_FREQUENCY = 10; /** * Holds class specific debug levels. */ static final HashMap<String, IntSlot> classLevels = new HashMap<String, IntSlot>(); /** * Global hi watermark. debug() messages with a level less than or equal to this will get printed * out. */ // private static PrefInt level = Pref.usePrefInt("debug_global_level", 0);; static final long initTimestamp = System.currentTimeMillis(); public static void initialize() { // class specific String levels = Pref.lookupString("debug_levels"); // println("Debug.initialize(" + Debug.level+", "+ levels+")"); if (levels != null) { StringTokenizer tokenizer = new StringTokenizer(levels, ";"); { try { while (tokenizer.hasMoreTokens()) { String thisSpec = tokenizer.nextToken(); StringTokenizer specTokenizer = new StringTokenizer(thisSpec); try { String thisClassName = specTokenizer.nextToken(); int thisLevel = Integer.parseInt(specTokenizer.nextToken()); Debug.println("Debug.level\t" + thisClassName + "\t" + thisLevel); classLevels.put(thisClassName, new IntSlot(thisLevel)); } catch (Exception e) { } } } catch (NoSuchElementException e) { } } } } protected Debug() { // AllocationDebugger.constructed(this); } public final int level() { return level(this); } public static final int level(Object that) { // return level(getClassName(that)); return 0; } public static final int level(String className) { /* * int result = level.value(); IntSlot slot = (IntSlot) classLevels.get(className); if (slot != * null) result = slot.value; return result; */ return 0; } /** * @param messageLevel * If less than or equal to the static level, message will get logged. Otherwise, the * statement will be ignored. */ // TODO make levels work again public static void println(int messageLevel, CharSequence message) { // if (messageLevel <= level.value()) // println(message); } public static void printlnI(int messageLevel, CharSequence message) { if (interactive) println(message); } public static void println(Object o, CharSequence message) { print(o.toString()); print(SEPARATOR); println(message); } public static void println(String className, CharSequence message) { print(className); print(SEPARATOR); println(message); } public static void printlnI(Object o, CharSequence message) { if (interactive) println(o, message); } public static void printlnI(CharSequence message) { if (interactive) println(message); } /** * Print the message to System.err. * <p/> * If we are logging to a file, also write to the file, but in this case, prepend the Date:<tab> * * @param message */ public static void println(CharSequence message) { if (logToFile) { try { writer.append(new Date().toString()); writer.append(':').append('\t'); writer.append(message); writer.append('\n'); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } else System.out.println(message); } public static void print(char c) { print(c, System.out); } public static void print(char c, PrintStream pStream) { if (logToFile) { try { writer.append(c); } catch (IOException e) { e.printStackTrace(); } } else pStream.print(c); } public static void print(CharSequence message) { print(message, System.out); } public static void print(CharSequence message, PrintStream pStream) { if (logToFile) { try { writer.append(message); } catch (IOException e) { e.printStackTrace(); } } else pStream.print(message); } /** * Print a debug message, starting with the abbreviated class name of the object. */ public static void printlnA(Object that, CharSequence message) { println(getClassSimpleName(that) + SEPARATOR + message/* +" " +level(that) */); } /** * Print a debug message, starting with the abbreviated class name. */ public static void printlnA(Class<?> c, CharSequence message) { println(classSimpleName(c) + SEPARATOR + message); } static char PERIOD = '.'; static char SPACE = ' '; /** * This actually seems to be much more efficient than Class. getSimpleName(), because we are smart * about using lazy evaluation and caching results in a HashMap. * * @return the abbreviated name of the class - without the package qualifier. */ public static String classSimpleName(Class<?> thatClass) { String abbrevName = classAbbrevNames.get(thatClass); if (abbrevName == null) { String fullName = thatClass.toString(); abbrevName = fullName.substring(fullName.lastIndexOf(PERIOD) + 1); synchronized (classAbbrevNames) { classAbbrevNames.put(thatClass, abbrevName); } } return abbrevName; } /** * @return the abbreviated name of the class - without the package qualifier. */ public static String getPackageName(Class<?> thatClass) { // System.out.println("thatClass.toString() is " + thatClass.toString()); String packageName = packageNames.get(thatClass); if (packageName == null) { String className = thatClass.toString(); packageName = className.substring(className.indexOf(SPACE) + 1, className.lastIndexOf(PERIOD)); synchronized (packageNames) { packageNames.put(thatClass, packageName); // packageNames.put(className, packageName); } } return packageName; } /** * @return the abbreviated name of the class - without the package qualifier. */ public static String getClassSimpleName(Object o) { return (o == null) ? "null" : classSimpleName(o.getClass()); } /** * @return the abbreviated name of this class - without the package qualifier. */ public String getClassSimpleName() { return getClassSimpleName(this); } /** * @return the package name of the class - without the package qualifier. */ public static String getPackageName(Object o) { return getPackageName(o.getClass()); } /** * @return the package name of this class - without the package qualifier. */ public String getPackageName() { return getPackageName(this); } @Override public String toString() { return getClassSimpleName(this); } public String superString() { return super.toString(); } public static String toString(Object o) { return getClassSimpleName(o); } public final void debugT(CharSequence message) { debugT(this, message); } public static final void debugT(Object that, CharSequence message) { // FIXME: This may be slow. Use sparingly String tStamp = "{t=" + (System.currentTimeMillis() - initTimestamp) + "}"; print(tStamp); print(that.toString()); println(message); } /** * Print a debug message that starts with this.toString(). */ public final void debug(CharSequence message) { println(this, message); } /** * Print a message about an error, starting with this.toString(). */ public void error(CharSequence message) { error(this, message); } /** * Print a message about a warning, starting with this.toString(). */ public void warning(CharSequence message) { warning(this, message); } /** * Print a message about something that should never happen, starting with this.toString(). */ public void weird(CharSequence message) { weird(this, message); } /** * Print a message about an error, starting with that.toString(). */ public static void error(Object that, CharSequence message) { emphasized(that, "ERROR - ", message); } /** * Print a message with emphasis. * * @param that * @param header * @param message */ static void emphasized(Object that, String header, CharSequence message) { print('\n', System.err); print(header, System.err); print(that.toString(), System.err); print(SEPARATOR, System.err); print(' ', System.err); print(message, System.err); print('\n', System.err); print('\n', System.err); } /** * Print a message about a warning, starting with that.toString(). */ public static void warning(Object that, CharSequence message) { emphasized(that, "WARNING - ", message); } /** * Print a message about something that should never happen, starting with that.toString(). */ public static void weird(Object that, CharSequence message) { emphasized(that, "WEIRD - ", message); } /** * Print a debug message that starts with this.toString(). */ public final void debug(StringBuffer message) { println(this, message); } /** * Print a debug message that starts with the abbreviated class name of this. */ public final void debugA(CharSequence message) { printlnA(this, message); } /** * Print a debug message that starts with the abbreviated class name of this. */ public final void debugA(StringBuffer message) { printlnA(this, message.toString()); } public final void debugI(CharSequence message) { printlnI(this, message); } public final void debugI(StringBuffer message) { printlnI(this, message.toString()); } /** * Evaluates the same conditional as Debug usually does implicitly, for explicit use in special * static Debug printing scenarios. **/ public static final boolean show(Object that, int messageLevel) { return messageLevel <= level(that); } public boolean show(int messageLevel) { return show(this, messageLevel); } /** * Print a debug message that starts with the abbreviated class name of this, but only if * messageLevel is greater than the debug <code>level</code> for this class (see above). */ public final void debug(int messageLevel, CharSequence message) { // if (show(messageLevel)) if (messageLevel <= level()) println(this, message); } public final void debugA(int messageLevel, CharSequence message) { if (messageLevel <= level()) printlnA(this, message); } public static final void println(Object that, int messageLevel, CharSequence message) { if (messageLevel <= level(that)) println(that, message); } public static final void println(String className, int messageLevel, CharSequence message) { if (messageLevel <= level(className)) println(message); } public static final void printlnA(Object that, int messageLevel, CharSequence message) { if (messageLevel <= level(that)) printlnA(that, message); } public static final void printlnI(Object that, int messageLevel, CharSequence message) { if (messageLevel <= level(that)) printlnI(that, message); } public final void debugI(int messageLevel, CharSequence message) { if (messageLevel <= level()) printlnI(this, message); } public static final void debug(Object o, CharSequence message, Exception e) { println(o, message); e.printStackTrace(); } // public static final void toggleInteractive() // { // interactive = !interactive; // String msg = "Toggle interactive debug to " + interactive; // Environment.the.get().status(msg); // println(msg); // } private static BufferedWriter writer; public static final void setLoggingFile(String loggingFilePath) { writer = Files.openWriter(loggingFilePath); if (writer == null) println("Debug.setLoggingFile() CANT OPEN LOGGING FILE: " + loggingFilePath); else logToFile = true; } public static void closeLoggingFile() { Files.closeWriter(writer); } /** * @return state of the global flag for printing "interactive" debug statements. */ public static boolean getInteractive() { return interactive; } public static boolean logToFile() { return logToFile; } // protected void finalize() // { // AllocationDebugger.finalized(this); // } /** * Check to make sure that condition is true. If not, throw an AssertionError. * This is equivalent to assert, but added since assertion is often disabled by default, and * thus ignored :( * * @param condition * A condition that is expected to be true. * @param format * The format string of the message for the thrown exception. * @param args * The arguments to format. */ public static void check(boolean condition, String format, Object... args) { if (!condition) { throw new AssertionError(String.format(format, args)); } } }