001: /*
002: * @(#)GroboReportTask.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.ant;
028:
029: import java.io.File;
030: import java.io.IOException;
031: import java.util.Enumeration;
032: import java.util.Vector;
033:
034: import net.sourceforge.groboutils.codecoverage.v2.IAnalysisModule;
035: import net.sourceforge.groboutils.codecoverage.v2.IChannelLogReader;
036: import net.sourceforge.groboutils.codecoverage.v2.datastore.AnalysisModuleSet;
037: import net.sourceforge.groboutils.codecoverage.v2.datastore.DirMetaDataReader;
038: import net.sourceforge.groboutils.codecoverage.v2.datastore.IMetaDataReader;
039: import net.sourceforge.groboutils.codecoverage.v2.logger.DirectoryChannelLogReader;
040: import net.sourceforge.groboutils.codecoverage.v2.report.AnalysisModuleData;
041: import net.sourceforge.groboutils.codecoverage.v2.report.IReportGenerator;
042: import net.sourceforge.groboutils.codecoverage.v2.report.XmlCombinedReportGenerator;
043: import net.sourceforge.groboutils.codecoverage.v2.report.XmlReportGenerator;
044: import net.sourceforge.groboutils.codecoverage.v2.util.ILogFilter;
045: import net.sourceforge.groboutils.codecoverage.v2.util.SingleLogFilter;
046:
047: import org.apache.tools.ant.BuildException;
048: import org.apache.tools.ant.Project;
049: import org.apache.tools.ant.Task;
050: import org.apache.tools.ant.types.EnumeratedAttribute;
051:
052: import org.w3c.dom.Document;
053: import org.w3c.dom.Element;
054:
055: /**
056: * A variation of the CoveragePostCompilerTask. This one is intended to
057: * simplify the Ant build files. See
058: * <a href="https://sourceforge.net/tracker/index.php?func=detail&aid=901588&group_id=22594&atid=375592">
059: * feature request 901588</a> for details.
060: *
061: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
062: * @version $Date: 2004/07/07 09:39:09 $
063: * @since March 13, 2004
064: */
065: public class GroboReportTask extends Task {
066: private File baselogdir;
067: private File logdir;
068: private File datadir;
069: private Vector singleStyles = new Vector();
070: private Vector comboStyles = new Vector();
071: private boolean failOnError = true;
072: private LogFilter filter = null;
073:
074: public static final class FilterTypeAttribute extends
075: EnumeratedAttribute {
076: private String[] types = { "single", "none" };
077:
078: public String[] getValues() {
079: return this .types;
080: }
081: }
082:
083: public static class LogFilter {
084: private String type = "none";
085:
086: public void setType(FilterTypeAttribute fta) {
087: this .type = fta.getValue();
088: }
089:
090: public ILogFilter getFilter() {
091: if ("single".equalsIgnoreCase(this .type)) {
092: return new SingleLogFilter();
093: }
094: // else
095: return null;
096: }
097: }
098:
099: /**
100: * Set the directory which contains the log and data directories.
101: */
102: public void setLogDir(File dir) {
103: this .baselogdir = dir;
104: }
105:
106: /**
107: * Add a filter to convert the Logger's output format to the standard
108: * format. If no filter is given, then we assume that the logs are
109: * already in the standard format.
110: */
111: public void addLogFilter(LogFilter lf) {
112: this .filter = lf;
113: }
114:
115: /**
116: *
117: */
118: public void addXml(XmlReportStyle xrs) {
119: // creates an XML report for both the single and combo files.
120: _addSingle(xrs);
121: _addCombo(xrs);
122: }
123:
124: /**
125: *
126: */
127: public void addXsl(SimpleXslReportStyle sxrs) {
128: _addSingle(sxrs);
129: }
130:
131: /**
132: *
133: */
134: public void addSourceXsl(SourceXslReportStyle sxrs) {
135: _addCombo(sxrs);
136: }
137:
138: /**
139: *
140: */
141: public void addSimpleHtml(SimpleHtmlReportStyle shrs) {
142: _addSingle(shrs);
143: }
144:
145: /**
146: *
147: */
148: public void addSimple(SimpleHtmlReportStyle shrs) {
149: _addSingle(shrs);
150: }
151:
152: /**
153: *
154: */
155: public void addSourceHtml(SourceHtmlReportStyle shrs) {
156: _addCombo(shrs);
157: }
158:
159: /**
160: *
161: */
162: public void addSource(SourceHtmlReportStyle shrs) {
163: _addCombo(shrs);
164: }
165:
166: /**
167: *
168: */
169: public void addFailOn(FailOnReportStyle fors) {
170: _addCombo(fors);
171: }
172:
173: public void setFailOnError(boolean f) {
174: this .failOnError = f;
175: }
176:
177: public void execute() throws BuildException {
178: try {
179: setupDirectories();
180: } catch (IOException ioe) {
181: throw new BuildException(
182: "Error setting up the directories.", ioe);
183: }
184:
185: IMetaDataReader mdr = createMetaDataReader();
186: IReportGenerator rg = new XmlReportGenerator();
187: boolean errors = false;
188: Vector reports = new Vector();
189: Vector errorList = new Vector();
190: try {
191: AnalysisModuleSet ams = mdr.getAnalysisModuleSet();
192: IAnalysisModule amL[] = ams.getAnalysisModules();
193: for (int i = 0; i < amL.length; ++i) {
194: try {
195: IChannelLogReader clr = createChannelLogReader(
196: amL[i], ams);
197: AnalysisModuleData amd = new AnalysisModuleData(
198: amL[i], mdr, clr);
199: Element rootEl = createReport(amL[i], amd, rg);
200: if (rootEl == null) {
201: log("Creating the report returned null",
202: Project.MSG_WARN);
203: errors = true;
204: } else {
205: Document doc = rootEl.getOwnerDocument();
206: reports.addElement(doc);
207: processSingleStyles(doc, amL[i]
208: .getMeasureName());
209: }
210: } catch (IllegalArgumentException iae) {
211: iae.printStackTrace();
212: log(iae.getMessage(), Project.MSG_WARN);
213: errors = true;
214: }
215: }
216: finishSingleStyles(errorList);
217: } catch (IOException e) {
218: throw new BuildException(
219: "I/O Exception while creating a report.", e,
220: getLocation());
221: } finally {
222: try {
223: mdr.close();
224: } catch (IOException e) {
225: throw new BuildException(
226: "I/O Exception while closing meta-data reader.",
227: e, getLocation());
228: }
229: }
230: rg = null;
231: mdr = null;
232: try {
233: processCombos(reports, errorList);
234: } catch (IOException e) {
235: throw new BuildException(
236: "I/O Exception while creating a report.", e,
237: getLocation());
238: }
239:
240: if (errors && this .failOnError) {
241: throw new BuildException(
242: "No coverage logs were generated, or the logs "
243: + "are not located under '" + this .logdir
244: + "'.", getLocation());
245: }
246:
247: if (errorList.size() > 0) {
248: StringBuffer sb = new StringBuffer();
249: Enumeration e = errorList.elements();
250: while (e.hasMoreElements()) {
251: sb.append("; ").append((String) e.nextElement());
252: }
253: if (this .failOnError) {
254: throw new BuildException(
255: "Error conditions caused failure"
256: + sb.toString());
257: } else {
258: log("Warning: " + sb.toString(), Project.MSG_WARN);
259: }
260: }
261: }
262:
263: private void processCombos(Vector reports, Vector errorList)
264: throws BuildException, IOException {
265: // only process the combos (and thus create the uber XML file)
266: // if needed.
267: if (this .comboStyles.size() > 0) {
268: // Create the uber document
269: Document docs[] = new Document[reports.size()];
270: reports.copyInto(docs);
271:
272: // a memory conservation item. This means this can only be
273: // the last method to run with the reports from execute()
274: reports.removeAllElements();
275:
276: XmlCombinedReportGenerator gen = new XmlCombinedReportGenerator();
277: log("Creating combined coverage report", Project.MSG_INFO);
278: Element ret = null;
279: try {
280: ret = gen.createReport(docs);
281: } catch (IllegalArgumentException iae) {
282: // throw new BuildException( iae );
283:
284: // this is a hack: really, the underlying code should be made
285: // more robust.
286: iae.printStackTrace();
287: log(iae.getMessage(), Project.MSG_WARN);
288: ret = null;
289: }
290: docs = null;
291:
292: if (ret != null) {
293: processComboStyles(ret.getOwnerDocument());
294: finishComboStyles(errorList);
295: }
296: }
297: }
298:
299: private void processSingleStyles(Document doc, String moduleName)
300: throws BuildException, IOException {
301: processStyles(doc, moduleName, this .singleStyles.elements());
302: }
303:
304: private void finishSingleStyles(Vector errorList)
305: throws BuildException, IOException {
306: finishStyles(this .singleStyles.elements(), errorList);
307: }
308:
309: private void processComboStyles(Document doc)
310: throws BuildException, IOException {
311: processStyles(doc, "all", this .comboStyles.elements());
312: }
313:
314: private void finishComboStyles(Vector errorList)
315: throws BuildException, IOException {
316: finishStyles(this .comboStyles.elements(), errorList);
317: }
318:
319: private void processStyles(Document doc, String moduleName,
320: Enumeration styles) throws BuildException, IOException {
321: while (styles.hasMoreElements()) {
322: IReportStyle rs = (IReportStyle) styles.nextElement();
323: rs.generateReport(getProject(), doc, moduleName);
324: }
325: }
326:
327: private void finishStyles(Enumeration styles, Vector errors)
328: throws BuildException, IOException {
329: while (styles.hasMoreElements()) {
330: IReportStyle rs = (IReportStyle) styles.nextElement();
331: rs.reportComplete(getProject(), errors);
332: }
333: }
334:
335: /**
336: * Even though these are public, they take an interface, and so can't
337: * be called through the Ant script.
338: */
339: public void _addSingle(IReportStyle rs) {
340: if (rs != null) {
341: this .singleStyles.addElement(rs);
342: }
343: }
344:
345: /**
346: * Even though these are public, they take an interface, and so can't
347: * be called through the Ant script.
348: */
349: public void _addCombo(IReportStyle rs) {
350: if (rs != null) {
351: this .comboStyles.addElement(rs);
352: }
353: }
354:
355: private Element createReport(IAnalysisModule am,
356: AnalysisModuleData amd, IReportGenerator rg)
357: throws IOException, BuildException {
358: log("Creating coverage report for module "
359: + am.getMeasureName(), Project.MSG_INFO);
360: Element ret = null;
361: try {
362: ret = rg.createReport(am, amd);
363: } catch (IllegalArgumentException iae) {
364: // throw new BuildException( iae );
365:
366: // this is a hack: really, the underlying code should be made
367: // more robust.
368: iae.printStackTrace();
369: log(iae.getMessage(), Project.MSG_WARN);
370: ret = null;
371: }
372:
373: return ret;
374: }
375:
376: private IChannelLogReader createChannelLogReader(
377: IAnalysisModule am, AnalysisModuleSet ams)
378: throws IOException {
379: short mi = ams.getAnalysisModuleIndex(am);
380: IChannelLogReader clr = new DirectoryChannelLogReader(
381: this .logdir, mi);
382: return clr;
383: }
384:
385: private IMetaDataReader createMetaDataReader()
386: throws BuildException {
387: try {
388: return new DirMetaDataReader(this .datadir);
389: } catch (IOException e) {
390: throw new BuildException(
391: "I/O error creating meta-data reader.", e,
392: getLocation());
393: }
394: }
395:
396: /**
397: * setup the directories in the logdir - ensure we have adequate
398: * setup protections. This saves some head banging in figuring out
399: * why bad exceptions are thrown from the Ant script.
400: */
401: private void setupDirectories() throws IOException, BuildException {
402: // check specifications
403: if (this .baselogdir == null) {
404: throw new BuildException(
405: "Did not specify attribute 'datadir'.");
406: }
407: if (this .datadir == null) {
408: this .datadir = new File(this .baselogdir, "data");
409: }
410: if (this .logdir == null) {
411: this .logdir = new File(this .baselogdir, "logs");
412: }
413:
414: if (!this .datadir.exists() || !this .datadir.isDirectory()) {
415: throw new BuildException("Data directory setting ("
416: + this .datadir
417: + ") does not exist or is not a directory.");
418: }
419: if (!this .logdir.exists()) {
420: this .logdir.mkdirs();
421: }
422: if (!this .logdir.isDirectory()) {
423: throw new BuildException("Log directory setting ("
424: + this .logdir + ") is not a directory.");
425: }
426: String modules[] = this .datadir.list();
427: if (modules == null || modules.length <= 0) {
428: throw new BuildException(
429: "There are no module data directories in "
430: + this .datadir + ".");
431: }
432:
433: String indicies[] = this .logdir.list();
434: if (indicies == null) {
435: indicies = new String[0];
436: }
437: int count = modules.length;
438:
439: for (int i = 0; i <= count; ++i) {
440: String dirname = Integer.toString(i);
441: boolean found = false;
442: for (int j = 0; j < indicies.length; ++j) {
443: if (indicies[j].equals(dirname)) {
444: found = true;
445: break;
446: }
447: }
448: if (!found) {
449: File f = new File(this .logdir, dirname);
450: f.mkdirs();
451: }
452: }
453:
454: if (this .filter != null) {
455: ILogFilter lf = this.filter.getFilter();
456: if (lf != null) {
457: lf.process(count, this.logdir);
458: }
459: }
460: }
461: }
|