View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.log4j.chainsaw;
    18
    19  import java.text.DateFormat;
    20  import java.util.ArrayList;
    21  import java.util.Comparator;
    22  import java.util.Date;
    23  import java.util.Iterator;
    24  import java.util.List;
    25  import java.util.SortedSet;
    26  import java.util.TreeSet;
    27  import javax.swing.table.AbstractTableModel;
    28  import org.apache.log4j.Priority;
    29  import org.apache.log4j.Logger;
    30
    31  /**
    32   * Represents a list of <code>EventDetails</code> objects that are sorted on
    33   * logging time. Methods are provided to filter the events that are visible.
    34   *
    35   * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
    36   */
    37  class MyTableModel
    38      extends AbstractTableModel
    39  {
    40
    41      /** used to log messages **/
    42      private static final Logger LOG = Logger.getLogger(MyTableModel.class);
    43
    44      /** use the compare logging events **/
    45      private static final Comparator MY_COMP = new Comparator()
    46      {
    47          /** @see Comparator **/
    48          public int compare(Object aObj1, Object aObj2) {
    49              if ((aObj1 == null) && (aObj2 == null)) {
    50                  return 0; // treat as equal
    51              } else if (aObj1 == null) {
    52                  return -1; // null less than everything
    53              } else if (aObj2 == null) {
    54                  return 1; // think about it. :->
    55              }
    56
    57              // will assume only have LoggingEvent
    58              final EventDetails le1 = (EventDetails) aObj1;
    59              final EventDetails le2 = (EventDetails) aObj2;
    60
    61              if (le1.getTimeStamp() < le2.getTimeStamp()) {
    62                  return 1;
    63              }
    64              // assume not two events are logged at exactly the same time
    65              return -1;
    66          }
    67          };
    68
    69      /**
    70       * Helper that actually processes incoming events.
    71       * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
    72       */
    73      private class Processor
    74          implements Runnable
    75      {
    76          /** loops getting the events **/
    77          public void run() {
    78              while (true) {
    79                  try {
    80                      Thread.sleep(1000);
    81                  } catch (InterruptedException e) {
    82                      // ignore
    83                  }
    84
    85                  synchronized (mLock) {
    86                      if (mPaused) {
    87                          continue;
    88                      }
    89
    90                      boolean toHead = true; // were events added to head
    91                      boolean needUpdate = false;
    92                      final Iterator it = mPendingEvents.iterator();
    93                      while (it.hasNext()) {
    94                          final EventDetails event = (EventDetails) it.next();
    95                          mAllEvents.add(event);
    96                          toHead = toHead && (event == mAllEvents.first());
    97                          needUpdate = needUpdate || matchFilter(event);
    98                      }
    99                      mPendingEvents.clear();
    100
    101                     if (needUpdate) {
    102                         updateFilteredEvents(toHead);
    103                     }
    104                 }
    105             }
    106
    107         }
    108     }
    109
    110
    111     /** names of the columns in the table **/
    112     private static final String[] COL_NAMES = {
    113         "Time", "Priority", "Trace", "Category", "NDC", "Message"};
    114
    115     /** definition of an empty list **/
    116     private static final EventDetails[] EMPTY_LIST =  new EventDetails[] {};
    117
    118     /** used to format dates **/
    119     private static final DateFormat DATE_FORMATTER =
    120         DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
    121
    122     /** the lock to control access **/
    123     private final Object mLock = new Object();
    124     /** set of all logged events - not filtered **/
    125     private final SortedSet mAllEvents = new TreeSet(MY_COMP);
    126     /** events that are visible after filtering **/
    127     private EventDetails[] mFilteredEvents = EMPTY_LIST;
    128     /** list of events that are buffered for processing **/
    129     private final List mPendingEvents = new ArrayList();
    130     /** indicates whether event collection is paused to the UI **/
    131     private boolean mPaused = false;
    132
    133     /** filter for the thread **/
    134     private String mThreadFilter = "";
    135     /** filter for the message **/
    136     private String mMessageFilter = "";
    137     /** filter for the NDC **/
    138     private String mNDCFilter = "";
    139     /** filter for the category **/
    140     private String mCategoryFilter = "";
    141     /** filter for the priority **/
    142     private Priority mPriorityFilter = Priority.DEBUG;
    143
    144
    145     /**
    146      * Creates a new <code>MyTableModel</code> instance.
    147      *
    148      */
    149     MyTableModel() {
    150         final Thread t = new Thread(new Processor());
    151         t.setDaemon(true);
    152         t.start();
    153     }
    154
    155
    156     ////////////////////////////////////////////////////////////////////////////
    157     // Table Methods
    158     ////////////////////////////////////////////////////////////////////////////
    159
    160     /** @see javax.swing.table.TableModel **/
    161     public int getRowCount() {
    162         synchronized (mLock) {
    163             return mFilteredEvents.length;
    164         }
    165     }
    166
    167     /** @see javax.swing.table.TableModel **/
    168     public int getColumnCount() {
    169         // does not need to be synchronized
    170         return COL_NAMES.length;
    171     }
    172
    173     /** @see javax.swing.table.TableModel **/
    174     public String getColumnName(int aCol) {
    175         // does not need to be synchronized
    176         return COL_NAMES[aCol];
    177     }
    178
    179     /** @see javax.swing.table.TableModel **/
    180     public Class getColumnClass(int aCol) {
    181         // does not need to be synchronized
    182         return (aCol == 2) ? Boolean.class : Object.class;
    183     }
    184
    185     /** @see javax.swing.table.TableModel **/
    186     public Object getValueAt(int aRow, int aCol) {
    187         synchronized (mLock) {
    188             final EventDetails event = mFilteredEvents[aRow];
    189
    190             if (aCol == 0) {
    191                 return DATE_FORMATTER.format(new Date(event.getTimeStamp()));
    192             } else if (aCol == 1) {
    193                 return event.getPriority();
    194             } else if (aCol == 2) {
    195                 return (event.getThrowableStrRep() == null)
    196                     ? Boolean.FALSE : Boolean.TRUE;
    197             } else if (aCol == 3) {
    198                 return event.getCategoryName();
    199             } else if (aCol == 4) {
    200                 return event.getNDC();
    201             }
    202             return event.getMessage();
    203         }
    204     }
    205
    206     ////////////////////////////////////////////////////////////////////////////
    207     // Public Methods
    208     ////////////////////////////////////////////////////////////////////////////
    209
    210     /**
    211      * Sets the priority to filter events on. Only events of equal or higher
    212      * property are now displayed.
    213      *
    214      * @param aPriority the priority to filter on
    215      */
    216     public void setPriorityFilter(Priority aPriority) {
    217         synchronized (mLock) {
    218             mPriorityFilter = aPriority;
    219             updateFilteredEvents(false);
    220         }
    221     }
    222
    223     /**
    224      * Set the filter for the thread field.
    225      *
    226      * @param aStr the string to match
    227      */
    228     public void setThreadFilter(String aStr) {
    229         synchronized (mLock) {
    230             mThreadFilter = aStr.trim();
    231             updateFilteredEvents(false);
    232         }
    233     }
    234
    235     /**
    236      * Set the filter for the message field.
    237      *
    238      * @param aStr the string to match
    239      */
    240     public void setMessageFilter(String aStr) {
    241         synchronized (mLock) {
    242             mMessageFilter = aStr.trim();
    243             updateFilteredEvents(false);
    244         }
    245     }
    246
    247     /**
    248      * Set the filter for the NDC field.
    249      *
    250      * @param aStr the string to match
    251      */
    252     public void setNDCFilter(String aStr) {
    253         synchronized (mLock) {
    254             mNDCFilter = aStr.trim();
    255             updateFilteredEvents(false);
    256         }
    257     }
    258
    259     /**
    260      * Set the filter for the category field.
    261      *
    262      * @param aStr the string to match
    263      */
    264     public void setCategoryFilter(String aStr) {
    265         synchronized (mLock) {
    266             mCategoryFilter = aStr.trim();
    267             updateFilteredEvents(false);
    268         }
    269     }
    270
    271     /**
    272      * Add an event to the list.
    273      *
    274      * @param aEvent a <code>EventDetails</code> value
    275      */
    276     public void addEvent(EventDetails aEvent) {
    277         synchronized (mLock) {
    278             mPendingEvents.add(aEvent);
    279         }
    280     }
    281
    282     /**
    283      * Clear the list of all events.
    284      */
    285     public void clear() {
    286         synchronized (mLock) {
    287             mAllEvents.clear();
    288             mFilteredEvents = new EventDetails[0];
    289             mPendingEvents.clear();
    290             fireTableDataChanged();
    291         }
    292     }
    293
    294     /** Toggle whether collecting events **/
    295     public void toggle() {
    296         synchronized (mLock) {
    297             mPaused = !mPaused;
    298         }
    299     }
    300
    301     /** @return whether currently paused collecting events **/
    302     public boolean isPaused() {
    303         synchronized (mLock) {
    304             return mPaused;
    305         }
    306     }
    307
    308     /**
    309      * Get the throwable information at a specified row in the filtered events.
    310      *
    311      * @param aRow the row index of the event
    312      * @return the throwable information
    313      */
    314     public EventDetails getEventDetails(int aRow) {
    315         synchronized (mLock) {
    316             return mFilteredEvents[aRow];
    317         }
    318     }
    319
    320     ////////////////////////////////////////////////////////////////////////////
    321     // Private methods
    322     ////////////////////////////////////////////////////////////////////////////
    323
    324     /**
    325      * Update the filtered events data structure.
    326      * @param aInsertedToFront indicates whether events were added to front of
    327      *        the events. If true, then the current first event must still exist
    328      *        in the list after the filter is applied.
    329      */
    330     private void updateFilteredEvents(boolean aInsertedToFront) {
    331         final long start = System.currentTimeMillis();
    332         final List filtered = new ArrayList();
    333         final int size = mAllEvents.size();
    334         final Iterator it = mAllEvents.iterator();
    335
    336         while (it.hasNext()) {
    337             final EventDetails event = (EventDetails) it.next();
    338             if (matchFilter(event)) {
    339                 filtered.add(event);
    340             }
    341         }
    342
    343         final EventDetails lastFirst = (mFilteredEvents.length == 0)
    344             ? null
    345             : mFilteredEvents[0];
    346         mFilteredEvents = (EventDetails[]) filtered.toArray(EMPTY_LIST);
    347
    348         if (aInsertedToFront && (lastFirst != null)) {
    349             final int index = filtered.indexOf(lastFirst);
    350             if (index < 1) {
    351                 LOG.warn("In strange state");
    352                 fireTableDataChanged();
    353             } else {
    354                 fireTableRowsInserted(0, index - 1);
    355             }
    356         } else {
    357             fireTableDataChanged();
    358         }
    359
    360         final long end = System.currentTimeMillis();
    361         LOG.debug("Total time [ms]: " + (end - start)
    362                   + " in update, size: " + size);
    363     }
    364
    365     /**
    366      * Returns whether an event matches the filters.
    367      *
    368      * @param aEvent the event to check for a match
    369      * @return whether the event matches
    370      */
    371     private boolean matchFilter(EventDetails aEvent) {
    372         if (aEvent.getPriority().isGreaterOrEqual(mPriorityFilter) &&
    373             (aEvent.getThreadName().indexOf(mThreadFilter) >= 0) &&
    374             (aEvent.getCategoryName().indexOf(mCategoryFilter) >= 0) &&
    375             ((mNDCFilter.length() == 0) ||
    376              ((aEvent.getNDC() != null) &&
    377               (aEvent.getNDC().indexOf(mNDCFilter) >= 0))))
    378         {
    379             final String rm = aEvent.getMessage();
    380             if (rm == null) {
    381                 // only match if we have not filtering in place
    382                 return (mMessageFilter.length() == 0);
    383             } else {
    384                 return (rm.indexOf(mMessageFilter) >= 0);
    385             }
    386         }
    387
    388         return false; // by default not match
    389     }
    390 }