/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.dev.shell.log; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.shell.Icons; import com.google.gwt.dev.util.log.AbstractTreeLogger; import java.awt.Color; import java.awt.EventQueue; import java.net.URL; import java.text.NumberFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; /** * Tree logger built on an Swing tree item. */ public final class SwingTreeLogger extends AbstractTreeLogger { /** * Represents an individual log event. */ public static class LogEvent { private static final Color DEBUG_COLOR = Color.decode("0x007777"); private static final Date firstLog = new Date(); private static final Map<Type, Color> logColors = new HashMap<Type, Color>(); private static final Map<Type, Icon> logIcons = new HashMap<Type, Icon>(); private static NumberFormat minHr = NumberFormat.getIntegerInstance(); private static NumberFormat seconds = NumberFormat.getNumberInstance(); private static final Color SPAM_COLOR = Color.decode("0x005500"); private static final Color WARN_COLOR = Color.decode("0x888800"); static { seconds.setMinimumFractionDigits(3); seconds.setMaximumFractionDigits(3); seconds.setMinimumIntegerDigits(2); minHr.setMinimumIntegerDigits(2); logColors.put(ERROR, Color.RED); logIcons.put(ERROR, Icons.getLogItemError()); logColors.put(WARN, WARN_COLOR); logIcons.put(WARN, Icons.getLogItemWarning()); logColors.put(INFO, Color.BLACK); logIcons.put(INFO, Icons.getLogItemInfo()); logColors.put(TRACE, Color.DARK_GRAY); logIcons.put(TRACE, Icons.getLogItemTrace()); logColors.put(DEBUG, DEBUG_COLOR); logIcons.put(DEBUG, Icons.getLogItemDebug()); logColors.put(SPAM, SPAM_COLOR); logIcons.put(SPAM, Icons.getLogItemSpam()); } /** * Logger for this event. */ public final SwingTreeLogger childLogger; /** * Detail message for the exception (ie, the stack trace). */ public final String exceptionDetail; /** * The name of the exception, or null if none. */ public final String exceptionName; /** * Extra info for this message, or null if none. */ public final HelpInfo helpInfo; /** * Index within the parent logger. */ public final int index; /** * True if this is a branch commit. */ public final boolean isBranchCommit; /** * Log message. */ public final String message; /** * Timestamp of when the message was logged. */ public final Date timestamp; /** * Log level of this message. */ public final TreeLogger.Type type; /** * Maintains the highest priority of any child events. */ private Type inheritedPriority; /** * Create a log event. * * @param logger * @param isBranchCommit * @param index * @param type * @param message * @param caught * @param helpInfo */ public LogEvent(SwingTreeLogger logger, boolean isBranchCommit, int index, Type type, String message, Throwable caught, HelpInfo helpInfo) { this.childLogger = logger; this.isBranchCommit = isBranchCommit; this.index = index; this.type = type; this.inheritedPriority = type; this.message = message; this.helpInfo = helpInfo; this.timestamp = new Date(); this.exceptionDetail = AbstractTreeLogger.getStackTraceAsString(caught); this.exceptionName = AbstractTreeLogger.getExceptionName(caught); } /** * @return full text of log event. */ public String getFullText() { StringBuffer sb = new StringBuffer(); formatTimestamp(timestamp.getTime() - firstLog.getTime(), sb); sb.append(" "); // Show the message type. // if (type != null) { sb.append("["); sb.append(type.getLabel()); sb.append("] "); } // Show the item text. // sb.append(htmlEscape(message)); sb.append("\n"); // Show the exception info for anything other than "UnableToComplete". // if (exceptionDetail != null) { sb.append("<pre>" + htmlEscape(exceptionDetail) + "</pre>"); } if (helpInfo != null) { URL url = helpInfo.getURL(); String anchorText = helpInfo.getAnchorText(); if (anchorText == null && url != null) { anchorText = url.toExternalForm(); } String prefix = helpInfo.getPrefix(); if (url != null) { sb.append("<p>" + prefix + "<a href=\""); sb.append(url.toString()); sb.append("\">"); sb.append(anchorText); sb.append("</a>"); sb.append("\n"); } } return sb.toString(); } /** * @return the inherited priority, which will be the highest priority of * this event or any child. */ public Type getInheritedPriority() { return inheritedPriority; } /** * Set the properties of a label to match this log entry. * * @param treeLabel label to set properties for. */ public void setDisplayProperties(JLabel treeLabel) { Icon image = logIcons.get(type); Color color = logColors.get(inheritedPriority); if (color == null) { color = Color.BLACK; } treeLabel.setForeground(color); treeLabel.setIcon(image); StringBuffer sb = new StringBuffer(); formatTimestamp(timestamp.getTime() - firstLog.getTime(), sb); sb.append(" "); // Show the message type. // if (type != null) { sb.append("["); sb.append(type.getLabel()); sb.append("] "); } // Show the item text. // sb.append(message); // Show the exception info for anything other than "UnableToComplete". // if (exceptionName != null) { sb.append(" -- exception: " + exceptionName); } treeLabel.setText(sb.toString()); } @Override public String toString() { String s = ""; s += "[logger " + childLogger.toString(); s += ", " + (isBranchCommit ? "BRANCH" : "LOG"); s += ", index " + index; s += ", type " + type.toString(); s += ", msg '" + message + "'"; s += "]"; return s; } /** * Update this log event's inherited priority, which is the highest priority * of this event and any child events. * * @param childPriority * @return true if the priority was upgraded */ public boolean updateInheritedPriority(Type childPriority) { if (this.inheritedPriority.isLowerPriorityThan(childPriority)) { this.inheritedPriority = childPriority; return true; } return false; } private void formatTimestamp(long ts, StringBuffer sb) { sb.append(minHr.format(ts / (1000 * 60 * 60))); sb.append(':'); sb.append(minHr.format((ts / (1000 * 60)) % 60)); sb.append(':'); sb.append(seconds.format((ts % 60000) / 1000.0)); } private String htmlEscape(String str) { // TODO(jat): call real implementation instead return str.replace("&", "&").replace("<", "<").replace(">", ">").replace( "\n", "<br>"); } } // package protected so SwingLoggerPanel can access final DefaultMutableTreeNode treeNode; private SwingLoggerPanel panel; /** * Constructs the top-level TreeItemLogger. * * @param panel */ public SwingTreeLogger(SwingLoggerPanel panel) { this(panel, (DefaultMutableTreeNode) panel.treeModel.getRoot()); } /** * Used to create a branch treelogger, supplying a tree node to use rather * than the panel's. * * @param panel * @param treeNode */ private SwingTreeLogger(SwingLoggerPanel panel, DefaultMutableTreeNode treeNode) { this.panel = panel; this.treeNode = treeNode; } @Override protected AbstractTreeLogger doBranch() { SwingTreeLogger newLogger = new SwingTreeLogger(panel, new DefaultMutableTreeNode(null)); return newLogger; } @Override protected void doCommitBranch(AbstractTreeLogger childBeingCommitted, Type type, String msg, Throwable caught, HelpInfo helpInfo) { SwingTreeLogger commitChild = (SwingTreeLogger) childBeingCommitted; assert commitChild.treeNode.getUserObject() == null; addUpdate(new LogEvent(commitChild, true, commitChild.getBranchedIndex(), type, msg, caught, helpInfo)); } @Override protected void doLog(int index, Type type, String msg, Throwable caught, HelpInfo helpInfo) { addUpdate(new LogEvent(this, false, index, type, msg, caught, helpInfo)); } /** * Add a log event to be processed on the event thread. * * @param logEvent LogEvent to process */ private void addUpdate(final LogEvent logEvent) { // TODO(jat): investigate not running all of this on the event thread EventQueue.invokeLater(new Runnable() { public void run() { // TODO(jat): apply filter criteria SwingTreeLogger logger = logEvent.childLogger; DefaultMutableTreeNode node; DefaultMutableTreeNode parentNode; int idx; if (logEvent.isBranchCommit) { SwingTreeLogger parentLogger = (SwingTreeLogger) logger.getParentLogger(); logger.treeNode.setUserObject(logEvent); parentNode = parentLogger.treeNode; idx = logger.getBranchedIndex(); node = logger.treeNode; } else { parentNode = logger.treeNode; idx = logEvent.index; node = new DefaultMutableTreeNode(logEvent); } int insertIndex = findInsertionPoint(parentNode, idx); panel.treeModel.insertNodeInto(node, parentNode, insertIndex); if (logEvent.type.needsAttention()) { panel.tree.makeVisible(new TreePath(node.getPath())); } if (parentNode == panel.treeModel.getRoot() && parentNode.getChildCount() == 1) { panel.treeModel.reload(); } // Propagate our priority to our ancestors Type priority = logEvent.getInheritedPriority(); while (parentNode != panel.treeModel.getRoot()) { LogEvent parentEvent = (LogEvent) parentNode.getUserObject(); if (!parentEvent.updateInheritedPriority(priority)) { break; } parentNode = ((DefaultMutableTreeNode) parentNode.getParent()); } } private int findInsertionPoint(DefaultMutableTreeNode parentNode, int index) { int high = parentNode.getChildCount() - 1; if (high < 0) { return 0; } int low = 0; while (low <= high) { final int mid = low + ((high - low) >> 1); DefaultMutableTreeNode midChild = (DefaultMutableTreeNode) parentNode.getChildAt(mid); final Object userObject = midChild.getUserObject(); int compIdx = -1; if (userObject instanceof LogEvent) { LogEvent event = (LogEvent) userObject; compIdx = event.index; } if (compIdx < index) { low = mid + 1; } else if (compIdx > index) { high = mid - 1; } else { return mid; } } return low; } }); } }