package org.cdlib.xtf.util;
/**
* Copyright (c) 2004, Regents of the University of California
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the University of California nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.PrintStream;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.WeakHashMap;
////////////////////////////////////////////////////////////////////////////////
/** The <code>Trace</code> class provides a mechanism for logging output
* messages to the console or any PrintStream or Writer.
*
* A number of output message levels are supported by this class, including
* errors, warnings, info messages, and debug messages. Outputting messages
* for any one of these levels is accomplished by passing a message string
* to the {@link Trace#error(String) error()}, {@link Trace#warning(String) warning()},
* {@link Trace#info(String) info()}, or {@link Trace#debug(String) debug()}
* functions. <br><br>
*
* Messages may be indented to reflect the nesting of function calls by using
* the {@link Trace#tab() tab()} and {@link Trace#untab() untab()} functions.
* Single-line multi-part messages (where the first part is tabbed from the
* left, and additional parts are tacked on to the end of the line after
* additional processing) may be created using the {@link Trace#more(String) more() }
* functions. Indentation is normally two spaces per tab level, but this
* may be changed by setting the {@link #defaultTabSize} variable.<br><br>
*
* The message level actually output by the trace system may be
* adjusted to allow all or some message types to be displayed. The output
* level for the trace system is set using the function
* {@link Trace#setOutputLevel(int) setOutputLevel()} function.<br><br>
*
* Each output line can be automatically prefixed with a timestamp by
* calling the {@link Trace#printTimestamps(boolean)} method. The default
* is to print <i>without</i> timestamps. When enabled, timestamps will be
* printed with a simple date format: YYYY-MM-DD:HH:MM:SS, but this can be
* changed if necessary by changing the value of the {@link Trace#dateFormat}
* member variable.<br><br>
*
* All output goes to the console (System.out) by default, but it can
* optionally be redirected to any PrintStream or Writer by calling
* {@link #setPrintStream(PrintStream)} or {@link #setWriter(Writer)}
* respectively.<br><br>
*
* If multiple threads use the Trace facility, the output of each will
* be marked with "[1]" for the first thread, "[2]" for the second, etc.
* (but if only one thread uses Trace, no such markers will be printed.)
* Trace automatically maintains a separate tab level for each thread, but
* the other variables, such as PrintStream/Writer, output level, etc.,
* are static and apply to all threads.
*/
public class Trace
{
//////////////////////////////////////////////////////////////////////////////
// Error reporting level constants. Note that these currently accumulate
// lower level output. That is, selecting 'info' will also output 'warnings'
// and 'errors'. However, since the error levels are separate bits, this
// could be changed to show any combination of output levels.
//
/** Print no messages */
public static final int silent = 0;
/** Print errors only */
public static final int errors = 1;
/** Print errors and warnings */
public static final int warnings = 2;
/** Print info messages, errors, and warnings */
public static final int info = 4;
/** Print all messages (debug, info, errors, and warnings.) */
public static final int debug = 8;
/** Amount to indent when {@link #tab()} is called. Default value: 2 */
public static int defaultTabSize = 2;
/** Format to output dates in. Default: yyyy-MM-dd:HH:mm:ss */
public static DateFormat dateFormat = new SimpleDateFormat(
"yyyy-MM-dd:HH:mm:ss");
//////////////////////////////////////////////////////////////////////////////
/** Set the level of message output. Messages below this level will not be
* printed.
*
* @param level One of {@link #silent}, {@link #errors},
* {@link #warnings}, {@link #info}, or {@link #debug}.
*/
public static void setOutputLevel(int level)
{
// Right now, treat the passed level as cumulative (i.e., "warnings" is
// actually treated as "errors" + "warnings".) We could however change
// the passed value to be a mask if that was more useful.
//
outputMask = silent;
if (level >= errors)
outputMask |= errors;
if (level >= warnings)
outputMask |= warnings;
if (level >= info)
outputMask |= info;
if (level >= debug)
outputMask |= debug;
} // setOutputLevel()
//////////////////////////////////////////////////////////////////////////////
/** Retrieves the current output level established by
* {@link #setOutputLevel(int)}.
*/
public static int getOutputLevel()
{
// Right now, return the highest output level only. We could however change
// the returned value to be the mask if that was more useful.
//
if ((outputMask & debug) != 0)
return debug;
if ((outputMask & info) != 0)
return info;
if ((outputMask & warnings) != 0)
return warnings;
if ((outputMask & errors) != 0)
return errors;
return silent;
} // getOutputLevel()
//////////////////////////////////////////////////////////////////////////////
/** Enables or disables prefixing each output line with a timestamp.
* Default is disabled.
*/
public static void printTimestamps(boolean flag) {
printTimestamps = flag;
}
//////////////////////////////////////////////////////////////////////////////
/** Enables or disables immediate newline and flushing of each output line
* (note that this somewhat defeats the more() feature.) Normally,
* a newline isn't printed until Trace can be sure that more() won't be
* used to add to the existing line.
*/
public static void setAutoFlush(boolean flag) {
autoFlush = flag;
}
//////////////////////////////////////////////////////////////////////////////
/** Indent all subsequent output lines by {@link #defaultTabSize}
* (default 2) spaces. Call {@link #untab()} or {@link #clearTabs()} to
* undo this effect.
*/
public static void tab() {
getThreadTrace().t_tab();
}
/** Internal threaded helper for {@link #tab()} */
private void t_tab()
{
// Tab in by the specified amount.
tabCount += tabSize;
} // tab()
//////////////////////////////////////////////////////////////////////////////
/** Undoes effect of {@link #tab()}, un-indenting subsequent output lines. */
public static void untab() {
getThreadTrace().t_untab();
}
/** Internal threaded helper for {@link #untab()} */
private void t_untab()
{
// Tab out by the specified amount, but not less than zero.
tabCount -= tabSize;
if (tabCount < 0)
tabCount = 0;
} // untab()
//////////////////////////////////////////////////////////////////////////////
/** Resets the tab level to zero, undoing the effects of any calls to
* {@link #tab()}.
*/
public static void clearTabs() {
getThreadTrace().t_clearTabs();
}
/** Internal threaded helper for {@link #clearTabs()}. */
private void t_clearTabs()
{
// Reset the tabs to zero indent.
tabCount = 0;
} // clearTabs()
//////////////////////////////////////////////////////////////////////////////
/** Overrides the default output destination, System.out, with the given
* alternate print stream.
*
* @param pstream Where to direct subsequent output to
*/
public static void setPrintStream(PrintStream pstream) {
printStream = pstream;
writer = null;
} // setPrintStream()
//////////////////////////////////////////////////////////////////////////////
/** Overrides the default output destination, System.out, with the given
* alternate Writer.
*
* @param w Where to direct subsequent output to
*/
public static void setWriter(Writer w) {
writer = w;
printStream = null;
} // setPrintStream()
//////////////////////////////////////////////////////////////////////////////
/** Retrieve the thread identifier that is printed for the current thread.
*/
public static String getCurrentThreadId() {
return getThreadTrace().threadId;
}
//////////////////////////////////////////////////////////////////////////////
/** Retrieve the thread identifier that is printed for messages from the
* specified thread.
*/
public static String getThreadId(Thread thread) {
Trace trace = (Trace)threadTraces.get(thread);
if (trace == null)
return null;
return trace.threadId;
}
//////////////////////////////////////////////////////////////////////////////
/** Output a message at the 'error' level. If the output level established
* by {@link #setOutputLevel(int)} is {@link #errors}, {@link #warnings},
* {@link #info}, or {@link #debug}, the message will be printed. If
* the output level is {@link #silent}, it will be suppressed.
*/
public static void error(String msg) {
getThreadTrace().t_error(msg);
}
public static void error(String msg, Object ... args) {
getThreadTrace().t_error(String.format(msg, args));
}
/** Internal threaded helper for {@link #error(String)}. */
private void t_error(String msg)
{
// Set the previous message level to "error", so that more() can decide
// whether to filter it's message or not.
//
prevMsgLevel = errors;
// If we need to suppress error messages, return early.
if ((outputMask & errors) == 0)
return;
// Issue a linefeed and output the tabbed message.
output(msg, true, true);
} // error()
//////////////////////////////////////////////////////////////////////////////
/** Output a message at the 'warning' level. If the output level
* established by {@link #setOutputLevel(int)} is {@link #warnings},
* {@link #info}, or {@link #debug}, the message will be printed. If
* the output level is {@link #errors} or {@link #silent}, it will be
* suppressed.
*/
public static void warning(String msg) {
getThreadTrace().t_warning(msg);
}
public static void warning(String msg, Object ... args) {
getThreadTrace().t_warning(String.format(msg, args));
}
/** Internal threaded helper for {@link #warning(String)}. */
private void t_warning(String msg)
{
// Set the previous message level to "warning", so that more() can decide
// whether to filter it's message or not.
//
prevMsgLevel = warnings;
// If we need to suppress warning messages, return early.
if ((outputMask & warnings) == 0)
return;
// Issue a linefeed and output the tabbed message.
output(msg, true, true);
} // warning()
//////////////////////////////////////////////////////////////////////////////
/** Output a message at the 'info' level. If the output level
* established by {@link #setOutputLevel(int)} is
* {@link #info}, or {@link #debug}, the message will be printed. If
* the output level is {@link #warnings}, {@link #errors}, or
* {@link #silent}, it will be suppressed.
*/
public static void info(String msg) {
getThreadTrace().t_info(msg);
}
public static void info(String msg, Object ... args) {
getThreadTrace().t_info(String.format(msg, args));
}
/** Internal threaded helper for {@link #info(String)}. */
private void t_info(String msg)
{
// Set the previous message level to "info", so that more() can decide
// whether to filter it's message or not.
//
prevMsgLevel = info;
// If we need to suppress info messages, return early.
if ((outputMask & info) == 0)
return;
// Issue a linefeed and output the tabbed message.
output(msg, true, true);
} // info()
//////////////////////////////////////////////////////////////////////////////
/** Output a message at the 'debug' level. If the output level
* established by {@link #setOutputLevel(int)} is {@link #debug}, the
* message will be printed. If the output level is {@link #info},
* {@link #warnings}, {@link #errors}, or {@link #silent}, it will be
* suppressed.
*/
public static void debug(String msg) {
getThreadTrace().t_debug(msg);
}
public static void debug(String msg, Object ... args) {
getThreadTrace().t_debug(String.format(msg, args));
}
/** Internal threaded helper for {@link #debug(String)}. */
private void t_debug(String msg)
{
// Set the previous message level to "debug", so that more() can decide
// whether to filter it's message or not.
//
prevMsgLevel = debug;
// If we need to suppress debug messages, return early.
if ((outputMask & debug) == 0)
return;
// Issue a linefeed and output the tabbed message.
output(msg, true, true);
} // debug()
//////////////////////////////////////////////////////////////////////////////
/** Append more text to the previous output line (unless of course it was
* suppressed).
*/
public static void more(String msg) {
getThreadTrace().t_more(msg);
}
/** Internal threaded helper for {@link #more(String)}. */
private void t_more(String msg)
{
// If we need to suppress the current message level, return early.
if ((outputMask & prevMsgLevel) == 0)
return;
// Output the tabbed message without a linefeed.
output(msg, false, false);
} // more()
//////////////////////////////////////////////////////////////////////////////
/** Append more text to the previous output line if it was the same
* output level; if not, write the text to a new output line.
*
* @param level Output level to write the message at (one of
* {@link #errors}, {@link #warnings}, {@link #info}, or
* {@link #debug}.
* @param msg Message to write
*/
public static void more(int level, String msg) {
getThreadTrace().t_more(level, msg);
}
/** Internal threaded helper for {@link #more(int, String)}. */
private void t_more(int level, String msg)
{
// If the output level changed and the previous output level was enabled,
// force change in level to start on next line.
//
if (prevMsgLevel != level && ((prevMsgLevel & outputMask) != 0))
output("", true, true);
// Set the previous message level to the new value set.
prevMsgLevel = level;
// And call the simple more() function to output the message.
more(msg);
} // more()
//////////////////////////////////////////////////////////////////////////////
/** Gets a thread-specific instance of Trace. If there wasn't one already,
* a new one is created.
*/
private static Trace getThreadTrace()
{
Trace trace = (Trace)threadTraces.get(Thread.currentThread());
if (trace == null) {
trace = new Trace();
threadTraces.put(Thread.currentThread(), trace);
}
return trace;
} // getThreadTrace()
//////////////////////////////////////////////////////////////////////////////
/** Private constructor -- used by {@link #getThreadTrace()}. Calculates
* a thread ID for this thread, used to prefix output messages.
*/
private Trace()
{
// Assign a unique thread ID to this instance of the trace. To keep
// output looking nice in the case of a single thread only, the first
// thread has no visible ID until a second thread is created.
//
synchronized (getClass())
{
nThreads++;
if (firstTrace == null) {
threadId = "";
firstTrace = this;
}
else {
assert nThreads >= 2;
if (firstTrace.threadId.length() == 0)
firstTrace.threadId = "[1] ";
threadId = "[" + nThreads + "] ";
}
}
} // constructor
//////////////////////////////////////////////////////////////////////////////
/** Workhorse output function -- handles tabbing, prefixing the output with
* a thread ID, timestamping, adding newlines, and directing to the proper
* output PrintStream or Writer.
*/
private void output(String msg, boolean tabbed, boolean linefeed)
{
String outMsg;
synchronized (getClass())
{
// If the previous output was from a different thread, force a newline
// and tabbing.
//
if (prevTrace != this) {
tabbed = true;
linefeed = true;
prevTrace = this;
}
// If the caller wants tabbed output, build a final tabbed message
// string.
//
if (tabbed)
outMsg = spaces.substring(0, tabCount) + msg;
else
outMsg = msg;
try
{
// Force output to start on the next line.
if (linefeed)
{
String newLine = autoFlush ? "" : "\n";
String dateStr = "";
if (printTimestamps) {
dateStr = dateFormat.format(new Date(System.currentTimeMillis())) +
" ";
}
if (writer != null)
writer.write(newLine + dateStr + threadId);
else
printStream.print(newLine + dateStr + threadId);
}
if (writer != null)
writer.write(autoFlush ? (outMsg + "\n") : outMsg);
else
printStream.print(autoFlush ? (outMsg + "\n") : outMsg);
}
catch (IOException e) {
}
} // synchronized
} // output()
/** Trace instance for the current thread */
private static final WeakHashMap threadTraces = new WeakHashMap();
/** Trace instance that last wrote to the output stream */
private static Trace prevTrace = null;
/** First trace instance ever created */
private static Trace firstTrace = null;
/** Number of threads that have accessed Trace */
private static int nThreads = 0;
/** PrintStream to write to, or null to write to {@link #writer} */
private static PrintStream printStream = System.out;
/** Writer to write to, or null to write to {@link #printStream} */
private static Writer writer = null;
/** Previous message level */
private static int prevMsgLevel = silent;
/** Current mask of which messages to output */
private static int outputMask = errors | warnings | info;
/** True to print a timestamp on each line */
private static boolean printTimestamps = false;
/** True to force an immediate newline after every output */
private static boolean autoFlush = false;
/** Used for tabbing */
private static final String spaces = " " +
" " +
" ";
// Following are the only thread-specific variables.
/** String to prefix messages from this thread with */
private String threadId = "";
/** Current tab level for this thread */
private int tabCount = 0;
/** Amount to indent when {@link #tab()} is called. */
private int tabSize = defaultTabSize;
} // class Trace