/* **************************************************************************

  $Archive: /njcl_v2/src/com/novell/service/session/util/Debug.java $
  $Revision: 25 $
  $Modtime: 1/08/03 5:43p $

  Copyright (c) 1997 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/
package com.novell.service.session.util;

import com.novell.java.lang.HasRootCause;
import com.novell.java.lang.HasRootCauses;
import com.novell.service.session.*;
import java.util.*;
import java.io.*;

/** @internal
 */
public class Debug
{
   private  static   boolean  append               = false;
   private  static   boolean  assert               = false;
   private  static   int      callDepth            = 30;
   private  static   boolean  caller               = false;
   private  static   boolean  debug                = false;
   private  static   boolean  exitOnAssert         = false;
   private  static   boolean  fixTimeZone          = false;
   private  static   String   outputFile           = "debug.out";
   private  static   boolean  outputAutoFlush      = true;
   private  static   boolean  outputToFile         = false;
   private  static   boolean  prefixEachLine       = true;
   private  static   boolean  printHeader          = false;
   private  static   boolean  quiet                = false;
   private  static   boolean  synchOutput          = true;
   private  static   boolean  systemTimer          = false;
   private  static   boolean  threads              = false;
   private  static   boolean  traceInstructions    = false;
   private  static   boolean  traceMethods         = false;
   private  static   int      truncate             = 0;
//   private  static   String   jvmTag               = "";
//   private  static   long     jvmTagFilter         = 0x000000000000FFFF;

   private  static PrintStream oriout = System.out;
   private  static PrintStream orierr = System.err;
   private  static String className           =
                         "com.novell.service.session.util.Debug";
   private static final String header        = ":::990823a:::";
   private static PrintWriter output;
   private static PrintWriter outputAlways;
   private static final String padding       = "   ";
   private static long startTimeMillis = System.currentTimeMillis();
   private static long lastElapsed = 0;
   private static long lastDelta = 0;

   private static final boolean forceDEBUG = false;

   static
   {
      try
      {
         OutputStreamWriter osw = new OutputStreamWriter(System.out);
         outputAlways = new PrintWriter(osw, outputAutoFlush);

         // Create short "extra" file name modifier, hopefully unique
         // among JVMs
//         long ticks = System.currentTimeMillis();
//         int smallTicks = (int)(ticks & jvmTagFilter);
//         jvmTag = Integer.toHexString(smallTicks);
         if (forceDEBUG)
         {
            append               = false;
            assert               = false;
            callDepth            = -1;
            caller               = true;
            debug                = true;
            exitOnAssert         = false;
            fixTimeZone          = false;
            outputFile           = "debug.out";
            outputAutoFlush      = true;
            outputToFile         = true;
            prefixEachLine       = false;
            printHeader          = false;
            quiet                = false;
            synchOutput          = true;
            systemTimer          = true;
            threads              = true;
            traceInstructions    = false;
            traceMethods         = false;
            truncate             = 0;
         }
        initFromSystemProperties();
        if (debug)
        {
           readDebugProperties("debug.properties");
           setDebug(debug);
        }
//         getDebugProperties();
      }
      catch (Throwable e)
      {
         e.printStackTrace ();

         throw new ExceptionInInitializerError(e);
      }
   }

   synchronized public static void setAppend(boolean append)
   {
      Debug.append = append;
   }

   synchronized public static void setAssert(boolean assert)
   {
      Debug.assert = assert;

      setExitOnAssert(exitOnAssert);
   }

   synchronized public static void setCallDepth(int callDepth)
   {
      Debug.callDepth = callDepth;
   }

   synchronized public static void setCaller(boolean caller)
   {
      Debug.caller = caller;

      setCallDepth(callDepth);
   }

   synchronized public static void setDebug(boolean debug)
   {
      Debug.debug = debug;

      if (debug)
      {
         setOutputToFile(outputToFile);
         setFixTimeZone(fixTimeZone);
         setAssert(assert);
         setTraceInstructions(traceInstructions);
         setTraceMethods(traceMethods);

         String msg = "---New debug session: " +
            new GregorianCalendar().getTime().toString() +
            "(" + header + ")";
         printlnOnly(msg, output);
         dumpDebugProperties();
      }
   }

   synchronized public static void setExitOnAssert(boolean exitOnAssert)
   {
      Debug.exitOnAssert = exitOnAssert;
   }

