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