blob: b0d8898aaffb82fc41aad9fa2a0975185b74ee7d [file] [log] [blame]
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software
* License version 1.1, a copy of which has been included with this
* distribution in the LICENSE.txt file. */
package org.apache.log4j.chainsaw;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.table.AbstractTableModel;
import org.apache.log4j.Priority;
import org.apache.log4j.Logger;
/**
* Represents a list of <code>EventDetails</code> objects that are sorted on
* logging time. Methods are provided to filter the events that are visible.
*
* @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
*/
class MyTableModel
extends AbstractTableModel
{
/** used to log messages **/
private static final Logger LOG = Logger.getLogger(MyTableModel.class);
/** use the compare logging events **/
private static final Comparator MY_COMP = new Comparator()
{
/** @see Comparator **/
public int compare(Object aObj1, Object aObj2) {
if ((aObj1 == null) && (aObj2 == null)) {
return 0; // treat as equal
} else if (aObj1 == null) {
return -1; // null less than everything
} else if (aObj2 == null) {
return 1; // think about it. :->
}
// will assume only have LoggingEvent
final EventDetails le1 = (EventDetails) aObj1;
final EventDetails le2 = (EventDetails) aObj2;
if (le1.getTimeStamp() < le2.getTimeStamp()) {
return 1;
}
// assume not two events are logged at exactly the same time
return -1;
}
};
/**
* Helper that actually processes incoming events.
* @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
*/
private class Processor
implements Runnable
{
/** loops getting the events **/
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
synchronized (mLock) {
if (mPaused) {
continue;
}
boolean toHead = true; // were events added to head
boolean needUpdate = false;
final Iterator it = mPendingEvents.iterator();
while (it.hasNext()) {
final EventDetails event = (EventDetails) it.next();
mAllEvents.add(event);
toHead = toHead && (event == mAllEvents.first());
needUpdate = needUpdate || matchFilter(event);
}
mPendingEvents.clear();
if (needUpdate) {
updateFilteredEvents(toHead);
}
}
}
}
}
/** names of the columns in the table **/
private static final String[] COL_NAMES = {
"Time", "Priority", "Trace", "Category", "NDC", "Message"};
/** definition of an empty list **/
private static final EventDetails[] EMPTY_LIST = new EventDetails[] {};
/** used to format dates **/
private static final DateFormat DATE_FORMATTER =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
/** the lock to control access **/
private final Object mLock = new Object();
/** set of all logged events - not filtered **/
private final SortedSet mAllEvents = new TreeSet(MY_COMP);
/** events that are visible after filtering **/
private EventDetails[] mFilteredEvents = EMPTY_LIST;
/** list of events that are buffered for processing **/
private final List mPendingEvents = new ArrayList();
/** indicates whether event collection is paused to the UI **/
private boolean mPaused = false;
/** filter for the thread **/
private String mThreadFilter = "";
/** filter for the message **/
private String mMessageFilter = "";
/** filter for the NDC **/
private String mNDCFilter = "";
/** filter for the category **/
private String mCategoryFilter = "";
/** filter for the priority **/
private Priority mPriorityFilter = Priority.DEBUG;
/**
* Creates a new <code>MyTableModel</code> instance.
*
*/
MyTableModel() {
final Thread t = new Thread(new Processor());
t.setDaemon(true);
t.start();
}
////////////////////////////////////////////////////////////////////////////
// Table Methods
////////////////////////////////////////////////////////////////////////////
/** @see javax.swing.table.TableModel **/
public int getRowCount() {
synchronized (mLock) {
return mFilteredEvents.length;
}
}
/** @see javax.swing.table.TableModel **/
public int getColumnCount() {
// does not need to be synchronized
return COL_NAMES.length;
}
/** @see javax.swing.table.TableModel **/
public String getColumnName(int aCol) {
// does not need to be synchronized
return COL_NAMES[aCol];
}
/** @see javax.swing.table.TableModel **/
public Class getColumnClass(int aCol) {
// does not need to be synchronized
return (aCol == 2) ? Boolean.class : Object.class;
}
/** @see javax.swing.table.TableModel **/
public Object getValueAt(int aRow, int aCol) {
synchronized (mLock) {
final EventDetails event = mFilteredEvents[aRow];
if (aCol == 0) {
return DATE_FORMATTER.format(new Date(event.getTimeStamp()));
} else if (aCol == 1) {
return event.getPriority();
} else if (aCol == 2) {
return (event.getThrowableStrRep() == null)
? Boolean.FALSE : Boolean.TRUE;
} else if (aCol == 3) {
return event.getCategoryName();
} else if (aCol == 4) {
return event.getNDC();
}
return event.getMessage();
}
}
////////////////////////////////////////////////////////////////////////////
// Public Methods
////////////////////////////////////////////////////////////////////////////
/**
* Sets the priority to filter events on. Only events of equal or higher
* property are now displayed.
*
* @param aPriority the priority to filter on
*/
public void setPriorityFilter(Priority aPriority) {
synchronized (mLock) {
mPriorityFilter = aPriority;
updateFilteredEvents(false);
}
}
/**
* Set the filter for the thread field.
*
* @param aStr the string to match
*/
public void setThreadFilter(String aStr) {
synchronized (mLock) {
mThreadFilter = aStr.trim();
updateFilteredEvents(false);
}
}
/**
* Set the filter for the message field.
*
* @param aStr the string to match
*/
public void setMessageFilter(String aStr) {
synchronized (mLock) {
mMessageFilter = aStr.trim();
updateFilteredEvents(false);
}
}
/**
* Set the filter for the NDC field.
*
* @param aStr the string to match
*/
public void setNDCFilter(String aStr) {
synchronized (mLock) {
mNDCFilter = aStr.trim();
updateFilteredEvents(false);
}
}
/**
* Set the filter for the category field.
*
* @param aStr the string to match
*/
public void setCategoryFilter(String aStr) {
synchronized (mLock) {
mCategoryFilter = aStr.trim();
updateFilteredEvents(false);
}
}
/**
* Add an event to the list.
*
* @param aEvent a <code>EventDetails</code> value
*/
public void addEvent(EventDetails aEvent) {
synchronized (mLock) {
mPendingEvents.add(aEvent);
}
}
/**
* Clear the list of all events.
*/
public void clear() {
synchronized (mLock) {
mAllEvents.clear();
mFilteredEvents = new EventDetails[0];
mPendingEvents.clear();
fireTableDataChanged();
}
}
/** Toggle whether collecting events **/
public void toggle() {
synchronized (mLock) {
mPaused = !mPaused;
}
}
/** @return whether currently paused collecting events **/
public boolean isPaused() {
synchronized (mLock) {
return mPaused;
}
}
/**
* Get the throwable information at a specified row in the filtered events.
*
* @param aRow the row index of the event
* @return the throwable information
*/
public EventDetails getEventDetails(int aRow) {
synchronized (mLock) {
return mFilteredEvents[aRow];
}
}
////////////////////////////////////////////////////////////////////////////
// Private methods
////////////////////////////////////////////////////////////////////////////
/**
* Update the filtered events data structure.
* @param aInsertedToFront indicates whether events were added to front of
* the events. If true, then the current first event must still exist
* in the list after the filter is applied.
*/
private void updateFilteredEvents(boolean aInsertedToFront) {
final long start = System.currentTimeMillis();
final List filtered = new ArrayList();
final int size = mAllEvents.size();
final Iterator it = mAllEvents.iterator();
while (it.hasNext()) {
final EventDetails event = (EventDetails) it.next();
if (matchFilter(event)) {
filtered.add(event);
}
}
final EventDetails lastFirst = (mFilteredEvents.length == 0)
? null
: mFilteredEvents[0];
mFilteredEvents = (EventDetails[]) filtered.toArray(EMPTY_LIST);
if (aInsertedToFront && (lastFirst != null)) {
final int index = filtered.indexOf(lastFirst);
if (index < 1) {
LOG.warn("In strange state");
fireTableDataChanged();
} else {
fireTableRowsInserted(0, index - 1);
}
} else {
fireTableDataChanged();
}
final long end = System.currentTimeMillis();
LOG.debug("Total time [ms]: " + (end - start)
+ " in update, size: " + size);
}
/**
* Returns whether an event matches the filters.
*
* @param aEvent the event to check for a match
* @return whether the event matches
*/
private boolean matchFilter(EventDetails aEvent) {
if (aEvent.getPriority().isGreaterOrEqual(mPriorityFilter) &&
(aEvent.getThreadName().indexOf(mThreadFilter) >= 0) &&
(aEvent.getCategoryName().indexOf(mCategoryFilter) >= 0) &&
((mNDCFilter.length() == 0) ||
((aEvent.getNDC() != null) &&
(aEvent.getNDC().indexOf(mNDCFilter) >= 0))))
{
final String rm = aEvent.getMessage();
if (rm == null) {
// only match if we have not filtering in place
return (mMessageFilter.length() == 0);
} else {
return (rm.indexOf(mMessageFilter) >= 0);
}
}
return false; // by default not match
}
}