   synchronized public static void setFixTimeZone(boolean fixTimeZone)
   {
      Debug.fixTimeZone = fixTimeZone;

      // Default doesn't work in jdk 1.1...
      if (fixTimeZone)
         TimeZone.setDefault(TimeZone.getTimeZone("MST"));

   }

   synchronized public static void setOutputFile(String outputFile)
   {
      Debug.outputFile = outputFile;
   }

   synchronized public static void setOutputAutoFlush(boolean outputAutoFlush)
   {
      Debug.outputAutoFlush = outputAutoFlush;
   }

   synchronized public static void setOutputToFile(boolean outputToFile)
   {
      setAppend(append);
      setQuiet(quiet);
      setSystemTimer(systemTimer);
      setThreads(threads);
      setCaller(caller);
      setPrefixEachLine(prefixEachLine);
      setPrintHeader(printHeader);
      setOutputFile(outputFile);
      setOutputAutoFlush(outputAutoFlush);

      Debug.outputToFile = outputToFile;

      try
      {
         OutputStream os;
         //String randomNum = Integer.toString(new Random().nextInt());
         //outputFile = outputFile + randomNum;
         if (outputToFile)
//               os = new FileOutputStream(jvmTag + ".out", append);
            os = new FileOutputStream(outputFile, append);
         else
            os = System.out;

         OutputStreamWriter osw = new OutputStreamWriter(os);
         output = new PrintWriter(osw, outputAutoFlush);
      }
      catch (IOException e)
      {
         throw new SessionRuntimeException("Problem - exception", e);
      }
   }

   synchronized public static void setPrefixEachLine(boolean prefixEachLine)
   {
      Debug.prefixEachLine = prefixEachLine;
   }

   synchronized public static void setPrintHeader(boolean printHeader)
   {
      Debug.printHeader = printHeader;
   }

   synchronized public static void setQuiet(boolean quiet)
   {
      Debug.quiet = quiet;
   }

   synchronized public static void setSystemTimer(boolean systemTimer)
   {
      Debug.systemTimer = systemTimer;
   }

   synchronized public static void setThreads(boolean threads)
   {
      Debug.threads = threads;
   }

   synchronized public static void setTraceInstructions(boolean traceInstructions)
   {
      Debug.traceInstructions = traceInstructions;
   }

   synchronized public static void setTraceMethods(boolean traceMethods)
   {
      Debug.traceMethods = traceMethods;
   }

   synchronized public static void setTruncate(int truncate)
   {
      Debug.truncate = truncate;
   }

   synchronized public static boolean getAppend()
   {
      return append;
   }

   synchronized public static boolean getAssert()
   {
      return assert;
   }

   synchronized public static int getCallDepth()
   {
      return callDepth;
   }

   synchronized public static boolean getCaller()
   {
      return caller;
   }

   synchronized public static boolean getDebug()
   {
      return debug;
   }

   synchronized public static boolean getExitOnAssert()
   {
      return exitOnAssert;
   }

   synchronized public static boolean getFixTimeZone()
   {
      return fixTimeZone;
   }

   synchronized public static String getOutputFile()
   {
      return outputFile;
   }

   synchronized public static boolean getOutputAutoFlush()
   {
      return outputAutoFlush;
   }

   synchronized public static boolean getOutputToFile()
   {
      return outputToFile;
   }

   synchronized public static boolean getPrefixEachLine()
   {
      return prefixEachLine;
   }

   synchronized public static boolean getPrintHeader()
   {
      return printHeader;
   }

   synchronized public static boolean getQuiet()
   {
      return quiet;
   }

   synchronized public static boolean getSystemTimer()
   {
      return systemTimer;
   }

   synchronized public static boolean getThreads()
   {
      return threads;
   }

   synchronized public static boolean getTraceInstructions()
   {
      return traceInstructions;
   }

   synchronized public static boolean getTraceMethods()
   {
      return traceMethods;
   }

   synchronized public static int getTruncate()
   {
      return truncate;
   }

   synchronized static public void dumpDebugProperties()
   {
      if (debug)
      {
         Properties properties = getDebugProperties();
         properties.list(output);
      }
   }

   /**
    * Allow certain statics to be overridden via system properties.
    */
   synchronized static public void initFromSystemProperties()
   {
      try
      {
         if (!debug)  // can turn on, not off
         {
            debug             = new Boolean(
               System.getProperty(className + ".debug", "false")).
               booleanValue();
         }
      }
      catch (SecurityException e)
      {
         System.out.println(
            "Exception reading system properties: " + e);
      }
   }

