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.lf5.viewer;
018
019import org.apache.log4j.lf5.LogRecord;
020import org.apache.log4j.lf5.LogRecordFilter;
021import org.apache.log4j.lf5.PassingLogRecordFilter;
022
023import javax.swing.table.AbstractTableModel;
024import java.util.ArrayList;
025import java.util.Date;
026import java.util.Iterator;
027import java.util.List;
028
029
030/**
031 * A TableModel for LogRecords which includes filtering support.
032 *
033 * @author Richard Wan
034 * @author Brent Sprecher
035 */
036
037// Contributed by ThoughtWorks Inc.
038
039public class FilteredLogTableModel
040    extends AbstractTableModel {
041  //--------------------------------------------------------------------------
042  //   Constants:
043  //--------------------------------------------------------------------------
044
045  //--------------------------------------------------------------------------
046  //   Protected Variables:
047  //--------------------------------------------------------------------------
048
049  protected LogRecordFilter _filter = new PassingLogRecordFilter();
050  protected List _allRecords = new ArrayList();
051  protected List _filteredRecords;
052  protected int _maxNumberOfLogRecords = 5000;
053  protected String[] _colNames = {"Date",
054                                  "Thread",
055                                  "Message #",
056                                  "Level",
057                                  "NDC",
058                                  "Category",
059                                  "Message",
060                                  "Location",
061                                  "Thrown"};
062
063  //--------------------------------------------------------------------------
064  //   Private Variables:
065  //--------------------------------------------------------------------------
066
067  //--------------------------------------------------------------------------
068  //   Constructors:
069  //--------------------------------------------------------------------------
070
071  public FilteredLogTableModel() {
072    super();
073  }
074
075  //--------------------------------------------------------------------------
076  //   Public Methods:
077  //--------------------------------------------------------------------------
078
079  public void setLogRecordFilter(LogRecordFilter filter) {
080    _filter = filter;
081  }
082
083  public LogRecordFilter getLogRecordFilter() {
084    return _filter;
085  }
086
087  public String getColumnName(int i) {
088    return _colNames[i];
089  }
090
091  public int getColumnCount() {
092    return _colNames.length;
093  }
094
095  public int getRowCount() {
096    return getFilteredRecords().size();
097  }
098
099  public int getTotalRowCount() {
100    return _allRecords.size();
101  }
102
103  public Object getValueAt(int row, int col) {
104    LogRecord record = getFilteredRecord(row);
105    return getColumn(col, record);
106  }
107
108  public void setMaxNumberOfLogRecords(int maxNumRecords) {
109    if (maxNumRecords > 0) {
110      _maxNumberOfLogRecords = maxNumRecords;
111    }
112
113  }
114
115  public synchronized boolean addLogRecord(LogRecord record) {
116
117    _allRecords.add(record);
118
119    if (_filter.passes(record) == false) {
120      return false;
121    }
122    getFilteredRecords().add(record);
123    fireTableRowsInserted(getRowCount(), getRowCount());
124    trimRecords();
125    return true;
126  }
127
128  /**
129   * Forces the LogTableModel to requery its filters to determine
130   * which records to display.
131   */
132  public synchronized void refresh() {
133    _filteredRecords = createFilteredRecordsList();
134    fireTableDataChanged();
135  }
136
137  public synchronized void fastRefresh() {
138    _filteredRecords.remove(0);
139    fireTableRowsDeleted(0, 0);
140  }
141
142
143  /**
144   * Clears all records from the LogTableModel
145   */
146  public synchronized void clear() {
147    _allRecords.clear();
148    _filteredRecords.clear();
149    fireTableDataChanged();
150  }
151
152  //--------------------------------------------------------------------------
153  //   Protected Methods:
154  //--------------------------------------------------------------------------
155
156  protected List getFilteredRecords() {
157    if (_filteredRecords == null) {
158      refresh();
159    }
160    return _filteredRecords;
161  }
162
163  protected List createFilteredRecordsList() {
164    List result = new ArrayList();
165    Iterator records = _allRecords.iterator();
166    LogRecord current;
167    while (records.hasNext()) {
168      current = (LogRecord) records.next();
169      if (_filter.passes(current)) {
170        result.add(current);
171      }
172    }
173    return result;
174  }
175
176  protected LogRecord getFilteredRecord(int row) {
177    List records = getFilteredRecords();
178    int size = records.size();
179    if (row < size) {
180      return (LogRecord) records.get(row);
181    }
182    // a minor problem has happened. JTable has asked for
183    // a row outside the bounds, because the size of
184    // _filteredRecords has changed while it was looping.
185    // return the last row.
186    return (LogRecord) records.get(size - 1);
187
188  }
189
190  protected Object getColumn(int col, LogRecord lr) {
191    if (lr == null) {
192      return "NULL Column";
193    }
194    String date = new Date(lr.getMillis()).toString();
195    switch (col) {
196      case 0:
197        return date + " (" + lr.getMillis() + ")";
198      case 1:
199        return lr.getThreadDescription();
200      case 2:
201        return new Long(lr.getSequenceNumber());
202      case 3:
203        return lr.getLevel();
204      case 4:
205        return lr.getNDC();
206      case 5:
207        return lr.getCategory();
208      case 6:
209        return lr.getMessage();
210      case 7:
211        return lr.getLocation();
212      case 8:
213        return lr.getThrownStackTrace();
214      default:
215        String message = "The column number " + col + "must be between 0 and 8";
216        throw new IllegalArgumentException(message);
217    }
218  }
219
220  // We don't want the amount of rows to grow without bound,
221  // leading to a out-of-memory-exception.  Especially not good
222  // in a production environment :)
223
224  // This method & clearLogRecords() are synchronized so we don't
225  // delete rows that don't exist.
226  protected void trimRecords() {
227    if (needsTrimming()) {
228      trimOldestRecords();
229    }
230  }
231
232  protected boolean needsTrimming() {
233    return (_allRecords.size() > _maxNumberOfLogRecords);
234  }
235
236  protected void trimOldestRecords() {
237    synchronized (_allRecords) {
238      int trim = numberOfRecordsToTrim();
239      if (trim > 1) {
240        List oldRecords =
241            _allRecords.subList(0, trim);
242        oldRecords.clear();
243        refresh();
244      } else {
245        _allRecords.remove(0);
246        fastRefresh();
247      }
248    }
249
250  }
251
252  //--------------------------------------------------------------------------
253  //   Private Methods:
254  //--------------------------------------------------------------------------
255  private int numberOfRecordsToTrim() {
256    return _allRecords.size() - _maxNumberOfLogRecords;
257  }
258
259  //--------------------------------------------------------------------------
260  //   Nested Top-Level Classes or Interfaces
261  //--------------------------------------------------------------------------
262}
263