001: package csdl.jblanket.ant;
002:
003: import java.util.ArrayList;
004: import java.util.Date;
005: import java.util.Iterator;
006: import java.util.List;
007:
008: import org.apache.tools.ant.BuildException;
009: import org.apache.tools.ant.DirectoryScanner;
010: import org.apache.tools.ant.types.FileSet;
011:
012: /**
013: * Implements the JBlanket Apache Ant task definition that instruments the byte code. All files
014: * (.class or .jar files) specified by the fileSet element in the Ant task are processed by the
015: * Modifier class and stored in the directory specified by the jblanket.dir system property.
016: * If not set, the default directory is <user_home>/<user_account>/jblanket/.
017: * <p>
018: * <b>Required</b> nested element for the instrumentation task:
019: * <ul>
020: * <p>
021: * 'fileset' - describes the files to be modified<br>
022: * <i>For example</i>: see <a href="http://jakarta.apache.org/ant/manual/index.html">Ant</a>
023: * </ul>
024: * <p>
025: * Use multiple 'fileset' elements to specify different locations of files to modify.
026: * <p>
027: * <b>Optional</b> nested element for the instrumentation task:
028: * <ul>
029: * <p>
030: * 'packageprefix' - describes the package prefixes to be modified in a JAR file. To instrument
031: * all classes in a JAR file, do not included this nested element.<br>
032: * <i>For example</i>: see <a href="PackagePrefix.html" target="classFrame">PackagePrefix</a>
033: * <p>
034: * 'sysproperty' - describes additional system properties not set in the environment. If
035: * jblanket.dir is not set as an environment variable, include this nested element
036: * to set it.
037: * <i>For example</i>: see <a href="http://jakarta.apache.org/ant/manual/index.html">Ant</a>
038: * </ul>
039: * <p>
040: * <b>Required</b> attribute for the instrumentation task:
041: * <ul>
042: * <p>
043: * 'testgrammar' - grammar describing the names of JUnit test classes<br>
044: * <i>For example</i>: testgrammar="Test*.class"
045: * </ul>
046: * <p>
047: * <b>Optional</b> attributes for the instrumentation task:
048: * <ul>
049: * <p>
050: * 'enable' - describes if the byte code should be modified. Valid values include "true", "on",
051: * "yes" to modify byte code or "false", "off", or "no" to not modify byte code.<br>
052: * <i>For example</i>: enable="true"
053: * <p>
054: * 'verbose' - describes if additional output should be sent to standard out.
055: * Valid values include "true", "on", "yes" for additional output
056: * or "false", "off", or "no" for no additional output.<br>
057: * <i>For example</i>: verbose="false"
058: * <p>
059: * 'excludeonelinemethods' - describes if methods with one line of code should be excluded from
060: * the coverage measurement. Values include "true", "on", "yes" to
061: * exclude one-line methods or "false", "off", or "no" to include them.
062: * <br>
063: * <i>For example</i>: excludeonelinemethods="false"
064: * <p>
065: * 'excludeconstructors' - describes if constructors should be excluded from the coverage
066: * measurement. Values include "true", "on", "yes" to exclude constructors
067: * or "false", "off", or "no" to include them.<br>
068: * <i>For example</i>: excludeconstructors="false"
069: * <p>
070: * 'totalfile' - name of XML file to contain all methods included in the coverage measurement<br>
071: * <i>For example</i>: totalfile="totalMethods.xml"
072: * <p>
073: * 'untestablefile' - name of XML file to contain all abstract or native methods<br>
074: * <i>For example</i>: untestablefile="untestableMethods.xml"
075: * <p>
076: * 'excludedfile' - name of XML file to contain all methods excluded from the coverage measurement
077: * by either the fileset or packageprefix nested element<br>
078: * <i>For example</i>: excludedfile="excludedMethods.xml"
079: * <p>
080: * 'onelinefile' - name of XML file to contain all one-line methods<br>
081: * <i>For example</i>: onelinefile="oneLineMethods.xml"
082: * <p>
083: * 'constructionfile' - name of XML file to contain all constructors<br>
084: * <i>For example</i>: constructorfile="constructorMethods.xml"
085: * </ul>
086: * <p>
087: * If any of the optional attributes are not specified, their default values specified in the
088: * examples are used. For example, to exclude methods with one line of source code from the
089: * coverage measurement, specify the 'excludeonelinemethods' attribute with a 'true' value.
090: * When not specified, one-line methods will be included in the coverage measurement.
091: * <p>
092: * Use this class to execute JBlanket with Ant. One example of the 'jblanket' Ant target is:
093: * <pre>
094: * <taskdef name="jblanket" classname="csdl.jblanket.ant.JBlanketModifierTask"/>
095: * <jblanket testgrammar="Test*.class"
096: * excludeonelinemethods="true"
097: * totalfile="myTotalMethods.xml"
098: * untestablefile="myUntestableMethods.xml">
099: * <fileset dir="${basedir}">
100: * <include name="**/stackmvc/**/*.class"/>
101: * </fileset>
102: * <fileset dir="${lib.dir}">
103: * <include name="stack.jar"/>
104: * </fileset>
105: * </jblanket>
106: * </pre>
107: * From the example, one-line methods are excluded and constructors are included. The total
108: * methods will be stored in 'myTotalMethods.xml' and untestable methods are stored in
109: * 'myUntestableMethods.xml'. The default file names for excluded methods and one-line methods are
110: * used. No additional output will be sent to standard out. The files to be modified are the
111: * .class files found in the subdirectories beginning with 'stackmvc' and the 'stack.jar' file in
112: * ${lib.dir}.
113: * <p>
114: * Another example is:
115: * <pre>
116: * <jblanket testgrammar="*Test.class"
117: * verbose="true">
118: * <fileset dir="${src.dir}">
119: * <include name="**/foo/*.class"/>
120: * </fileset>
121: * </jblanket>
122: * </pre>
123: * where the total methods, untestable methods, and excluded methods are stored in the default
124: * files. One-line methods and constructors are included in coverage. Additional output will be
125: * sent to standard out. The files to be modified are the .class files found in the
126: * subdirectories beginning with 'foo'.
127: * <p>
128: * NOTE: For JBlanket to collect data, all files MUST be compiled with debug turned "on".
129: *
130: * @author Joy M. Agustin
131: * @version $Id: JBlanketModifierTask.java,v 1.2 2005/02/19 05:55:19 timshadel Exp $id
132: */
133: public class JBlanketModifierTask extends JBlanketTask {
134:
135: /** Grammar for test class names */
136: private String testGrammar;
137:
138: /** Name of file for untestable methods, i.e., abstract and native methods */
139: protected String untestableFile;
140: /** Name of file for excluded methods, i.e., methods in classes user specifically excludes */
141: protected String excludedFile;
142:
143: /** Set(s) of files to be modified */
144: private ArrayList fileSets;
145: /** Package prefixes of packages modify */
146: private ArrayList packagePrefixes;
147:
148: /**
149: * Constructs a new JBlanketModifierTask object.
150: */
151: public JBlanketModifierTask() {
152: super ();
153:
154: this .testGrammar = null;
155:
156: this .untestableFile = null;
157: this .excludedFile = null;
158:
159: this .fileSets = new ArrayList();
160: this .packagePrefixes = new ArrayList();
161: }
162:
163: /**
164: * Adds a set of files specified by a nested fileset element.
165: *
166: * @param fileSet set of files to be modified.
167: */
168: public void addFileSet(FileSet fileSet) {
169: this .fileSets.add(fileSet);
170: }
171:
172: /**
173: * Adds a PackagePrefix specified by a nested packageprefix element.
174: *
175: * @param packagePrefix the PackagePrefix to include in coverage.
176: */
177: public void addPackagePrefix(PackagePrefix packagePrefix) {
178: this .packagePrefixes.add(packagePrefix);
179: }
180:
181: /**
182: * Sets the grammar describing the names of test classes.
183: * <p>
184: * For example, testGrammar="Test*.class" means that the names of all test class begin with
185: * "Test".
186: *
187: * @param testGrammar the new grammar.
188: */
189: public void setTestgrammar(String testGrammar) {
190: this .testGrammar = testGrammar;
191: }
192:
193: /**
194: * Sets the name of the XML file for untestable methods. I.e., abstract and native methods.
195: *
196: * @param untestableFile the untestable file name.
197: */
198: public void setUntestablefile(String untestableFile) {
199: this .untestableFile = untestableFile;
200: }
201:
202: /**
203: * Sets the name of the XML file for excluded methods. I.e., methods found in classes
204: * that are excluded by either the fileset or the packageprefix.
205: *
206: * @param excludedFile the excluded file name.
207: */
208: public void setExcludedfile(String excludedFile) {
209: this .excludedFile = excludedFile;
210: }
211:
212: /**
213: * Transforms <code>array</code> to a ';' delimited String.
214: *
215: * @param array the array of Strings to transform.
216: * @param message a message to associate with each <code>array</code> element.
217: * Used for verbose mode.
218: * @return a ';' delimited String.
219: */
220: private String arrayToString(String[] array, String message) {
221:
222: StringBuffer delimitedString = new StringBuffer();
223:
224: for (int i = 0; i < array.length; i++) {
225:
226: if (i > 0) {
227: delimitedString.append(";");
228: }
229:
230: delimitedString.append(array[i]);
231:
232: if (super .verbose) {
233: System.out.println(message + ": " + array[i]);
234: }
235: }
236:
237: return delimitedString.toString();
238: }
239:
240: /**
241: * Transforms <code>list</code> to a ';' delimited String.
242: *
243: * @param list the List of Strings to transform.
244: * @param message a message to associate with each message. Used for verbose mode.
245: * @return a ';' delimited String.
246: */
247: private String listToString(List list, String message) {
248:
249: StringBuffer delimitedString = new StringBuffer();
250:
251: for (Iterator i = list.iterator(); i.hasNext();) {
252:
253: if (delimitedString.length() != 0) {
254: delimitedString.append(";");
255: }
256:
257: String next = ((PackagePrefix) i.next()).getName();
258: delimitedString.append(next);
259:
260: if (super .verbose) {
261: System.out.println(message + ": " + next);
262: }
263: }
264:
265: return delimitedString.toString();
266: }
267:
268: /**
269: * Executes the byte code instrumentation Ant taskdef.
270: */
271: public void execute() {
272:
273: Date startTime = new Date();
274:
275: // Return without modifying files if jblanket is disabled.
276: if (!super .enable) {
277:
278: if (super .verbose) {
279: System.out
280: .println("JBlanket disabled; no files modified.");
281: }
282:
283: return;
284: }
285:
286: // process each fileset separately because each may have different base directories
287: for (Iterator i = this .fileSets.iterator(); i.hasNext();) {
288:
289: // Format Modifier arguments.
290: ArrayList args = new ArrayList();
291:
292: // add the directory containing all the files
293: FileSet fileSet = (FileSet) i.next();
294: DirectoryScanner scanner = fileSet
295: .getDirectoryScanner(getProject());
296: args.add("-classDir");
297: args.add(scanner.getBasedir().getAbsolutePath());
298:
299: // add all files to modify
300: args.add("-include");
301: args.add(arrayToString(scanner.getIncludedFiles(),
302: "modifying"));
303:
304: // add all files to not modify
305: args.add("-exclude");
306: args.add(arrayToString(scanner.getExcludedFiles(),
307: "excluding"));
308:
309: // add verbose
310: args.add("-verbose");
311: args.add(new Boolean(super .verbose));
312:
313: // add grammar for test file names
314: args.add("-testGrammar");
315: args.add(this .testGrammar);
316:
317: // add oneLineFile if user specified
318: if (super .excludeOneLineMethods) {
319: args.add("-excludeOneLineMethods");
320: args.add(new Boolean(super .excludeOneLineMethods));
321: if (super .oneLineFile != null) {
322: args.add("-oneLineFile");
323: args.add(super .oneLineFile);
324: }
325: }
326:
327: // add constructorFile if user specified
328: if (super .excludeConstructors) {
329: args.add("-excludeConstructors");
330: args.add(new Boolean(super .excludeConstructors));
331: if (super .constructorFile != null) {
332: args.add("-constructorFile");
333: args.add(super .constructorFile);
334: }
335: }
336:
337: // add totalFile if user specified
338: if (super .totalFile != null) {
339: args.add("-totalFile");
340: args.add(super .totalFile);
341: }
342:
343: // add untestableFile if user specified
344: if (this .untestableFile != null) {
345: args.add("-untestableFile");
346: args.add(this .untestableFile);
347: }
348:
349: // add constructorFile if user specified
350: if (super .excludeIndividualMethods) {
351: args.add("-excludeIndividualMethods");
352: args.add(new Boolean(super .excludeIndividualMethods));
353: }
354:
355: // add excludedFile if user specified
356: if (this .excludedFile != null) {
357: args.add("-excludedFile");
358: args.add(this .excludedFile);
359: }
360:
361: // add any package prefixes
362: if (this .packagePrefixes.size() != 0) {
363: args.add("-packagePrefix");
364: args.add(listToString(packagePrefixes,
365: "package prefixes"));
366: }
367:
368: // Execute Modifier.main on arguments.
369: // TODO write out Exceptions to a Log, and print an error message to the screen.
370: try {
371: csdl.jblanket.modifier.Modifier.main(args);
372: } catch (Exception e) {
373: e.printStackTrace();
374: throw new BuildException("Error in JBlanket.");
375: } catch (Throwable t) {
376: t.printStackTrace();
377: throw new BuildException(
378: "Unsuccessful modification of files");
379: }
380: }
381:
382: Date endTime = new Date();
383: long elapsedTime = (endTime.getTime() - startTime.getTime()) / 1000;
384: System.out.println("JBlanket modify task completed " + "("
385: + elapsedTime + " secs.)");
386: }
387: }
|