   /**
    * Return current statics as properties.
    */
   synchronized static public Properties getDebugProperties()
   {
      Properties p = new Properties();
      p.put("append",            new Boolean(append).toString());
      p.put("assert",            new Boolean(assert).toString());
      p.put("callDepth",         new Integer(callDepth).toString());
      p.put("caller",            new Boolean(caller).toString());
      p.put("debug",             new Boolean(debug).toString());
      p.put("exitOnAssert",      new Boolean(exitOnAssert).toString());
      p.put("fixTimeZone",       new Boolean(fixTimeZone).toString());
      p.put("outputAutoFlush",   new Boolean(outputAutoFlush).toString());
      p.put("outputToFile",      new Boolean(outputToFile).toString());
      p.put("outputFile",        outputFile);
      p.put("prefixEachLine",    new Boolean(prefixEachLine).toString());
      p.put("printHeader",       new Boolean(printHeader).toString());
      p.put("quiet",             new Boolean(quiet).toString());
      p.put("systemTimer",       new Boolean(systemTimer).toString());
      p.put("threads",           new Boolean(threads).toString());
      p.put("traceInstructions", new Boolean(traceInstructions).toString());
      p.put("traceMethods",      new Boolean(traceMethods).toString());
      p.put("truncate",          new Integer(truncate).toString());

      return p;
   }

   synchronized static public void writeDebugProperties(String name)
   {
      try
      {
         // Write out the properties for later recall
         FileOutputStream out = new FileOutputStream(name);
         Properties properties = getDebugProperties();
         properties.save(out, "Debug properties");
         out.flush();
         out.close();
      }
      catch (IOException e)
      {
         // Ignore
      }
   }

   synchronized static public void readDebugProperties(String name)
   {
      try
      {
         // Read in the properties, defaulting to the current values
         Properties p = new Properties(
            getDebugProperties());
         FileInputStream in = new FileInputStream(name);
         p.load(in);
         in.close();
         append            = new Boolean(
            p.getProperty("append")).booleanValue();
         assert            = new Boolean(
            p.getProperty("assert")).booleanValue();
         callDepth         = new Integer(
            p.getProperty("callDepth")).intValue();
         caller            = new Boolean(
            p.getProperty("caller")).booleanValue();
         debug             = new Boolean(
            p.getProperty("debug")).booleanValue();
         exitOnAssert      = new Boolean(
            p.getProperty("exitOnAssert")).booleanValue();
         fixTimeZone       = new Boolean(
            p.getProperty("fixTimeZone")).booleanValue();
         outputAutoFlush   = new Boolean(
            p.getProperty("outputAutoFlush")).booleanValue();
         outputToFile      = new Boolean(
            p.getProperty("outputToFile")).booleanValue();
         outputFile        =
            p.getProperty("outputFile");
         prefixEachLine    = new Boolean(
            p.getProperty("prefixEachLine")).booleanValue();
         printHeader       = new Boolean(
            p.getProperty("printHeader")).booleanValue();
         quiet             = new Boolean(
            p.getProperty("quiet")).booleanValue();
         systemTimer       = new Boolean(
            p.getProperty("systemTimer")).booleanValue();
         threads           = new Boolean(
            p.getProperty("threads")).booleanValue();
         traceInstructions = new Boolean(
            p.getProperty("traceInstructions")).booleanValue();
         traceMethods      = new Boolean(
            p.getProperty("traceMethods")).booleanValue();
         truncate          = new Integer(
            p.getProperty("truncate")).intValue();
         setDebug(debug);
      }
      catch (Exception e)
      {
         // Ignore
      }
   }

   synchronized static public void assert
   (
      boolean condition,
      String description
   )
   {
      if (assert)
      {
         if (!condition)
         {
            printlnOnly("ASSERT FAILURE: " + description);
            if (exitOnAssert)
            {
               Thread.dumpStack();
               System.exit(-1);
            }
         }
      }
   }

   synchronized static public void traceOff()
   {
      if (traceInstructions)
         Runtime.getRuntime().traceInstructions(false);
      if (traceMethods)
         Runtime.getRuntime().traceMethodCalls(false);
   }

