Open Source Repository

Home /log4j/log4j-1.2.9 | Repository Home



org/apache/log4j/chainsaw/MyTableModel.java
/*
 * 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:[email protected]">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 = (EventDetailsaObj1;
            final EventDetails le2 = (EventDetailsaObj2;

            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:[email protected]">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 = (EventDetailsit.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 = (EventDetailsit.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
    }
}