package nl.tudelft.simulation.dsol.swing.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.util.EnumSet;
import java.util.Set;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.djutils.logger.CategoryLogger;
import org.pmw.tinylog.Configuration;
import org.pmw.tinylog.Configurator;
import org.pmw.tinylog.Level;
import org.pmw.tinylog.LogEntry;
import org.pmw.tinylog.writers.LogEntryValue;
import org.pmw.tinylog.writers.Writer;
import nl.tudelft.simulation.dsol.swing.gui.appearance.AppearanceControl;
/**
* Console for a swing application where the log messages are displayed.
*
* Copyright (c) 2003-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information DSOL Manual. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* DSOL License.
*
* @author Alexander Verbraeck
*/
public class ConsoleLogger extends JPanel implements AppearanceControl
{
/** */
private static final long serialVersionUID = 1L;
/** */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected ConsoleLogWriter consoleLogWriter;
/** current message format. */
private String messageFormat = CategoryLogger.DEFAULT_MESSAGE_FORMAT;
/** the current logging level. */
private Level level = Level.INFO;
/** the text pane. */
private JTextPane textPane;
/**
* Constructor for Logger Console.
* @param logLevel Level the logLevel to use;
*/
public ConsoleLogger(final Level logLevel)
{
this.level = logLevel;
setLayout(new BorderLayout());
this.textPane = new JTextPane();
this.textPane.setEditable(false);
this.textPane.setBackground(Color.WHITE);
this.textPane.setOpaque(true);
this.consoleLogWriter = new ConsoleLogWriter(this.textPane);
Configurator.currentConfig().addWriter(this.consoleLogWriter, this.level, this.messageFormat).activate();
JScrollPane scrollPane = new JScrollPane(this.textPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBackground(Color.WHITE);
scrollPane.setOpaque(true);
add(scrollPane, BorderLayout.CENTER);
}
/**
* Set a new logging format for the message lines of the Console writer. The default message format is:
* {class_name}.{method}:{line} {message|indent=4}
*
* A few popular placeholders that can be used:
* - {class} Fully-qualified class name where the logging request is issued
* - {class_name} Class name (without package) where the logging request is issued
* - {date} Date and time of the logging request, e.g. {date:yyyy-MM-dd HH:mm:ss} [SimpleDateFormat]
* - {level} Logging level of the created log entry
* - {line} Line number from where the logging request is issued
* - {message} Associated message of the created log entry
* - {method} Method name from where the logging request is issued
* - {package} Package where the logging request is issued
* @see https://tinylog.org/configuration
* @param newMessageFormat String; the new formatting pattern to use
*/
public void setLogMessageFormat(final String newMessageFormat)
{
Configurator.currentConfig().removeWriter(this.consoleLogWriter).activate();
this.messageFormat = newMessageFormat;
Configurator.currentConfig().addWriter(this.consoleLogWriter, this.level, this.messageFormat).activate();
}
/**
* @param newLevel Level; the new log level for the Console
*/
public void setLogLevel(final Level newLevel)
{
Configurator.currentConfig().removeWriter(this.consoleLogWriter).activate();
this.level = newLevel;
Configurator.currentConfig().addWriter(this.consoleLogWriter, this.level, this.messageFormat).activate();
}
/**
* Set the maximum number of lines in the console before the first lines will be erased. The number of lines should be at
* least 1. If the provided number of lines is less than 1, it wil be set to 1.
* @param maxLines int; set the maximum number of lines before the first lines will be erased
*/
public void setMaxLines(final int maxLines)
{
this.consoleLogWriter.maxLines = Math.max(1, maxLines);
}
/** {@inheritDoc} */
@Override
public boolean isBackground()
{
return true;
}
/**
* LogWriter takes care of writing the log records to the console.
*
* Copyright (c) 2003-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
* See for project information www.simulation.tudelft.nl.
* The source code and binary code of this software is proprietary information of Delft University of Technology.
* @author Alexander Verbraeck
*/
public static class ConsoleLogWriter implements Writer
{
/** the text pane. */
@SuppressWarnings("checkstyle:visibilitymodifier")
JTextPane textPane;
/** the document to write to. */
@SuppressWarnings("checkstyle:visibilitymodifier")
StyledDocument doc;
/** the color style. */
@SuppressWarnings("checkstyle:visibilitymodifier")
Style style;
/** number of lines. */
@SuppressWarnings("checkstyle:visibilitymodifier")
int nrLines = 0;
/** the maximum number of lines before the first lines will be erased. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected int maxLines = 20000;
/**
* @param textPane JTextPane; the text area to write the messages to.
*/
public ConsoleLogWriter(final JTextPane textPane)
{
this.textPane = textPane;
this.doc = textPane.getStyledDocument();
this.style = textPane.addStyle("colorStyle", null);
}
/** {@inheritDoc} */
@Override
public Set getRequiredLogEntryValues()
{
return EnumSet.of(LogEntryValue.RENDERED_LOG_ENTRY); // Only the final rendered log entry is required
}
/** {@inheritDoc} */
@Override
public void init(final Configuration configuration) throws Exception
{
// nothing to do
}
/** {@inheritDoc} */
@Override
public synchronized void write(final LogEntry logEntry) throws Exception
{
Runnable runnable = new Runnable()
{
@Override
public void run()
{
String[] lines = logEntry.getRenderedLogEntry().split("\\r?\\n");
while (ConsoleLogWriter.this.nrLines > Math.max(0, ConsoleLogWriter.this.maxLines - lines.length))
{
Document document = ConsoleLogWriter.this.doc;
Element root = document.getDefaultRootElement();
Element line = root.getElement(0);
int end = line.getEndOffset();
try
{
document.remove(0, end);
ConsoleLogWriter.this.nrLines--;
}
catch (BadLocationException exception)
{
CategoryLogger.always().error(exception);
break;
}
}
switch (logEntry.getLevel())
{
case TRACE:
StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.DARK_GRAY);
break;
case DEBUG:
StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.BLUE);
break;
case INFO:
StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.BLACK);
break;
case WARNING:
StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.MAGENTA);
break;
case ERROR:
StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.RED);
break;
default:
break;
}
try
{
for (String line : lines)
{
ConsoleLogWriter.this.doc.insertString(ConsoleLogWriter.this.doc.getLength(), line + "\n",
ConsoleLogWriter.this.style);
ConsoleLogWriter.this.nrLines++;
}
}
catch (Exception exception)
{
// we cannot log this -- that would generate an infinite loop
System.err.println("Was not able to insert text in the Console");
}
ConsoleLogWriter.this.textPane.setCaretPosition(ConsoleLogWriter.this.doc.getLength());
}
};
SwingUtilities.invokeLater(runnable);
}
/** {@inheritDoc} */
@Override
public void flush() throws Exception
{
// nothing to do
}
/** {@inheritDoc} */
@Override
public void close() throws Exception
{
// nothing to do
}
}
}