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  import com.gargoylesoftware.base.util.DetailedNullPointerException;
41  import com.gargoylesoftware.base.util.StringUtil;
42  import java.io.PrintStream;
43  import java.lang.reflect.InvocationTargetException;
44  import java.lang.reflect.Method;
45  import java.text.Format;
46  import java.text.SimpleDateFormat;
47  import java.util.Date;
48  import java.util.Iterator;
49  import java.util.Set;
50  
51  /***
52   * <p style="color: orange">Internal use only.</p>.
53   * <p>A dispatcher for TraceItems</p>
54   *
55   * @version $Revision: 1.9 $
56   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
57   */
58  public class TraceItemDispatcher implements Runnable {
59  
60      // Format used for the timestamp in the trace lines
61      private static final Format TIMESTAMP_FORMAT = new SimpleDateFormat("HH:mm");
62  
63      private static final int BUFFER_ENABLED       = 1;
64      private static final int BUFFER_SHUTTING_DOWN = 2;
65      private static final int BUFFER_DISABLED      = 3;
66  
67      private static int bufferStatus_ = BUFFER_DISABLED;
68  
69      private final TraceItemQueue traceQueue_ = new TraceItemQueue();
70  
71      // For caching TraceItems
72      private TraceItemQueue cacheTraceItemQueue_ = new TraceItemQueue();
73      private int cacheMaxSize_ = 50;
74  
75      /***
76       *
77       */
78      public TraceItemDispatcher() {
79          new Thread(this, "TraceItemDispatcher Thread").start();
80  
81          // Add a hook so that buffering will be shut down before the JVM
82          // exits.  This will ensure that all information written to Trace
83          // will be flushed properly before shutdown.
84          //
85          // This is done with reflection so that the class will still compile and
86          // run in a 1.2 JVM.
87          final Runtime runtime = Runtime.getRuntime();
88          try {
89              final Method method = runtime.getClass().getMethod( "addShutdownHook",
90                                                                   new Class[] { Thread.class } );
91              final Thread thread =  new Thread() {
92                  public void run() {
93                      Trace.getController().setBufferingEnabled(false);
94                  }
95              };
96              method.invoke( runtime, new Object[] { thread } );
97          }
98          catch( NoSuchMethodException e ) {
99              // The method wasn't found - we must be running on a 1.2 JVM.  Oh well.
100         }
101         catch( final IllegalAccessException e ) {
102             // Theoretically impossible.
103             e.printStackTrace();
104         }
105         catch( final InvocationTargetException e ) {
106             // Theoretically impossible.
107             e.getTargetException().printStackTrace();
108         }
109     }
110 
111     /***
112      *
113      */
114     public void run() {
115         TraceItem item;
116 
117         // Enable the buffering.  Note that we can't call
118         // setBufferingEnabled() at this point because of deadlocks.
119         bufferStatus_ = BUFFER_ENABLED;
120 
121         // The reason for the double try blocks is that we want to continue
122         // looping if we get an exception but if we get an error then
123         // something is severely wrong so we exit the thread.
124         try {
125             while(true) {
126                 try {
127                     item = traceQueue_.pop();
128                     if( item == null ) {
129                         Thread.sleep(500);
130                     }
131                     else {
132                         dumpTraceElement(item);
133                     }
134                 }
135                 catch( final Exception e ) {
136                     System.out.print("Exception when printing debug information e=");
137                     e.printStackTrace();
138                 }
139             }
140         }
141         catch( final Throwable t ) {
142             System.out.print("Exception when printing debug information e=");
143             t.printStackTrace();
144         }
145     }
146 
147     /***
148      * Format an item and print it to standard out.
149      * @param item the item to print.
150      */
151     private void dumpTraceElement( final TraceItem item ) {
152         assertNotNull("item", item);
153 
154         final TraceChannel channel = item.getChannel();
155         if( channel != null && channel.isEnabled() && item.containsText() ) {
156             final Set traceWriters = channel.getTraceWriters();
157             synchronized( traceWriters ) {
158                 final Iterator iterator = traceWriters.iterator();
159                 if( iterator.hasNext() == false ) {
160                     // There aren't any trace writers for this channel
161                     defaultTraceWriter(item);
162                 }
163                 else {
164                     while( iterator.hasNext() ) {
165                         ((TraceWriter)iterator.next()).write(item);
166                     }
167                 }
168             }
169         }
170 
171         // Is this a synchronous call
172         final Object lock = item.getLock();
173         if( lock != null ) {
174             synchronized(lock) {
175                 lock.notify();
176             }
177             return;
178         }
179 
180         disposeTraceItem(item);
181     }
182 
183     /***
184      * Provide default behaviour
185      * @param item The item to print
186      */
187     private static void defaultTraceWriter( final TraceItem item ) {
188 
189         final PrintStream outStream
190         = Trace.getController().getRealSystemOut();
191         final StringBuffer prefixBuffer = new StringBuffer();
192 
193         prefixBuffer.append("[");
194         final Date timestamp = item.getTime();
195         if( timestamp != null ) {
196             prefixBuffer.append( TIMESTAMP_FORMAT.format(timestamp) );
197             prefixBuffer.append(" ");
198         }
199 
200         final String threadName = item.getThreadName();
201         if( threadName != null ) {
202             prefixBuffer.append( threadName );
203         }
204 
205         prefixBuffer.append("] ");
206 
207         final String prefix = prefixBuffer.toString();
208         final String message = item.getMessage();
209 
210         if( message != null ) {
211             outStream.print(prefix);
212             outStream.print( StringUtil.expandTabs(message,3) );
213         }
214 
215         final Throwable throwable = item.getThrowable();
216 
217         if( throwable != null ) {
218             int i;
219             final String strings[] = Trace.throwableToStringArray(throwable);
220 
221             outStream.print(prefix);
222             outStream.println(strings[0]);
223 
224             final String blanks = StringUtil.nCopies(prefix.length(), ' ');
225             for( i=1; i<strings.length; i++ ) {
226                 outStream.print(blanks);
227                 outStream.println( StringUtil.expandTabs(strings[i],3) );
228             }
229         }
230     }
231 
232     /***
233      * Get the queue.
234      * @return The queue.
235      */
236     public TraceItemQueue getTraceItemQueue() {
237         return traceQueue_;
238     }
239 
240     /***
241      * Add an item to the trace queue.
242      * @param item The item to add.
243      */
244     public void dispatch( final TraceItem item ) {
245         item.setThread( Thread.currentThread() );
246         item.setTime( new Date() );
247 
248         switch( bufferStatus_ ) {
249             case BUFFER_ENABLED:
250                 traceQueue_.push(item);
251                 break;
252 
253             case BUFFER_SHUTTING_DOWN:
254                 flush();
255                 dumpTraceElement(item);
256                 break;
257 
258             case BUFFER_DISABLED:
259                 dumpTraceElement(item);
260                 break;
261 
262             default:
263                 throw new IllegalStateException(
264                                                "Unexpected value: bufferStatus_="
265                                                + bufferStatus_);
266         }
267     }
268 
269     /***
270      *
271      */
272     // TODO: Use lock_ in TraceItem
273     private synchronized void waitForQueueToEmpty() {
274         while( traceQueue_.size() != 0 ) {
275             try {
276                 Thread.sleep(100);
277             }
278             catch( final InterruptedException e ) {
279                 return;
280             }
281         }
282     }
283 
284     /***
285      * Set whether or not to buffer the output of the trace calls.  Buffering
286      * will increase perceived performance significantly.
287      * @param enabled True if buffering should be enabled
288      */
289     public synchronized void setBufferingEnabled( final boolean enabled ) {
290 
291         if( enabled ) {
292             if( bufferStatus_ == BUFFER_SHUTTING_DOWN ) {
293                 // Block until the buffer has finished shutting down
294                 flush();
295             }
296 
297             if( bufferStatus_ == BUFFER_DISABLED ) {
298                 bufferStatus_ = BUFFER_ENABLED;
299             }
300         }
301         else {
302             if( bufferStatus_ == BUFFER_ENABLED ) {
303                 bufferStatus_ = BUFFER_SHUTTING_DOWN;
304                 flush();
305                 bufferStatus_ = BUFFER_DISABLED;
306             }
307         }
308     }
309 
310     /***
311      * Return true if buffering is enabled.
312      * @return true if buffering is enabled.
313      */
314     public boolean isBufferingEnabled() {
315         return bufferStatus_ == BUFFER_ENABLED;
316     }
317 
318     /***
319      *
320      */
321     public void flush() {
322 
323         switch( bufferStatus_ ) {
324             case BUFFER_ENABLED:
325                 final TraceItem item = getNewTraceItem();
326                 final Object lock = new Object();
327                 item.setLock( lock );
328                 try {
329                     synchronized(lock) {
330                         dispatch( item );
331                         lock.wait();
332                     }
333                 }
334                 catch( final InterruptedException e ) {
335                     // Expected path
336                 }
337                 break;
338 
339             case BUFFER_SHUTTING_DOWN:
340                 waitForQueueToEmpty();
341                 break;
342 
343             case BUFFER_DISABLED:
344                 return;
345 
346             default:
347                 throw new IllegalStateException(
348                                                "Unexpected value: bufferStatus_="
349                                                + bufferStatus_);
350         }
351     }
352 
353     /***
354      * Return a trace item
355      * @return The new trace item.
356      */
357     public TraceItem getNewTraceItem() {
358         TraceItem item = cacheTraceItemQueue_.pop();
359         if( item == null ) {
360             item = new TraceItem();
361         }
362 
363         return item;
364     }
365 
366     /***
367      * Dispose of a trace item.  Disposing will put the trace item back on a queue for reuse.
368      * @param item The item to dispose.
369      */
370     public void disposeTraceItem( final TraceItem item ) {
371         if( cacheTraceItemQueue_.size() < cacheMaxSize_ ) {
372             item.clear();
373             cacheTraceItemQueue_.push(item);
374         }
375     }
376 
377 
378     /***
379      * Verify that the specified value is not null.  If it is then throw an exception
380      *
381      * @param fieldName The name of the field to check
382      * @param fieldValue The value of the field to check
383      * @exception DetailedNullPointerException If fieldValue is null
384      */
385     protected final void assertNotNull( final String fieldName, final Object fieldValue )
386         throws DetailedNullPointerException {
387 
388         if( fieldValue == null ) {
389             throw new DetailedNullPointerException(fieldName);
390         }
391     }
392 }
393