Source code: org/geotools/gui/swing/LoggingTableModel.java
1 /*
2 * -*- mode: java; c-basic-indent: 4; indent-tabs-mode: nil -*-
3 * :indentSize=4:noTabs=true:tabSize=4:indentOnTab=true:indentOnEnter=true:mode=java:
4 * ex: set tabstop=4 expandtab:
5 *
6 * MrPostman - webmail <-> email gateway
7 * Copyright (C) 2002-2003 MrPostman Development Group
8 * Projectpage: http://mrbook.org/mrpostman/
9 *
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 * In particular, this implies that users are responsible for
21 * using MrPostman after reading the terms and conditions given
22 * by their web-mail provider.
23 *
24 * You should have received a copy of the GNU General Public License
25 * Named LICENSE in the base directory of this distribution,
26 * if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 */
29
30 /*
31 * Geotools - OpenSource mapping toolkit
32 * (C) 2002, Centre for Computational Geography
33 * (C) 2002, Institut de Recherche pour le Développement
34 *
35 * This library is free software; you can redistribute it and/or
36 * modify it under the terms of the GNU Lesser General Public
37 * License as published by the Free Software Foundation; either
38 * version 2.1 of the License, or (at your option) any later version.
39 *
40 * This library is distributed in the hope that it will be useful,
41 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43 * Lesser General Public License for more details.
44 *
45 * You should have received a copy of the GNU Lesser General Public
46 * License along with this library; if not, write to the Free Software
47 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
48 *
49 *
50 * Contacts:
51 * UNITED KINGDOM: James Macgill
52 * mailto:j.macgill@geog.leeds.ac.uk
53 *
54 * FRANCE: Surveillance de l'Environnement Assistée par Satellite
55 * Institut de Recherche pour le Développement / US-Espace
56 * mailto:seasnet@teledetection.fr
57 *
58 * CANADA: Observatoire du Saint-Laurent
59 * Institut Maurice-Lamontagne
60 * mailto:osl@osl.gc.ca
61 */
62 package org.geotools.gui.swing;
63
64
65 // Resources
66 import org.geotools.resources.XArray;
67 import org.geotools.resources.gui.ResourceKeys;
68 import org.geotools.resources.gui.Resources;
69
70 import java.awt.EventQueue;
71
72 import java.text.DateFormat;
73
74
75 // Formatting
76 import java.util.Date;
77 import java.util.LinkedHashMap;
78 // Collections
79 import java.util.Map;
80 import java.util.logging.Handler;
81 // Logging
82 import java.util.logging.Level;
83 import java.util.logging.LogRecord;
84 import java.util.logging.SimpleFormatter;
85
86 import javax.swing.event.EventListenerList;
87 import javax.swing.event.TableModelEvent;
88 import javax.swing.event.TableModelListener;
89 // Table model
90 import javax.swing.table.TableModel;
91
92
93 /**
94 * A logging {@link Handler} storing {@link LogRecords} as a {@link TableModel}.
95 * This model is used by {@link LoggingPanel} for displaying logging messages in
96 * a {@link javax.swing.JTable}.
97 *
98 * @version $Id: LoggingTableModel.java,v 1.5 2003/02/09 23:38:11 lbruand Exp $
99 * @author Martin Desruisseaux
100 */
101 final class LoggingTableModel extends Handler implements TableModel {
102 public static final String CVSID = "$Id: LoggingTableModel.java,v 1.5 2003/02/09 23:38:11 lbruand Exp $";
103
104 /**
105 * Resource keys for column names.
106 */
107 private static final int[] COLUMN_NAMES = new int[] {
108 ResourceKeys.LOGGER, ResourceKeys.CLASS, ResourceKeys.METHOD, ResourceKeys.TIME_OF_DAY, ResourceKeys.LEVEL,
109 ResourceKeys.MESSAGE
110 };
111
112 /**
113 * The last {@link LogRecord}s stored. This array will grows as needed up to
114 * {@link #capacity}. Once the maximal capacity is reached, early records
115 * are discarted.
116 */
117 private LogRecord[] records = new LogRecord[16];
118
119 /**
120 * The maximum amount of records that can be stored in this logging panel.
121 * If more than {@link #capacity} messages are logged, early messages will
122 * be discarted.
123 */
124 private int capacity = 500;
125
126 /**
127 * The total number of logging messages published by this panel. This number may be
128 * greater than the amount of {@link LogRecord} actually memorized, since early records
129 * may have been discarted. The slot in <code>records</code> where to write the next
130 * message can be computed by <code>recordCount % capacity</code>.
131 */
132 private int recordCount;
133
134 /**
135 * String representations of latest required records. Keys are {@link LogRecord} objects
136 * and values are <code>String[]</code>. This is a cache for faster rendering.
137 */
138 private final Map cache = new LinkedHashMap() {
139 protected boolean removeEldestEntry(final Map.Entry eldest) {
140 return size() >= Math.min(capacity, 80);
141 }
142 };
143
144 /**
145 * The list of registered listeners.
146 */
147 private final EventListenerList listenerList = new EventListenerList();
148
149 /**
150 * The format to use for formatting time.
151 */
152 private final DateFormat dateFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
153
154 /**
155 * Construct the handler.
156 */
157 public LoggingTableModel() {
158 setLevel(Level.CONFIG);
159 setFormatter(new SimpleFormatter());
160 }
161
162 /**
163 * Returns the capacity. This is the maximum number of {@link LogRecord}s this handler
164 * can memorize. If more messages are logged, then the oldiest messages will be discarted.
165 */
166 public int getCapacity() {
167 return capacity;
168 }
169
170 /**
171 * Set the capacity. This is the maximum number of {@link LogRecord}s this handler can
172 * memorize. If more messages are logged, then the oldiest messages will be discarted.
173 */
174 public synchronized void setCapacity(final int capacity) {
175 if (recordCount != 0) {
176 throw new IllegalStateException("Not yet implemented.");
177 }
178 this.capacity = capacity;
179 }
180
181 /**
182 * Publish a {@link LogRecord}. If the maximal capacity has been reached,
183 * the oldiest record will be discarted.
184 */
185 public synchronized void publish(final LogRecord record) {
186 if (!isLoggable(record)) {
187 return;
188 }
189 final int nextSlot = recordCount % capacity;
190
191 if (nextSlot >= records.length) {
192 records = (LogRecord[]) XArray.resize(records, Math.min(records.length * 2, capacity));
193 }
194 records[nextSlot] = record;
195 final TableModelEvent event;
196
197 if (++recordCount <= capacity) {
198 event = new TableModelEvent(this, nextSlot, nextSlot, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT);
199 } else {
200 event = new TableModelEvent(this, 0, capacity - 1, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE);
201 }
202
203 //
204 // Notify all listeners that a record has been added.
205 //
206 EventQueue.invokeLater(new Runnable() {
207 public void run() {
208 fireTableChanged(event);
209 }
210 });
211 }
212
213 /**
214 * Returns the log record for the specified row.
215 *
216 * @param row The row in the table. This is the visible row,
217 * not the record number from the first record.
218 */
219 public synchronized LogRecord getLogRecord(int row) {
220 /* assert row < getRowCount(); */
221 if (recordCount > capacity) {
222 row += (recordCount % capacity);
223 row %= capacity;
224 }
225 return records[row];
226 }
227
228 /**
229 * Returns the number of columns in the model.
230 */
231 public int getColumnCount() {
232 return COLUMN_NAMES.length;
233 }
234
235 /**
236 * Returns the number of rows in the model.
237 */
238 public synchronized int getRowCount() {
239 return Math.min(recordCount, capacity);
240 }
241
242 /**
243 * Returns the most specific superclass for all the cell values in the column.
244 */
245 public Class getColumnClass(final int columnIndex) {
246 return String.class;
247 }
248
249 /**
250 * Returns the name of the column at <code>columnIndex</code>.
251 */
252 public String getColumnName(final int columnIndex) {
253 return Resources.format(COLUMN_NAMES[columnIndex]);
254 }
255
256 /**
257 * Returns the value for the cell at <code>columnIndex</code> and <code>rowIndex</code>.
258 */
259 public synchronized Object getValueAt(final int rowIndex, final int columnIndex) {
260 final LogRecord record = getLogRecord(rowIndex);
261 String[] row = (String[]) cache.get(record);
262
263 if (row == null) {
264 row = new String[getColumnCount()];
265 row[0] = record.getLoggerName();
266 row[1] = getShortClassName(record.getSourceClassName());
267 row[2] = record.getSourceMethodName();
268 row[3] = dateFormat.format(new Date(record.getMillis()));
269 row[4] = record.getLevel().getLocalizedName();
270 row[5] = getFormatter().formatMessage(record);
271 cache.put(record, row);
272 /*assert cache.size() <= capacity;*/
273 }
274 return row[columnIndex];
275 }
276
277 /**
278 * Returns the class name in a shorter form (without package).
279 */
280 private static String getShortClassName(String name) {
281 final int dot = name.lastIndexOf('.');
282
283 if (dot >= 0) {
284 name = name.substring(dot + 1);
285 }
286 return name.replace('$', '.');
287 }
288
289 /**
290 * Do nothing since cells are not editable.
291 */
292 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
293 }
294
295 /**
296 * Returns <code>false</code> since cells are not editable.
297 */
298 public boolean isCellEditable(int rowIndex, int columnIndex) {
299 return false;
300 }
301
302 /**
303 * Adds a listener that is notified each time a change to the data model occurs.
304 */
305 public void addTableModelListener(final TableModelListener listener) {
306 listenerList.add(TableModelListener.class, listener);
307 }
308
309 /**
310 * Removes a listener from the list that is notified each time a change occurs.
311 */
312 public void removeTableModelListener(final TableModelListener listener) {
313 listenerList.remove(TableModelListener.class, listener);
314 }
315
316 /**
317 * Forwards the given notification event to all {@link TableModelListeners}.
318 */
319 private void fireTableChanged(final TableModelEvent event) {
320 final Object[] listeners = listenerList.getListenerList();
321
322 for (int i = listeners.length - 2; i >= 0; i -= 2) {
323 if (listeners[i] == TableModelListener.class) {
324 ((TableModelListener) listeners[i + 1]).tableChanged(event);
325 }
326 }
327 }
328
329 /**
330 * Flush any buffered output.
331 */
332 public void flush() {
333 }
334
335 /**
336 * Close the <code>Handler</code> and free all associated resources.
337 */
338 public void close() {
339 }
340 }