KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > groboutils > codecoverage > v2 > logger > AbstractSingleSourceLoggerFactory


1 /*
2  * @(#)AbstractSingleSourceLoggerFactory.java
3  *
4  * Copyright (C) 2004 Matt Albrecht
5  * groboclown@users.sourceforge.net
6  * http://groboutils.sourceforge.net
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  */

26
27 package net.sourceforge.groboutils.codecoverage.v2.logger;
28
29 import java.io.Writer JavaDoc;
30 import java.io.IOException JavaDoc;
31
32 import java.util.Properties JavaDoc;
33 import java.util.HashSet JavaDoc;
34 import java.util.Set JavaDoc;
35
36 import net.sourceforge.groboutils.codecoverage.v2.IChannelLogger;
37 import net.sourceforge.groboutils.codecoverage.v2.IChannelLoggerFactory;
38
39
40 /**
41  * A shared mark writer.
42  *
43  * @author Matt Albrecht <a HREF="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
44  * @version $Date: 2004/07/07 09:39:10 $
45  * @since April 16, 2004
46  */

47 public abstract class AbstractSingleSourceLoggerFactory
48         implements ISingleSource, IChannelLoggerFactory, Runnable JavaDoc
49 {
50     public static final String JavaDoc WRITES_PER_FLUSH_PROP = "writes-per-flush";
51     private static final int DEFAULT_WRITES_PER_FLUSH = 2;
52     private static final int INIT_BUFFER_SIZE = 4096;
53     
54     public static final String JavaDoc MILLIS_PER_FLUSH_PROP = "millis-per-flush";
55     private static final long DEFAULT_MILLIS_PER_FLUSH = 0;
56     
57     public static final String JavaDoc USE_CACHE_PROP = "use-cache";
58     
59     // remember that StringBuffers are thread safe
60
private StringBuffer JavaDoc buffer = new StringBuffer JavaDoc( INIT_BUFFER_SIZE );
61     private Writer JavaDoc source;
62     private int writesPerFlush = DEFAULT_WRITES_PER_FLUSH;
63     private long millisPerFlush = DEFAULT_MILLIS_PER_FLUSH;
64     private boolean reloadSourceAfterError = false;
65     
66     // due to how we access the writeCount outside synch blocks, it needs to be
67
// volatile.
68
private volatile int writeCount = 0;
69     
70     private boolean isSetup = false;
71     private boolean useCache = true;
72     private Thread JavaDoc flushThread = null;
73     
74     private Set JavaDoc covered = new HashSet JavaDoc( 20000, 0.75f );
75     
76     // synchronize on a private member, so nobody else can interfere
77
// with our threaded behavior. Make this the string buffer, to
78
// speed up with the string buffer operations (it is thread safe,
79
// after all, and string buffers synchronize on "this", not an inner
80
// object).
81
private Object JavaDoc sync = buffer;
82     
83     
84     /**
85      * Need a thread to flush the buffer.
86      */

87     private class FlushRunner implements Runnable JavaDoc
88     {
89         public void run()
90         {
91             Thread JavaDoc t = Thread.currentThread();
92             try
93             {
94                 while (true)
95                 {
96                     Thread.sleep( millisPerFlush );
97                     flushBuffer();
98                     if (t.interrupted())
99                     {
100                         break;
101                     }
102                 }
103             }
104             catch (InterruptedException JavaDoc e)
105             {
106                 // stop the run thread
107
//e.printStackTrace();
108
}
109         }
110     }
111     
112     
113     
114     public IChannelLogger createChannelLogger(
115             String JavaDoc propertyPrefix, Properties JavaDoc props, short channelIndex )
116     {
117         // we only set ourself up once
118
synchronized( this.sync )
119         {
120             if (!this.isSetup)
121             {
122                 setupProps( propertyPrefix, props );
123                 
124                 this.source = setupSource();
125                 
126                 addShutdownHook();
127                 this.isSetup = true;
128             }
129         }
130         
131         return new SingleSourceLogger( channelIndex, this );
132     }
133     
134     
135     /**
136      * Outputs all the data associated with a probe to the shared single source.
137      * This class manages its own buffering for safety.
138      */

139     public void cover( short channelIndex, String JavaDoc classSig,
140             short methodIndex, short markIndex )
141     {
142         synchronized (this.sync)
143         {
144             if (this.source == null)
145             {
146                 if (this.reloadSourceAfterError)
147                 {
148                     // don't write this time, but write next time.
149
this.source = setupSource();
150                     if (this.source == null)
151                     {
152                         // no need to continue - we don't want the buffer to get
153
// huge while we're not flushing it.
154
//System.out.println("++ source is null");
155
return;
156                     }
157                 }
158                 else
159                 {
160                     // same as the comment for the above return.
161
// they could be merged into a single block, but
162
// let's reduce the number of if statements where
163
// necessary.
164
//System.out.println("++ source is null and don't reload source");
165
return;
166                 }
167                 
168                 // We had a failed source, but now we're going to resume
169
// where we know we left off.
170
// We're not sure where we failed, so add a line
171
// boundary to the beginning of our buffer.
172
// Yes, this is slow, but so is setting up the source.
173
this.buffer.insert( 0, '\n' );
174                 this.writeCount = this.writesPerFlush;
175             }
176         }
177         
178         // For the non-cached version, this may be a bit slower, but
179
// it avoids locking on the buffer until absolutely necessary.
180
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
181         appendToLog( sb, channelIndex, classSig, methodIndex, markIndex );
182         String JavaDoc s = sb.toString();
183         if (this.useCache)
184         {
185             synchronized (this.sync)
186             {
187                 if (this.covered.contains( s ))
188                 {
189                     // early out
190
//System.out.println("++ cache already contains "+s);
191
return;
192                 }
193                 // else add the temp string, and do flush checking
194
//System.out.println("++ adding ["+s+"] to cache");
195
this.covered.add( s );
196             }
197         }
198         // else, just blindly add the string to our buffer.
199
// Note that buffers are thread safe, so we don't need an extra
200
// sync around this
201
this.buffer.append( s );
202         
203         // if writesPerFlush is < 0, then we always flush, but we need
204
// to keep track of the writeCount due to the way flushBuffer is
205
// implemented. If writesPerFlush is 0, then we never flush.
206
// Since the write count is an int, singlular operations (like inc
207
// and assign, the only two operations we use on it) are thread safe
208
int wpf = this.writesPerFlush;
209         if (wpf != 0 && ++this.writeCount >= wpf)
210         {
211             flushBuffer();
212         }
213     }
214     
215     
216     /**
217      * This is the shutdown hook. We flush the buffer to the source,
218      * then we call <tt>close()</tt> on the writer.
219      */

220     public void run()
221     {
222 //System.out.println("++ Start shutdown hook");
223
synchronized( this.buffer )
224         {
225             if (this.flushThread != null)
226             {
227                 this.flushThread.interrupt();
228                 this.flushThread = null;
229             }
230             
231             // this is a modified cut-n-paste of the cover command above.
232
// however, we also close off the stream with a close();
233

234             if (this.source == null)
235             {
236                 if (this.reloadSourceAfterError)
237                 {
238                     this.source = setupSource();
239                 }
240                 if (this.source == null)
241                 {
242 //System.out.println("++ Early leave from shutdown hook");
243

244                     // Don't keep generating source files if another shutdown
245
// hook is registered for code coverage. Force us not to!
246
this.reloadSourceAfterError = false;
247                     return;
248                 }
249                 this.buffer.insert( 0, '\n' );
250             }
251             
252             flushBuffer();
253             
254             try
255             {
256                 this.source.close();
257             }
258             catch (Exception JavaDoc e)
259             {
260                 // ignore
261
//e.printStackTrace();
262
}
263             
264             // Don't keep generating source files if another shutdown
265
// hook is registered for code coverage. Force us not to!
266
this.source = null;
267             this.reloadSourceAfterError = false;
268 //System.out.println("++ End of shutdown hook");
269
}
270     }
271     
272     
273     /**
274      * Setup the source writer. This can return <tt>null</tt>. Its
275      * actions will be synchronized for you.
276      */

277     protected abstract Writer JavaDoc setupSource();
278     
279     
280     /**
281      * Extend this method to setup your own properties. Be sure to
282      * call the super's setupProps, though. Be sure to keep the
283      * properties around for how to connect to the source.
284      */

285     protected void setupProps( String JavaDoc prefix, Properties JavaDoc props )
286     {
287         String JavaDoc wpf = props.getProperty( prefix + WRITES_PER_FLUSH_PROP );
288         if (wpf != null)
289         {
290             try
291             {
292                 this.writesPerFlush = Integer.parseInt( wpf );
293             }
294             catch (NumberFormatException JavaDoc e)
295             {
296                 // default
297
this.writesPerFlush = DEFAULT_WRITES_PER_FLUSH;
298 //e.printStackTrace();
299
}
300         }
301 //System.out.println("++ writes per flush = "+this.writesPerFlush);
302

303         String JavaDoc uc = props.getProperty( prefix + USE_CACHE_PROP );
304         if (uc != null)
305         {
306             this.useCache = Boolean.valueOf( uc ).booleanValue();
307         }
308         
309         
310         String JavaDoc mpf = props.getProperty( prefix + MILLIS_PER_FLUSH_PROP );
311         if (mpf != null)
312         {
313             try
314             {
315                 this.millisPerFlush = Long.parseLong( mpf );
316             }
317             catch (NumberFormatException JavaDoc e)
318             {
319                 this.millisPerFlush = DEFAULT_MILLIS_PER_FLUSH;
320 //e.printStackTrace();
321
}
322         }
323 //System.out.println("++ millis per flush = "+this.millisPerFlush);
324
if (this.millisPerFlush > 0)
325         {
326             if (this.flushThread == null)
327             {
328                 // create our flush thread
329
Thread JavaDoc t = new Thread JavaDoc( new FlushRunner() );
330                 t.setDaemon( true );
331                 t.setPriority( Thread.MIN_PRIORITY );
332                 t.setName( "Code Coverage Flusher" );
333                 t.start();
334                 this.flushThread = t;
335             }
336         }
337     }
338     
339     
340     /**
341      * Set to <tt>true</tt> if you want to try to reconnect to the
342      * source everytime the I/O write fails. This can be changed
343      * at any time.
344      */

345     protected void setReloadSourceAfterError( boolean yes )
346     {
347         this.reloadSourceAfterError = yes;
348     }
349     
350     
351     /**
352      * A consistent way to flush the write buffer. Set as "private final" to
353      * improve performance. Note that this method blocks.
354      */

355     private final void flushBuffer()
356     {
357         synchronized (this.sync)
358         {
359             if (this.writeCount <= 0 || this.source == null)
360             {
361                 // there were no writes since the last flush - exit.
362
//System.out.println("++ Nothing to flush.");
363
return;
364             }
365             try
366             {
367 //System.out.println("++ Writing to source.");
368
this.source.write( this.buffer.toString() );
369                 
370                 // ensure we flush out the source.
371
this.source.flush();
372                 
373                 // only delete and reset count if the write doesn't fail.
374
// This ensures that the data is actually written. Since
375
// we don't care about duplicates (on line boundaries,
376
// that is), this should be safe. Using a setLength is
377
// faster (avoids a System.arraycopy call) than delete.
378
this.buffer.setLength( 0 );
379                 this.writeCount = 0;
380             }
381             catch (IOException JavaDoc e)
382             {
383                 e.printStackTrace();
384                 // failed I/O, so signal this.
385
this.source = null;
386             }
387         }
388     }
389     
390     
391     
392     protected void addShutdownHook()
393     {
394         Class JavaDoc c = Runtime JavaDoc.class;
395         try
396         {
397             java.lang.reflect.Method JavaDoc m = c.getMethod(
398                 "addShutdownHook", new Class JavaDoc[] { Thread JavaDoc.class } );
399             Thread JavaDoc t = new Thread JavaDoc( this,
400                 this.getClass().getName()+" shutdown hook" );
401             m.invoke( Runtime.getRuntime(), new Object JavaDoc[] { t } );
402         }
403         catch (Exception JavaDoc ex)
404         {
405             // prolly JDK 1.3 not supported.
406
// but it's not necessary...
407
//ex.printStackTrace();
408
}
409     }
410     
411     
412     private static final void appendToLog( StringBuffer JavaDoc log,
413             short channelIndex, String JavaDoc classSig, short methodIndex,
414             short markIndex )
415     {
416         log.append( channelIndex ).
417             append( ',' ).append( classSig ).append( ',' ).
418             append( DirectoryChannelLogger.createCoverString(
419                 methodIndex, markIndex ) );
420     }
421 }
422
423
Popular Tags