// ============================================================================
// File:               $File$
//
// Project:
//
// Purpose:
//
// Author:             Rammi
//
// Copyright Notice:   (c) 1999-2006  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date: 2006-02-01 13:48:49 +0100 (Mi, 01 Feb 2006) $
//
// History:	       $Log$
//=============================================================================

package de.caff.util.debug;

import de.caff.util.Utility;

import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.text.StringCharacterIterator;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

/**
 *  General tool for debugging purposes.
 *
 *  This class defines static methods allowing output of debugging messages and 
 *  assertion checks.
 *  <p>
 *  Debugging messages fall into 6 categories handled by approbiately named methods.
 *  <ul>
 *  <li><code>trace()</code> -- trace messages (ultra low level priority)</li>
 *  <li><code>message()</code> -- standard messages (low level priority)</li>
 *  <li><code>warn()</code> -- warning messages (normal priority)</li>
 *  <li><code>error()</code> -- error messages (high priority)</li>
 *  <li><code>fatal()</code> -- fatal error messages (ultra high priority), 
 *                              allowing to interrupt program</li>
 *  <li><code>log()</code> -- logging messages</li>
 *  </ul>
 *  Each of these methods is overloaded to accept 
 *  <ul>
 *  <li>a String parameter</li>
 *  <li>an Exception parameter</li>
 *  <li>a format parameter and one Object argument</li>
 *  <li>a format parameter and two Object arguments</li>
 *  <li>a format parameter and three Object arguments</li>
 *  <li>a format parameter and four Object arguments</li>
 *  <li>a format parameter and five Object arguments</li>
 *  <li>a format parameter and six Object arguments</li>
 *  <li>a format parameter and an Object array argument for these rare cases where more than 6 arguments are needed</li>
 *  </ul>
 *  There is an <code>insist()</code> (formerly called <code>assert</code> until it became a Java keyword)
 *  method allowing to define assertions, too.
 *  <p>
 *  Each of the 6 categories and the handling of assertions can be switched on or off
 *  globally. Switching a category off means that debug messages in that category are not captured but
 *  discarded. Discarding is done as early as possible so there is only a small overhead with discarded messages
 *  (a method call, possibly parameter boxing, one if).
 *  Capturing <b>a lot</b> of messages may slow down a program considerably (here a lot depends on the
 *  registered listeners).
 *  <p>
 *  For every category there's a dedicated listener interface allowing listeners to
 *  register for that kind of messages (including an <code>AnyMessageDebugListener</code>
 *  interface for registration on all categories). These listeners will receive <em>raw</em>
 *  messages, i.e. get directly the messages as they are sent in the code. There's another
 *  <code>CookedMessageDebugListener</code> interface where listeners receive <em>cooked</em>
 *  messages. Cooked messages include the time and the sender's file position as well as an indicator
 *  of the category of the message.
 *  <p>
 *  There are no listeners registered by default but there are convenience methods allowing
 *  to create a simple listener allowing output on stdout (see {@link #installCookedOutput()})
 *  or a stream (see {@link #installCookedOutput(java.io.OutputStream)}) and a method which
 *  creates a JFrame to display the messages and allowing switching of the categories
 *  (see {@link #createDebugWindow()} or {@link #createDebugWindow(boolean)}).
 *  <p>
 *  It's possible to capture the standard output streams of the program. When this is done
 *  text printed to <tt>stdout</tt> is send via the <code>message()</code> interface and text
 *  printed to <tt>stderr</tt> is send via the <code>error()</code> interface. When captured these
 *  streams are buffered until another kind of message arrives or capturing is switched off.
 *  <p>
 *  You should prefer the following syntax when writing debug messages
 *  <pre>
 *    Debug.trace("foo=%1", new Integer(foo));
 *  </pre>
 *  instead of
 *  <pre>
 *    Debug.trace("foo="+foo);
 *  </pre>
 *  because in the first call only one object is unnecessarily created if trace messages are switched off
 *  while in the latter case two objects are created (a StringBuffer and a String). In the given example you
 *  will even gain more if <code>foo</code> already is an object and not a primitive which has to be boxed
 *  like assumed (no extra creations vs 2).
 *  <p>
 *
 *
 *  @see Format
 *  @see AnyMessageDebugListener
 *  @see AssertionMessageDebugListener
 *  @see CookedMessageDebugListener
 *  @see ErrorMessageDebugListener
 *  @see FatalMessageDebugListener
 *  @see LogMessageListener
 *  @see StandardMessageDebugListener
 *  @see WarningMessageDebugListener
 *
 *  @author Rammi
 */
