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
    18  package org.apache.log4j.chainsaw;
    19
    20  import java.beans.PropertyChangeEvent;
    21  import java.beans.PropertyChangeListener;
    22  import java.beans.PropertyChangeSupport;
    23  import java.util.ArrayList;
    24  import java.util.Collection;
    25  import java.util.Collections;
    26  import java.util.Date;
    27  import java.util.HashSet;
    28  import java.util.Iterator;
    29  import java.util.List;
    30  import java.util.Locale;
    31  import java.util.Map;
    32  import java.util.Set;
    33
    34  import javax.swing.ProgressMonitor;
    35  import javax.swing.event.EventListenerList;
    36  import javax.swing.table.AbstractTableModel;
    37
    38  import org.apache.log4j.LogManager;
    39  import org.apache.log4j.Logger;
    40  import org.apache.log4j.chainsaw.color.RuleColorizer;
    41  import org.apache.log4j.chainsaw.helper.SwingHelper;
    42  import org.apache.log4j.helpers.Constants;
    43  import org.apache.log4j.rule.Rule;
    44  import org.apache.log4j.spi.LocationInfo;
    45  import org.apache.log4j.spi.LoggingEvent;
    46
    47
    48  /**
    49   * A CyclicBuffer implementation of the EventContainer.
    50   *
    51   * NOTE:  This implementation prevents duplicate rows from being added to the model.
    52   *
    53   * Ignoring duplicates was added to support receivers which may attempt to deliver the same
    54   * event more than once but can be safely ignored (for example, the database receiver
    55   * when set to retrieve in a loop).
    56   *
    57   * @author Paul Smith &lt;psmith@apache.org&gt;
    58   * @author Scott Deboy &lt;sdeboy@apache.org&gt;
    59   * @author Stephen Pain
    60   *
    61   */
    62  class ChainsawCyclicBufferTableModel extends AbstractTableModel
    63    implements EventContainer, PropertyChangeListener {
    64
    65    private static final int DEFAULT_CAPACITY = 5000;
    66    //cyclic field used internally in this class, but not exposed via the eventcontainer
    67    private boolean cyclic = true;
    68    private int cyclicBufferSize = DEFAULT_CAPACITY;
    69    //original list of LoggingEventWrapper instances
    70    List unfilteredList;
    71    //filtered list of LoggingEventWrapper instances
    72    List filteredList;
    73    private boolean currentSortAscending;
    74    private int currentSortColumn;
    75    private final EventListenerList eventListenerList = new EventListenerList();
    76    private final List columnNames = new ArrayList(ChainsawColumns.getColumnsNames());
    77    private boolean sortEnabled = false;
    78    private boolean reachedCapacity = false;
    79    private final Logger logger = LogManager.getLogger(ChainsawCyclicBufferTableModel.class);
    80
    81    //  protected final Object syncLock = new Object();
    82    private final LoggerNameModel loggerNameModelDelegate = new LoggerNameModelSupport();
    83    private final Object mutex = new Object();
    84
    85    //because we may be using a cyclic buffer, if an ID is not provided in the property,
    86    //use and increment this row counter as the ID for each received row
    87    int uniqueRow;
    88    private final Set uniquePropertyKeys = new HashSet();
    89    private Rule ruleMediator;
    90    private final PropertyChangeSupport propertySupport = new PropertyChangeSupport(this);
    91    private RuleColorizer colorizer;
    92    private final String tableModelName;
    93
    94    public ChainsawCyclicBufferTableModel(int cyclicBufferSize, RuleColorizer colorizer, String tableModelName) {
    95      propertySupport.addPropertyChangeListener("cyclic", new ModelChanger());
    96      this.cyclicBufferSize = cyclicBufferSize;
    97      this.colorizer = colorizer;
    98      this.tableModelName = tableModelName;
    99
    100     unfilteredList = new CyclicBufferList(cyclicBufferSize);
    101     filteredList = new CyclicBufferList(cyclicBufferSize);
    102   }
    103
    104   /* (non-Javadoc)
    105    * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
    106    */
    107   public void propertyChange(PropertyChangeEvent evt) {
    108     if (evt.getSource() instanceof Rule) {
    109       if (evt.getSource() == ruleMediator && evt.getPropertyName().equals("findRule")) {
    110         if (((RuleMediator) evt.getSource()).isFindRuleRequired()) {
    111           //only refilter if find rule is required
    112           reFilter();
    113         }
    114       } else {
    115         reFilter();
    116       }
    117     }
    118   }
    119
    120   public List getMatchingEvents(Rule rule) {
    121     List list = new ArrayList();
    122     List unfilteredCopy;
    123     synchronized (mutex) {
    124         unfilteredCopy = new ArrayList(unfilteredList);
    125     }
    126     Iterator iter = unfilteredCopy.iterator();
    127
    128     while (iter.hasNext()) {
    129       LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) iter.next();
    130
    131       if (rule.evaluate(loggingEventWrapper.getLoggingEvent(), null)) {
    132         list.add(loggingEventWrapper);
    133       }
    134     }
    135
    136     return list;
    137   }
    138
    139   public void reFilter() {
    140     final int previousSize;
    141     final int newSize;
    142           synchronized (mutex) {
    143             //post refilter with newValue of TRUE (filtering is about to begin)
    144             propertySupport.firePropertyChange("refilter", Boolean.FALSE, Boolean.TRUE);
    145             previousSize = filteredList.size();
    146             filteredList.clear();
    147             if (ruleMediator == null) {
    148                 LoggingEventWrapper lastEvent = null;
    149                 for (Iterator iter = unfilteredList.iterator();iter.hasNext();) {
    150                     LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)iter.next();
    151                     loggingEventWrapper.setDisplayed(true);
    152                     updateEventMillisDelta(loggingEventWrapper, lastEvent);
    153                     filteredList.add(loggingEventWrapper);
    154                     lastEvent = loggingEventWrapper;
    155                 }
    156             } else {
    157                 Iterator iter = unfilteredList.iterator();
    158                 LoggingEventWrapper lastEvent = null;
    159                 while (iter.hasNext()) {
    160                   LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) iter.next();
    161
    162                   if (ruleMediator.evaluate(loggingEventWrapper.getLoggingEvent(), null)) {
    163                     loggingEventWrapper.setDisplayed(true);
    164                     filteredList.add(loggingEventWrapper);
    165                     updateEventMillisDelta(loggingEventWrapper, lastEvent);
    166                     lastEvent = loggingEventWrapper;
    167                   } else {
    168                     loggingEventWrapper.setDisplayed(false);
    169                   }
    170                 }
    171             }
    172             newSize = filteredList.size();
    173           }
    174       	SwingHelper.invokeOnEDT(new Runnable() {
    175       		public void run() {
    176       			if (newSize > 0) {
    177 	      			if (previousSize == newSize) {
    178 	      				//same - update all
    179 	      				fireTableRowsUpdated(0, newSize - 1);
    180 	      			} else if (previousSize > newSize) {
    181 	      				//less now..update and delete difference
    182 	      				fireTableRowsUpdated(0, newSize - 1);
    183                         //swing bug exposed by variable height rows when calling fireTableRowsDeleted..use tabledatacchanged
    184                         fireTableDataChanged();
    185 	      			} else if (previousSize < newSize) {
    186 	      				//more now..update and insert difference
    187                         if (previousSize > 0) {
    188 	      				    fireTableRowsUpdated(0, previousSize - 1);
    189                         }
    190 	      				fireTableRowsInserted(Math.max(0, previousSize), newSize - 1);
    191 	      			}
    192       			} else {
    193       				//no rows to show
    194       				fireTableDataChanged();
    195       			}
    196 	      	notifyCountListeners();
    197             //post refilter with newValue of FALSE (filtering is complete)
    198             SwingHelper.invokeOnEDT(new Runnable() {
    199                 public void run() {
    200                     propertySupport.firePropertyChange("refilter", Boolean.TRUE, Boolean.FALSE);
    201                 }
    202             });
    203       	}});
    204   }
    205
    206   public int locate(Rule rule, int startLocation, boolean searchForward) {
    207     List filteredListCopy;
    208     synchronized (mutex) {
    209       filteredListCopy = new ArrayList(filteredList);
    210     }
    211       if (searchForward) {
    212         for (int i = startLocation; i < filteredListCopy.size(); i++) {
    213           if (rule.evaluate(((LoggingEventWrapper) filteredListCopy.get(i)).getLoggingEvent(), null)) {
    214             return i;
    215           }
    216         }
    217         //if there was no match, start at row zero and go to startLocation
    218         for (int i = 0; i < startLocation; i++) {
    219           if (rule.evaluate(((LoggingEventWrapper) filteredListCopy.get(i)).getLoggingEvent(), null)) {
    220             return i;
    221           }
    222         }
    223       } else {
    224         for (int i = startLocation; i > -1; i--) {
    225           if (rule.evaluate(((LoggingEventWrapper) filteredListCopy.get(i)).getLoggingEvent(), null)) {
    226             return i;
    227           }
    228         }
    229         //if there was no match, start at row list.size() - 1 and go to startLocation
    230         for (int i = filteredListCopy.size() - 1; i > startLocation; i--) {
    231           if (rule.evaluate(((LoggingEventWrapper) filteredListCopy.get(i)).getLoggingEvent(), null)) {
    232             return i;
    233           }
    234         }
    235       }
    236
    237     return -1;
    238   }
    239
    240   /**
    241    * @param l
    242    */
    243   public void removeLoggerNameListener(LoggerNameListener l) {
    244     loggerNameModelDelegate.removeLoggerNameListener(l);
    245   }
    246
    247   /**
    248    * @param loggerName
    249    * @return
    250    */
    251   public boolean addLoggerName(String loggerName) {
    252     return loggerNameModelDelegate.addLoggerName(loggerName);
    253   }
    254
    255   public String toString() {
    256     return "ChainsawCyclicBufferTableModel{" +
    257         "name='" + tableModelName + '\'' +
    258         '}';
    259   }
    260
    261   public void reset() {
    262       loggerNameModelDelegate.reset();
    263   }
    264
    265   /**
    266    * @param l
    267    */
    268   public void addLoggerNameListener(LoggerNameListener l) {
    269     loggerNameModelDelegate.addLoggerNameListener(l);
    270   }
    271
    272   /**
    273    * @return
    274    */
    275   public Collection getLoggerNames() {
    276     return loggerNameModelDelegate.getLoggerNames();
    277   }
    278
    279   public void addEventCountListener(EventCountListener listener) {
    280     eventListenerList.add(EventCountListener.class, listener);
    281   }
    282
    283   public boolean isSortable(int col) {
    284     return true;
    285   }
    286
    287   public void notifyCountListeners() {
    288     EventCountListener[] listeners =
    289       (EventCountListener[]) eventListenerList.getListeners(
    290         EventCountListener.class);
    291
    292     int filteredListSize;
    293     int unfilteredListSize;
    294     synchronized (mutex) {
    295         filteredListSize = filteredList.size();
    296         unfilteredListSize = unfilteredList.size();
    297     }
    298     for (int i = 0; i < listeners.length; i++) {
    299       listeners[i].eventCountChanged(
    300         filteredListSize, unfilteredListSize);
    301     }
    302   }
    303
    304   /**
    305    * Changes the underlying display rule in use.  If there was
    306    * a previous Rule defined, this Model removes itself as a listener
    307    * from the old rule, and adds itself to the new rule (if the new Rule is not Null).
    308    *
    309    * In any case, the model ensures the Filtered list is made up to date in a separate thread.
    310    */
    311   public void setRuleMediator(RuleMediator ruleMediator) {
    312     if (this.ruleMediator != null) {
    313       this.ruleMediator.removePropertyChangeListener(this);
    314     }
    315
    316     this.ruleMediator = ruleMediator;
    317
    318     if (this.ruleMediator != null) {
    319       this.ruleMediator.addPropertyChangeListener(this);
    320     }
    321     reFilter();
    322   }
    323
    324   /* (non-Javadoc)
    325      * @see org.apache.log4j.chainsaw.EventContainer#sort()
    326      */
    327   public void sort() {
    328       boolean sort;
    329       final int filteredListSize;
    330       synchronized (mutex) {
    331           filteredListSize = filteredList.size();
    332           sort = (sortEnabled && filteredListSize > 0);
    333         if (sort) {
    334             //reset display (used to ensure row height is updated)
    335             LoggingEventWrapper lastEvent = null;
    336             for (Iterator iter = filteredList.iterator();iter.hasNext();) {
    337                 LoggingEventWrapper e = (LoggingEventWrapper)iter.next();
    338                 e.setDisplayed(true);
    339                 updateEventMillisDelta(e, lastEvent);
    340                 lastEvent = e;
    341             }
    342             Collections.sort(
    343               filteredList,
    344               new ColumnComparator(
    345                 getColumnName(currentSortColumn), currentSortColumn,
    346                 currentSortAscending));
    347         }
    348       }
    349       if (sort) {
    350         SwingHelper.invokeOnEDT(new Runnable() {
    351             public void run() {
    352                 fireTableRowsUpdated(0, Math.max(filteredListSize - 1, 0));
    353             }
    354         });
    355       }
    356   }
    357
    358   public boolean isSortEnabled() {
    359     return sortEnabled;
    360   }
    361
    362   public void sortColumn(int col, boolean ascending) {
    363     logger.debug("request to sort col=" + col);
    364     currentSortAscending = ascending;
    365     currentSortColumn = col;
    366     sortEnabled = true;
    367     sort();
    368   }
    369
    370   /* (non-Javadoc)
    371    * @see org.apache.log4j.chainsaw.EventContainer#clear()
    372    */
    373   public void clearModel() {
    374     reachedCapacity = false;
    375
    376     synchronized (mutex) {
    377       unfilteredList.clear();
    378       filteredList.clear();
    379       uniqueRow = 0;
    380     }
    381
    382     SwingHelper.invokeOnEDT(new Runnable() {
    383     	public void run() {
    384     	    fireTableDataChanged();
    385     	}
    386     });
    387
    388     notifyCountListeners();
    389     loggerNameModelDelegate.reset();
    390   }
    391
    392   public List getAllEvents() {
    393       synchronized (mutex) {
    394           return new ArrayList(unfilteredList);
    395       }
    396   }
    397
    398
    399   public List getFilteredEvents() {
    400
    401   	synchronized (mutex) {
    402   		return new ArrayList(filteredList);
    403   	}
    404   }
    405
    406   public int getRowIndex(LoggingEventWrapper loggingEventWrapper) {
    407     synchronized (mutex) {
    408       return filteredList.indexOf(loggingEventWrapper);
    409     }
    410   }
    411
    412     public void removePropertyFromEvents(String propName) {
    413         //first remove the event from any displayed events, so we can fire row updated event
    414         List filteredListCopy;
    415         List unfilteredListCopy;
    416         synchronized(mutex) {
    417             filteredListCopy = new ArrayList(filteredList);
    418             unfilteredListCopy = new ArrayList(unfilteredList);
    419         }
    420         for (int i=0;i<filteredListCopy.size();i++) {
    421             LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)filteredListCopy.get(i);
    422             Object result = loggingEventWrapper.removeProperty(propName);
    423             if (result != null) {
    424                 fireRowUpdated(i, false);
    425             }
    426         }
    427         //now remove the event from all events
    428         for (Iterator iter = unfilteredListCopy.iterator();iter.hasNext();) {
    429             LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper)iter.next();
    430             loggingEventWrapper.removeProperty(propName);
    431         }
    432     }
    433
    434     public int updateEventsWithFindRule(Rule findRule) {
    435         int count = 0;
    436         List unfilteredListCopy;
    437         synchronized(mutex) {
    438             unfilteredListCopy = new ArrayList(unfilteredList);
    439         }
    440         for (Iterator iter = unfilteredListCopy.iterator();iter.hasNext();) {
    441             LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) iter.next();
    442             loggingEventWrapper.evaluateSearchRule(findRule);
    443             //return the count of visible search matches
    444             if (loggingEventWrapper.isSearchMatch() && loggingEventWrapper.isDisplayed()) {
    445                 count++;
    446             }
    447         }
    448         return count;
    449     }
    450
    451     public int findColoredRow(int startLocation, boolean searchForward) {
    452         List filteredListCopy;
    453         synchronized (mutex) {
    454             filteredListCopy = new ArrayList(filteredList);
    455         }
    456         if (searchForward) {
    457           for (int i = startLocation; i < filteredListCopy.size(); i++) {
    458             LoggingEventWrapper event = (LoggingEventWrapper)filteredListCopy.get(i);
    459             if (!event.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND) ||
    460                     !event.getColorRuleForeground().equals(ChainsawConstants.COLOR_DEFAULT_FOREGROUND)) {
    461                 return i;
    462             }
    463           }
    464           //searching forward, no colorized event was found - now start at row zero and go to startLocation
    465           for (int i = 0; i < startLocation; i++) {
    466             LoggingEventWrapper event = (LoggingEventWrapper)filteredListCopy.get(i);
    467             if (!event.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND) ||
    468                     !event.getColorRuleForeground().equals(ChainsawConstants.COLOR_DEFAULT_FOREGROUND)) {
    469                 return i;
    470             }
    471           }
    472         } else {
    473           for (int i = startLocation; i > -1; i--) {
    474               LoggingEventWrapper event = (LoggingEventWrapper)filteredListCopy.get(i);
    475               if (!event.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND) ||
    476                       !event.getColorRuleForeground().equals(ChainsawConstants.COLOR_DEFAULT_FOREGROUND)) {
    477                   return i;
    478             }
    479           }
    480           //searching backward, no colorized event was found - now start at list.size() - 1 and go to startLocation
    481           for (int i = filteredListCopy.size() - 1; i > startLocation; i--) {
    482               LoggingEventWrapper event = (LoggingEventWrapper)filteredListCopy.get(i);
    483               if (!event.getColorRuleBackground().equals(ChainsawConstants.COLOR_DEFAULT_BACKGROUND) ||
    484                       !event.getColorRuleForeground().equals(ChainsawConstants.COLOR_DEFAULT_FOREGROUND)) {
    485                   return i;
    486             }
    487           }
    488         }
    489
    490       return -1;
    491     }
    492
    493   public int getSearchMatchCount() {
    494     int searchMatchCount = 0;
    495     synchronized(mutex) {
    496       for (Iterator iter = filteredList.iterator();iter.hasNext();) {
    497         LoggingEventWrapper wrapper = (LoggingEventWrapper) iter.next();
    498         if (wrapper.isSearchMatch() && wrapper.isDisplayed()) {
    499           searchMatchCount++;
    500         }
    501       }
    502     }
    503     return searchMatchCount;
    504   }
    505
    506   public int getColumnCount() {
    507     return columnNames.size();
    508   }
    509
    510   public String getColumnName(int column) {
    511       return (String) columnNames.get(column);
    512   }
    513
    514   public LoggingEventWrapper getRow(int row) {
    515     synchronized (mutex) {
    516       if (row < filteredList.size() && row > -1) {
    517         return (LoggingEventWrapper) filteredList.get(row);
    518       }
    519     }
    520
    521     return null;
    522   }
    523
    524   public int getRowCount() {
    525     synchronized (mutex) {
    526       return filteredList.size();
    527     }
    528   }
    529
    530   public Object getValueAt(int rowIndex, int columnIndex) {
    531     LoggingEvent event = null;
    532
    533     synchronized (mutex) {
    534       if (rowIndex < filteredList.size() && rowIndex > -1) {
    535         event = ((LoggingEventWrapper) filteredList.get(rowIndex)).getLoggingEvent();
    536       }
    537     }
    538
    539     if (event == null) {
    540       return null;
    541     }
    542
    543     LocationInfo info = null;
    544
    545     if (event.locationInformationExists()) {
    546       info = event.getLocationInformation();
    547     }
    548
    549     switch (columnIndex + 1) {
    550     case ChainsawColumns.INDEX_ID_COL_NAME:
    551
    552       Object id = event.getProperty(Constants.LOG4J_ID_KEY);
    553
    554       if (id != null) {
    555         return id;
    556       }
    557
    558       return new Integer(rowIndex);
    559
    560     case ChainsawColumns.INDEX_LEVEL_COL_NAME:
    561       return event.getLevel();
    562
    563     case ChainsawColumns.INDEX_LOG4J_MARKER_COL_NAME:
    564       return event.getProperty(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE);
    565
    566     case ChainsawColumns.INDEX_MILLIS_DELTA_COL_NAME:
    567       return event.getProperty(ChainsawConstants.MILLIS_DELTA_COL_NAME_LOWERCASE);
    568
    569     case ChainsawColumns.INDEX_LOGGER_COL_NAME:
    570       return event.getLoggerName();
    571
    572     case ChainsawColumns.INDEX_TIMESTAMP_COL_NAME:
    573       return new Date(event.getTimeStamp());
    574
    575     case ChainsawColumns.INDEX_MESSAGE_COL_NAME:
    576       return event.getRenderedMessage();
    577
    578     case ChainsawColumns.INDEX_NDC_COL_NAME:
    579       return event.getNDC();
    580
    581     case ChainsawColumns.INDEX_THREAD_COL_NAME:
    582       return event.getThreadName();
    583
    584     case ChainsawColumns.INDEX_THROWABLE_COL_NAME:
    585       return event.getThrowableStrRep();
    586
    587     case ChainsawColumns.INDEX_CLASS_COL_NAME:
    588       return ((info == null) || ("?".equals(info.getClassName()))) ? "" : info.getClassName();
    589
    590         case ChainsawColumns.INDEX_FILE_COL_NAME:
    591       return ((info == null) || ("?".equals(info.getFileName()))) ? "" : info.getFileName();
    592
    593         case ChainsawColumns.INDEX_LINE_COL_NAME:
    594       return ((info == null) || ("?".equals(info.getLineNumber()))) ? "" : info.getLineNumber();
    595
    596         case ChainsawColumns.INDEX_METHOD_COL_NAME:
    597       return ((info == null) || ("?".equals(info.getMethodName()))) ? "" : info.getMethodName();
    598
    599         default:
    600
    601             if (columnIndex < columnNames.size()) {
    602         //case may not match..try case sensitive and fall back to case-insensitive
    603         String result = event.getProperty(columnNames.get(columnIndex).toString());
    604         if (result == null) {
    605             String lowerColName = columnNames.get(columnIndex).toString().toLowerCase(Locale.ENGLISH);
    606             Set entrySet = event.getProperties().entrySet();
    607             for (Iterator iter = entrySet.iterator();iter.hasNext();) {
    608                 Map.Entry thisEntry = (Map.Entry) iter.next();
    609                 if (thisEntry.getKey().toString().equalsIgnoreCase(lowerColName)) {
    610                     result = thisEntry.getValue().toString();
    611                 }
    612             }
    613         }
    614         if (result != null) {
    615             return result;
    616         }
    617       }
    618     }
    619     return "";
    620   }
    621
    622   public boolean isAddRow(LoggingEventWrapper loggingEventWrapper) {
    623     Object id = loggingEventWrapper.getLoggingEvent().getProperty(Constants.LOG4J_ID_KEY);
    624
    625     //only set the property if it doesn't already exist
    626     if (id == null) {
    627       id = new Integer(++uniqueRow);
    628       loggingEventWrapper.setProperty(Constants.LOG4J_ID_KEY, id.toString());
    629     }
    630
    631     loggingEventWrapper.updateColorRuleColors(colorizer.getBackgroundColor(loggingEventWrapper.getLoggingEvent()), colorizer.getForegroundColor(loggingEventWrapper.getLoggingEvent()));
    632     Rule findRule = colorizer.getFindRule();
    633     if (findRule != null) {
    634       loggingEventWrapper.evaluateSearchRule(colorizer.getFindRule());
    635     }
    636
    637     boolean rowAdded = false;
    638
    639     /**
    640          * If we're in cyclic mode and over budget on the size, the addition of a new event will
    641          * cause the oldest event to fall off the cliff. We need to remove that events ID from the
    642          * Set so we are not keeping track of IDs for all events ever received (we'd run out of
    643          * memory...)
    644          */
    645     synchronized(mutex) {
    646         if (cyclic) {
    647             CyclicBufferList bufferList = (CyclicBufferList) unfilteredList;
    648             if (bufferList.size() == bufferList.getMaxSize()) {
    649                 reachedCapacity = true;
    650             }
    651         }
    652         int unfilteredSize = unfilteredList.size();
    653         LoggingEventWrapper lastLoggingEventWrapper = null;
    654         if (unfilteredSize > 0) {
    655             lastLoggingEventWrapper = (LoggingEventWrapper) unfilteredList.get(unfilteredSize - 1);
    656         }
    657         unfilteredList.add(loggingEventWrapper);
    658         if ((ruleMediator == null) || (ruleMediator.evaluate(loggingEventWrapper.getLoggingEvent(), null))) {
    659             loggingEventWrapper.setDisplayed(true);
    660             updateEventMillisDelta(loggingEventWrapper, lastLoggingEventWrapper);
    661             filteredList.add(loggingEventWrapper);
    662             rowAdded = true;
    663         } else {
    664             loggingEventWrapper.setDisplayed(false);
    665         }
    666     }
    667
    668     checkForNewColumn(loggingEventWrapper);
    669
    670     return rowAdded;
    671   }
    672
    673     private void updateEventMillisDelta(LoggingEventWrapper loggingEventWrapper, LoggingEventWrapper lastLoggingEventWrapper) {
    674       if (lastLoggingEventWrapper != null) {
    675         loggingEventWrapper.setPreviousDisplayedEventTimestamp(lastLoggingEventWrapper.getLoggingEvent().getTimeStamp());
    676       } else {
    677         //delta to same event = 0
    678         loggingEventWrapper.setPreviousDisplayedEventTimestamp(loggingEventWrapper.getLoggingEvent().getTimeStamp());
    679       }
    680     }
    681
    682    private void checkForNewColumn(LoggingEventWrapper loggingEventWrapper)
    683    {
    684       /**
    685        * Is this a new Property key we haven't seen before?  Remember that now MDC has been merged
    686        * into the Properties collection
    687        */
    688       boolean newColumn = uniquePropertyKeys.addAll(loggingEventWrapper.getPropertyKeySet());
    689
    690       if (newColumn) {
    691         /**
    692          * If so, we should add them as columns and notify listeners.
    693          */
    694         for (Iterator iter = loggingEventWrapper.getPropertyKeySet().iterator(); iter.hasNext();) {
    695           String key = iter.next().toString().toUpperCase();
    696
    697           //add all keys except the 'log4jid' key (columnNames is all-caps)
    698           if (!columnNames.contains(key) && !(Constants.LOG4J_ID_KEY.equalsIgnoreCase(key))) {
    699             columnNames.add(key);
    700             logger.debug("Adding col '" + key + "', columnNames=" + columnNames);
    701             fireNewKeyColumnAdded(
    702               new NewKeyEvent(
    703                 this, columnNames.indexOf(key), key, loggingEventWrapper.getLoggingEvent().getProperty(key)));
    704           }
    705         }
    706       }
    707    }
    708
    709   public void fireTableEvent(final int begin, final int end, final int count) {
    710   	SwingHelper.invokeOnEDT(new Runnable() {
    711   		public void run() {
    712     if (cyclic) {
    713       if (!reachedCapacity) {
    714         //if we didn't loop and it's the 1st time, insert
    715         if ((begin + count) < cyclicBufferSize) {
    716           fireTableRowsInserted(begin, end);
    717         } else {
    718           //we did loop - insert and then update rows
    719           //rows are zero-indexed, subtract 1 from cyclicbuffersize for the event notification
    720           fireTableRowsInserted(begin, cyclicBufferSize - 1);
    721           fireTableRowsUpdated(0, cyclicBufferSize - 1);
    722           reachedCapacity = true;
    723         }
    724       } else {
    725         fireTableRowsUpdated(0, cyclicBufferSize - 1);
    726       }
    727     } else {
    728       fireTableRowsInserted(begin, end);
    729     }
    730   }});
    731   }
    732
    733     public void fireRowUpdated(int row, boolean checkForNewColumns) {
    734         LoggingEventWrapper loggingEventWrapper = getRow(row);
    735         if (loggingEventWrapper != null)
    736         {
    737             loggingEventWrapper.updateColorRuleColors(colorizer.getBackgroundColor(loggingEventWrapper.getLoggingEvent()), colorizer.getForegroundColor(loggingEventWrapper.getLoggingEvent()));
    738             Rule findRule = colorizer.getFindRule();
    739             if (findRule != null) {
    740               loggingEventWrapper.evaluateSearchRule(colorizer.getFindRule());
    741             }
    742
    743             fireTableRowsUpdated(row, row);
    744             if (checkForNewColumns) {
    745                 //row may have had a column added..if so, make sure a column is added
    746                 checkForNewColumn(loggingEventWrapper);
    747             }
    748         }
    749     }
    750
    751     /**
    752   * @param e
    753   */
    754   private void fireNewKeyColumnAdded(NewKeyEvent e) {
    755     NewKeyListener[] listeners =
    756       (NewKeyListener[]) eventListenerList.getListeners(NewKeyListener.class);
    757
    758     for (int i = 0; i < listeners.length; i++) {
    759       NewKeyListener listener = listeners[i];
    760       listener.newKeyAdded(e);
    761     }
    762   }
    763
    764   /**
    765    * @return
    766    */
    767   public int getMaxSize() {
    768     return cyclicBufferSize;
    769   }
    770
    771   /* (non-Javadoc)
    772    * @see org.apache.log4j.chainsaw.EventContainer#addNewKeyListener(org.apache.log4j.chainsaw.NewKeyListener)
    773    */
    774   public void addNewKeyListener(NewKeyListener l) {
    775     eventListenerList.add(NewKeyListener.class, l);
    776   }
    777
    778   /* (non-Javadoc)
    779    * @see org.apache.log4j.chainsaw.EventContainer#removeNewKeyListener(org.apache.log4j.chainsaw.NewKeyListener)
    780    */
    781   public void removeNewKeyListener(NewKeyListener l) {
    782     eventListenerList.remove(NewKeyListener.class, l);
    783   }
    784
    785   /* (non-Javadoc)
    786    * @see javax.swing.table.TableModel#isCellEditable(int, int)
    787    */
    788   public boolean isCellEditable(int rowIndex, int columnIndex) {
    789     if (getColumnName(columnIndex).equalsIgnoreCase(ChainsawConstants.LOG4J_MARKER_COL_NAME_LOWERCASE)) {
    790       return true;
    791     }
    792
    793     if (columnIndex >= columnNames.size()) {
    794         return false;
    795     }
    796
    797     return super.isCellEditable(rowIndex, columnIndex);
    798   }
    799
    800   /* (non-Javadoc)
    801    * @see org.apache.log4j.chainsaw.EventContainer#setCyclic(boolean)
    802    */
    803   public void setCyclic(final boolean cyclic) {
    804     if (this.cyclic == cyclic) {
    805       return;
    806     }
    807
    808     final boolean old = this.cyclic;
    809     this.cyclic = cyclic;
    810     propertySupport.firePropertyChange("cyclic", old, cyclic);
    811   }
    812
    813   /* (non-Javadoc)
    814    * @see org.apache.log4j.chainsaw.EventContainer#addPropertyChangeListener(java.beans.PropertyChangeListener)
    815    */
    816   public void addPropertyChangeListener(PropertyChangeListener l) {
    817     propertySupport.addPropertyChangeListener(l);
    818   }
    819
    820   /* (non-Javadoc)
    821    * @see org.apache.log4j.chainsaw.EventContainer#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
    822    */
    823   public void addPropertyChangeListener(
    824     String propertyName, PropertyChangeListener l) {
    825     propertySupport.addPropertyChangeListener(propertyName, l);
    826   }
    827
    828   /* (non-Javadoc)
    829    * @see org.apache.log4j.chainsaw.EventContainer#size()
    830    */
    831   public int size() {
    832     synchronized(mutex) {
    833       return unfilteredList.size();
    834     }
    835   }
    836
    837   private class ModelChanger implements PropertyChangeListener {
    838     /* (non-Javadoc)
    839      * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
    840      */
    841     public void propertyChange(PropertyChangeEvent arg0) {
    842       Thread thread =
    843         new Thread(
    844           new Runnable() {
    845             public void run() {
    846               ProgressMonitor monitor = null;
    847
    848               int index = 0;
    849
    850               try {
    851                 synchronized (mutex) {
    852                   monitor =
    853                     new ProgressMonitor(
    854                       null, "Switching models...",
    855                       "Transferring between data structures, please wait...", 0,
    856                       unfilteredList.size() + 1);
    857                   monitor.setMillisToDecideToPopup(250);
    858                   monitor.setMillisToPopup(100);
    859                   logger.debug(
    860                     "Changing Model, isCyclic is now " + cyclic);
    861
    862                   List newUnfilteredList = null;
    863                   List newFilteredList = null;
    864
    865                   if (cyclic) {
    866                     newUnfilteredList = new CyclicBufferList(cyclicBufferSize);
    867                     newFilteredList = new CyclicBufferList(cyclicBufferSize);
    868                   } else {
    869                     newUnfilteredList = new ArrayList(cyclicBufferSize);
    870                     newFilteredList = new ArrayList(cyclicBufferSize);
    871                   }
    872
    873                   int increment = 0;
    874
    875                   for (Iterator iter = unfilteredList.iterator();
    876                       iter.hasNext();) {
    877                     LoggingEventWrapper loggingEventWrapper = (LoggingEventWrapper) iter.next();
    878                     newUnfilteredList.add(loggingEventWrapper);
    879                     monitor.setProgress(index++);
    880                   }
    881
    882                   unfilteredList = newUnfilteredList;
    883                   filteredList = newFilteredList;
    884                 }
    885
    886                 monitor.setNote("Refiltering...");
    887                 reFilter();
    888
    889                 monitor.setProgress(index++);
    890               } finally {
    891                 monitor.close();
    892               }
    893
    894               logger.debug("Model Change completed");
    895             }
    896           });
    897       thread.setPriority(Thread.MIN_PRIORITY + 1);
    898       thread.start();
    899     }
    900   }
    901 }