//$Header: /cvsroot-fuse/mec-as2/39/mendelson/util/log/JTextPaneLoggingHandler.java,v 1.1 2012/04/18 14:10:45 heller Exp $ package de.mendelson.util.log; import java.awt.Color; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.logging.ErrorManager; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.swing.JTextPane; import javax.swing.SwingUtilities; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.StyledDocument; /* * Copyright (C) mendelson-e-commerce GmbH Berlin Germany * * This software is subject to the license agreement set forth in the license. * Please read and agree to all terms before using this software. * Other product and brand names are trademarks of their respective owners. */ /** * Handler to log logger data to a swing text component * @author S.Heller * @version $Revision: 1.1 $ */ public class JTextPaneLoggingHandler extends Handler { /**The max number of bytes that are displayed. If the content exceeds this there is data * removed at the start*/ private long maxBuffersize = 30000; private boolean doneHeader; private JTextPane jTextPane = null; private Style currentStyle; private boolean bold = false; private boolean underline = false; private boolean italic = false; // Line separator string. This is the value of the line.separator // property at the moment that the Formatter was created. private String lineSeparator = System.getProperty("line.separator"); /**Stores the logging colors for the logging levels*/ private final Map<Level, String> colorMap = Collections.synchronizedMap(new HashMap<Level, String>()); public JTextPaneLoggingHandler(JTextPane jTextPane, Formatter formatter) { //set default colors, these could be overwritten using the setColor method synchronized (this.colorMap) { this.colorMap.put(Level.WARNING, IRCColors.BLUE); this.colorMap.put(Level.SEVERE, IRCColors.RED); this.colorMap.put(Level.FINE, IRCColors.DARK_GREEN); } this.setFormatter(formatter); this.jTextPane = jTextPane; StyleContext context = StyleContext.getDefaultStyleContext(); this.currentStyle = context.getStyle(StyleContext.DEFAULT_STYLE); this.resetStyle(); } /**Sets a color for the log levels. The color is a constant of the class IRCColor * */ public void setColor(Level loglevel, String color) { synchronized (this.colorMap) { this.colorMap.put(loglevel, color); } } /**Returns the current set color for the passed log level. May return null * if no color is defined for the passed level */ public String getColor(Level loglevel) { synchronized (this.colorMap) { return (this.colorMap.get(loglevel)); } } /** Appends a message to the output area. IRC color codes will be decoded first. */ public void messageDecode(String message) { // quick checks to speed things up if ((message.indexOf('\002') >= 0) || (message.indexOf('\003') >= 0) || (message.indexOf('\026') >= 0) || (message.indexOf(0x0f) >= 0) || (message.indexOf('\037') >= 0)) { StringBuilder buf = new StringBuilder(); int len = message.length(); int i; char c; for (i = 0; i < len; i++) { c = message.charAt(i); switch (c) { case '\002': // bold this.messageDecodeWrite(buf); this.toggleStyleBold(); break; case '\003': // colors { char c1; char c2; if (i < (len - 1)) { c1 = message.charAt(i + 1); if ((c1 >= '0') && (c1 <= '9')) { if (i < (len - 2)) { c2 = message.charAt(i + 2); if ((c2 >= '0') && (c2 <= '9')) { this.messageDecodeWrite(buf); this.setStyleForeground((c1 - '0') * 10 + c2 - '0'); i += 2; } } else { this.messageDecodeWrite(buf); this.setStyleForeground(c1 - '0'); i++; } } } } break; case '\026': // italic this.messageDecodeWrite(buf); toggleStyleItalic(); break; case '\037': // underline this.messageDecodeWrite(buf); toggleStyleUnderline(); break; case 0x0f: //reset all styles this.messageDecodeWrite(buf); this.resetStyle(); break; default: buf.append(c); break; } } this.messageDecodeWrite(buf); } else { this.resetStyle(); messageDecodeWrite(new StringBuilder(message)); } } /** * Appends the current buffer's contents to the output area while decoding a message. * The StringBuffer will be setLength(0) afterwards. */ private void messageDecodeWrite(StringBuilder buffer) { final StyledDocument document = (StyledDocument) this.jTextPane.getDocument(); synchronized (document) { try { long documentLength = document.getLength(); long oversize = (documentLength + buffer.length()) - this.maxBuffersize; if (oversize > 0) { document.remove(0, (int) oversize); } document.insertString(document.getLength(), buffer.toString(), this.currentStyle); } catch (Throwable ex) { if( ex instanceof Exception ){ reportError(null, (Exception)ex, ErrorManager.WRITE_FAILURE); } } } buffer.setLength(0); //scroll to the last line, enqueue into the swing paint queue SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { JTextPaneLoggingHandler.this.jTextPane.setCaretPosition(document.getLength()); } catch (Throwable e) { //ignore } } }); } /** Reset all style attributes to plain text. */ private void resetStyle() { this.setStyleBold(false); this.setStyleItalic(false); this.setStyleUnderline(false); this.setStyleForeground(1); } /** Enable or disable boldface mode for subsequent messages. */ public void setStyleBold(boolean bold) { synchronized (this.currentStyle) { this.currentStyle.removeAttribute(StyleConstants.Bold); if (bold) { this.currentStyle.addAttribute(StyleConstants.Bold, Boolean.TRUE); } this.bold = bold; } } /** Enable or disable italic mode for subsequent messages. */ public void setStyleItalic(boolean italic) { synchronized (this.currentStyle) { this.currentStyle.removeAttribute(StyleConstants.Italic); if (italic) { this.currentStyle.addAttribute(StyleConstants.Italic, Boolean.TRUE); } this.italic = italic; } } /** Toggle boldface mode for subsequent messages. */ private void toggleStyleBold() { this.setStyleBold(!bold); } /** Enable or disable underline mode for subsequent messages. */ private void setStyleUnderline(boolean underline) { synchronized (this.currentStyle) { this.currentStyle.removeAttribute(StyleConstants.Underline); if (underline) { this.currentStyle.addAttribute(StyleConstants.Underline, Boolean.TRUE); } this.underline = underline; } } /** Toggle underline mode for subsequent messages. */ private void toggleStyleItalic() { this.setStyleItalic(!this.italic); } /** Toggle underline mode for subsequent messages. */ private void toggleStyleUnderline() { this.setStyleUnderline(!this.underline); } /** Set foreground for subsequent messages (IRC standard indexed color). */ private void setStyleForeground(int index) { setStyleForeground(IRCColors.indexedColors[index]); } /** Set foreground for subsequent messages. */ private void setStyleForeground(Color col) { synchronized (this.currentStyle) { this.currentStyle.removeAttribute(StyleConstants.Foreground); this.currentStyle.addAttribute(StyleConstants.Foreground, col); } } /** * Set (or change) the character encoding used by this <tt>Handler</tt>. * <p> * The encoding should be set before any <tt>LogRecords</tt> are written * to the <tt>Handler</tt>. * * @param encoding The name of a supported character encoding. * May be null, to indicate the default platform encoding. * @exception SecurityException if a security manager exists and if * the caller does not have <tt>LoggingPermission("control")</tt>. * @exception UnsupportedEncodingException if the named encoding is * not supported. */ @Override public void setEncoding(String encoding) throws SecurityException, java.io.UnsupportedEncodingException { super.setEncoding(encoding); } /** * Format and publish a LogRecord. * @param record description of the log event */ @Override public synchronized void publish(LogRecord record) { if (!isLoggable(record)) { return; } String msg; int rawMessageLength = 0; try { msg = this.getFormatter().format(record); String rawMessage = this.getFormatter().formatMessage(record); if (rawMessage != null) { rawMessageLength = rawMessage.length(); } } catch (Exception ex) { // We don't want to throw an exception here, but we // report the exception to any registered ErrorManager. reportError(null, ex, ErrorManager.FORMAT_FAILURE); return; } try { if (!doneHeader) { this.logMessage(record.getLevel(), this.getFormatter().getHead(this), rawMessageLength); doneHeader = true; } this.logMessage(record.getLevel(), msg, rawMessageLength); } catch (Exception ex) { // We don't want to throw an exception here, but we // report the exception to any registered ErrorManager. reportError(null, ex, ErrorManager.WRITE_FAILURE); } } /** * Check if this Handler would actually log a given LogRecord, depending of the * log level * @param record a LogRecord * @return true if the LogRecord would be logged. * */ @Override public boolean isLoggable(LogRecord record) { return super.isLoggable(record); } /** * Flush any buffered messages. */ @Override public synchronized void flush() { } /**Just flushes the current message */ @Override public synchronized void close() throws SecurityException { this.flush(); } /**Finally logs the passed message to the text component and sets the canvas pos */ private void logMessage(Level level, String message, int rawMessageLength) { int timeStampPos = message.length() - rawMessageLength - this.lineSeparator.length(); String color = null; synchronized (this.colorMap) { if (this.colorMap.containsKey(level)) { color = this.colorMap.get(level); } else { color = IRCColors.BLACK; } } if (timeStampPos >= 0) { message = message.substring(0, timeStampPos) + color + message.substring(timeStampPos); } this.messageDecode(message); this.resetStyle(); } /** * @return the maxBuffersize */ public long getMaxBuffersize() { return maxBuffersize; } /** * @param maxBuffersize the maxBuffersize to set */ public void setMaxBuffersize(long maxBuffersize) { this.maxBuffersize = maxBuffersize; } }