// This file is part of PleoCommand:
// Interactively control Pleo with psychobiological parameters
//
// Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Boston, USA.
package pleocmd;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import pleocmd.itfc.gui.ErrorDialog;
import pleocmd.itfc.gui.MainFrame;
/**
* Contains all relevant content of one log message (one line in log view) as
* well as static methods to create log messages of any {@link Type}.
*
* @author oliver
*/
public final class Log {
/**
* Specifies the type of a message.
*
* @author oliver
*/
public enum Type {
/**
* Detailed or debug messages.
*/
Detail,
/**
* Informational messages.
*/
Info,
/**
* Warnings and other severe messages.
*/
Warn,
/**
* Errors and Exceptions.
*/
Error,
/**
* The standard output is captured via this {@link Type} in GUI mode.<br>
* Will not be used in console mode.
*/
ConsoleOutput,
/**
* The standard input is represented via this {@link Type} in GUI mode.<br>
* Will not be used in console mode.
*/
ConsoleInput
}
private static boolean minLogTypeKnown;
private static boolean quiStatusKnown;
private static List<Log> queuedLogs = new ArrayList<Log>(128);
private final Type type;
private final StackTraceElement caller;
private final String msg;
private final Throwable backtrace;
private final long time;
private Log(final Type type, final StackTraceElement caller,
final String msg, final String msgAlt, final Throwable backtrace) {
this.type = type;
this.caller = caller;
this.msg = msgAlt == null ? msg : msgAlt;
this.backtrace = backtrace;
time = System.currentTimeMillis();
switch (type) {
case Error:
if (MainFrame.hasGUI()) {
MainFrame.the().addLog(this);
ErrorDialog.show(this);
} else if (quiStatusKnown)
System.err.println(toString()); // CS_IGNORE
else
queuedLogs.add(this);
break;
case ConsoleOutput:
System.out.println(msg); // CS_IGNORE
if (MainFrame.hasGUI())
MainFrame.the().addLog(this);
else if (!quiStatusKnown) queuedLogs.add(this);
break;
case ConsoleInput:
if (MainFrame.hasGUI())
MainFrame.the().addLog(this);
else if (!quiStatusKnown) queuedLogs.add(this);
break;
default:
if (!minLogTypeKnown)
// we have to queue it for later output, because
// we currently don't now, if we really have to output this log
queuedLogs.add(this);
else if (MainFrame.hasGUI())
MainFrame.the().addLog(this);
else if (quiStatusKnown)
System.err.println(toString()); // CS_IGNORE
else
queuedLogs.add(this);
break;
}
}
/**
* @return the {@link Type} of this log entry.
*/
public Type getType() {
return type;
}
/**
* @return a {@link StackTraceElement} describing the method which created
* this log entry.
*/
public StackTraceElement getCaller() {
return caller;
}
/**
* @return a {@link String} describing the class and method which created
* this log entry.
*/
public String getFormattedCaller() {
return String.format("%s.%s()",
caller.getClassName().replaceFirst("^.*\\.([^.]*)$", "$1"),
caller.getMethodName());
}
/**
* @return a {@link String} with the message of this log entry.
*/
public String getMsg() {
return msg;
}
/**
* @return the complete backtrace for this log entry if this log is an
* {@link Type#Error} or backtracing for all kind of logs has been
* enabled or <b>null</b> if not.
*/
public Throwable getBacktrace() {
return backtrace;
}
/**
* @return the time in milliseconds since the epoch when this log entry has
* been created.
*/
public long getTime() {
return time;
}
/**
* @return a {@link Color} matching the {@link Type} of this log entry.
* @see #getType()
*/
public Color getTypeColor() {
switch (type) {
case Detail:
return Color.GRAY;
case Info:
return Color.BLUE;
case Warn:
return new Color(160, 100, 0); // dark orange
case Error:
return Color.RED;
case ConsoleOutput:
case ConsoleInput:
default:
return Color.BLACK;
}
}
/**
* @return a {@link String} with an HTML color code matching the
* {@link Type} of this log entry.
* @see #getType()
*/
public String getTypeHTMLColor() {
switch (type) {
case Detail:
return "gray";
case Info:
return "blue";
case Warn:
return "#A06400";// dark orange
case Error:
return "red";
case ConsoleOutput:
case ConsoleInput:
default:
return "black";
}
}
/**
* @return a {@link String} with an Latex color code matching the
* {@link Type} of this log entry.
* @see #getType()
*/
public String getTypeTexColor() {
switch (type) {
case Detail:
return "gray";
case Info:
return "blue";
case Warn:
return "orange";
case Error:
return "red";
case ConsoleOutput:
case ConsoleInput:
default:
return "black";
}
}
/**
* @return the time (with milliseconds - no date), from {@link #getTime()}
* formatted as a {@link String}
*/
public String getFormattedTime() {
return LogConfig.DATE_FORMATTER.format(new Date(time));
}
/**
* @return a three character long {@link String} matching the {@link Type}
* of this log entry.
* @see #getType()
*/
public String getTypeShortString() {
switch (type) {
case Detail:
return "DTL";
case Info:
return "INF";
case Warn:
return "WRN";
case Error:
return "ERR";
case ConsoleOutput:
return "OUT";
case ConsoleInput:
return "IN ";
default:
return "!?!";
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
final String ec = getExportColumns();
if (ec.contains("T")) {
if (sb.length() > 0) sb.append(' ');
sb.append(getFormattedTime());
}
if (ec.contains("Y")) {
if (sb.length() > 0) sb.append(' ');
sb.append(getTypeShortString());
}
if (ec.contains("S")) {
if (sb.length() > 0) sb.append(' ');
final String s = caller.toString();
sb.append(String.format("%-50s",
s.substring(0, Math.min(50, s.length()))));
}
if (ec.contains("M")) {
if (sb.length() > 0) sb.append(' ');
final StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < sb.length(); ++i)
sb2.append(' ');
sb.append(StringManip.removePseudoHTML(msg).replace("\n",
"\n" + sb2.toString()));
}
return sb.toString();
}
public String toHTMLString() {
final StringBuilder sb = new StringBuilder();
final String ec = getExportColumns();
if (ec.contains("T")) {
sb.append("<td>");
sb.append(StringManip.safeHTML(getFormattedTime()));
sb.append("</td>");
}
if (ec.contains("Y")) {
sb.append("<td>");
sb.append(StringManip.safeHTML(getTypeShortString()));
sb.append("</td>");
}
if (ec.contains("S")) {
sb.append("<td>");
sb.append(StringManip.safeHTML(caller.toString()));
sb.append("</td>");
}
if (ec.contains("M")) {
sb.append("<td>");
sb.append(StringManip.convertPseudoToRealHTML(msg));
sb.append("</td>");
}
return sb.toString();
}
public String toTexString(final Set<String> colorNames) {
final StringBuilder sb = new StringBuilder();
final String ec = getExportColumns();
int cnt = 0;
if (ec.contains("T")) {
if (sb.length() > 0) sb.append(" & ");
sb.append(String.format("\\textcolor{%s}{", getTypeTexColor()));
sb.append(StringManip.safeTex(getFormattedTime()));
sb.append("}");
++cnt;
}
if (ec.contains("Y")) {
if (sb.length() > 0) sb.append(" & ");
sb.append(String.format("\\textcolor{%s}{", getTypeTexColor()));
sb.append(StringManip.safeTex(getTypeShortString()));
sb.append("}");
++cnt;
}
if (ec.contains("S")) {
if (sb.length() > 0) sb.append(" & ");
sb.append(String.format("\\textcolor{%s}{", getTypeTexColor()));
sb.append(StringManip.safeTex(caller.toString()));
sb.append("}");
++cnt;
}
if (ec.contains("M")) {
if (sb.length() > 0) sb.append(" & ");
sb.append(String.format("\\textcolor{%s}{", getTypeTexColor()));
final StringBuilder sb2 = new StringBuilder("}\\\\\n ");
for (int i = 0; i < cnt; ++i)
sb2.append("& ");
sb2.append(String.format("\\textcolor{%s}{", getTypeTexColor()));
sb.append(StringManip.convertPseudoHTMLToTex(msg, colorNames)
.replace("\\\\\n", sb2.toString()));
sb.append("}");
}
return sb.toString();
}
private static StackTraceElement getCallerSTE(final int stepsBack) {
final StackTraceElement[] st = new Throwable().getStackTrace();
return st.length < stepsBack ? st[st.length - 1] : st[stepsBack];
}
/**
* Prints messages to the GUI's log, if any, or to the standard error
* otherwise if their {@link #type} is not "lower" than the
* {@link LogConfig#CFG_MIN_LOG_TYPE}.<br>
* Always prints messages of type Console to the standard output (instead of
* standard error) no matter if a GUI exists or not.
*
* @param type
* type {@link Type} of the message
* @param throwable
* a backtrace for the message or <b>null</b>
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param msgAlt
* if not <b>null</b>, this is the message which will be
* displayed in the GUI, while "msg" will only be used for the
* standard-output
* @param caller
* the name of the creator of this message
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
private static void msg(final Type type, final Throwable throwable,
final StackTraceElement caller, final String msg,
final String msgAlt, final Object... args) {
if (type.ordinal() >= LogConfig.CFG_MIN_LOG_TYPE.getEnum().ordinal()) {
final String msgStr = args.length == 0 ? msg : String.format(msg,
args);
final String msgAltStr = msgAlt == null || args.length == 0 ? msgAlt
: String.format(msgAlt, args);
new Log(type, caller, msgStr, msgAltStr, throwable);
}
}
/**
* Creates a new message of {@link Type#Detail} which will be printed to
* error output or send to the GUI.
*
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void detail(final String msg, final Object... args) {
msg(Type.Detail, null, getCallerSTE(2), msg, null, args);
}
/**
* Creates a new message of {@link Type#Info} which will be printed to error
* output or send to the GUI.
*
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void info(final String msg, final Object... args) {
msg(Type.Info, null, getCallerSTE(2), msg, null, args);
}
/**
* Creates a new message of {@link Type#Warn} which will be printed to error
* output or send to the GUI.
*
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void warn(final String msg, final Object... args) {
msg(Type.Warn, null, getCallerSTE(2), msg, null, args);
}
/**
* Creates a new message of {@link Type#Error} which will be printed to
* error output or send to the GUI.
*
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void error(final String msg, final Object... args) {
msg(Type.Error, null, getCallerSTE(2), msg, null, args);
}
/**
* Creates a new message of {@link Type#ConsoleOutput} which will be printed
* to standard output <b>and</b> send to the GUI.
*
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void consoleOut(final String msg, final Object... args) {
msg(Type.ConsoleOutput, null, getCallerSTE(2), msg, null, args);
}
/**
* Creates a new message of {@link Type#ConsoleOutput} which will be printed
* to standard output <b>and</b> send to the GUI.
*
* @param msgStdOut
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise. This one will be printed
* to the standard output.
* @param msgGUI
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise. This one will be added to
* the GUI's log if GUI is currently available.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void consoleOut2(final String msgStdOut, final String msgGUI,
final Object... args) {
msg(Type.ConsoleOutput, null, getCallerSTE(2), msgStdOut, msgGUI, args);
}
/**
* Creates a new message of {@link Type#ConsoleInput} which will be send to
* the GUI and represents a message from standard input.
*
* @param msg
* the message - interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero)
*/
public static void consoleIn(final String msg, final Object... args) {
msg(Type.ConsoleInput, null, getCallerSTE(2), msg, null, args);
}
/**
* Creates a new message of {@link Type#Error} which will be printed to
* error output or send to the GUI. This message will contain a complete
* backtrace.
*
* @param throwable
* the {@link Exception} that occurred as a reason for this
* message.
* @see #getBacktrace()
*/
public static void error(final Throwable throwable) {
error(throwable, "");
}
/**
* Creates a new message of {@link Type#Error} which will be printed to
* error output or send to the GUI. This message will contain a complete
* backtrace.
*
* @param throwable
* the {@link Exception} that occurred as a reason for this
* message.
* @param msg
* an optional message prepended before the exception -
* interpreted as a format string (like in
* {@link String#format(String, Object...)}) if any arguments are
* given or just used as is otherwise.
* @param args
* arbitrary number of arguments for the format string (may also
* be zero) - will be ignored if the message string is empty.
* @see #getBacktrace()
*/
public static void error(final Throwable throwable, final String msg,
final Object... args) {
final StringBuilder sb = new StringBuilder();
if (!msg.isEmpty()) {
sb.append(args.length == 0 ? msg : String.format(msg, args));
sb.append(": ");
}
Throwable t = throwable;
while (true) {
sb.append(t.getClass().getSimpleName());
sb.append(": '");
sb.append(t.getMessage());
sb.append("'");
if (t.getCause() == null) break;
t = t.getCause();
sb.append(" caused by ");
}
msg(Type.Error, throwable, t.getStackTrace()[0], null, sb.toString());
}
/**
* Sets the "lowest" {@link Type} which will be processed. Messages of
* "lower" types will be ignored.
* <p>
* Possible values:
* <table>
* <tr>
* <td>{@link Type#Detail}</td>
* <td>all messages will be processed</td>
* </tr>
* <tr>
* <td>{@link Type#Info}</td>
* <td>all but detailed messages will be processed</td>
* </tr>
* <tr>
* <td>{@link Type#Warn}</td>
* <td>only warnings and errors will be processed</td>
* </tr>
* <tr>
* <td>{@link Type#Error}</td>
* <td>only errors will be processed</td>
* </tr>
* <tr>
* <td>{@link Type#ConsoleOutput}</td>
* <td>no messages will be processed</td>
* </tr>
* </table>
* <p>
* Note that messages of {@link Type#ConsoleOutput} and
* {@link Type#ConsoleInput} are treated as wrapped standard output and not
* as normal messages and are therefore always be processed even if
* minLogType is set to {@link Type#ConsoleOutput}.
*
* @param minLogType
* true if {@link Type#Detail} will be processed
*/
public static void setMinLogType(final Type minLogType) {
if (minLogType.ordinal() < Type.Detail.ordinal()
|| minLogType.ordinal() > Type.ConsoleOutput.ordinal())
throw new IllegalArgumentException("Invalid value for minLogType");
LogConfig.CFG_MIN_LOG_TYPE.setEnum(minLogType);
minLogTypeKnown = true;
processQueue();
}
public static void setGUIStatusKnown() {
quiStatusKnown = true;
processQueue();
}
private static void processQueue() {
// wait with processing until known
if (!minLogTypeKnown || !quiStatusKnown) return;
if (MainFrame.hasGUI()) {
for (final Log log : queuedLogs)
if (Log.canLog(log.getType())) {
MainFrame.the().addLog(log);
if (log.getType() == Type.Error) ErrorDialog.show(log);
}
} else
for (final Log log : queuedLogs)
if (Log.canLog(log.getType()))
System.err.println(log.toString()); // CS_IGNORE
queuedLogs.clear();
}
/**
* @return the "lowest" {@link Type} which will be processed. Messages of
* "lower" types will be ignored.
*/
public static Type getMinLogType() {
return LogConfig.CFG_MIN_LOG_TYPE.getEnum();
}
/**
* The columns of the LogTable, which should be exported with the to...
* methods. Must contains characters 'T', 'Y', 'S' and 'M' for Time, Type,
* Source and Message.
*
* @return a String with one or more of "TYSM"
*/
public static String getExportColumns() {
return LogConfig.CFG_EXPORT_COLUMNS.getContent().toUpperCase();
}
/**
* @param type
* one of the log {@link Type}s
* @return true if messages of the given {@link Type} can be logged
*/
public static boolean canLog(final Type type) {
return type.ordinal() >= LogConfig.CFG_MIN_LOG_TYPE.getEnum().ordinal();
}
/**
* @return true if messages of {@link Type#Detail} can be logged
*/
public static boolean canLogDetail() {
return Type.Detail.ordinal() >= LogConfig.CFG_MIN_LOG_TYPE.getEnum()
.ordinal();
}
/**
* @return true if messages of {@link Type#Info} can be logged
*/
public static boolean canLogInfo() {
return Type.Info.ordinal() >= LogConfig.CFG_MIN_LOG_TYPE.getEnum()
.ordinal();
}
/**
* @return true if messages of {@link Type#Warn} can be logged
*/
public static boolean canLogWarning() {
return Type.Warn.ordinal() >= LogConfig.CFG_MIN_LOG_TYPE.getEnum()
.ordinal();
}
/**
* @return true if messages of {@link Type#Error} can be logged
*/
public static boolean canLogError() {
return Type.Error.ordinal() >= LogConfig.CFG_MIN_LOG_TYPE.getEnum()
.ordinal();
}
}