001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.log4j.chainsaw; 018 019import java.text.DateFormat; 020import java.util.ArrayList; 021import java.util.Comparator; 022import java.util.Date; 023import java.util.Iterator; 024import java.util.List; 025import java.util.SortedSet; 026import java.util.TreeSet; 027import javax.swing.table.AbstractTableModel; 028import org.apache.log4j.Priority; 029import org.apache.log4j.Logger; 030 031/** 032 * Represents a list of <code>EventDetails</code> objects that are sorted on 033 * logging time. Methods are provided to filter the events that are visible. 034 * 035 * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a> 036 */ 037class MyTableModel 038 extends AbstractTableModel 039{ 040 041 /** used to log messages **/ 042 private static final Logger LOG = Logger.getLogger(MyTableModel.class); 043 044 /** use the compare logging events **/ 045 private static final Comparator MY_COMP = new Comparator() 046 { 047 /** @see Comparator **/ 048 public int compare(Object aObj1, Object aObj2) { 049 if ((aObj1 == null) && (aObj2 == null)) { 050 return 0; // treat as equal 051 } else if (aObj1 == null) { 052 return -1; // null less than everything 053 } else if (aObj2 == null) { 054 return 1; // think about it. :-> 055 } 056 057 // will assume only have LoggingEvent 058 final EventDetails le1 = (EventDetails) aObj1; 059 final EventDetails le2 = (EventDetails) aObj2; 060 061 if (le1.getTimeStamp() < le2.getTimeStamp()) { 062 return 1; 063 } 064 // assume not two events are logged at exactly the same time 065 return -1; 066 } 067 }; 068 069 /** 070 * Helper that actually processes incoming events. 071 * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a> 072 */ 073 private class Processor 074 implements Runnable 075 { 076 /** loops getting the events **/ 077 public void run() { 078 while (true) { 079 try { 080 Thread.sleep(1000); 081 } catch (InterruptedException e) { 082 // ignore 083 } 084 085 synchronized (mLock) { 086 if (mPaused) { 087 continue; 088 } 089 090 boolean toHead = true; // were events added to head 091 boolean needUpdate = false; 092 final Iterator it = mPendingEvents.iterator(); 093 while (it.hasNext()) { 094 final EventDetails event = (EventDetails) it.next(); 095 mAllEvents.add(event); 096 toHead = toHead && (event == mAllEvents.first()); 097 needUpdate = needUpdate || matchFilter(event); 098 } 099 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}