001: /*
002: * @(#)AbstractSingleSourceLoggerFactory.java
003: *
004: * Copyright (C) 2004 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.codecoverage.v2.logger;
028:
029: import java.io.Writer;
030: import java.io.IOException;
031:
032: import java.util.Properties;
033: import java.util.HashSet;
034: import java.util.Set;
035:
036: import net.sourceforge.groboutils.codecoverage.v2.IChannelLogger;
037: import net.sourceforge.groboutils.codecoverage.v2.IChannelLoggerFactory;
038:
039: /**
040: * A shared mark writer.
041: *
042: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
043: * @version $Date: 2004/07/07 09:39:10 $
044: * @since April 16, 2004
045: */
046: public abstract class AbstractSingleSourceLoggerFactory implements
047: ISingleSource, IChannelLoggerFactory, Runnable {
048: public static final String WRITES_PER_FLUSH_PROP = "writes-per-flush";
049: private static final int DEFAULT_WRITES_PER_FLUSH = 2;
050: private static final int INIT_BUFFER_SIZE = 4096;
051:
052: public static final String MILLIS_PER_FLUSH_PROP = "millis-per-flush";
053: private static final long DEFAULT_MILLIS_PER_FLUSH = 0;
054:
055: public static final String USE_CACHE_PROP = "use-cache";
056:
057: // remember that StringBuffers are thread safe
058: private StringBuffer buffer = new StringBuffer(INIT_BUFFER_SIZE);
059: private Writer source;
060: private int writesPerFlush = DEFAULT_WRITES_PER_FLUSH;
061: private long millisPerFlush = DEFAULT_MILLIS_PER_FLUSH;
062: private boolean reloadSourceAfterError = false;
063:
064: // due to how we access the writeCount outside synch blocks, it needs to be
065: // volatile.
066: private volatile int writeCount = 0;
067:
068: private boolean isSetup = false;
069: private boolean useCache = true;
070: private Thread flushThread = null;
071:
072: private Set covered = new HashSet(20000, 0.75f);
073:
074: // synchronize on a private member, so nobody else can interfere
075: // with our threaded behavior. Make this the string buffer, to
076: // speed up with the string buffer operations (it is thread safe,
077: // after all, and string buffers synchronize on "this", not an inner
078: // object).
079: private Object sync = buffer;
080:
081: /**
082: * Need a thread to flush the buffer.
083: */
084: private class FlushRunner implements Runnable {
085: public void run() {
086: Thread t = Thread.currentThread();
087: try {
088: while (true) {
089: Thread.sleep(millisPerFlush);
090: flushBuffer();
091: if (t.interrupted()) {
092: break;
093: }
094: }
095: } catch (InterruptedException e) {
096: // stop the run thread
097: //e.printStackTrace();
098: }
099: }
100: }
101:
102: public IChannelLogger createChannelLogger(String propertyPrefix,
103: Properties props, short channelIndex) {
104: // we only set ourself up once
105: synchronized (this .sync) {
106: if (!this .isSetup) {
107: setupProps(propertyPrefix, props);
108:
109: this .source = setupSource();
110:
111: addShutdownHook();
112: this .isSetup = true;
113: }
114: }
115:
116: return new SingleSourceLogger(channelIndex, this );
117: }
118:
119: /**
120: * Outputs all the data associated with a probe to the shared single source.
121: * This class manages its own buffering for safety.
122: */
123: public void cover(short channelIndex, String classSig,
124: short methodIndex, short markIndex) {
125: synchronized (this .sync) {
126: if (this .source == null) {
127: if (this .reloadSourceAfterError) {
128: // don't write this time, but write next time.
129: this .source = setupSource();
130: if (this .source == null) {
131: // no need to continue - we don't want the buffer to get
132: // huge while we're not flushing it.
133: //System.out.println("++ source is null");
134: return;
135: }
136: } else {
137: // same as the comment for the above return.
138: // they could be merged into a single block, but
139: // let's reduce the number of if statements where
140: // necessary.
141: //System.out.println("++ source is null and don't reload source");
142: return;
143: }
144:
145: // We had a failed source, but now we're going to resume
146: // where we know we left off.
147: // We're not sure where we failed, so add a line
148: // boundary to the beginning of our buffer.
149: // Yes, this is slow, but so is setting up the source.
150: this .buffer.insert(0, '\n');
151: this .writeCount = this .writesPerFlush;
152: }
153: }
154:
155: // For the non-cached version, this may be a bit slower, but
156: // it avoids locking on the buffer until absolutely necessary.
157: StringBuffer sb = new StringBuffer();
158: appendToLog(sb, channelIndex, classSig, methodIndex, markIndex);
159: String s = sb.toString();
160: if (this .useCache) {
161: synchronized (this .sync) {
162: if (this .covered.contains(s)) {
163: // early out
164: //System.out.println("++ cache already contains "+s);
165: return;
166: }
167: // else add the temp string, and do flush checking
168: //System.out.println("++ adding ["+s+"] to cache");
169: this .covered.add(s);
170: }
171: }
172: // else, just blindly add the string to our buffer.
173: // Note that buffers are thread safe, so we don't need an extra
174: // sync around this
175: this .buffer.append(s);
176:
177: // if writesPerFlush is < 0, then we always flush, but we need
178: // to keep track of the writeCount due to the way flushBuffer is
179: // implemented. If writesPerFlush is 0, then we never flush.
180: // Since the write count is an int, singlular operations (like inc
181: // and assign, the only two operations we use on it) are thread safe
182: int wpf = this .writesPerFlush;
183: if (wpf != 0 && ++this .writeCount >= wpf) {
184: flushBuffer();
185: }
186: }
187:
188: /**
189: * This is the shutdown hook. We flush the buffer to the source,
190: * then we call <tt>close()</tt> on the writer.
191: */
192: public void run() {
193: //System.out.println("++ Start shutdown hook");
194: synchronized (this .buffer) {
195: if (this .flushThread != null) {
196: this .flushThread.interrupt();
197: this .flushThread = null;
198: }
199:
200: // this is a modified cut-n-paste of the cover command above.
201: // however, we also close off the stream with a close();
202:
203: if (this .source == null) {
204: if (this .reloadSourceAfterError) {
205: this .source = setupSource();
206: }
207: if (this .source == null) {
208: //System.out.println("++ Early leave from shutdown hook");
209:
210: // Don't keep generating source files if another shutdown
211: // hook is registered for code coverage. Force us not to!
212: this .reloadSourceAfterError = false;
213: return;
214: }
215: this .buffer.insert(0, '\n');
216: }
217:
218: flushBuffer();
219:
220: try {
221: this .source.close();
222: } catch (Exception e) {
223: // ignore
224: //e.printStackTrace();
225: }
226:
227: // Don't keep generating source files if another shutdown
228: // hook is registered for code coverage. Force us not to!
229: this .source = null;
230: this .reloadSourceAfterError = false;
231: //System.out.println("++ End of shutdown hook");
232: }
233: }
234:
235: /**
236: * Setup the source writer. This can return <tt>null</tt>. Its
237: * actions will be synchronized for you.
238: */
239: protected abstract Writer setupSource();
240:
241: /**
242: * Extend this method to setup your own properties. Be sure to
243: * call the super's setupProps, though. Be sure to keep the
244: * properties around for how to connect to the source.
245: */
246: protected void setupProps(String prefix, Properties props) {
247: String wpf = props.getProperty(prefix + WRITES_PER_FLUSH_PROP);
248: if (wpf != null) {
249: try {
250: this .writesPerFlush = Integer.parseInt(wpf);
251: } catch (NumberFormatException e) {
252: // default
253: this .writesPerFlush = DEFAULT_WRITES_PER_FLUSH;
254: //e.printStackTrace();
255: }
256: }
257: //System.out.println("++ writes per flush = "+this.writesPerFlush);
258:
259: String uc = props.getProperty(prefix + USE_CACHE_PROP);
260: if (uc != null) {
261: this .useCache = Boolean.valueOf(uc).booleanValue();
262: }
263:
264: String mpf = props.getProperty(prefix + MILLIS_PER_FLUSH_PROP);
265: if (mpf != null) {
266: try {
267: this .millisPerFlush = Long.parseLong(mpf);
268: } catch (NumberFormatException e) {
269: this .millisPerFlush = DEFAULT_MILLIS_PER_FLUSH;
270: //e.printStackTrace();
271: }
272: }
273: //System.out.println("++ millis per flush = "+this.millisPerFlush);
274: if (this .millisPerFlush > 0) {
275: if (this .flushThread == null) {
276: // create our flush thread
277: Thread t = new Thread(new FlushRunner());
278: t.setDaemon(true);
279: t.setPriority(Thread.MIN_PRIORITY);
280: t.setName("Code Coverage Flusher");
281: t.start();
282: this .flushThread = t;
283: }
284: }
285: }
286:
287: /**
288: * Set to <tt>true</tt> if you want to try to reconnect to the
289: * source everytime the I/O write fails. This can be changed
290: * at any time.
291: */
292: protected void setReloadSourceAfterError(boolean yes) {
293: this .reloadSourceAfterError = yes;
294: }
295:
296: /**
297: * A consistent way to flush the write buffer. Set as "private final" to
298: * improve performance. Note that this method blocks.
299: */
300: private final void flushBuffer() {
301: synchronized (this .sync) {
302: if (this .writeCount <= 0 || this .source == null) {
303: // there were no writes since the last flush - exit.
304: //System.out.println("++ Nothing to flush.");
305: return;
306: }
307: try {
308: //System.out.println("++ Writing to source.");
309: this .source.write(this .buffer.toString());
310:
311: // ensure we flush out the source.
312: this .source.flush();
313:
314: // only delete and reset count if the write doesn't fail.
315: // This ensures that the data is actually written. Since
316: // we don't care about duplicates (on line boundaries,
317: // that is), this should be safe. Using a setLength is
318: // faster (avoids a System.arraycopy call) than delete.
319: this .buffer.setLength(0);
320: this .writeCount = 0;
321: } catch (IOException e) {
322: e.printStackTrace();
323: // failed I/O, so signal this.
324: this .source = null;
325: }
326: }
327: }
328:
329: protected void addShutdownHook() {
330: Class c = Runtime.class;
331: try {
332: java.lang.reflect.Method m = c.getMethod("addShutdownHook",
333: new Class[] { Thread.class });
334: Thread t = new Thread(this , this .getClass().getName()
335: + " shutdown hook");
336: m.invoke(Runtime.getRuntime(), new Object[] { t });
337: } catch (Exception ex) {
338: // prolly JDK 1.3 not supported.
339: // but it's not necessary...
340: //ex.printStackTrace();
341: }
342: }
343:
344: private static final void appendToLog(StringBuffer log,
345: short channelIndex, String classSig, short methodIndex,
346: short markIndex) {
347: log.append(channelIndex).append(',').append(classSig).append(
348: ',').append(
349: DirectoryChannelLogger.createCoverString(methodIndex,
350: markIndex));
351: }
352: }
|