blob: eb6d65641998dfb35b44505591835c2a18a24206 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.log4j.chainsaw;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import org.apache.log4j.chainsaw.color.RuleColorizer;
import org.apache.log4j.chainsaw.icons.LevelIconFactory;
import org.apache.log4j.helpers.Constants;
import org.apache.log4j.rule.Rule;
/**
* A specific TableCellRenderer that colourizes a particular cell based on
* some ColourFilters that have been stored according to the value for the row
*
* @author Claude Duguay
* @author Scott Deboy <sdeboy@apache.org>
* @author Paul Smith <psmith@apache.org>
*
*/
public class TableColorizingRenderer extends DefaultTableCellRenderer {
private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(Constants.SIMPLE_TIME_PATTERN);
private static final Map iconMap = LevelIconFactory.getInstance().getLevelToIconMap();
private RuleColorizer colorizer;
private boolean levelUseIcons = false;
private boolean wrapMsg = false;
private DateFormat dateFormatInUse = DATE_FORMATTER;
private int loggerPrecision = 0;
private boolean toolTipsVisible;
private String dateFormatTZ;
private boolean useRelativeTimes = false;
private long relativeTimestampBase;
private static int borderWidth = 2;
private static Color borderColor = (Color)UIManager.get("Table.selectionBackground");
private static final Border LEFT_BORDER = BorderFactory.createMatteBorder(borderWidth, borderWidth, borderWidth, 0, borderColor);
private static final Border MIDDLE_BORDER = BorderFactory.createMatteBorder(borderWidth, 0, borderWidth, 0, borderColor);
private static final Border RIGHT_BORDER = BorderFactory.createMatteBorder(borderWidth, 0, borderWidth, borderWidth, borderColor);
private static final Border LEFT_EMPTY_BORDER = BorderFactory.createEmptyBorder(borderWidth, borderWidth, borderWidth, 0);
private static final Border MIDDLE_EMPTY_BORDER = BorderFactory.createEmptyBorder(borderWidth, 0, borderWidth, 0);
private static final Border RIGHT_EMPTY_BORDER = BorderFactory.createEmptyBorder(borderWidth, 0, borderWidth, borderWidth);
private final JTextArea msgTextArea = new JTextArea();
private final JLabel levelLabel = new JLabel();
private final JLabel generalLabel = new JLabel();
private final JPanel msgPanel = new JPanel();
private final JPanel generalPanel = new JPanel();
private final JPanel levelPanel = new JPanel();
/**
* Creates a new TableColorizingRenderer object.
*/
public TableColorizingRenderer(RuleColorizer colorizer) {
msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.Y_AXIS));
generalPanel.setLayout(new BoxLayout(generalPanel, BoxLayout.Y_AXIS));
levelPanel.setLayout(new BoxLayout(levelPanel, BoxLayout.Y_AXIS));
msgPanel.setAlignmentX(TOP_ALIGNMENT);
generalPanel.setAlignmentX(TOP_ALIGNMENT);
levelPanel.setAlignmentX(TOP_ALIGNMENT);
generalLabel.setVerticalAlignment(SwingConstants.TOP);
levelLabel.setVerticalAlignment(SwingConstants.TOP);
levelLabel.setOpaque(true);
levelLabel.setText("");
msgTextArea.setMargin(null);
msgTextArea.setEditable(false);
msgPanel.add(msgTextArea);
generalPanel.add(generalLabel);
levelPanel.add(levelLabel);
this.colorizer = colorizer;
}
public void setToolTipsVisible(boolean toolTipsVisible) {
this.toolTipsVisible = toolTipsVisible;
}
public Component getTableCellRendererComponent(
final JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int col) {
value = formatField(value);
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, col);
TableColumn tableColumn = table.getColumnModel().getColumn(col);
//chainsawcolumns uses one-based indexing
int colIndex = tableColumn.getModelIndex() + 1;
EventContainer container = (EventContainer) table.getModel();
ExtendedLoggingEvent loggingEvent = container.getRow(row);
//no event, use default renderer
if (loggingEvent == null) {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
}
JComponent component;
switch (colIndex) {
case ChainsawColumns.INDEX_THROWABLE_COL_NAME:
if (value instanceof String[] && ((String[])value).length > 0){
generalLabel.setText(((String[]) value)[0]);
} else {
generalLabel.setText("");
}
component = generalPanel;
break;
case ChainsawColumns.INDEX_LOGGER_COL_NAME:
String logger = value.toString();
int startPos = -1;
for (int i = 0; i < loggerPrecision; i++) {
startPos = logger.indexOf(".", startPos + 1);
if (startPos < 0) {
break;
}
}
generalLabel.setText(logger.substring(startPos + 1));
component = generalPanel;
break;
case ChainsawColumns.INDEX_ID_COL_NAME:
case ChainsawColumns.INDEX_LOG4J_MARKER_COL_NAME:
case ChainsawColumns.INDEX_CLASS_COL_NAME:
case ChainsawColumns.INDEX_FILE_COL_NAME:
case ChainsawColumns.INDEX_LINE_COL_NAME:
case ChainsawColumns.INDEX_NDC_COL_NAME:
case ChainsawColumns.INDEX_THREAD_COL_NAME:
case ChainsawColumns.INDEX_TIMESTAMP_COL_NAME:
case ChainsawColumns.INDEX_METHOD_COL_NAME:
generalLabel.setText(value.toString());
component = generalPanel;
break;
case ChainsawColumns.INDEX_MESSAGE_COL_NAME:
String string = value.toString().trim();
int width = tableColumn.getWidth();
msgTextArea.setLineWrap(wrapMsg);
msgTextArea.setWrapStyleWord(wrapMsg);
msgTextArea.setFont(label.getFont());
msgTextArea.setText(string);
int tableRowHeight = table.getRowHeight(row);
if (wrapMsg) {
Map paramMap = new HashMap();
paramMap.put(TextAttribute.FONT, msgTextArea.getFont());
int calculatedHeight = calculateHeight(string, width, paramMap);
msgTextArea.setSize(new Dimension(width, calculatedHeight));
int msgPanelPrefHeight = msgPanel.getPreferredSize().height;
if(tableRowHeight < msgPanelPrefHeight/* && calculatedHeight > tableRowHeight*/) {
table.setRowHeight(row, Math.max(ChainsawConstants.DEFAULT_ROW_HEIGHT, msgPanelPrefHeight));
}
}
component = msgPanel;
break;
case ChainsawColumns.INDEX_LEVEL_COL_NAME:
if (levelUseIcons) {
levelLabel.setIcon((Icon) iconMap.get(value.toString()));
if (levelLabel.getIcon() != null) {
levelLabel.setText("");
}
if (!toolTipsVisible) {
levelLabel.setToolTipText(value.toString());
}
} else {
levelLabel.setIcon(null);
levelLabel.setText(value.toString());
if (!toolTipsVisible) {
levelLabel.setToolTipText(null);
}
}
if (toolTipsVisible) {
levelLabel.setToolTipText(label.getToolTipText());
}
levelLabel.setForeground(label.getForeground());
levelLabel.setBackground(label.getBackground());
component = levelPanel;
break;
//remaining entries are properties
default:
Set propertySet = loggingEvent.getPropertyKeySet();
String headerName = tableColumn.getHeaderValue().toString().toLowerCase();
String thisProp = null;
//find the property in the property set...case-sensitive
for (Iterator iter = propertySet.iterator();iter.hasNext();) {
String entry = iter.next().toString();
if (entry.toLowerCase().equals(headerName)) {
thisProp = entry;
break;
}
}
if (thisProp != null) {
generalLabel.setText(loggingEvent.getProperty(thisProp));
} else {
generalLabel.setText("");
}
component = generalPanel;
break;
}
Color background;
Color foreground;
Rule loggerRule = colorizer.getLoggerRule();
//use logger colors in table instead of event colors if event passes logger rule
if (loggerRule != null && loggerRule.evaluate(loggingEvent)) {
background = ChainsawConstants.FIND_LOGGER_BACKGROUND;
foreground = ChainsawConstants.FIND_LOGGER_FOREGROUND;
} else {
background = loggingEvent.getBackground();
foreground = loggingEvent.getForeground();
}
/**
* Colourize background based on row striping if the event still has a background color
*/
if (background.equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND)) {
if ((row % 2) != 0) {
background = ChainsawConstants.COLOR_ODD_ROW;
} else {
background = ChainsawConstants.COLOR_EVEN_ROW;
}
}
component.setBackground(background);
component.setForeground(foreground);
//set the colors of the components inside 'component'
msgTextArea.setBackground(background);
msgTextArea.setForeground(foreground);
levelLabel.setBackground(background);
levelLabel.setForeground(foreground);
generalLabel.setBackground(background);
generalLabel.setForeground(foreground);
if (isSelected) {
if (col == 0) {
component.setBorder(LEFT_BORDER);
} else if (col == table.getColumnCount() - 1) {
component.setBorder(RIGHT_BORDER);
} else {
component.setBorder(MIDDLE_BORDER);
}
} else {
if (col == 0) {
component.setBorder(LEFT_EMPTY_BORDER);
} else if (col == table.getColumnCount() - 1) {
component.setBorder(RIGHT_EMPTY_BORDER);
} else {
component.setBorder(MIDDLE_EMPTY_BORDER);
}
}
return component;
}
/**
* Changes the Date Formatting object to be used for rendering dates.
* @param formatter
*/
void setDateFormatter(DateFormat formatter) {
this.dateFormatInUse = formatter;
if (dateFormatInUse != null && dateFormatTZ != null && !("".equals(dateFormatTZ))) {
dateFormatInUse.setTimeZone(TimeZone.getTimeZone(dateFormatTZ));
} else {
dateFormatInUse.setTimeZone(TimeZone.getDefault());
}
}
/**
* Changes the Logger precision.
* @param loggerPrecisionText
*/
void setLoggerPrecision(String loggerPrecisionText) {
try {
loggerPrecision = Integer.parseInt(loggerPrecisionText);
} catch (NumberFormatException nfe) {
loggerPrecision = 0;
}
}
/**
*Format date field
*
* @param o object
*
* @return formatted object
*/
private Object formatField(Object o) {
if (!(o instanceof Date)) {
return (o == null ? "" : o);
}
//handle date field
if (useRelativeTimes)
{
return "" + (((Date)o).getTime() - relativeTimestampBase);
}
return dateFormatInUse.format((Date) o);
}
/**
* Sets the property which determines whether to wrap the message
* @param wrapMsg
*/
public void setWrapMessage(boolean wrapMsg) {
this.wrapMsg = wrapMsg;
}
/**
* Sets the property which determines whether to use Icons or text
* for the Level column
* @param levelUseIcons
*/
public void setLevelUseIcons(boolean levelUseIcons) {
this.levelUseIcons = levelUseIcons;
}
public void setTimeZone(String dateFormatTZ) {
this.dateFormatTZ = dateFormatTZ;
if (dateFormatInUse != null && dateFormatTZ != null && !("".equals(dateFormatTZ))) {
dateFormatInUse.setTimeZone(TimeZone.getTimeZone(dateFormatTZ));
} else {
dateFormatInUse.setTimeZone(TimeZone.getDefault());
}
}
public void setUseRelativeTimes(long timeStamp) {
useRelativeTimes = true;
relativeTimestampBase = timeStamp;
}
public void setUseNormalTimes() {
useRelativeTimes = false;
}
private int calculateHeight(String string, int width, Map paramMap) {
if (string.trim().length() == 0) {
return ChainsawConstants.DEFAULT_ROW_HEIGHT;
}
AttributedCharacterIterator paragraph = new AttributedString(string, paramMap).getIterator();
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, new FontRenderContext(null, true, true));
float height = 0;
lineMeasurer.setPosition(paragraph.getBeginIndex());
TextLayout layout;
while (lineMeasurer.getPosition() < paragraph.getEndIndex()) {
layout = lineMeasurer.nextLayout(width);
float layoutHeight = layout.getAscent() + layout.getDescent() + layout.getLeading();
height += layoutHeight;
}
return Math.max(ChainsawConstants.DEFAULT_ROW_HEIGHT, (int) height);
}
}