public class Debug
        implements DebugConstants
{
  /** Boolean property or applet parameter for showing a debug window. */
  public static final String PARAM_DEBUG_SHOW_WINDOW = "debug.show.window";
  /** Boolean property or applet parameter for printing debug messages to the console. */
  public static final String PARAM_DEBUG_PRINT_CONSOLE = "debug.print.console";
  /**
   *  Property or applet parameter for selecting handled debug channels. A character is used to
   *  indicate a channel:
   *  <table>
   *   <tr><td><b>{@link #TRACE_CHAR}</b></td><td>Trace level messages</td></tr>
   *   <tr><td><b>{@link #MESSAGE_CHAR}</b></td><td>Standard level debug messages</td></tr>
   *   <li><td><b>{@link #WARNING_CHAR}</b></td><td>Warning level debug messages</td></tr>
   *   <li><td><b>{@link #ERROR_CHAR}</b></td><td>Error level debug messages</td></tr>
   *   <li><td><b>{@link #FATAL_CHAR}</b></td><td>Fatal level debug messages</td></tr>
   *   <li><td><b>{@link #LOG_CHAR}</b></td><td>Log messages</td></tr>
   *   <li><td><b>{@link #ASSERT_CHAR}</b></td><td>Assertions</td></tr>
   *  </table>
   */
  public static final String PARAM_DEBUG_MASK = "debug.mask";

  private static long             _mask            = 0;

  private static boolean          _bufferStreams   = false;

  private static DebugMessageCook                          _cook            = new DebugMessageCook();
  private static Collection<TraceMessageDebugListener>     _traceList       = new LinkedList<TraceMessageDebugListener>();
  private static Collection<StandardMessageDebugListener>  _stdList         = new LinkedList<StandardMessageDebugListener>();
  private static Collection<WarningMessageDebugListener>   _warnList        = new LinkedList<WarningMessageDebugListener>();
  private static Collection<ErrorMessageDebugListener>     _errorList       = new LinkedList<ErrorMessageDebugListener>();
  private static Collection<FatalMessageDebugListener>     _fatalList       = new LinkedList<FatalMessageDebugListener>();
  private static Collection<AssertionMessageDebugListener> _assertList      = new LinkedList<AssertionMessageDebugListener>();
  private static Collection<LogMessageListener>            _logList         = new LinkedList<LogMessageListener>();

  private static Collection<ModeChangeListener>            _modeList        = new LinkedList<ModeChangeListener>();

  private static PrintStream      _oldStdout       = null;
  private static StringBuffer     _collectedStdout = null;
  private static PrintStream      _oldStderr       = null;
  private static StringBuffer     _collectedStderr = null;

  static {
    // _cook receives all messages
    addAnyMessageDebugListener(_cook);
  }

  /**
   *  A Java interface for listeners which wants to be informed about the internal
   *  mode of the debug interface.
   */
  public static interface ModeChangeListener {
    /**
     *  The debugging mode for an id is changed.
     *  @see DebugConstants
     *  @param  modeID  one of <code>TRACE</code>, <code>MESSAGE</code>, <code>WARNING>/code>,
     *                  <code>ERROR</code>, <code>FATAL</code>, <code>LOG</code>,
     *                  or <code>ASSERT</code>
     *  @param  state   new state of the given category
     */
    public void modeChanged(int modeID, boolean state);

    /**
     *  The capturing mode for stdout is changed.
     *  @param  state   new capturing mode
     */
    public void stdoutCaptureChanged(boolean state);

    /**
     *  The capturing mode for stderr is changed.
     *  @param  state   new capturing mode
     */
    public void stderrCaptureChanged(boolean state);
  }

  /**
   *  Add a ModeChangeListener.
   *  @param  listener   the listener to add
   */
  public static void addModeChangeListener(ModeChangeListener listener)
  {
    _modeList.add(listener);
  }


  /**
   *  Remove a mode change listener
   *  @param  listener   the listener to remove
   */
  public static void removeModeChangeListener(ModeChangeListener listener)
  {
    _modeList.remove(listener);
  }


  /**
   *  Informs the registered listeners of a mode change.
   *  @param  modeID   mode number
   *  @param  state    new state
   */
  private static void informModeChangeListeners(int modeID, boolean state)
  {
    for (Iterator<ModeChangeListener> it = _modeList.iterator();   it.hasNext();  ) {
      (it.next()).modeChanged(modeID, state);
    }
  }

  /**
   *  Informs the registered listeners of a mode change concerning stdout capturing.
   *  @param  state    new state
   */
  private static void informStdoutCaptureModeListeners(boolean state)
  {
    for (Iterator<ModeChangeListener> it = _modeList.iterator();   it.hasNext();  ) {
      (it.next()).stdoutCaptureChanged(state);
    }
  }

  /**
   *  Informs the registered listeners of a mode change concerning stdout capturing.
   *  @param  state    new state
   */
  private static void informStderrCaptureModeListeners(boolean state)
  {
    for (Iterator<ModeChangeListener> it = _modeList.iterator();   it.hasNext();  ) {
      (it.next()).stderrCaptureChanged(state);
    }
  }


  /**
   *  Append a trace message listener. This listener will receive debug messages
   *  sent with <code>trace</code>.
   *  @param  listener  new listener
   */
  public static void addTraceMessageDebugListener(TraceMessageDebugListener listener)
  {
    _traceList.add(listener);
  }

  /**
   *  Remove a trace message listener
   *  @param  listener  listener to remove
   */
  public static void removeTraceMessageDebugListener(TraceMessageDebugListener listener)
  {
    _traceList.remove(listener);
  }

  /**
   *  Append a standard debug message listener. This listener will receive debug
   *  messages sent with <code>message</code>.
   *  @param  listener  new listener
   */
  public static void addStandardMessageDebugListener(StandardMessageDebugListener listener)
  {
    _stdList.add(listener);
  }

  /**
   *  Remove a standard debug message listener.
   *  @param  listener  listener to remove
   */
  public static void removeStandardMessageDebugListener(StandardMessageDebugListener listener)
  {
    _stdList.remove(listener);
  }

  /**
   *  Append a warning message debug listener. This listener will receive debug
   *  messages sent with <code>warn</code>.
   *  @param  listener  new listener
   */
  public static void addWarningMessageDebugListener(WarningMessageDebugListener listener)
  {
    _warnList.add(listener);
  }

  /**
   *  Remove a warning message debug listener.
   *  @param  listener  listener to remove
   */
  public static void removeWarningMessageDebugListener(WarningMessageDebugListener listener)
  {
    _warnList.remove(listener);
  }

  /**
   *  Append an error message debug listener. This listener will receive messages
   *  sent with <code>error</code>.
   *  @param  listener  new listener
   */
  public static void addErrorMessageDebugListener(ErrorMessageDebugListener listener)
  {
    _errorList.add(listener);
  }

  /**
   *  Remove an error message debug listener.
   *  @param  listener  listener to remove
   */
  public static void removeErrorMessageDebugListener(ErrorMessageDebugListener listener)
  {
    _errorList.remove(listener);
  }

  /**
   *  Append a fatal error message debug listener. This listener will receive messages
   *  sent with <code>fatal</code>.
   *  @param  listener  new listener
   */
  public static void addFatalMessageDebugListener(FatalMessageDebugListener listener)
  {
    _fatalList.add(listener);
  }

  /**
   *  Remove a fatal error message debug listener.
   *  @param  listener  listener to remove
   */
  public static void removeFatalMessageDebugListener(FatalMessageDebugListener listener)
  {
    _fatalList.remove(listener);
  }

  /**
   *  Add an assert message debug listener. This listener will receive assert messages on
   *  failed assertions and can initiate a exception.
   *  @param  listener  new listener
   */
  public static void addAssertionMessageDebugListener(AssertionMessageDebugListener listener)
  {
    _assertList.add(listener);
  }

  /**
   *  Remove an assert message debug listener.
   *  @param  listener  listener to remove
   */
  public static void removeAssertionMessageDebugListener(AssertionMessageDebugListener listener)
  {
    _assertList.remove(listener);
  }

  /**
   *  Append a log message listener. This listener will receive messages sent with 
   *  <code>log</code>.
   *  @param  listener  new listener
   */
  public static void addLogMessageListener(LogMessageListener listener)
  {
    _logList.add(listener);
  }

  /**
   *  Remove a log message listener.
   *  @param  listener  listener to remove
   */
  public static void removeLogMessageListener(LogMessageListener listener)
  {
    _logList.remove(listener);
  }

  /**
   *  Append a listener for cooked messages. This listener will receive messages
   *  of all allowed categories in an enhanced format.
   *  @param  listener  new listener
   */
  public static void addCookedMessageDebugListener(CookedMessageDebugListener listener)
  {
    _cook.addListener(listener);
  }

  /**
   *  Remove a listener for cooked messages.
   *  @param  listener  listener to remove
   */
  public static void removeCookedMessageDebugListener(CookedMessageDebugListener listener)
  {
    _cook.removeListener(listener);
  }

  /**
   *  Append a listener for all messages. This listener will receive messages of all
   *  allowed categories in raw format.
   *  @param  listener  new listener
   */
  public static void addAnyMessageDebugListener(AnyMessageDebugListener listener)
  {
    addTraceMessageDebugListener(listener);
    addStandardMessageDebugListener(listener);
    addWarningMessageDebugListener(listener);
    addErrorMessageDebugListener(listener);
    addFatalMessageDebugListener(listener);
    addAssertionMessageDebugListener(listener);
    addLogMessageListener(listener);
  }

  /**
   *  Remove a listener for all messages.
   *  @param  listener  listener to remove
   */
  public static void removeAnyMessageDebugListener(AnyMessageDebugListener listener)
  {
    removeTraceMessageDebugListener(listener);
    removeStandardMessageDebugListener(listener);
    removeWarningMessageDebugListener(listener);
    removeErrorMessageDebugListener(listener);
    removeFatalMessageDebugListener(listener);
    removeAssertionMessageDebugListener(listener);
    removeLogMessageListener(listener);
  }

  /**
   *  Set the debugging mask. This allows one step setting of which messages are
   *  handled or discarded.
   *  @param mask  debug mask. The possible values are defined in DebugConstants.
   */
  public static void setMask(long mask)
  {
    if (mask != _mask) {
      long diff = _mask ^ mask;
      for (int i = 0;   i < 64;   ++i) {
        if ((0x01L << i  &  diff) != 0) {
          informModeChangeListeners(i, (mask & 0x01L << i) != 0);
        }
      }
      _mask = mask;
    }
  }

  /**
   *  Get the active debugging mask.
   *  @return  debugging mask. The possible values are defined in DebugConstant.
   */
  public static long getMask()
  {
    return _mask;
  }

  /**
   *  Set the trace mode.
   *  @param  mode  <code>true</code> = trace messages are handled<br>
   *                <code>false</code> = trace messages are discarded
   */
  public static void setTraceMode(boolean mode)
  {
    if (getTraceMode() != mode) {
      if (mode) {
        _mask |= TRACE_FLAG;
      }
      else {
        _mask &= ~TRACE_FLAG;
      }
      informModeChangeListeners(TRACE, mode);
    }
  }

  /**
   *  Get the trace mode.
   *  @return  <code>true</code> = trace messages are handled<br>
   *           <code>false</code> = trace messages are discarded
   */
  public static boolean getTraceMode()
  {
    return (_mask & TRACE_FLAG) != 0;
  }

  /**
   *  Set the standard message mode.
   *  @param  mode  <code>true</code> = standard messages are handled<br>
   *                <code>false</code> = standard messages are discarded
   */
  public static void setStandardMode(boolean mode)
  {
    if (getStandardMode() != mode) {
      if (mode) {
        _mask |= MESSAGE_FLAG;
      }
      else {
        _mask &= ~MESSAGE_FLAG;
      }
      informModeChangeListeners(MESSAGE, mode);
    }
  }

  /**
   *  Get the standard message mode.
   *  @return  <code>true</code> = standard messages are handled<br>
   *           <code>false</code> = standard messages are discarded
   */
  public static boolean getStandardMode()
  {
    return (_mask & MESSAGE_FLAG) != 0;
  }

  /**
   *  Set the warning message mode.
   *  @param  mode  <code>true</code> = warning messages are handled<br>
   *                <code>false</code> = warning messages are discarded
   */
  public static void setWarningMode(boolean mode)
  {
    if (getWarningMode() != mode) {
      if (mode) {
        _mask |= WARNING_FLAG;
      }
      else {
        _mask &= ~WARNING_FLAG;
      }
      informModeChangeListeners(WARNING, mode);
    }
  }

  /**
   *  Get the warning message mode.
   *  @return  <code>true</code> = warning messages are handled<br>
   *           <code>false</code> = warning messages are discarded
   */
  public static boolean getWarningMode()
  {
    return (_mask & WARNING_FLAG) != 0;
  }

  /**
   *  Set the error message mode.
   *  @param  mode  <code>true</code> = error messages are handled<br>
   *                <code>false</code> = error messages are discarded
   */
  public static void setErrorMode(boolean mode)
  {
    if (getErrorMode() != mode) {
      if (mode) {
        _mask |= ERROR_FLAG;
      }
      else {
        _mask &= ~ERROR_FLAG;
      }
      informModeChangeListeners(ERROR, mode);
    }
  }

  /**
   *  Set the error message mode.
   *  @return  <code>true</code> = error messages are handled<br>
   *           <code>false</code> = error messages are discarded
   */
  public static boolean getErrorMode()
  {
    return (_mask & ERROR_FLAG) != 0;
  }

  /**
   *  Set the logging message mode.
   *  @param  mode  <code>true</code> = logging messages are handled<br>
   *                <code>false</code> = logging messages are discarded
   */
  public static void setLogMode(boolean mode)
  {
    if (getLogMode() != mode) {
      if (mode) {
        _mask |= LOG_FLAG;
      }
      else {
        _mask &= ~LOG_FLAG;
      }
      informModeChangeListeners(LOG, mode);
    }
  }

  /**
   *  Get the logging message mode.
   *  @return  <code>true</code> = logging messages are handled<br>
   *           <code>false</code> = logging messages are discarded
   */
  public static boolean getLogMode()
  {
    return (_mask & LOG_FLAG) != 0;
  }

  /**
   *  Set the fatal error message mode.
   *  @param  mode  <code>true</code> = fatal error messages are handled<br>
   *                <code>false</code> = fatal error messages are discarded (i.e. no exit)
   */
  public static void setFatalMode(boolean mode)
  {
    if (getFatalMode() != mode) {
      if (mode) {
        _mask |= FATAL_FLAG;
      }
      else {
        _mask &= ~FATAL_FLAG;
      }
      informModeChangeListeners(FATAL, mode);
    }
  }

  /**
   *  Get the fatal error message mode.
   *  @return  <code>true</code> = fatal error messages are handled<br>
   *           <code>false</code> = fatal error messages are discarded (i.e. no exit)
   */
  public static boolean getFatalMode()
  {
    return (_mask & FATAL_FLAG) != 0;
  }

  /**
   *  Set the assertion handling mode.
   *  @param  mode  <code>true</code> = assertions are handled<br>
   *                <code>false</code> = assertions are discarded (i.e. no Exception)
   */
  public static void setAssertionMode(boolean mode)
  {
    if (getAssertionMode() != mode) {
      if (mode) {
        _mask |= ASSERT_FLAG;
      }
      else {
        _mask &= ~ASSERT_FLAG;
      }
      informModeChangeListeners(ASSERT, mode);
    }
  }

  /**
   *  Get the assertion handling mode.
   *  @return  <code>true</code> = assertions are handled<br>
   *           <code>false</code> = assertions are discarded (i.e. no Exception)
   */
  public static boolean getAssertionMode()
  {
    return (_mask & ASSERT_FLAG) != 0;
  }

  /**
   *  Send a trace message to all listeners.
   *  @param  msg  the message
   */
  private static void distributeTraceMessage(String msg)
  {
    sendCollectedStreams();
    for (Iterator<TraceMessageDebugListener> it = _traceList.iterator();  it.hasNext();  ) {
      (it.next()).receiveTraceMessage(msg, getCallerPosition());
    }
  }

  /**
   *  Send a standard message to all listeners.
   *  @param  msg  the message
   */
  private static void distributeStandardMessage(String msg)
  {
    sendCollectedStreams();
    for (Iterator<StandardMessageDebugListener> it = _stdList.iterator();  it.hasNext();  ) {
      (it.next()).receiveStandardMessage(msg, getCallerPosition());
    }
  }

  /**
   *  Send a warning message to all listeners.
   *  @param  msg  the message
   */
  private static void distributeWarningMessage(String msg)
  {
    sendCollectedStreams();
    for (Iterator<WarningMessageDebugListener> it = _warnList.iterator();  it.hasNext();  ) {
      (it.next()).receiveWarningMessage(msg, getCallerPosition());
    }
  }

  /**
   *  Send an error message to all listeners.
   *  @param  msg  the message
   */
  private static void distributeErrorMessage(String msg)
  {
    sendCollectedStreams();
    for (Iterator<ErrorMessageDebugListener> it = _errorList.iterator();  it.hasNext();  ) {
      (it.next()).receiveErrorMessage(msg, getCallerPosition());
    }
  }

  /**
   *  Send a logging message to all listeners.
   *  @param  msg  the message
   */
  private static void distributeLogMessage(String msg)
  {
    sendCollectedStreams();
    for (Iterator<LogMessageListener> it = _logList.iterator();  it.hasNext();  ) {
      (it.next()).receiveLogMessage(msg, getCallerPosition());
    }
  }

  /**
   *  Send a fatal error message to all listeners.
   *  If any of them wants to exit, exit afterwards.
   *  @param  msg  the message
   */
  private static void distributeFatalMessage(String msg)
  {
    sendCollectedStreams();

    int ret = 0;

    for (Iterator<FatalMessageDebugListener> it = _fatalList.iterator();  it.hasNext();  ) {
      int retVal = (it.next()).receiveFatalMessage(msg, getCallerPosition());
      if (ret == 0  &&  retVal != 0) {
        ret = retVal;
      }
    }

    if (ret != 0) {
      System.exit(ret);
    }
  }

  /**
   *  Send an assertion message to all listeners.
   *  @param  msg  the message
   *  @return <code>true</code> if any of the listeners wants to throw an exception<br>
   *          <code>false</code> else
   */
  private static boolean distributeAssertionMessage(String msg)
  {
    sendCollectedStreams();

    boolean ret = false;

    for (Iterator<AssertionMessageDebugListener> it = _assertList.iterator();  it.hasNext();  ) {
      boolean retVal = (it.next()).receiveFailedAssertionMessage(msg, getCallerPosition());
      if (!ret  &&  retVal) {
        ret = retVal;
      }
    }

    return ret;
  }

  /**
   *  Print a trace message without further arguments.
   *  @param msg  message
   */
  public static void trace(String msg)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(msg);
    }
  }

  /**
   *  Print a trace message from a throwable.
   *  @param x throwable argument
   */
  public static void trace(Throwable x)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(getStackDump(x));
    }
  }

  /**
   *  Print a trace message without further arguments.
   *  @param obj  Objekt
   */
  public static void trace(Object obj)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.toString(obj));
    }
  }

  /**
   *  Print a trace message with one argument.
   *  @see Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   */
  public static void trace(String msg, Object arg1)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, arg1));
    }
  }

  /**
   *  Print a trace message with two arguments.
   *  @see Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   */
  public static void trace(String msg, Object arg1, Object arg2)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, arg1, arg2));
    }
  }

  /**
   *  Print a trace message with three arguments.
   *  @see Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   */
  public static  void trace(String msg, Object arg1, Object arg2, Object arg3)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, arg1, arg2, arg3));
    }
  }

  /**
   *  Print a trace message with four arguments.
   *  @see Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   */
  public static  void trace(String msg, Object arg1, Object arg2, Object arg3,
                            Object arg4)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, arg1, arg2, arg3, arg4));
    }
  }

  /**
   *  Print a trace message with five arguments.
   *  @see Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   */
  public static  void trace(String msg, Object arg1, Object arg2, Object arg3,
                            Object arg4, Object arg5)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5));
    }
  }

  /**
   *  Print a trace message with six arguments.
   *  @see Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   *  @param arg6 argument 6
   */
  public static  void trace(String msg, Object arg1, Object arg2, Object arg3,
                            Object arg4, Object arg5, Object arg6)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5, arg6));
    }
  }

  /**
   *  Print a trace message with up to nine arguments.
   *  @see Format#format
   *  @param msg  message
   *  @param args arguments
   */
  public static void trace(String msg, Object ... args)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeTraceMessage(Format.format(msg, args));
    }
  }

  /**
   *  Print a standard message without further arguments.
   *  @param msg  message
   */
  public static void message(String msg)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(msg);
    }
  }

  /**
   *  Print a standard message from a throwable.
   *  @param x throwable argument
   */
  public static void message(Throwable x)
  {
    if ((_mask & TRACE_FLAG) != 0) {
      distributeStandardMessage(getStackDump(x));
    }
  }

  /**
   *  Print a standard message without further arguments.
   *  @param obj  objekt
   */
  public static void message(Object obj)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.toString(obj));
    }
  }

  /**
   *  Print a standard message with one argument.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   */
  public static void message(String msg, Object arg1)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, arg1));
    }
  }

  /**
   *  Print a standard message with two arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   */
  public static void message(String msg, Object arg1, Object arg2)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, arg1, arg2));
    }
  }

  /**
   *  Print a standard message with three arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   */
  public static void message(String msg, Object arg1, Object arg2, Object arg3)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, arg1, arg2, arg3));
    }
  }

  /**
   *  Print a standard message with four arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   */
  public static void message(String msg, Object arg1, Object arg2, Object arg3,
                             Object arg4)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, arg1, arg2, arg3, arg4));
    }
  }

  /**
   *  Print a standard message with five arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   */
  public static void message(String msg, Object arg1, Object arg2, Object arg3,
                             Object arg4, Object arg5)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5));
    }
  }

  /**
   *  Print a standard message with six arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   *  @param arg6 argument 6
   */
  public static void message(String msg, Object arg1, Object arg2, Object arg3,
                             Object arg4, Object arg5, Object arg6)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5, arg6));
    }
  }

  /**
   *  Print a standard message with up to nine arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param args arguments
   */
  public static void message(String msg, Object ... args)
  {
    if ((_mask & MESSAGE_FLAG) != 0) {
      distributeStandardMessage(Format.format(msg, args));
    }
  }

  /**
   *  Print a warning message without further arguments.
   *  @param msg  message
   */
  public static void warn(String msg)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(msg);
    }
  }

  /**
   *  Print warning message from a throwable.
   *  @param x throwable argument
   */
  public static void warn(Throwable x)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(getStackDump(x));
    }
  }

  /**
   *  Print a warning message without further arguments.
   *  @param obj  message
   */
  public static void warn(Object obj)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.toString(obj));
    }
  }

  /**
   *  Print a warning message with one argument.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   */
  public static void warn(String msg, Object arg1)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, arg1));
    }
  }

  /**
   *  Print a warning message with two arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   */
  public static void warn(String msg, Object arg1, Object arg2)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, arg1, arg2));
    }
  }

  /**
   *  Print a warning message with three arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   */
  public static void warn(String msg, Object arg1, Object arg2, Object arg3)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, arg1, arg2, arg3));
    }
  }

  /**
   *  Print a warning message with four arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   */
  public static void warn(String msg, Object arg1, Object arg2, Object arg3,
                          Object arg4)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, arg1, arg2, arg3, arg4));
    }
  }

  /**
   *  Print a warning message with five arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   */
  public static void warn(String msg, Object arg1, Object arg2, Object arg3,
                          Object arg4, Object arg5)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5));
    }
  }

  /**
   *  Print a warning message with six arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   *  @param arg6 argument 6
   */
  public static void warn(String msg, Object arg1, Object arg2, Object arg3,
                          Object arg4, Object arg5, Object arg6)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5, arg6));
    }
  }

  /**
   *  Print a warning message with up to nine arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param args arguments
   */
  public static void warn(String msg, Object ... args)
  {
    if ((_mask & WARNING_FLAG) != 0) {
      distributeWarningMessage(Format.format(msg, args));
    }
  }

  /**
   *  Print an error message without further arguments.
   *  @param msg  message
   */
  public static void error(String msg)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(msg);
    }
  }

  /**
   *  Print an error message for a throwable.
   *  @param x throwable argument
   */
  public static void error(Throwable x)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(getStackDump(x));
    }
  }

  /**
   *  Print an error message without further arguments.
   *  @param obj  objekt
   */
  public static void error(Object obj)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.toString(obj));
    }
  }

  /**
   *  Print an error message with one argument.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   */
  public static void error(String msg, Object arg1)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, arg1));
    }
  }

  /**
   *  Print an error message with two arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   */
  public static void error(String msg, Object arg1, String arg2)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, arg1, arg2));
    }
  }

  /**
   *  Print an error message with three arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   */
  public static void error(String msg, Object arg1, Object arg2, Object arg3)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, arg1, arg2, arg3));
    }
  }

  /**
   *  Print an error message with four arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   */
  public static void error(String msg, Object arg1, Object arg2, Object arg3,
                           Object arg4)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, arg1, arg2, arg3, arg4));
    }
  }

  /**
   *  Print an error message with five arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   */
  public static void error(String msg, Object arg1, Object arg2, Object arg3,
                           Object arg4, Object arg5)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5));
    }
  }

  /**
   *  Print an error message with six arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   *  @param arg6 argument 6
   */
  public static void error(String msg, Object arg1, Object arg2, Object arg3,
                           Object arg4, Object arg5, Object arg6)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5, arg6));
    }
  }

  /**
   *  Print an error message with up to nine arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param args arguments
   */
  public static void error(String msg, Object ... args)
  {
    if ((_mask & ERROR_FLAG) != 0) {
      distributeErrorMessage(Format.format(msg, args));
    }
  }


  /**
   *  Print a fatal error message without further arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @param msg  message
   */
  public static void fatal(String msg)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(msg);
    }
  }

  /**
   *  Print a fatal error message for a throwable.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @param x throwable argument
   */
  public static void fatal(Throwable x)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(getStackDump(x));
    }
  }

  /**
   *  Print a fatal error message without further arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @param obj   object
   */
  public static void fatal(Object obj)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.toString(obj));
    }
  }

  /**
   *  Print a fatal error message with one argument.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   */
  public static void fatal(String msg, Object arg1)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, arg1));
    }
  }

  /**
   *  Print a fatal error message with two arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   */
  public static void fatal(String msg, Object arg1, Object arg2)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, arg1, arg2));
    }
  }

  /**
   *  Print a fatal error message with three arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   */
  public static void fatal(String msg, Object arg1, Object arg2, Object arg3)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, arg1, arg2, arg3));
    }
  }

  /**
   *  Print a fatal error message with four arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   */
  public static void fatal(String msg, Object arg1, Object arg2, Object arg3,
                           Object arg4)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, arg1, arg2, arg3, arg4));
    }
  }

  /**
   *  Print a fatal error message with five arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   */
  public static void fatal(String msg, Object arg1, Object arg2, Object arg3,
                           Object arg4, Object arg5)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5));
    }
  }

  /**
   *  Print a fatal error message with six arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   *  @param arg6 argument 6
   */
  public static void fatal(String msg, Object arg1, Object arg2, Object arg3,
                           Object arg4, Object arg5, Object arg6)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5, arg6));
    }
  }

  /**
   *  Print a fatal error message with up to nine arguments.
   *  This may end the program if a listener to fatal errors decides
   *  to do so.
   *  @see  Format#format
   *  @param msg  message
   *  @param args arguments
   */
  public static void fatal(String msg, Object ... args)
  {
    if ((_mask & FATAL_FLAG) != 0) {
      distributeFatalMessage(Format.format(msg, args));
    }
  }


  /**
   *  Print a logging message without further arguments.
   *  @param msg  message
   */
  public static void log(String msg)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(msg);
    }
  }

  /**
   *  Print a logging message for a throwable.
   *  @param x throwable argument
   */
  public static void log(Throwable x)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(getStackDump(x));
    }
  }

  /**
   *  Print a logging message without further arguments.
   *  @param obj  object
   */
  public static void log(Object obj)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.toString(obj));
    }
  }

  /**
   *  Print a logging message with one argument.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   */
  public static void log(String msg, Object arg1)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, arg1));
    }
  }

  /**
   *  Print a logging message with two arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   */
  public static void log(String msg, Object arg1, Object arg2)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, arg1, arg2));
    }
  }

  /**
   *  Print a logging message with three arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   */
  public static void log(String msg, Object arg1, Object arg2, Object arg3)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, arg1, arg2, arg3));
    }
  }

  /**
   *  Print a logging message with four arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   */
  public static void log(String msg, Object arg1, Object arg2, Object arg3,
                         Object arg4)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, arg1, arg2, arg3, arg4));
    }
  }

  /**
   *  Print a logging message with five arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   */
  public static void log(String msg, Object arg1, Object arg2, Object arg3,
                         Object arg4, Object arg5)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5));
    }
  }

  /**
   *  Print a logging message with six arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param arg1 argument 1
   *  @param arg2 argument 2
   *  @param arg3 argument 3
   *  @param arg4 argument 4
   *  @param arg5 argument 5
   *  @param arg6 argument 6
   */
  public static void log(String msg, Object arg1, Object arg2, Object arg3,
                         Object arg4, Object arg5, Object arg6)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, arg1, arg2, arg3, arg4, arg5, arg6));
    }
  }

  /**
   *  Print a logging message with up to nine arguments.
   *  @see  Format#format
   *  @param msg  message
   *  @param args arguments
   */
  public static void log(String msg, Object ... args)
  {
    if ((_mask & LOG_FLAG) != 0) {
      distributeLogMessage(Format.format(msg, args));
    }
  }

  /**
   *  Test an assertion. Depending on the registered listeners an assertion
   *  failure error is thrown.
   *  @see  AssertionFailedError
   *  @param  condition  if <code>false</code> the assertion has failed and the
   *                     listeners are informed with a null message
   */
  public static void insist(boolean condition)
  {
    if (!condition   &&   (_mask & ASSERT_FLAG) != 0) {
      if (distributeAssertionMessage(null)) {
        throw new AssertionFailedError();
      }
    }
  }

  /**
   *  Test an assertion. Depending on the registered listeners an assertion
   *  failure error is thrown.
   *  @see  AssertionFailedError
   *  @param  condition  if <code>false</code> the assertion has failed and the
   *                     listeners are informed
   *  @param  msg        the message to send to the listeners on failures
   */
  public static void insist(boolean condition, String msg)
  {
    if (!condition   &&   (_mask & ASSERT_FLAG) != 0) {
      if (distributeAssertionMessage(msg)) {
        throw new AssertionFailedError(msg);
      }
    }
  }

  /**
   *  Create a stack dump from a Throwable.
   *  @param x  the Throwable
   *  @return stack dump
   */
  public static String getStackDump(Throwable x)
  {
    StringWriter writer = new StringWriter();
    x.printStackTrace(new PrintWriter(writer));

    return writer.getBuffer().toString();
  }

  /**
   *  Creates a line with the position <code>up</code> frames above on the stack frame
   *  of the calling function.
   *  Fallback method if the direct approach fails.
   *  @param up number of frames upward
   *  @return position line or <code>"???"</code> on problems
   */
  private static String getCallerPositionOld(int up)
  {
    up += 2;			// we are on step deeper and the creation of the
                                // Throwable appends another level

    // parse local stach dump
    String stack = getStackDump(new Throwable());
    int    index = 0;

    while (index != -1   &&  up-- > 0) {
      index = stack.indexOf('\n', index+1);
    }
    if (index >= 0) {
      if (index > 0) {
        ++index;
      }
      int end = stack.indexOf('\n', index);

      String ret;
      if (end > -1) {
        ret = stack.substring(index, end).trim();
      }
      else {
        ret = stack.substring(index).trim();
      }
      if (ret.startsWith("at ")) {
        ret = ret.substring(3);
      }
      return ret;
    }
    // hat nicht geklappt
    return "???";
  }

  /**
   *  Creates a line with the position <code>up</code> frames above on the stack frame
   *  of the calling function.
   *  @param up number of frames upward
   *  @return position line or <code>"???"</code> on problems
   */
  public static String getCallerPosition(int up)
  {
    try {
      StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
      up += 3;
      if (up >= 0  &&  up < stackTrace.length) {
        return stackTrace[up + 1].toString();
      }
      else {
        return "???";
      }
    } catch (SecurityException x) {
      // try to fool the security handler
      return getCallerPositionOld(up);
    }
  }

  /**
   *  Create a position line with the stack position above the debug package.
   *  Steps up the stack until a position outside of the debug package is found.
   *  Fallback method if the direct approach fails.
   *  @return position line or <code>"???"</code> on problems
   */
  private static String getCallerPositionOld()
  {
    // lokalen Stackdump besorgen und parsen
    String stack = getStackDump(new Throwable());

    String pack = "at de.caff.util.debug.";
    int    start = stack.indexOf('\n') + 1;

    for (int index = stack.indexOf('\n', start);
         index != -1;
         index = stack.indexOf('\n', start)) {
      String line = stack.substring(start, index);
      if (line.indexOf(pack) == -1) {
        // nicht im Package
        String pos = line.trim();
        if (pos.startsWith("at ")) {
          pos = pos.substring(3);
        }
        return pos;
      }
      start = index + 1;
    }
    return "???";
  }

  /**
   *  Create a position line with the stack position above the debug package.
   *  Steps up the stack until a position outside of the debug package is found.
   *  @return position line or <code>"???"</code> on problems
   */
  private static String getCallerPosition()
  {
    try {
      String debugClassName = Debug.class.getName();
      StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
      for (int s = 2;  s < stackTrace.length;  ++s) {
        if (stackTrace[s].getClassName() != debugClassName) {
          return stackTrace[s].toString();
        }
      }
      return "???";
    } catch (SecurityException x) {
      // try to fool the security handler
      return getCallerPositionOld();
    }
  }

  /**
   *  Create a debug window which receives cooked messages.
   *  It contains some simple buttons allowing to change the categories and
   *  the capturing of stdout/stderr globally.
   *  This window does not exit when closing.
   *  @return  win  the window 
   */
  public static JFrame createDebugWindow()
  {
    return createDebugWindow(false);
  }

  /**
   *  Create a debug window which receives cooked messages.
   *  It contains some simple buttons allowing to change the categories and
   *  the capturing of stdout/stderr globally.
   *  @param   exitOnClose call <code>System.exit()</code> when window is closing?
   *  @return  win  the window 
   */
  public static JFrame createDebugWindow(boolean exitOnClose)
  {
    //    return new DebugMessageWindow(true);
    JFrame frame = new FilteringDebugMessageWindow();
    if (exitOnClose) {
      frame.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    }
    return frame;
  }


  /**
   *  Sends all buffered messages for stdout.
   */
  static void sendCollectedStdout()
  {
    if (_collectedStdout != null) {
      // somewhat complicated logic allowing to recall this function again
      // in distributeStandardMessage without doing anything more
      String send = _collectedStdout.toString();
      _collectedStdout = null;
      distributeStandardMessage(send);
    }
  }


  /**
   *  Sends all buffered messages for stderr.
   */
  static void sendCollectedStderr()
  {
    if (_collectedStderr != null) {
      // somewhat complicated logic allowing to recall this function again
      // in distributeErrorMessage without doing anything more
      String send = _collectedStderr.toString();
      _collectedStderr = null;
      distributeErrorMessage(send);
    }
  }


  /**
   *  Sends all buffered messages for stdout and stderr.
   */
  static void sendCollectedStreams()
  {
    sendCollectedStdout();
    sendCollectedStderr();
  }


  /**
   *  Handle a message to stdout.
   *  @param  str  string written to stdout
   */
  static void writeStdoutMessage(String str)
  {
    sendCollectedStderr();
    int length = str.length();
    if (length > 0) {
      if (str.charAt(length-1) == '\n') {
        str = str.substring(0, length-1);
      }

      if (_bufferStreams) {
        if (_collectedStdout == null) {
          _collectedStdout = new StringBuffer();
          str = "[STDOUT>\n"+str;
        }
        _collectedStdout.append(str).append('\n');
      }
      else {
         sendCollectedStdout();
        distributeStandardMessage("[STDERR>\n"+str);
     }
    }
  }

  /**
   *  Handle a message to stderr.
   *  @param  str  string written to stderr
   */
  static void writeStderrMessage(String str)
  {
    sendCollectedStdout();
    int length = str.length();
    if (length > 0) {
      if (str.charAt(length-1) == '\n') {
        str = str.substring(0, length-1);
      }

      if (_bufferStreams) {
        if (_collectedStderr == null) {
          _collectedStderr = new StringBuffer();
          str = "[STDERR>\n"+str;
        }
        _collectedStderr.append(str).append('\n');
      }
      else {
        sendCollectedStderr();
        distributeErrorMessage("[STDERR>\n"+str);
      }
    }
  }

  /**
   *  How messages to stdout shall be handled.
   *  @param  capture   <code>true</code> = messages are captured<br>
   *                    <code>false</code> = message are handled normally
   */
  public static void setStdoutCaptureMode(boolean capture)
  {
    if (capture != getStdoutCaptureMode()) {
      if (capture) {
        // stdout umleiten
        if (!getStdoutCaptureMode()) {
          _oldStdout = System.out;
          System.setOut(new PrintStream(new DebuggingOutputStream(DebuggingOutputStream.STDOUT)));
        }
      }
      else {
        if (getStdoutCaptureMode()) {
          sendCollectedStdout();
          System.setOut(_oldStdout);
          _oldStdout = null;
        }
      }
      informStdoutCaptureModeListeners(capture);
    }
  }

  /**
   *  Get the actual capturing mode for stdout.
   *  @return <code>true</code> = stdout is captured<br>
   *          <code>false</code> = stdout is handled normally
   */
  public static boolean getStdoutCaptureMode()
  {
    return _oldStdout != null;
  }

  /**
   *  Get the stdout stream which prints to the console.
   *  @return the real stdout stream
   */
  public static PrintStream getConsoleStdout() {
    return getStdoutCaptureMode() ? _oldStdout : System.out;
  }

  /**
   *  How messages to stderr shall be handled.
   *  @param  capture   <code>true</code> = messages are captured<br>
   *                    <code>false</code> = message are handled normally
   */
  public static void setStderrCaptureMode(boolean capture)
  {
    if (capture != getStderrCaptureMode()) {
      if (capture) {
        // stdout umleiten
        if (!getStderrCaptureMode()) {
          _oldStderr = System.err;
          System.setErr(new PrintStream(new DebuggingOutputStream(DebuggingOutputStream.STDERR)));
        }
      }
      else {
        if (getStderrCaptureMode()) {
          sendCollectedStderr();
          System.setErr(_oldStderr);
          _oldStderr = null;
        }
      }
      informStderrCaptureModeListeners(capture);
    }
  }

  /**
   *  Get the actual capturing mode for stderr.
   *  @return <code>true</code> = stderr is captured<br>
   *          <code>false</code> = stderr is handled normally
   */
  public static boolean getStderrCaptureMode()
  {
    return _oldStderr != null;
  }

  /**
   *  Get the stderr stream which prints to the console.
   *  @return the real stderr stream
   */
  public static PrintStream getConsoleStderr() {
    return getStderrCaptureMode() ? _oldStderr : System.err;
  }

  /**
   *  Install a DebugListener which prints all messages in &quot;cooked&quot; form
   *  to the given output stream.
   *  @param  stream output stream
   */
  public static void installCookedOutput(OutputStream stream)
  {
    if (stream != null) {
      addCookedMessageDebugListener(new SimpleOutputtingDebugListener(stream));
    }
  }

  /**
   *  Install a DebugListener which prints all messages in &quot;cooked&quot; form
   *  to the console.
   */
  public static void installCookedOutput()
  {
    installCookedOutput(getConsoleStdout());
  }


  /**
   *  Create an in-deep description of the given object.
   *  Not perfect yet.
   *  @param  obj  object to describe
   *  @return  description of <code>obj</code>
   */
  public static String getObjectInfo(Object obj)
  {
    return ObjectInspector.getInfo(obj);
  }


  /**
   *  Initialize debugging features from properties.
   *  Depending on the settings of certain properties the initial debugging settings are set:
   *  <table border="1">
   *   <tr><th>Property</th><th>Type</th><th>Description</th></tr>
   *   <tr>
   *     <td><code>debug.show.window</code></td>
   *     <td><code>boolean</code></td>
   *     <td>Display a debug window?</td>
   *   </tr>
   *   <tr>
   *     <td><code>debug.print.console</code></td>
   *     <td><code>boolean</code></td>
   *     <td>Print debug messages to console/stdout?</td>
   *   </tr>
   *   <tr>
   *     <td><code>debug.mask</code></td>
   *     <td><code>String</code></td>
   *     <td>Combination of characters indicating which types of debug messages should be captured:
   *      <table>
   *       <tr><td><tt>T</tt></td><td>Trace messages (lowest priority)</td></tr>
   *       <tr><td><tt>S</tt></td><td>Standard messages</td></tr>
   *       <tr><td><tt>W</tt></td><td>Warning messages</td></tr>
   *       <tr><td><tt>E</tt></td><td>Error messages</td></tr>
   *       <tr><td><tt>F</tt></td><td>Fatal error messages (highest priority)</td></tr>
   *       <tr><td><tt>L</tt></td><td>Log messages</td></tr>
   *       <tr><td><tt>A</tt></td><td>Assertion failure messages</td></tr>
   *      </table>
   *     </td>
   *   </tr>
   *  </table>
   *
   *  If neither a debug window is displayed nor something is printed to the console the mask is ignored and
   *  no type of message is captured.
   *  @param debugMaskDefault     default mask used if property <code>debug.mask</code> is not set
   *  @param showWindowDefault    default used if property <code>debug.show.window</code> is not set
   *  @param printConsoleDefault  default used if property <code>debug.print.console</code> is not set
   */
  public static void initFromProperties(long debugMaskDefault, boolean showWindowDefault, boolean printConsoleDefault)
  {
    boolean showWindow = Utility.getBooleanParameter(PARAM_DEBUG_SHOW_WINDOW, showWindowDefault);
    boolean printConsole = Utility.getBooleanParameter(PARAM_DEBUG_PRINT_CONSOLE, printConsoleDefault);
    if (showWindow || printConsole) {
      long debugMask = 0;
      String mask = Utility.getStringParameter(PARAM_DEBUG_MASK, null);
      if (mask != null) {
        for (StringCharacterIterator it = new StringCharacterIterator(mask);  it.current() != StringCharacterIterator.DONE;  it.next()) {
          char ch = it.current();
          switch (Character.toUpperCase(ch)) {
          case TRACE_CHAR:
            debugMask |= TRACE_FLAG;
            break;

          case MESSAGE_CHAR:
            debugMask |= MESSAGE_FLAG;
            break;

          case WARNING_CHAR:
            debugMask |= WARNING_FLAG;
            break;

          case ERROR_CHAR:
            debugMask |= ERROR_FLAG;
            break;

          case FATAL_CHAR:
            debugMask |= FATAL_FLAG;
            break;

          case LOG_CHAR:
            debugMask |= LOG_FLAG;
            break;

          case ASSERT_CHAR:
            debugMask |= ASSERT_FLAG;
            break;
          }
        }
      }
      else {
        debugMask = debugMaskDefault & DEBUG_ALL_MASK;
      }
      setMask(debugMask);
      if (showWindow) {
        createDebugWindow();
      }
      if (printConsole) {
        installCookedOutput();
      }
    }
    else {
      setMask(NO_DEBUG_MASK);
    }
  }

  /**
   *  Convenience method which calls {@link #initFromProperties(long, boolean, boolean)} with no debug window
   *  and no console output as default.
   *  @param debugMaskDefault compare {@link #initFromProperties(long, boolean, boolean)}
   */
  public static void initFromProperties(long debugMaskDefault)
  {
    initFromProperties(debugMaskDefault, false, false);
  }
}


