/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/util/Log.java
Java | 496 lines | 315 code | 44 blank | 137 comment | 43 complexity | 85a081bfa92e5a48117e2706fbe78ca1 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
1/*
2 * Log.java - A class for logging events
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1999, 2003 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23package org.gjt.sp.util;
24
25import java.io.*;
26import java.util.*;
27import javax.swing.*;
28import javax.swing.event.*;
29
30/**
31 * This class provides methods for logging events. In terms of functionality,
32 * it is somewhere in between <code>System.out.println()</code> and
33 * full-blown logging packages such as log4j.<p>
34 *
35 * All events are logged to an in-memory buffer and optionally a stream,
36 * and those with a high urgency (warnings and errors) are also printed
37 * to standard output.<p>
38 *
39 * Logging of exception tracebacks is supported.<p>
40 *
41 * This class can also optionally redirect standard output and error to the log.
42 *
43 * @author Slava Pestov
44 * @version $Id: Log.java 5004 2004-03-28 00:07:27Z spestov $
45 */
46public class Log
47{
48 //{{{ Constants
49 /**
50 * The maximum number of log messages that will be kept in memory.
51 * @since jEdit 2.6pre5
52 */
53 public static final int MAXLINES = 500;
54
55 /**
56 * Debugging message urgency. Should be used for messages only
57 * useful when debugging a problem.
58 * @since jEdit 2.2pre2
59 */
60 public static final int DEBUG = 1;
61
62 /**
63 * Message urgency. Should be used for messages which give more
64 * detail than notices.
65 * @since jEdit 2.2pre2
66 */
67 public static final int MESSAGE = 3;
68
69 /**
70 * Notice urgency. Should be used for messages that directly
71 * affect the user.
72 * @since jEdit 2.2pre2
73 */
74 public static final int NOTICE = 5;
75
76 /**
77 * Warning urgency. Should be used for messages that warrant
78 * attention.
79 * @since jEdit 2.2pre2
80 */
81 public static final int WARNING = 7;
82
83 /**
84 * Error urgency. Should be used for messages that signal a
85 * failure.
86 * @since jEdit 2.2pre2
87 */
88 public static final int ERROR = 9;
89 //}}}
90
91 //{{{ init() method
92 /**
93 * Initializes the log.
94 * @param stdio If true, standard output and error will be
95 * sent to the log
96 * @param level Messages with this log level or higher will
97 * be printed to the system console
98 * @since jEdit 3.2pre4
99 */
100 public static void init(boolean stdio, int level)
101 {
102 if(stdio)
103 {
104 if(System.out == realOut && System.err == realErr)
105 {
106 System.setOut(createPrintStream(NOTICE,null));
107 System.setErr(createPrintStream(ERROR,null));
108 }
109 }
110
111 Log.level = level;
112
113 // Log some stuff
114 log(MESSAGE,Log.class,"When reporting bugs, please"
115 + " include the following information:");
116 String[] props = {
117 "java.version", "java.vm.version", "java.runtime.version",
118 "java.vendor", "java.compiler", "os.name", "os.version",
119 "os.arch", "user.home", "java.home",
120 "java.class.path",
121 };
122 for(int i = 0; i < props.length; i++)
123 {
124 log(MESSAGE,Log.class,
125 props[i] + "=" + System.getProperty(props[i]));
126 }
127 } //}}}
128
129 //{{{ setLogWriter() method
130 /**
131 * Writes all currently logged messages to this stream if there was no
132 * stream set previously, and sets the stream to write future log
133 * messages to.
134 * @param stream The writer
135 * @since jEdit 3.2pre4
136 */
137 public static void setLogWriter(Writer stream)
138 {
139 if(Log.stream == null && stream != null)
140 {
141 try
142 {
143 if(wrap)
144 {
145 for(int i = logLineCount; i < log.length; i++)
146 {
147 stream.write(log[i]);
148 stream.write(lineSep);
149 }
150 }
151 for(int i = 0; i < logLineCount; i++)
152 {
153 stream.write(log[i]);
154 stream.write(lineSep);
155 }
156
157 stream.flush();
158 }
159 catch(Exception e)
160 {
161 // do nothing, who cares
162 }
163 }
164
165 Log.stream = stream;
166 } //}}}
167
168 //{{{ flushStream() method
169 /**
170 * Flushes the log stream.
171 * @since jEdit 2.6pre5
172 */
173 public static void flushStream()
174 {
175 if(stream != null)
176 {
177 try
178 {
179 stream.flush();
180 }
181 catch(IOException io)
182 {
183 io.printStackTrace(realErr);
184 }
185 }
186 } //}}}
187
188 //{{{ closeStream() method
189 /**
190 * Closes the log stream. Should be done before your program exits.
191 * @since jEdit 2.6pre5
192 */
193 public static void closeStream()
194 {
195 if(stream != null)
196 {
197 try
198 {
199 stream.close();
200 stream = null;
201 }
202 catch(IOException io)
203 {
204 io.printStackTrace(realErr);
205 }
206 }
207 } //}}}
208
209 //{{{ getLogListModel() method
210 /**
211 * Returns the list model for viewing the log contents.
212 * @since jEdit 4.2pre1
213 */
214 public static ListModel getLogListModel()
215 {
216 return listModel;
217 } //}}}
218
219 //{{{ log() method
220 /**
221 * Logs a message. This method is thread-safe.<p>
222 *
223 * The following code sends a typical debugging message to the activity
224 * log:
225 * <pre>Log.log(Log.DEBUG,this,"counter = " + counter);</pre>
226 * The corresponding activity log entry might read as follows:
227 * <pre>[debug] JavaParser: counter = 15</pre>
228 *
229 * @param urgency The urgency; can be one of
230 * <code>Log.DEBUG</code>, <code>Log.MESSAGE</code>,
231 * <code>Log.NOTICE</code>, <code>Log.WARNING</code>, or
232 * <code>Log.ERROR</code>.
233 * @param source The source of the message, either an object or a
234 * class instance. When writing log messages from macros, set
235 * this parameter to <code>BeanShell.class</code> to make macro
236 * errors easier to spot in the activity log.
237 * @param message The message. This can either be a string or
238 * an exception
239 *
240 * @since jEdit 2.2pre2
241 */
242 public static void log(int urgency, Object source, Object message)
243 {
244 String _source;
245 if(source == null)
246 {
247 _source = Thread.currentThread().getName();
248 if(_source == null)
249 {
250 _source = Thread.currentThread().getClass().getName();
251 }
252 }
253 else if(source instanceof Class)
254 _source = ((Class)source).getName();
255 else
256 _source = source.getClass().getName();
257 int index = _source.lastIndexOf('.');
258 if(index != -1)
259 _source = _source.substring(index+1);
260
261 if(message instanceof Throwable)
262 {
263 _logException(urgency,source,(Throwable)message);
264 }
265 else
266 {
267 String _message = String.valueOf(message);
268 // If multiple threads log stuff, we don't want
269 // the output to get mixed up
270 synchronized(LOCK)
271 {
272 StringTokenizer st = new StringTokenizer(
273 _message,"\r\n");
274 int lineCount = 0;
275 boolean oldWrap = wrap;
276 while(st.hasMoreTokens())
277 {
278 lineCount++;
279 _log(urgency,_source,st.nextToken()
280 .replace('\t',' '));
281 }
282 listModel.update(lineCount,oldWrap);
283 }
284 }
285 } //}}}
286
287 //{{{ Private members
288
289 //{{{ Instance variables
290 private static Object LOCK = new Object();
291 private static String[] log;
292 private static int logLineCount;
293 private static boolean wrap;
294 private static int level = WARNING;
295 private static Writer stream;
296 private static String lineSep;
297 private static PrintStream realOut;
298 private static PrintStream realErr;
299 private static LogListModel listModel;
300 //}}}
301
302 //{{{ Class initializer
303 static
304 {
305 level = WARNING;
306
307 realOut = System.out;
308 realErr = System.err;
309
310 log = new String[MAXLINES];
311 lineSep = System.getProperty("line.separator");
312 listModel = new LogListModel();
313 } //}}}
314
315 //{{{ createPrintStream() method
316 private static PrintStream createPrintStream(final int urgency,
317 final Object source)
318 {
319 return new PrintStream(new OutputStream() {
320 public void write(int b)
321 {
322 byte[] barray = { (byte)b };
323 write(barray,0,1);
324 }
325
326 public void write(byte[] b, int off, int len)
327 {
328 String str = new String(b,off,len);
329 log(urgency,source,str);
330 }
331 });
332 } //}}}
333
334 //{{{ _logException() method
335 private static void _logException(final int urgency,
336 final Object source,
337 final Throwable message)
338 {
339 PrintStream out = createPrintStream(urgency,source);
340
341 synchronized(LOCK)
342 {
343 message.printStackTrace(out);
344 }
345 } //}}}
346
347 //{{{ _log() method
348 private static void _log(int urgency, String source, String message)
349 {
350 String fullMessage = "[" + urgencyToString(urgency) + "] " + source
351 + ": " + message;
352
353 try
354 {
355 log[logLineCount] = fullMessage;
356 if(++logLineCount >= log.length)
357 {
358 wrap = true;
359 logLineCount = 0;
360 }
361
362 if(stream != null)
363 {
364 stream.write(fullMessage);
365 stream.write(lineSep);
366 }
367 }
368 catch(Exception e)
369 {
370 e.printStackTrace(realErr);
371 }
372
373 if(urgency >= level)
374 {
375 if(urgency == ERROR)
376 realErr.println(fullMessage);
377 else
378 realOut.println(fullMessage);
379 }
380 } //}}}
381
382 //{{{ urgencyToString() method
383 private static String urgencyToString(int urgency)
384 {
385 switch(urgency)
386 {
387 case DEBUG:
388 return "debug";
389 case MESSAGE:
390 return "message";
391 case NOTICE:
392 return "notice";
393 case WARNING:
394 return "warning";
395 case ERROR:
396 return "error";
397 }
398
399 throw new IllegalArgumentException("Invalid urgency: " + urgency);
400 } //}}}
401
402 //}}}
403
404 //{{{ LogListModel class
405 static class LogListModel implements ListModel
406 {
407 Vector listeners = new Vector();
408
409 private void fireIntervalAdded(int index1, int index2)
410 {
411 for(int i = 0; i < listeners.size(); i++)
412 {
413 ListDataListener listener = (ListDataListener)
414 listeners.elementAt(i);
415 listener.intervalAdded(new ListDataEvent(this,
416 ListDataEvent.INTERVAL_ADDED,
417 index1,index2));
418 }
419 }
420
421 private void fireIntervalRemoved(int index1, int index2)
422 {
423 for(int i = 0; i < listeners.size(); i++)
424 {
425 ListDataListener listener = (ListDataListener)
426 listeners.elementAt(i);
427 listener.intervalRemoved(new ListDataEvent(this,
428 ListDataEvent.INTERVAL_REMOVED,
429 index1,index2));
430 }
431 }
432
433 public void addListDataListener(ListDataListener listener)
434 {
435 listeners.addElement(listener);
436 }
437
438 public void removeListDataListener(ListDataListener listener)
439 {
440 listeners.removeElement(listener);
441 }
442
443 public Object getElementAt(int index)
444 {
445 if(wrap)
446 {
447 if(index < MAXLINES - logLineCount)
448 return log[index + logLineCount];
449 else
450 return log[index - MAXLINES + logLineCount];
451 }
452 else
453 return log[index];
454 }
455
456 public int getSize()
457 {
458 if(wrap)
459 return MAXLINES;
460 else
461 return logLineCount;
462 }
463
464 void update(final int lineCount, final boolean oldWrap)
465 {
466 if(lineCount == 0 || listeners.size() == 0)
467 return;
468
469 SwingUtilities.invokeLater(new Runnable()
470 {
471 public void run()
472 {
473 if(wrap)
474 {
475 if(oldWrap)
476 fireIntervalRemoved(0,lineCount - 1);
477 else
478 {
479 fireIntervalRemoved(0,
480 logLineCount);
481 }
482 fireIntervalAdded(
483 MAXLINES - lineCount + 1,
484 MAXLINES);
485 }
486 else
487 {
488 fireIntervalAdded(
489 logLineCount - lineCount + 1,
490 logLineCount);
491 }
492 }
493 });
494 }
495 } //}}}
496}