KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > gjt > sp > util > Log


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
23 package org.gjt.sp.util;
24
25 import java.io.*;
26 import java.util.*;
27 import javax.swing.*;
28 import 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 8347 2007-01-11 21:13:27Z kpouer $
45  */

46 public 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 JavaDoc[] 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 JavaDoc 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 an exception with a message.
222      *
223      * If an exception is the cause of a call to {@link #log}, then
224      * the exception should be explicitly provided so that it can
225      * be presented to the (debugging) user in a useful manner
226      * (not just the exception message, but also the exception stack trace)
227      *
228      * @since jEdit 4.3pre5
229      */

230     public static void log(int urgency, Object JavaDoc source, Object JavaDoc message,
231         Throwable JavaDoc exception)
232     {
233         // We can do nicer here, but this is a start...
234
log(urgency,source,message);
235         log(urgency,source,exception);
236     } //}}}
237

238     //{{{ log() method
239
/**
240      * Logs a message. This method is thread-safe.<p>
241      *
242      * The following code sends a typical debugging message to the activity
243      * log:
244      * <pre>Log.log(Log.DEBUG,this,"counter = " + counter);</pre>
245      * The corresponding activity log entry might read as follows:
246      * <pre>[debug] JavaParser: counter = 15</pre>
247      *
248      * @param urgency The urgency; can be one of
249      * <code>Log.DEBUG</code>, <code>Log.MESSAGE</code>,
250      * <code>Log.NOTICE</code>, <code>Log.WARNING</code>, or
251      * <code>Log.ERROR</code>.
252      * @param source The source of the message, either an object or a
253      * class instance. When writing log messages from macros, set
254      * this parameter to <code>BeanShell.class</code> to make macro
255      * errors easier to spot in the activity log.
256      * @param message The message. This can either be a string or
257      * an exception
258      *
259      * @since jEdit 2.2pre2
260      */

261     public static void log(int urgency, Object JavaDoc source, Object JavaDoc message)
262     {
263         String JavaDoc _source;
264         if(source == null)
265         {
266             _source = Thread.currentThread().getName();
267             if(_source == null)
268             {
269                 _source = Thread.currentThread().getClass().getName();
270             }
271         }
272         else if(source instanceof Class JavaDoc)
273             _source = ((Class JavaDoc)source).getName();
274         else
275             _source = source.getClass().getName();
276         int index = _source.lastIndexOf('.');
277         if(index != -1)
278             _source = _source.substring(index+1);
279
280         if(message instanceof Throwable JavaDoc)
281         {
282             _logException(urgency,source,(Throwable JavaDoc)message);
283         }
284         else
285         {
286             String JavaDoc _message = String.valueOf(message);
287             // If multiple threads log stuff, we don't want
288
// the output to get mixed up
289
synchronized(LOCK)
290             {
291                 StringTokenizer st = new StringTokenizer(
292                     _message,"\r\n");
293                 int lineCount = 0;
294                 boolean oldWrap = wrap;
295                 while(st.hasMoreTokens())
296                 {
297                     lineCount++;
298                     _log(urgency,_source,st.nextToken()
299                         .replace('\t',' '));
300                 }
301                 listModel.update(lineCount,oldWrap);
302             }
303         }
304     } //}}}
305

306     //{{{ Private members
307

308     //{{{ Instance variables
309
private static final Object JavaDoc LOCK = new Object JavaDoc();
310     private static final String JavaDoc[] log;
311     private static int logLineCount;
312     private static boolean wrap;
313     private static int level = WARNING;
314     private static Writer stream;
315     private static final String JavaDoc lineSep;
316     private static final PrintStream realOut;
317     private static final PrintStream realErr;
318     private static final LogListModel listModel;
319     //}}}
320

321     //{{{ Class initializer
322
static
323     {
324         level = WARNING;
325
326         realOut = System.out;
327         realErr = System.err;
328
329         log = new String JavaDoc[MAXLINES];
330         lineSep = System.getProperty("line.separator");
331         listModel = new LogListModel();
332     } //}}}
333

334     //{{{ createPrintStream() method
335
private static PrintStream createPrintStream(final int urgency,
336         final Object JavaDoc source)
337     {
338         return new LogPrintStream(urgency, source);
339     } //}}}
340

341     //{{{ _logException() method
342
private static void _logException(final int urgency,
343         final Object JavaDoc source,
344         final Throwable JavaDoc message)
345     {
346         PrintStream out = createPrintStream(urgency,source);
347
348         synchronized(LOCK)
349         {
350             message.printStackTrace(out);
351         }
352     } //}}}
353

354     //{{{ _log() method
355
private static void _log(int urgency, String JavaDoc source, String JavaDoc message)
356     {
357         String JavaDoc fullMessage = '[' + urgencyToString(urgency) + "] " + source
358             + ": " + message;
359
360         try
361         {
362             log[logLineCount] = fullMessage;
363             if(++logLineCount >= log.length)
364             {
365                 wrap = true;
366                 logLineCount = 0;
367             }
368
369             if(stream != null)
370             {
371                 stream.write(fullMessage);
372                 stream.write(lineSep);
373             }
374         }
375         catch(Exception JavaDoc e)
376         {
377             e.printStackTrace(realErr);
378         }
379
380         if(urgency >= level)
381         {
382             if(urgency == ERROR)
383                 realErr.println(fullMessage);
384             else
385                 realOut.println(fullMessage);
386         }
387     } //}}}
388

389     //{{{ urgencyToString() method
390
private static String JavaDoc urgencyToString(int urgency)
391     {
392         switch(urgency)
393         {
394         case DEBUG:
395             return "debug";
396         case MESSAGE:
397             return "message";
398         case NOTICE:
399             return "notice";
400         case WARNING:
401             return "warning";
402         case ERROR:
403             return "error";
404         }
405
406         throw new IllegalArgumentException JavaDoc("Invalid urgency: " + urgency);
407     } //}}}
408

409     //}}}
410

411     //{{{ LogListModel class
412
static class LogListModel implements ListModel
413     {
414         final List<ListDataListener> listeners = new ArrayList<ListDataListener>();
415
416         private void fireIntervalAdded(int index1, int index2)
417         {
418             for(int i = 0; i < listeners.size(); i++)
419             {
420                 ListDataListener listener = listeners.get(i);
421                 listener.intervalAdded(new ListDataEvent(this,
422                     ListDataEvent.INTERVAL_ADDED,
423                     index1,index2));
424             }
425         }
426
427         private void fireIntervalRemoved(int index1, int index2)
428         {
429             for(int i = 0; i < listeners.size(); i++)
430             {
431                 ListDataListener listener = listeners.get(i);
432                 listener.intervalRemoved(new ListDataEvent(this,
433                     ListDataEvent.INTERVAL_REMOVED,
434                     index1,index2));
435             }
436         }
437
438         public void addListDataListener(ListDataListener listener)
439         {
440             listeners.add(listener);
441         }
442
443         public void removeListDataListener(ListDataListener listener)
444         {
445             listeners.remove(listener);
446         }
447
448         public Object JavaDoc getElementAt(int index)
449         {
450             if(wrap)
451             {
452                 if(index < MAXLINES - logLineCount)
453                     return log[index + logLineCount];
454                 else
455                     return log[index - MAXLINES + logLineCount];
456             }
457             else
458                 return log[index];
459         }
460
461         public int getSize()
462         {
463             if(wrap)
464                 return MAXLINES;
465             else
466                 return logLineCount;
467         }
468
469         void update(final int lineCount, final boolean oldWrap)
470         {
471             if(lineCount == 0 || listeners.isEmpty())
472                 return;
473
474             SwingUtilities.invokeLater(new Runnable JavaDoc()
475             {
476                 public void run()
477                 {
478                     if(wrap)
479                     {
480                         if(oldWrap)
481                             fireIntervalRemoved(0,lineCount - 1);
482                         else
483                         {
484                             fireIntervalRemoved(0,
485                                 logLineCount);
486                         }
487                         fireIntervalAdded(
488                             MAXLINES - lineCount + 1,
489                             MAXLINES);
490                     }
491                     else
492                     {
493                         fireIntervalAdded(
494                             logLineCount - lineCount + 1,
495                             logLineCount);
496                     }
497                 }
498             });
499         }
500     } //}}}
501

502     /**
503      * A print stream that uses the "Log" class to output the messages,
504      * and has special treatment for the printf() function. Using this
505      * stream has one caveat: printing messages that don't have a line
506      * break at the end will have one added automatically...
507      */

508     private static class LogPrintStream extends PrintStream {
509
510         private final ByteArrayOutputStream buffer;
511         private final OutputStream orig;
512
513         LogPrintStream(int urgency, Object JavaDoc source)
514         {
515             super(new LogOutputStream(urgency, source));
516             buffer = new ByteArrayOutputStream();
517             orig = out;
518         }
519
520         /**
521          * This is a hack to allow "printf" to not print weird
522          * stuff to the output. Since "printf" doesn't seem to
523          * print the whole message in one shot, our output
524          * stream above would break a line of log into several
525          * lines; so we buffer the result of the printf call and
526          * print the whole thing in one shot. A similar hack
527          * would be needed for the "other" printf method, but
528          * I'll settle for the common case only.
529          */

530         public PrintStream printf(String JavaDoc format, Object JavaDoc... args)
531         {
532             synchronized (orig)
533             {
534                 buffer.reset();
535                 out = buffer;
536                 super.printf(format, args);
537
538                 try
539                 {
540                     byte[] data = buffer.toByteArray();
541                     orig.write(data, 0, data.length);
542                     out = orig;
543                 }
544                 catch (IOException ioe)
545                 {
546                     // don't do anything?
547
}
548                 finally
549                 {
550                     buffer.reset();
551                 }
552             }
553             return this;
554         }
555     }
556
557     private static class LogOutputStream extends OutputStream
558     {
559         private final int urgency;
560         private final Object JavaDoc source;
561
562         LogOutputStream(int urgency, Object JavaDoc source)
563         {
564             this.urgency = urgency;
565             this.source = source;
566         }
567
568         public synchronized void write(int b)
569         {
570             byte[] barray = { (byte)b };
571             write(barray,0,1);
572         }
573
574         public synchronized void write(byte[] b, int off, int len)
575         {
576             String JavaDoc str = new String JavaDoc(b,off,len);
577             log(urgency,source,str);
578         }
579     }
580
581 }
582
583
Popular Tags