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