   synchronized static public void traceOn()
   {
      if (traceInstructions)
         Runtime.getRuntime().traceInstructions(true);
      if (traceMethods)
         Runtime.getRuntime().traceMethodCalls(true);
   }

   synchronized static public void traceInstructions(boolean trace)
   {
      traceInstructions = trace;
      Runtime.getRuntime().traceInstructions(traceInstructions);
   }

   synchronized static public void traceMethods(boolean trace)
   {
      traceMethods = trace;
      Runtime.getRuntime().traceMethodCalls(traceMethods);
   }

   synchronized static public boolean traceInstructionsOn()
   {
      return Debug.traceInstructions;
   }

   synchronized static public boolean traceMethodsOn()
   {
      return Debug.traceMethods;
   }

   /**
    * Dump a class hierarchy if debug true.
    */
   synchronized static public void dumpParents(Class c)
   {
      if (debug)
      {
         dumpParents(c, padding);
      }
   }

   /**
    * Dump a class hierarchy if debug true.
    */
   synchronized static public void dumpParents(Class c, int indent)
   {
      if (debug)
      {
         StringBuffer pad = new StringBuffer();
         for (int i =0; i < indent; i++)
            pad.append(" ");

         dumpParents(c, pad.toString());
      }
   }

   /**
    * Dump a class hierarchy if debug true.
    */
   synchronized static public void dumpParents(Class c, String indent)
   {
      if (debug)
      {
         Class[] parents = c.getClasses();
         for (int i = 0; i < parents.length; i++)
         {
            Class parent = parents[i];
            printlnOnly(indent + parent.getName());
            dumpParents(parent, indent + padding);
         }
      }
   }

   /**
    * Dump members of a class if debug true.
    */
   synchronized static public void dumpMembers(Class c)
   {
      if (debug)
      {
         dumpMembers(c, padding);
      }
   }

   /**
    * Dump members of a class if debug true.
    */
   synchronized static public void dumpMembers(Class c, int indent)
   {
      if (debug)
      {
         StringBuffer pad = new StringBuffer();
         for (int i =0; i < indent; i++)
            pad.append(" ");

         dumpMembers(c, pad.toString());
      }
   }

   /**
    * Dump members of a class if debug true.
    */
   synchronized static public void dumpMembers(Class c, String indent)
   {
      if (debug)
      {
         Class[] members = c.getDeclaredClasses();
         for (int i = 0; i < members.length; i++)
         {
            Class member = members[i];
            printlnOnly(indent + member.getName());
            dumpMembers(member, indent + padding);
         }
      }
   }

   /**
    * Report that an exception is being ignored if debug true.
    */
   synchronized static public void ignoreException(Throwable e)
   {
      ignoreException("", e);
   }

   /**
    * Report that an exception is being ignored if debug true.
    */
   synchronized static public void ignoreException(String context, Throwable e)
   {
      if (debug)
      {
         if (quiet)
            ignoreExceptionQuietly(context, e);
         else
         {
            println("IGNORING EXCEPTION: " + context + ":");
            dumpException(e);
         }
      }
   }

   /**
    * Report that an exception is being ignored if debug true.
    */
   synchronized static public void ignoreExceptionQuietly(Throwable e)
   {
      ignoreExceptionQuietly("", e);
   }

   /**
    * Report that an exception is being ignored if debug true.
    */
   synchronized static public void ignoreExceptionQuietly(String context, Throwable e)
   {
      if (debug)
      {
         printlnOnly("IGNORING EXCEPTION:" + context + ":");
         printlnOnly(e.toString());
      }
   }

   /*
    * Print with beep if debug true
    */
   synchronized static public void printlnBeep(String output)
   {
      printlnOnly("\007" + output);
   }

   synchronized static public void dumpExceptionAlways(Throwable e)
   {
      dumpExceptionAlways(e, outputAlways);
   }

   synchronized static public void dumpExceptionAlways(Throwable e, PrintWriter output)
   {
      dumpException(e, output, padding, true);
   }

   synchronized static public void dumpException(Throwable e)
   {
      dumpException(e, output);
   }

   synchronized static public void dumpException(Throwable e, PrintWriter output)
   {
      if (debug)
      {
         printlnOnly("", output, debug);
         printlnOnly("---EXCEPTION...", output, debug);
         dumpException(e, output, padding, debug);
      }
   }

