alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 * 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.Category;

/**
 * Represents a list of EventDetails objects that are sorted on
 * logging time. Methods are provided to filter the events that are visible.
 *
 * @author Oliver Burn
 */
class MyTableModel
    extends AbstractTableModel
{

    /** used to log messages **/
    private static final Category LOG =
        Category.getInstance(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 Oliver Burn
     */
    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 MyTableModel 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 EventDetails 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
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.