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 }