1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
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
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
82
83
84
85
86
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
100 }
101 catch( final IllegalAccessException e ) {
102
103 e.printStackTrace();
104 }
105 catch( final InvocationTargetException e ) {
106
107 e.getTargetException().printStackTrace();
108 }
109 }
110
111 /***
112 *
113 */
114 public void run() {
115 TraceItem item;
116
117
118
119 bufferStatus_ = BUFFER_ENABLED;
120
121
122
123
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
161 defaultTraceWriter(item);
162 }
163 else {
164 while( iterator.hasNext() ) {
165 ((TraceWriter)iterator.next()).write(item);
166 }
167 }
168 }
169 }
170
171
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
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
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
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