001: /* StmtRegistry.java */
002: package org.quilt.cover.stmt;
003:
004: import java.lang.reflect.Field;
005: import java.util.Iterator;
006: import java.util.Hashtable;
007: import java.util.Map;
008: import java.util.Set;
009: import org.quilt.cl.*;
010: import org.quilt.reg.*;
011:
012: /**
013: * <p>Registry for statement coverage information. As Quilt-instrumented
014: * classes are loaded, they register their <code>q$$q</code> hit count
015: * arrays and are assigned an ID unique in the life of the registry.
016: * The registry maintains: </p>
017: * <ul>
018: * <li><b>hit counts</b>, keyed on class name</li>
019: * <li><b>method end counter indexes</b>, keyed on class and method name</li>
020: * <li><b>line number ranges</b>, keyed on class and counter index</li>
021: * </ul>
022: * <p>This and other information in the registry allows it the generate
023: * a number of reports summarizing coverage at</p>
024: * <ul>
025: * <li><b>package</b> level (soon)</li>
026: * <li><b>class</b> level (now)</li>
027: * <li><b>method</b> level (now)</li>
028: * <li><b>line</b> level (soonish)</li>
029: * </ul>
030: *
031: * <p>The registry is associated with the Quilt class loader when it
032: * is created. Information can been retrieved from the registry at
033: * any time. It will be accumulated as new instances of Quilt-instrumented
034: * classes are run.</p>
035: *
036: * @author <a href="mailto:jddixon@users.sourceforge.net">Jim Dixon</a>
037: */
038: public class StmtRegistry extends QuiltRegistry {
039:
040: /** XXX */
041: private static StmtRegistry INSTANCE = null;
042:
043: /** Returns a reference to the latest instance to announce itself. */
044: public static StmtRegistry getInstance() {
045: return INSTANCE;
046: }
047:
048: /** Maps class name to array of names of instrumented methods */
049: private Map methodNames = new Hashtable(); // key className, value String[]
050: /** Maps class name to array of index of last counters for each method */
051: private Map methodEnds = new Hashtable(); // key className, value int[]
052:
053: /**
054: * Constructor specifying Quilt class loader the registry is
055: * associated with.
056: */
057: public StmtRegistry(QuiltClassLoader qcl) {
058: super (qcl);
059: INSTANCE = this ; // XXX the horror
060: ClassAction classAct = new ClassAction(this );
061:
062: cxf = new ClassXformer[] { classAct };
063: mxf = new MethodXformer[] { new MethodAction(this ) };
064: gxf = new GraphXformer[] { new GraphAction(this , classAct) };
065: setTransformers();
066: }
067:
068: /**
069: * Clear counters associated with registry entries.
070: */
071: public void reset() {
072: // XXX DO NOTHING FOR NOW XXX
073: }
074:
075: /**
076: * Dump the registry as plain text.
077: * @todo More elaborate reports.
078: */
079: public String getReport() {
080: StringBuffer sb = new StringBuffer().append(
081: "\n=========================\n").append(
082: " QUILT COVERAGE REPORT \n");
083: if (isEmpty()) {
084: sb.append("* the registry is empty *\n");
085: } else {
086: Set keys = keySet();
087: Iterator i = keys.iterator();
088: while (i.hasNext()) {
089: // class --> hit count arrays
090: String[] name = (String[]) i.next();
091: int[] counts = (int[]) get(name);
092: int count = counts.length;
093: String className = name[0];
094:
095: sb.append(className + ": " + count + " counters, "
096: + getClassCoverage(className)
097: + "% coverage\n ");
098: // // DEBUG ONLY: DUMP COUNTER ARRAY
099: // for (int k = 0; k < count; k++) {
100: // sb.append(" " + counts[k]);
101: // }
102: // sb.append("\n");
103: // // END
104:
105: String[] methods = (String[]) methodNames
106: .get(className);
107: if (methods == null) {
108: sb.append(" NULL\n");
109: } else {
110: // there can be more than one <init> method :-(
111: for (int k = 0; k < methods.length; k++) {
112: sb.append(" ").append(methods[k]).append(
113: " ").append(
114: getMethodCoverage(className, k))
115: .append("% coverage\n");
116: }
117: }
118: }
119: }
120: sb.append("=========================\n");
121: return sb.toString();
122: }
123:
124: /**
125: * Get the percentage of counters in the class that have counts
126: * greater than zero. The percentage is rounded down. If no
127: * class information is found, returns zero.
128: *
129: * @return an integer between 0 and 100, zero if class not found
130: */
131: int getClassCoverage(String className) {
132: int nonZero = 0;
133: int[] hitCounts = (int[]) get(new String[] { className });
134: if (hitCounts != null) {
135: for (int k = 0; k < hitCounts.length; k++) {
136: if (hitCounts[k] > 0) {
137: nonZero++;
138: }
139: }
140: nonZero = (nonZero * 100) / hitCounts.length;
141: }
142: return nonZero;
143: }
144:
145: /**
146: * Get the percentage of counters in the Nth method that have counts
147: * greater than zero. The percentage is rounded down. If no
148: * class or method information is found, returns zero.
149: *
150: * XXX The 'methodEnds' array actually contains cumulative counts,
151: * so values must be reduced by one.
152: *
153: * @return an integer between 0 and 100, zero if class not found
154: */
155: int getMethodCoverage(String className, int n) {
156: int nonZero = 0;
157: // XXX should report if either of these two is null or if
158: // cardinalities differ
159: int[] hitCounts = (int[]) get(new String[] { className });
160: int[] ends = (int[]) methodEnds.get(className);
161: if (n < 0 || n >= hitCounts.length) {
162: throw new IllegalArgumentException("index out of range");
163: }
164: int counterCount = 0;
165: int lastCounter = ends[n] - 1;
166: int firstCounter = n == 0 ? 0 : ends[n - 1];
167: if (hitCounts != null && ends != null) {
168: for (int k = firstCounter; k <= lastCounter; k++) {
169: counterCount++;
170: if (hitCounts[k] > 0) {
171: nonZero++;
172: }
173: }
174: if (counterCount > 0) {
175: nonZero = (nonZero * 100) / counterCount;
176: }
177: }
178: return nonZero;
179: }
180:
181: // GET/PUT METHODS //////////////////////////////////////////////
182: // CLASSID ////////////////////////////////////////////
183: private static int nextClassID = 0;
184:
185: public int getClassID(String className) {
186: int classID = -1;
187: try {
188: Field qField = Class.forName(className).getField("q$$qID");
189: // a trifle uncertain about the argument
190: classID = qField.getInt(qField);
191: } catch (Exception e) {
192: // just ignore any errors
193: // DEBUG
194: System.out.println("StmtRegistry.getClassID(" + className
195: + ") failed - " + e);
196: // END
197: }
198: return classID;
199: }
200:
201: // HIT COUNTS /////////////////////////////////////////
202: /**
203: * Get a reference to the hit count array for a class.
204: */
205: public int[] getCounts(String className) {
206: int[] counts = null;
207: try {
208: counts = (int[]) get(new String[] { className });
209: } catch (Exception e) {
210: // just ignore any errors
211: System.out.println("StmtRegistry.getCounts (" + className
212: + ") failed - " + e);
213: }
214: return counts;
215: }
216:
217: /**
218: * Register a class by passing a reference to its integer hit count
219: * array. Returns a class ID which is unique within this run. The
220: * ID will be stored in a static in the class, public static int q$$qID
221: *
222: * XXX So far absolutely no value to using String array as key; could
223: * just be String.
224: *
225: * @param className Name of the class being registered.
226: * @param counts Reference to the class's public static hit count array.
227: * @return A unique class ID on success, -1 on failure.
228: */
229:
230: public int registerCounts(String className, int[] counts) {
231: int classID = -1;
232: try {
233: put(new String[] { className }, counts);
234: classID = nextClassID++;
235: } catch (Exception e) {
236: // just ignore any errors
237: System.out.println("StmtRegistry.registerCounts for "
238: + className + ", q$$q) failed - " + e);
239: }
240: return classID;
241: }
242:
243: public void registerMethods(String className, String[] methods,
244: int[] endCounts) {
245: if (className == null || methods == null || endCounts == null) {
246: throw new IllegalArgumentException("null parameter");
247: }
248: methodNames.put(className, methods);
249: methodEnds.put(className, endCounts);
250: }
251:
252: // VERSION ////////////////////////////////////////////
253: public int getQuiltVersion(String className) {
254: int quiltVersion = -1;
255: try {
256: Field qField = Class.forName(className).getField("q$$qVer");
257: // a trifle uncertain about the argument
258: quiltVersion = qField.getInt(qField);
259: } catch (Exception e) {
260: // just ignore any errors
261: // DEBUG
262: System.out.println("StmtRegistry.getClassID(" + className
263: + ") failed - " + e);
264: // END
265: }
266: return quiltVersion;
267: }
268:
269: // EPHEMERA ///////////////////////////////////////////
270: /** Maps class name to temporary data structure */
271: private Map ephemera = new Hashtable(); // key className
272:
273: /** get reference to temporary data for class */
274: Ephemera getEphemera(String className) {
275: if (ephemera.containsKey(className)) {
276: return (Ephemera) ephemera.get(className);
277: } else {
278: return null;
279: }
280: }
281:
282: /** add temporary data for class to registry */
283: boolean putEphemera(String className, Ephemera eph) {
284: if (ephemera.containsKey(className)) {
285: return false; // operation failed
286: }
287: // XXX Should allow for failure
288: ephemera.put(className, eph);
289: return true;
290: }
291:
292: /**
293: * Remove the reference from the registry.
294: *
295: * @param className Name of the class we are storing information about
296: * @return The reference or null if none was found or null was the
297: * stored value.
298: */
299: Ephemera removeEphemera(String className) {
300: return (Ephemera) ephemera.remove(className);
301: }
302:
303: // EXPERIMENT ///////////////////////////////////////////////////
304: /** STUB */
305: public int registerClass(String name, org.quilt.cover.stmt.QIC junk) {
306: System.out.println("**************************"
307: + "\nQCL.registerClass " + name
308: + "\n**************************");
309: return 52; // <=================================
310: }
311:
312: }
|