001: /*
002: * @(#)CoveragePostCompilerTask.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.ant;
028:
029: import java.io.File;
030: import java.io.FileInputStream;
031: import java.io.FileOutputStream;
032: import java.io.IOException;
033: import java.util.Enumeration;
034: import java.util.Properties;
035: import java.util.Vector;
036:
037: import net.sourceforge.groboutils.codecoverage.v2.IAnalysisModule;
038: import net.sourceforge.groboutils.codecoverage.v2.compiler.AlreadyPostCompiledException;
039: import net.sourceforge.groboutils.codecoverage.v2.compiler.PostCompileClass;
040: import net.sourceforge.groboutils.codecoverage.v2.datastore.DirMetaDataWriter;
041: import net.sourceforge.groboutils.util.io.v1.ReadByteStream;
042:
043: import org.apache.tools.ant.BuildException;
044: import org.apache.tools.ant.DirectoryScanner;
045: import org.apache.tools.ant.Project;
046: import org.apache.tools.ant.Task;
047: import org.apache.tools.ant.types.FileSet;
048: import org.apache.tools.ant.util.FileUtils;
049:
050: /**
051: * Ant task the compiles the class files into coverage files. Now, also
052: * generates the properties file for you.
053: *
054: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
055: * @version $Date: 2004/04/15 05:48:25 $
056: * @since December 18, 2002
057: * @deprecated Please use the GroboInstrumentTask instead of this one.
058: */
059: public class CoveragePostCompilerTask extends Task {
060: private static final FileUtils FILEUTILS = FileUtils.newFileUtils();
061:
062: private static final String CLASSNAME_EXT = ".class";
063:
064: private Vector filesets = new Vector();
065: private File datadir = null;
066: private File outfiledir = null;
067: private Vector analysisModules = new Vector();
068: private Configuration config = null;
069:
070: /**
071: * Add a new fileset instance to this compilation. Whatever the fileset is,
072: * only filename that are <tt>.class</tt> will be considered as
073: * 'candidates'. Currently, jar files are not read; you'll have to
074: * uncompress them to a directory before running this step.
075: *
076: * @param fs the new fileset containing the rules to get the testcases.
077: */
078: public void addFileSet(FileSet fs) {
079: this .filesets.addElement(fs);
080: }
081:
082: /**
083: * Sets the directory in which all the data accumulated from the
084: * post compilation step will be placed. This should be a directory
085: * dedicated just to the output data.
086: */
087: public void setDataDir(File f) {
088: this .datadir = f;
089: }
090:
091: /**
092: * Sets the directory in which all the recompiled class files will be
093: * placed. This directory should never be confused with the original
094: * class file location.
095: */
096: public void setOutClassDir(File f) {
097: this .outfiledir = f;
098: }
099:
100: /**
101: * Creates a new analysis module.
102: */
103: public AnalysisModuleType createAnalysisModule() {
104: AnalysisModuleType amt = new AnalysisModuleType();
105: this .analysisModules.addElement(amt);
106: return amt;
107: }
108:
109: /**
110: * Create the property file settings.
111: */
112: public Configuration createLogSettings() throws BuildException {
113: if (this .config != null) {
114: throw new BuildException(
115: "Only one logsetting tag can be in this target.");
116: }
117: this .config = new Configuration();
118: return this .config;
119: }
120:
121: /**
122: * Perform the task
123: */
124: public void execute() throws BuildException {
125: log(
126: "Use of the CoveragePostCompiler task is deprecated; please "
127: + "use the 'grobo-instrument' task instead. You may need to "
128: + "change the taskdef to reference the new resource file, located "
129: + "at [ant-grobocoverage.properties], instead of "
130: + "[net/sourceforge/groboutils/codecoverage/grobocoverage.properties"
131: + "].", Project.MSG_WARN);
132:
133: // pre-check
134: if (this .datadir == null) {
135: throw new BuildException(
136: "Attribute 'datadir' was never set.");
137: }
138: if (this .outfiledir == null) {
139: throw new BuildException(
140: "Attribute 'outfiledir' was never set.");
141: }
142:
143: // bug 906316: ensure the directories exist...
144: if (!this .datadir.exists()) {
145: this .datadir.mkdirs();
146: }
147: if (!this .outfiledir.exists()) {
148: this .outfiledir.mkdirs();
149: }
150:
151: ClassFile classFiles[] = getFilenames();
152: try {
153: log("Writing meta-data to directory '" + this .datadir
154: + "'.", Project.MSG_VERBOSE);
155: DirMetaDataWriter dmdw = new DirMetaDataWriter(this .datadir);
156: try {
157: PostCompileClass pcc = new PostCompileClass(dmdw,
158: getAnalysisModules());
159: for (int i = 0; i < classFiles.length; ++i) {
160: File infile = classFiles[i].srcFile;
161: String filename = classFiles[i].filename;
162:
163: // create the output class file, and ensure that
164: // its directory structure exists before creating it
165: File outfile = new File(this .outfiledir, filename);
166: log("Recompiling class '" + infile + "' to file '"
167: + outfile + "'.", Project.MSG_VERBOSE);
168: File parent = outfile.getParentFile();
169: if (!parent.exists()) {
170: parent.mkdirs();
171: }
172:
173: // need some code handle the situation where the
174: // outfile may be the same as the infile. This will
175: // also allow us to correctly handle the situation of
176: // an exception not properly creating the instrumented
177: // class. See bug 929332.
178: File tmpout = FILEUTILS.createTempFile(outfile
179: .getName(), ".tmp", parent);
180: FileOutputStream fos = new FileOutputStream(tmpout);
181:
182: try {
183: pcc
184: .postCompile(filename,
185: readFile(infile), fos);
186: fos.close();
187: fos = null;
188: FILEUTILS.copyFile(tmpout, outfile);
189: } catch (AlreadyPostCompiledException apce) {
190: // see bug 903837
191: log("Ignoring '" + infile
192: + "': it has already been "
193: + "post-compiled.", Project.MSG_INFO);
194: } finally {
195: if (fos != null) {
196: fos.close();
197: }
198: if (tmpout.exists()) {
199: tmpout.delete();
200: }
201: }
202: }
203: } finally {
204: dmdw.close();
205: }
206: } catch (IOException ioe) {
207: throw new BuildException("I/O exception during execution.",
208: ioe, getLocation());
209: }
210:
211: // generate the property file
212: if (this .config == null) {
213: // use defaults if it wasn't set.
214: this .config = new Configuration();
215: }
216: try {
217: this .config.generatePropertyFile(this .outfiledir,
218: this .analysisModules.size());
219: } catch (IOException ioe) {
220: throw new BuildException("I/O exception during execution.",
221: ioe, getLocation());
222: }
223: }
224:
225: /**
226: *
227: */
228: private IAnalysisModule[] getAnalysisModules()
229: throws BuildException
230: {
231: final Vector v = new Vector();
232: final Enumeration enum = this .analysisModules.elements();
233: while (enum.hasMoreElements())
234: {
235: AnalysisModuleType amt = (AnalysisModuleType)enum.nextElement();
236: IAnalysisModule am = amt.getAnalysisModule();
237: v.addElement( am );
238: }
239: final IAnalysisModule[] amL = new IAnalysisModule[ v.size() ];
240: v.copyInto( amL );
241: return amL;
242: }
243:
244: /**
245: * Iterate over all filesets and return the filename of all files
246: * that end with <tt>.class</tt> (case insensitive). This is to avoid
247: * trying to parse a non-class file.
248: *
249: * @return an array of filenames to parse.
250: */
251: private ClassFile[] getFilenames() {
252: Vector v = new Vector();
253: final int size = this .filesets.size();
254: for (int j = 0; j < size; j++) {
255: FileSet fs = (FileSet) filesets.elementAt(j);
256: DirectoryScanner ds = fs.getDirectoryScanner(getProject());
257: File baseDir = ds.getBasedir();
258: ds.scan();
259: String[] f = ds.getIncludedFiles();
260: for (int k = 0; k < f.length; k++) {
261: String pathname = f[k];
262: if (pathname.toLowerCase().endsWith(CLASSNAME_EXT)) {
263: // this isn't right
264: v.addElement(new ClassFile(baseDir, pathname));
265: }
266: }
267: }
268:
269: ClassFile[] files = new ClassFile[v.size()];
270: v.copyInto(files);
271: return files;
272: }
273:
274: private static final class ClassFile {
275: public File srcFile;
276: public String filename;
277:
278: public ClassFile(File baseDir, String filename) {
279: if (baseDir == null || filename == null) {
280: throw new IllegalArgumentException("no null args.");
281: }
282: this .filename = filename;
283: this .srcFile = new File(baseDir, filename);
284: }
285: }
286:
287: public static final class Configuration {
288: public String factory = "net.sourceforge.groboutils.codecoverage.v2.logger.DirectoryChannelLoggerFactory";
289: public File logDir;
290: public int cacheSize = -1;
291:
292: public void setFactory(String name) {
293: if (name.indexOf(".") < 0) {
294: this .factory = "net.sourceforge.groboutils.codecoverage.v2.logger."
295: + name;
296: } else {
297: this .factory = name;
298: }
299: }
300:
301: public void setLogdir(File dirname) {
302: this .logDir = dirname;
303: }
304:
305: public void setCacheSize(int size) {
306: this .cacheSize = size;
307: }
308:
309: public void generatePropertyFile(File outfiledir,
310: int moduleCount) throws IOException {
311: Properties props = new Properties();
312: if (this .factory != null) {
313: props.setProperty("factory", this .factory);
314: }
315: if (this .logDir != null) {
316: props.setProperty("logger.dir", this .logDir
317: .getAbsolutePath());
318: }
319: if (this .cacheSize > 0) {
320: props.setProperty("logger.cache-size", Integer
321: .toString(this .cacheSize));
322: }
323: props.setProperty("channel-count", Integer
324: .toString(moduleCount));
325:
326: FileOutputStream fos = new FileOutputStream(new File(
327: outfiledir, "grobocoverage.properties"));
328: try {
329: props.store(fos, "CodeCoverage setup file");
330: } finally {
331: fos.close();
332: }
333: }
334: }
335:
336: /**
337: *
338: */
339: private byte[] readFile(File file) throws IOException {
340: FileInputStream fis = new FileInputStream(file);
341: try {
342: byte[] outfile = ReadByteStream.readByteStream(fis);
343: return outfile;
344: } finally {
345: fis.close();
346: }
347: }
348: }
|