| /* |
| * 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; |
| } |
| }); |
| } |
| } |