blob: ac5d464044bf48cca800bd2a0e242610439fd666 [file] [log] [blame]
/*
* 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").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;
}
});
}
}