View Javadoc

1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   */
38  package com.gargoylesoftware.base.trace;
39  
40  
41  //TODO: If a trace string contains newlines then prefix each line with the
42  //      required number of spaces
43  //TODO: Default channels should be thread specific.
44  //      - setDefaultChannel(Thread, Channel);
45  //      - setDefaultChannel(ThreadGroup, Channel);
46  //      - setDefaultChannel(Channel);
47  
48  import com.gargoylesoftware.base.util.DetailedNullPointerException;
49  import java.io.PrintWriter;
50  import java.io.StringWriter;
51  import java.util.ArrayList;
52  import java.util.List;
53  import java.util.StringTokenizer;
54  
55  /***
56   * A class that provides a mechanism for logging diagnostic messages.  This
57   * mechanism is significantly faster then calling System.out.println directly
58   * as the printing is done on a background thread which does not impact the
59   * performance of the application threads.
60   * <p>
61   * The methods for tracing are print(String), println(String) and
62   * printStackTrace(Throwable).  The basic idea is that we want the trace
63   * methods to return to the working threads as quickly as possible so we
64   * put the thing to be traced on a queue.  The queue is then processed by
65   * a second thread which performs all the formatting and actual output.<p>
66   *
67   * Trace can be configured using the {@link TraceController} class - see
68   * {@link #getController()}.  It is possible to redirect System.out and
69   * System.err so that calling System.out.println("foo") will be the same
70   * as calling Trace.println("foo")
71   * <pre>
72   * Trace.getController().setOutRedirected(true);
73   * Trace.getController().setErrRedirected(true);
74   * </pre>
75   *
76   * Calls to any of the print methods will print to a {@link TraceChannel}
77   * which in turn will print using whatever {@link TraceWriter}s have been
78   * added to it.  Print methods that do not take a {@link TraceChannel}
79   * will use the default channel - see
80   * {@link TraceController#setDefaultChannel(TraceChannel)}<p>
81   *
82   * <b>If you are using this code in a JVM prior to 1.3 then you need to read this:</b>
83   * <br />Because trace lines are being processed on a second thread, there
84   * might still be trace messages in the queue when the VM exits.  To solve
85   * this problem we introduce the method {@link #exit(int)} which will shut
86   * down the second thread and then call System.exit().  If you are running on
87   * a java 1.3 VM or higher then it is not neccessary to call {@link #exit(int)}
88   * as Trace will automatically install a shutdown hook which will do the
89   * neccessary cleanup.<p>
90   *
91   * @version $Revision: 1.7 $
92   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
93   */
94  public class Trace {
95      /*** An exception used to determine where the code is at any point in time. */
96      public static class WhereAmIException extends Exception {
97          private static final long serialVersionUID = 6876505080685244104L;
98  
99  		/*** Instantiate one */
100         public WhereAmIException() {
101             super("?? Where am I ??");
102         }
103     }
104 
105     /***
106      * The equivilent of "standard out"
107      */
108     public static final TraceChannel out = new TraceChannel("STDOUT");
109     /***
110      * The equivilent of "standard error"
111      */
112     public static final TraceChannel err = new TraceChannel("STDERR");
113 
114     // line separator used for println
115     private static final String LINE_SEPARATOR = System.getProperty("line.separator");
116 
117     private static final TraceItemDispatcher TRACE_ITEM_DISPATCHER = new TraceItemDispatcher();
118     private static final TraceController TRACE_CONTROLLER = new TraceController();
119 
120 
121     /***
122      * Private constructor to prevent instantiation of this class.
123      */
124     private Trace() {
125     }
126 
127     /***
128      * Print a line to the specified channel.
129      * If the channel is null then nothing will be printed.
130      * @param channel The trace channel to use
131      * @param string The text string to write.
132      */
133     public static void print( final TraceChannel channel, String string ) {
134         assertNotNull("channel", channel);
135         if( string == null ) {
136             string = "<null>";
137         }
138 
139         final TraceItemDispatcher dispatcher = getDispatcher();
140         final TraceItem item = dispatcher.getNewTraceItem();
141 
142         item.setMessage(string);
143         item.setChannel(channel);
144         dispatcher.dispatch(item);
145     }
146 
147     /***
148      * Print a line to the default channel
149      * If the channel is null then nothing will be printed.
150      * @param string The text string to write.
151      */
152     public static void print( final String string ) {
153         print( getController().getDefaultChannel(), string );
154     }
155 
156     /***
157      * Print the line to the specified channel with a new line at the end.
158      * If the channel is null then nothing will be printed.
159      * @param channel The trace channel to use
160      * @param string the string to write.
161      */
162     public static void println( final TraceChannel channel, final String string ) {
163         if( channel == null ) {
164             return;
165         }
166         print( channel, string + LINE_SEPARATOR );
167     }
168 
169     /***
170      * Print the line to the default channel with a new line at the end.
171      *
172      * @param string the string to write.
173      */
174     public static void println( final String string ) {
175         print( getController().getDefaultChannel(), string + LINE_SEPARATOR );
176     }
177 
178     /***
179      * Print the stack trace to the specified channel.
180      * If the channel is null then nothing will be printed.
181      *
182      * @param channel The trace channel to use
183      * @param throwable The exception to print
184      */
185     public static void printStackTrace( final TraceChannel channel,
186                                         final Throwable throwable ) {
187 
188         if( channel == null ) {
189             return;
190         }
191         if( throwable == null ) {
192             println( channel, "<null exception>" );
193             return;
194         }
195 
196         final TraceItemDispatcher dispatcher = getDispatcher();
197         final TraceItem item = dispatcher.getNewTraceItem();
198 
199         item.setThrowable(throwable);
200         item.setChannel(channel);
201         dispatcher.dispatch(item);
202     }
203 
204     /***
205      * Print the stack trace to the default channel.
206      * @param throwable The exception to print.
207      */
208     public static void printStackTrace( final Throwable throwable ) {
209         printStackTrace( getController().getDefaultChannel(), throwable );
210     }
211 
212     /***
213      * Print the specified lines to the default trace channel.
214      * @param lines The lines to print.
215      */
216     public static void printLines( final String lines[] ) {
217         printLines( getController().getDefaultChannel(), lines );
218     }
219 
220     /***
221      * Print the specified lines to the trace channel.
222      * If the channel is null then nothing will be printed.
223      *
224      * @param channel The trace channel to use
225      * @param lines the lines to print.
226      */
227     public static void printLines( final TraceChannel channel, final String lines[] ) {
228         if( channel == null ) {
229             return;
230         }
231         if( lines == null ) {
232             println( channel, "<null lines>" );
233             return;
234         }
235 
236         //TODO: Send the array as one request to the dispatcher so that the prefix
237         //      string is only printed once for the array
238         int i;
239         for( i=0; i<lines.length; i++ ) {
240             println( channel, lines[i] );
241         }
242     }
243 
244     /***
245      * Print a stack trace to show where we came from.  It will be printed to the default
246      * channel.
247      */
248     public static void whereAmI() {
249         whereAmI( getController().getDefaultChannel() );
250     }
251 
252     /***
253      * Print a stack trace to show where we came from. If the channel is null then
254      * nothing will be printed.
255      * @param channel The trace channel to use.
256      */
257     public static void whereAmI( final TraceChannel channel ) {
258         if( channel == null ) {
259             return;
260         }
261 
262         printStackTrace( channel, new WhereAmIException() );
263     }
264 
265     /***
266      * Dump the stack trace from the specified throwable into an array
267      * of strings where each line in the trace is a separate string.
268      * Additionally, expand all tabs to spaces.
269      *
270      * @param t The exception.
271      * @return The resulting string.
272      */
273     public static String[] throwableToStringArray( final Throwable t ) {
274         assertNotNull("t", t);
275 
276         String token;
277         char c;
278         StringBuffer buffer;
279         int length;
280         int i;
281 
282         final List vector = new ArrayList();
283         final StringTokenizer tokenizer
284         = new StringTokenizer( throwableToString(t), "\n");
285 
286         while( tokenizer.hasMoreTokens() ) {
287             token = tokenizer.nextToken();
288 
289             // Now expand the tabs
290             length = token.length();
291             buffer = new StringBuffer(length*2);
292             for( i=0; i<length; i++ ) {
293                 c = token.charAt(i);
294                 if( c == '\t' ) {
295                     buffer.append("   ");
296                 }
297                 else {
298                     buffer.append(c);
299                 }
300             }
301             vector.add(buffer.toString());
302         }
303 
304         final String array[] = new String[vector.size()];
305         vector.toArray(array);
306         return array;
307     }
308 
309     /***
310      * Dump the stack trace from the specified throwable into a String.
311      *
312      * @param t The exception.
313      * @return The resulting string.
314      */
315     public static String throwableToString( final Throwable t ) {
316         assertNotNull("t", t);
317 
318         final StringWriter writer = new StringWriter();
319         final PrintWriter printWriter = new PrintWriter(writer);
320         t.printStackTrace(printWriter);
321         printWriter.close();
322         return writer.toString();
323     }
324 
325     /***
326      * Return the controller object for the debugging stuff
327      *
328      * @return The controller in use.
329      */
330     public static TraceController getController() {
331         return TRACE_CONTROLLER;
332     }
333 
334     /*** @return The dispatcher */
335     static TraceItemDispatcher getDispatcher() {
336         return TRACE_ITEM_DISPATCHER;
337     }
338 
339     /***
340      * Flush the trace queue.
341      */
342     public static void flush() {
343         getDispatcher().flush();
344     }
345 
346     /***
347      * Shutdown all buffering and exit the VM with the specified
348      * status code.
349      * <p>
350      * <b>Note</b> This is no longer needed if you are running JDK1.3 or higher as
351      * we now register a shutdown hook to disable buffering before the VM exits.
352      * @param statusCode The status code returned when the application exits.
353      */
354     public static void exit( final int statusCode ) {
355         getController().setBufferingEnabled(false);
356         System.exit(statusCode);
357     }
358 
359 
360     /***
361      * Verify that the specified value is not null.  If it is then throw an exception
362      *
363      * @param fieldName The name of the field to check
364      * @param fieldValue The value of the field to check
365      * @exception DetailedNullPointerException If fieldValue is null
366      */
367     protected static final void assertNotNull( final String fieldName, final Object fieldValue )
368         throws DetailedNullPointerException {
369 
370         if( fieldValue == null ) {
371             throw new DetailedNullPointerException(fieldName);
372         }
373     }
374 }
375