   synchronized static public void dumpException(
      Throwable e,
      PrintWriter output,
      String indent,
      boolean debug)
   {
      if (debug)
      {
         printStackTrace(indent, e, output, debug);
         while (null != e)
         {
            if (e instanceof HasRootCauses)
            {
               Enumeration enum = ((HasRootCauses)e).getRootCauses();
               if (enum.hasMoreElements())
                  printlnOnly(indent + "Root causes...", output, debug);

               while (enum.hasMoreElements())
               {
                  dumpException(
                     (Throwable)enum.nextElement(),
                     output,
                     indent + padding,
                     debug);
               }
            }
            if (e instanceof HasRootCause)
               e = ((HasRootCause)e).getRootCause();
            else if (e instanceof javax.naming.NamingException)
               e = ((javax.naming.NamingException)e).getRootCause();
            else if (e instanceof java.rmi.RemoteException)
               e = ((java.rmi.RemoteException)e).detail;
            else
               e = null;
            if (null != e)
            {
                printlnOnly(indent + "Root cause...", output, debug);
                printStackTrace(indent + padding, e, output, debug);
            }
         }
         output.flush();
      }
   }

   synchronized static public void printStackTrace(
      String indent,
      Throwable e,
      PrintWriter output,
      boolean debug)
   {
      if (debug)
      {
         try
         {
            StringWriter stringW = new StringWriter();
            PrintWriter printW = new PrintWriter(stringW, outputAutoFlush);

            e.printStackTrace(printW);
   //         printW.flush();

            StringReader stringR = new StringReader(
               stringW.getBuffer().
               toString());

            BufferedReader buffR = new BufferedReader(stringR);

            String line = buffR.readLine();
            while (null != line)
            {
               printlnOnly(indent + line, output, debug);
               line = buffR.readLine();
            }
         }
         catch (Throwable newE)
         {
            ignoreException("Debug.dumpExceptionCaller(" + e + "): ", newE);
         }
      }
   }

   synchronized static public void printCaller()
   {
      printCaller(callDepth);
   }

   synchronized static public void printCaller(int callDepth)
   {
      printCaller(callDepth, output, debug);
   }

   synchronized static public void printCaller(
      int callDepth,
      PrintWriter output,
      boolean debug)
   {
      if (debug)
      {
         try
         {
            // -1 means dump as much as possible
            if (0 > callDepth)
               callDepth = Integer.MAX_VALUE;
            Vector callers = getCallerReader();
            Enumeration lines = callers.elements();
            while (lines.hasMoreElements() && callDepth > 0)
            {
               String line = (String)lines.nextElement();
               printlnOnly(line, output, debug);
               callDepth--;
            }
         }
         catch (NoSuchElementException e)
         {
            // ignore...probably ran out of lines
         }
      }
   }

   synchronized static public String getCallerLine()
   {
      Vector lines = getCallerReader();
      String line = null;

      try
      {
         line = (String)lines.firstElement();
         if (0 != truncate)
         {
            // pseudo: remove up to 'truncate' leading package qualifiers
            //         but don't remove method name
            // line format: x.y.z.method(...

            // begin of method parameters
            int method = line.indexOf('(');
            // begin of method
            method = line.lastIndexOf('.', method);

            int curr, next = 0;  // start at beginning
            int count = truncate;
            do
            {
               curr = next + 1;
               next = line.indexOf('.', curr);  // find next '.'
            } while (curr < method &&
                     --count > 0 &&
                     next != -1);
            line = line.substring(curr);
         }
         else
            line = line.substring(4);

      }
      catch (NoSuchElementException e)
      {
         ignoreException("Debug.getCallerLine(): ", e);
      }
      return line;
   }

   synchronized static public Vector getCallerReader()
   {
      Vector callers = new Vector();
      try
      {
         StringWriter stringW = new StringWriter();
         PrintWriter printW = new PrintWriter(stringW, outputAutoFlush);

         Exception callStack = new DebugStackTraceException();
         callStack.fillInStackTrace();
         callStack.printStackTrace(printW);
//         printW.flush();

         StringReader stringR = new StringReader(
            stringW.getBuffer().
            toString());

         BufferedReader buffR = new BufferedReader(stringR);

         // remove header and this function
//         buffR.readLine();
//         buffR.readLine();

         String caller;

         // Find first line not from this class
         do
         {
            caller = buffR.readLine();
         } while (-1 == caller.indexOf("getCallerReader"));
         do
         {
            caller = buffR.readLine();
         } while (-1 != caller.indexOf(className));

         do
         {
            callers.addElement(caller);
            caller = buffR.readLine();
         } while (caller != null);
      }
      catch (Throwable newE)
      {
         ignoreException("Debug.getCallerReader(): ", newE);
      }

      return callers;
   }

