1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
51 } else if (aObj1 == null) {
52 return -1;
53 } else if (aObj2 == null) {
54 return 1;
55 }
56
57
58 final EventDetails le1 = (EventDetails) aObj1;
59 final EventDetails le2 = (EventDetails) aObj2;
60
61 if (le1.getTimeStamp() < le2.getTimeStamp()) {
62 return 1;
63 }
64
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
83 }
84
85 synchronized (mLock) {
86 if (mPaused) {
87 continue;
88 }
89
90 boolean toHead = true;
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
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
170 return COL_NAMES.length;
171 }
172
173 /*** @see javax.swing.table.TableModel **/
174 public String getColumnName(int aCol) {
175
176 return COL_NAMES[aCol];
177 }
178
179 /*** @see javax.swing.table.TableModel **/
180 public Class getColumnClass(int aCol) {
181
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
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
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
382 return (mMessageFilter.length() == 0);
383 } else {
384 return (rm.indexOf(mMessageFilter) >= 0);
385 }
386 }
387
388 return false;
389 }
390 }