001: /*
002: * @(#)CoverageLogger.java
003: *
004: * Copyright (C) 2002-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.InputStream;
030: import java.util.Properties;
031:
032: import net.sourceforge.groboutils.codecoverage.v2.IChannelLogger;
033: import net.sourceforge.groboutils.codecoverage.v2.IChannelLoggerFactory;
034:
035: /**
036: * The singleton invoked at runtime to log each marked bytecode instruction
037: * covered.
038: * <P>
039: * This class needs to be fast, efficient, thread-safe, and classloader-safe.
040: * "Classloader-safe" means that it needs to be resiliant to multiple instances
041: * of this class being loaded, and possibly interfering with each other.
042: * <P>
043: * As of 12-Feb-2003, this class loads up its properties from a property
044: * file, in the same way that Log4J loads its properties. It attempts to
045: * load the property file "/grobocoverage.properties" from the system
046: * resources. If the file cannot be found, then a warning is displayed
047: * to STDERR.
048: *
049: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
050: * @version $Date: 2004/05/14 21:12:11 $
051: * @since December 15, 2002
052: */
053: public final class CoverageLogger implements ICoverageLoggerConst {
054: private static final String PROP_FILE_NAME = "grobocoverage.properties";
055: private static final String PROP_FILE_RES = '/' + PROP_FILE_NAME;
056:
057: private static final boolean DEBUG = false;
058:
059: private static final String FACTORY_PROP = "factory";
060: private static final String CHANNEL_COUNT_PROP = "channel-count";
061: private static final String LOGGER_INIT_BASE_PROP = "logger.";
062: private static final int DEFAULT_CHANNEL_COUNT = 0;
063: private static final Class DEFAULT_CHANNEL_FACTORY = NoOpChannelLoggerFactory.class;
064:
065: // this must be defined before initBase(), and shouldn't set the loggers
066: // to anything, due to the static processing order
067: // of Java. We avoid the null pointer checking by using the loggers count
068: // field.
069: private static IChannelLogger LOGGERS[];
070: private static short LOGGERS_COUNT = (short) 0;
071:
072: /**
073: * Initialize the logger on class loading.
074: */
075: static {
076: initBase();
077: }
078:
079: /**
080: * The primary entry method. This must be lean, mean, thread-safe,
081: * and classloader-safe.
082: */
083: public static final void cover(String classSig, short methodIndex,
084: short channel, short markIndex) {
085: if (channel >= 0 && channel < LOGGERS_COUNT) {
086: //debug( "Logging ["+classSig+";"+methodIndex+":"+channel+
087: // "-"+markIndex+"]" );
088: //error( "Logging ["+classSig+";"+methodIndex+":"+channel+
089: // "-"+markIndex+"]" );
090: LOGGERS[(int) channel].cover(classSig, methodIndex,
091: markIndex);
092: }
093: /*
094: else
095: {
096: if (LOGGERS == null)
097: {
098: debug( "Couldn't log ["+classSig+";"+channel+":"+methodIndex+"."+
099: "-"+markIndex+"]: LOGGERS=null" );
100: }
101: else
102: {
103: debug( "Couldn't log ["+classSig+";"+channel+":"+methodIndex+"."+
104: "-"+markIndex+"]: LOGGERS length="+LOGGERS.length );
105: }
106: }
107: */
108: }
109:
110: /**
111: * Initializes or reinitializes the static logger object based on the
112: * logger property file, which will be used as the argument to
113: * <tt>init( Properties )</tt>. If no such file is found, then a
114: * warning is reported to STDERR, and the System properties are passed
115: * into the <tt>init</tt> method.
116: */
117: public static final void initBase() {
118: // make sure we set the loggers count to 0 before we get started.
119: // this way, we don't have to deal with LOGGERS null checking.
120: LOGGERS_COUNT = (short) 0;
121:
122: Properties props = null;
123: try {
124: InputStream is = CoverageLogger.class
125: .getResourceAsStream(PROP_FILE_RES);
126: if (is == null) {
127: is = ClassLoader
128: .getSystemResourceAsStream(PROP_FILE_NAME);
129: }
130: if (is != null) {
131: debug("Loading " + PROP_FILE_NAME);
132: props = new Properties();
133: props.load(is);
134: } else {
135: error("No resource named " + PROP_FILE_NAME + ".");
136: }
137: } catch (ThreadDeath td) {
138: // never catch these
139: throw td;
140: } catch (Throwable t) {
141: error(t.toString());
142: // ignore
143: }
144:
145: if (props == null) {
146: props = System.getProperties();
147: error("Please create and/or add the file " + PROP_FILE_NAME
148: + " to the classpath.");
149: if (DEBUG) {
150: System.exit(1);
151: }
152: }
153: init(props);
154: }
155:
156: /**
157: * Initializes or reinitializes the static logger object with a specific
158: * set of properties.
159: *
160: * @param props collection of properties used to discover the channel
161: * logger factory, and to initialize the new channel logger.
162: */
163: public static final void init(Properties props) {
164: // start out by saying that there are no loggers. This will only
165: // change if we get to the end without errors.
166: LOGGERS_COUNT = (short) 0;
167:
168: if (props == null) {
169: error("Encountered null properties instance.");
170: return;
171: }
172:
173: short channelCount = getChannelCount(props);
174: IChannelLoggerFactory factory = getChannelLoggerFactory(props);
175: if (factory != null) {
176: // only assign the loggers at the end, after we've created
177: // everything
178:
179: IChannelLogger ls[] = new IChannelLogger[channelCount];
180: for (short i = 0; i < channelCount; ++i) {
181: ls[(int) i] = factory.createChannelLogger(
182: LOGGER_INIT_BASE_PROP, props, i);
183: debug("Logger " + i + " = " + ls[(int) i]);
184: }
185:
186: LOGGERS = ls;
187:
188: }
189: // else keep the loggers list as it was.
190:
191: if (LOGGERS == null) {
192: debug("Logger list is null.");
193: LOGGERS = new IChannelLogger[0];
194: }
195:
196: LOGGERS_COUNT = (short) LOGGERS.length;
197: }
198:
199: private static final short getChannelCount(Properties props) {
200: short channelCount = DEFAULT_CHANNEL_COUNT;
201: String countStr = props.getProperty(CHANNEL_COUNT_PROP);
202: if (countStr != null && countStr.length() > 0) {
203: try {
204: // if this line throws an exception, it will not
205: // disturb the default count.
206: int i = Integer.parseInt(countStr);
207:
208: if (i < 0 || i > Short.MAX_VALUE) {
209: error("Channel count is outside range [0.."
210: + Short.MAX_VALUE + "]. Using default.");
211: } else {
212: // no exception was thrown, so assign the new count.
213: channelCount = (short) i;
214: }
215: } catch (NumberFormatException ex) {
216: error("Trouble translating channel count ('" + countStr
217: + "') to a number. Using default.");
218: }
219: }
220: debug("Channel Count = " + channelCount);
221: return channelCount;
222: }
223:
224: private static final IChannelLoggerFactory getChannelLoggerFactory(
225: Properties props) {
226: String factoryClassName = props.getProperty(FACTORY_PROP);
227: Class factoryClass = DEFAULT_CHANNEL_FACTORY;
228: IChannelLoggerFactory factory = null;
229:
230: if (factoryClassName != null) {
231: try {
232: // if this line throws an exception, it will not
233: // disturb the factoryClass object.
234: Class c = Class.forName(factoryClassName);
235:
236: // no exception was thrown, so assign the new class.
237: factoryClass = c;
238: } catch (ThreadDeath td) {
239: // never catch these
240: throw td;
241: } catch (Throwable t) {
242: error("Could not load factory class '"
243: + factoryClassName + "'. Using default.");
244: }
245: }
246:
247: try {
248: factory = (IChannelLoggerFactory) factoryClass
249: .newInstance();
250: } catch (ThreadDeath td) {
251: // never catch these
252: throw td;
253: } catch (Throwable t) {
254: error("Couldn't create a new factory or cast it to a "
255: + (IChannelLoggerFactory.class.getName())
256: + ". Not using a logger.");
257: }
258:
259: debug("Factory = " + factory);
260: return factory;
261: }
262:
263: private static final void error(String message) {
264: System.err.println(CoverageLogger.class.getName() + ": "
265: + message);
266: }
267:
268: private static final void debug(String message) {
269: if (DEBUG) {
270: System.out.println("DEBUG ["
271: + CoverageLogger.class.getName() + "]: " + message);
272: }
273: }
274: }
|