   synchronized static public void dumpExceptionCaller(Throwable e)
   {
      dumpExceptionCaller(e, 3);
   }

   // count is number of calls to show
   synchronized static public void dumpExceptionCaller(Throwable e, int count)
   {
      printlnOnly("Caller: ");
      try
      {
         StringWriter stringW = new StringWriter();
         PrintWriter printW = new PrintWriter(stringW, outputAutoFlush);

         e.printStackTrace(printW);
//         printW.flush();

         StringReader stringR = new StringReader(
            stringW.getBuffer().
            toString());

         BufferedReader buffR = new BufferedReader(stringR);

         // throw away first line
         buffR.readLine();

         // show count lines of call stack
         for (int i = 0; i < count; i++)
         {
            printlnOnly(
               buffR.readLine());
         }
      }
      catch (Throwable newE)
      {
         ignoreException("Debug.dumpExceptionCaller(" + e + "): ", newE);
      }
   }

   synchronized public static void dumpThreads()
   {
      if (debug)
      {
         printlnOnly("Current threads:");
         ThreadGroup tg = Thread.currentThread().getThreadGroup();
         while (null != tg.getParent())
            tg = tg.getParent();

         Thread[] threads = new Thread[tg.activeCount()];
         int count = tg.enumerate(threads, true);
         for (int i = 0; i < count; i++)
         {
            Thread t = threads[i];
            printlnOnly("   " + t.getName());
         }
      }
   }

   synchronized public static String getShortThreadID()
   {
      // FIX: Need to get unique thread id
      String s ="0x" + Integer.toHexString(Thread.currentThread().hashCode());
      return s;
   }

   synchronized public static void print(String msg)
   {
      if (debug)
         printlnOnly(msg);
   }

   synchronized public static void printlnOnly(String msg)
   {
      if (debug)
      {
         printlnOnly(msg, output);
      }
   }

   public static void printlnOnly(String msg, PrintWriter output)
   {
      printlnOnly(msg, output, debug);
   }

   public static void printlnOnly(String msg, PrintWriter output, boolean debug)
   {
      if (debug)
      {
         if (prefixEachLine)
            msg = buildMsg(msg);
//         msg = jvmTag + ": " + msg;

         output.println(msg);

//         output.flush();
      }
   }

   synchronized public static void println(String msg)
   {
      if (debug)
      {
         println(msg, output);
      }
   }

   public static void println(String msg, PrintWriter output)
   {
      println(msg, output, debug);
   }

   public static void updateTimers()
   {
      long newTimeMillis = System.currentTimeMillis();

      long elapsed = newTimeMillis - startTimeMillis;
      if (elapsed < 0)
         elapsed += Long.MAX_VALUE;

      long delta = elapsed - lastElapsed;
      if (delta < 0)
         delta += Long.MAX_VALUE;

      lastElapsed = elapsed;
      lastDelta = delta;
   }

   public static String buildMsg(String msg)
   {
      if (systemTimer)
      {
         msg = "(e:" + Long.toString(lastElapsed) + ", d:" + Long.toString(lastDelta) + ") " + msg;
      }
      if (threads)
         msg = Thread.currentThread().getName() + ": " + msg;
      if (printHeader && null != header)
         msg = header + msg;
      return msg;
   }


   public static void println(
      String msg,
      PrintWriter output,
      boolean debug)
   {
      if (debug)
      {
         updateTimers();
         if (!prefixEachLine)
         {
            msg = buildMsg(msg);
         }
         if (caller)
         {
            printlnOnly(msg, output, debug);
            printCaller(callDepth, output, debug);
         }
         else
            printlnOnly(getCallerLine() + ": " + msg, output, debug);
         if (synchOutput)
            System.err.println(msg);
      }
   }

   public static void printAlways(String msg)
   {
      printlnOnly(msg, output, debug);
   }

   public static void printAlways(String msg, PrintWriter output)
   {
      printlnOnly(msg, output, true);
   }

   public static void begin()
   {
   }

   public static void end()
   {
   }
}


