001: /*
002: * @(#)SourceXslReportStyle.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.FileOutputStream;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.net.URL;
034: import java.util.Enumeration;
035: import java.util.Vector;
036:
037: import javax.xml.parsers.DocumentBuilder;
038: import javax.xml.parsers.DocumentBuilderFactory;
039:
040: import net.sourceforge.groboutils.codecoverage.v2.report.IXmlReportConst;
041: import net.sourceforge.groboutils.codecoverage.v2.report.XmlSourceReportGenerator2;
042:
043: import org.apache.tools.ant.BuildException;
044: import org.apache.tools.ant.Project;
045: import org.w3c.dom.Document;
046: import org.w3c.dom.Element;
047: import org.w3c.dom.NodeList;
048:
049: /**
050: * This is a combo report, which means that it only works
051: * with the combined coverage file. Use of this style is really delicate.
052: *
053: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
054: * @version $Date: 2004/07/07 09:39:09 $
055: * @since March 15, 2004
056: */
057: public class SourceXslReportStyle implements IReportStyle,
058: IXmlReportConst {
059: private File outdir;
060: private Vector srcdirs = new Vector();
061: private boolean removeEmpties = false;
062: private Vector rootStyles = new Vector();
063: private Vector sourceStyles = new Vector();
064: private Vector packageStyles = new Vector();
065: private Vector files = new Vector();
066: private Vector params = new Vector();
067: private int totalCachedCount = 100;
068:
069: public static final class DirType {
070: private File dir;
071:
072: public void setName(File name) {
073: this .dir = name;
074: }
075:
076: public File getDir() {
077: return this .dir;
078: }
079: }
080:
081: public static final class StyleType {
082: String url;
083: File file;
084: String dest;
085: StyleTransformer transformer;
086: int count;
087: int totalCount = -1;
088:
089: public void setTotalCount(int totalCount) {
090: if (this .totalCount < 0) {
091: this .totalCount = totalCount;
092: if (totalCount == 0) {
093: this .transformer = null;
094: }
095: }
096: }
097:
098: public void setFile(File f) throws IOException {
099: this .file = f;
100: }
101:
102: public void setUrl(String resource) {
103: this .url = resource;
104: }
105:
106: // may be ignored
107: public void setDest(String d) {
108: this .dest = d;
109: }
110:
111: protected StyleTransformer getTransformer() {
112: if (this .transformer != null
113: && ++this .count > this .totalCount) {
114: this .transformer = null;
115: // fall through
116: }
117: return this .transformer;
118: }
119:
120: protected void setTransformer(StyleTransformer st) {
121: if (this .totalCount != 0) {
122: this .count = 0;
123: this .transformer = st;
124: }
125: }
126:
127: public String getURL(SourceXslReportStyle sxrs)
128: throws IOException {
129: String ret = null;
130: if (this .file != null) {
131: ret = sxrs.getStylesheetSystemIdForFile(this .file);
132: } else if (this .url != null) {
133: ret = sxrs.getStylesheetSystemIdForClass(this .url);
134: } else {
135: throw new BuildException(
136: "Neither 'file' nor 'url' were set for style");
137: }
138: return ret;
139: }
140: }
141:
142: public void setStyleCacheCount(int count) throws BuildException {
143: if (count < 0) {
144: throw new BuildException("count cannot be less than 0");
145: }
146: this .totalCachedCount = count;
147: }
148:
149: public void setDestDir(File dir) {
150: this .outdir = dir;
151: }
152:
153: public void setSrcDir(File dir) {
154: if (dir != null) {
155: DirType dt = new DirType();
156: dt.setName(dir);
157: this .srcdirs.addElement(dt);
158: }
159: }
160:
161: public void addRootStyle(StyleType s) {
162: if (s != null) {
163: this .rootStyles.addElement(s);
164: }
165: }
166:
167: public void addSourceStyle(StyleType s) {
168: if (s != null) {
169: this .sourceStyles.addElement(s);
170: }
171: }
172:
173: public void addPackageStyle(StyleType s) {
174: if (s != null) {
175: this .packageStyles.addElement(s);
176: }
177: }
178:
179: public void addFile(StyleType s) {
180: if (s != null) {
181: this .files.addElement(s);
182: }
183: }
184:
185: /**
186: * Add a new source directory to this report.
187: */
188: public void addSrcDir(DirType dt) {
189: this .srcdirs.addElement(dt);
190: }
191:
192: public void addParam(SimpleXslReportStyle.ParamType pt) {
193: if (pt != null) {
194: this .params.addElement(pt);
195: }
196: }
197:
198: public void setRemoveEmpty(boolean on) {
199: this .removeEmpties = on;
200: }
201:
202: /**
203: * Called when the task is finished generating all the reports. This
204: * may be useful for styles that join all the reports together.
205: */
206: public void reportComplete(Project project, Vector errors)
207: throws BuildException, IOException {
208: // do nothing
209: }
210:
211: public void generateReport(Project project, Document doc,
212: String moduleName) throws BuildException, IOException {
213: project.log("Generating source report", Project.MSG_VERBOSE);
214: if (this .removeEmpties) {
215: project.log("Removing empty methods and classes...",
216: Project.MSG_VERBOSE);
217: doc = getRemoveEmptiesStyle(project).transform(doc);
218: }
219:
220: setupStyles();
221:
222: long start = System.currentTimeMillis();
223: // Transform the source files.
224: // Do this first so that the directory structure exists, and so that
225: // we know that all the source files exist.
226: transformSources(project, doc);
227:
228: transformPackages(project, doc);
229: transformRoots(project, doc);
230: copyResources(project);
231:
232: project.log("Total transform time = "
233: + (System.currentTimeMillis() - start) + " ms",
234: Project.MSG_VERBOSE);
235: }
236:
237: protected void setupStyles() {
238: setupStyles(this .rootStyles.elements());
239: setupStyles(this .sourceStyles.elements());
240: setupStyles(this .packageStyles.elements());
241: }
242:
243: protected void setupStyles(Enumeration e) {
244: while (e.hasMoreElements()) {
245: StyleType st = (StyleType) e.nextElement();
246: st.setTotalCount(this .totalCachedCount);
247: }
248: }
249:
250: /**
251: * This is the biggest time eater. This takes friggin' forever.
252: */
253: protected void transformSources(Project project, Document doc)
254: throws IOException {
255: project.log("Making reports for source files",
256: Project.MSG_VERBOSE);
257:
258: Element rootEl = doc.getDocumentElement();
259: XmlSourceReportGenerator2 xsrg = new XmlSourceReportGenerator2(
260: doc);
261: String sources[] = xsrg.getSourceNames();
262: for (int i = 0; i < sources.length; ++i) {
263: // existence of the source file logic is in the generator.
264: project.log("Making report for source file " + sources[i],
265: Project.MSG_DEBUG);
266: long start = System.currentTimeMillis();
267: Document srcDoc = xsrg.createXML(sources[i], getSourceFile(
268: sources[i], project));
269: srcDoc.getDocumentElement().setAttribute("updir",
270: getUpDir(sources[i]));
271: project.log("Generating source XML doc took "
272: + (System.currentTimeMillis() - start) + " ms",
273: Project.MSG_DEBUG);
274:
275: transform(project, srcDoc, this .sourceStyles.elements(),
276: getOutFileBase(sources[i]));
277:
278: // need a better way to clean up this document...
279: srcDoc = null;
280: System.gc();
281: }
282: clearCache(this .sourceStyles.elements());
283: }
284:
285: protected void transformPackages(Project project, Document doc)
286: throws IOException {
287: // do each individual package...
288: project.log("Making reports for packages", Project.MSG_VERBOSE);
289:
290: DocumentBuilder builder = getDocumentBuilder();
291: NodeList list = doc.getElementsByTagName("package");
292: Element typeEl = (Element) doc.getElementsByTagName(
293: "moduletypes").item(0);
294: NodeList classes = doc.getElementsByTagName("classcoverage");
295: for (int i = 0; i < list.getLength(); ++i) {
296: Document ndoc = builder.newDocument();
297: Element n = (Element) ndoc.importNode(list.item(i), true);
298: ndoc.appendChild(n);
299: String pkgname = n.getAttribute("name");
300:
301: n.appendChild(ndoc.importNode(typeEl, true));
302: for (int j = 0; j < classes.getLength(); ++j) {
303: n.appendChild(ndoc.importNode(classes.item(j), true));
304: }
305:
306: // turn the package name into a path
307: String dirname = "";
308: if (pkgname.length() > 0) {
309: dirname = pkgname.replace('.', File.separatorChar)
310: + File.separatorChar;
311: }
312:
313: n.setAttribute("updir", getUpDir(dirname));
314: n = null;
315:
316: transform(project, ndoc, this .packageStyles.elements(),
317: getOutFileBase(dirname));
318: ndoc = null;
319: System.gc();
320: }
321: clearCache(this .packageStyles.elements());
322: }
323:
324: protected void transformRoots(Project project, Document doc)
325: throws IOException {
326: transform(project, doc, this .rootStyles.elements(),
327: getOutFileBase(null));
328: clearCache(this .rootStyles.elements());
329: }
330:
331: protected void copyResources(Project project) throws IOException {
332: Enumeration e = files.elements();
333: while (e.hasMoreElements()) {
334: StyleType st = (StyleType) e.nextElement();
335: URL url = new URL(st.getURL(this ));
336: InputStream in = url.openStream();
337: if (in == null) {
338: throw new java.io.FileNotFoundException("URL " + url);
339: }
340: FileOutputStream fos = new FileOutputStream(new File(
341: this .outdir, st.dest));
342: try {
343: byte buff[] = new byte[4096];
344: int size = in.read(buff, 0, 4096);
345: while (size > 0) {
346: fos.write(buff, 0, size);
347: size = in.read(buff, 0, 4096);
348: }
349: } finally {
350: in.close();
351: fos.close();
352: }
353: }
354: }
355:
356: protected void transform(Project project, Document doc,
357: Enumeration styles, String baseOutName) throws IOException {
358: long start = System.currentTimeMillis();
359: while (styles.hasMoreElements()) {
360: StyleType s = (StyleType) styles.nextElement();
361: File out = new File(baseOutName + s.dest);
362: StyleTransformer st = getStyleTransformer(project, s);
363:
364: // ensure the directory exists
365: out.getParentFile().mkdirs();
366:
367: st.transform(doc, out);
368: }
369: project.log("Transform for all styles took "
370: + (System.currentTimeMillis() - start) + " ms",
371: Project.MSG_VERBOSE);
372: }
373:
374: protected void clearCache(Enumeration styles) {
375: while (styles.hasMoreElements()) {
376: StyleType s = (StyleType) styles.nextElement();
377: s.setTransformer(null);
378: }
379: System.gc();
380: }
381:
382: protected StyleTransformer getRemoveEmptiesStyle(Project project)
383: throws IOException {
384: return new StyleTransformer(
385: project,
386: getStylesheetSystemIdForClass("xsl/remove-empty-classes.xsl"),
387: this .outdir);
388: }
389:
390: /**
391: * Returns the first file from the list of source directories that
392: * contains the given sourcefile.
393: */
394: protected File getSourceFile( String sourceName, Project project )
395: {
396: Enumeration enum = this .srcdirs.elements();
397: while (enum.hasMoreElements())
398: {
399: File f = ((DirType)enum.nextElement()).getDir();
400: if (f != null)
401: {
402: File sf = new File( f, sourceName );
403: if (sf.exists() && sf.isFile())
404: {
405: return sf;
406: }
407: }
408: }
409: project.log( "Could not find a corresponding file for source '"+
410: sourceName+"'.", Project.MSG_INFO );
411: return null;
412: }
413:
414: protected String getOutFileBase(String baseName) {
415: File f;
416: if (baseName == null) {
417: f = this .outdir;
418: } else {
419: f = new File(this .outdir, baseName);
420: }
421:
422: return f.getAbsolutePath();
423: }
424:
425: protected String getUpDir(String filename) {
426: StringBuffer reldir = new StringBuffer();
427: int pos = filename.indexOf(File.separatorChar);
428: while (pos >= 0) {
429: reldir.append("../");
430: pos = filename.indexOf(File.separatorChar, pos + 1);
431: }
432: return reldir.toString();
433: }
434:
435: protected StyleTransformer getStyleTransformer(Project project,
436: StyleType s) throws IOException {
437: StyleTransformer st = s.getTransformer();
438: if (st != null) {
439: return st;
440: }
441:
442: // otherwise, generate the cache.
443: String url = s.getURL(this );
444: st = new StyleTransformer(project, url, this .outdir);
445: Enumeration e = this .params.elements();
446: while (e.hasMoreElements()) {
447: ((SimpleXslReportStyle.ParamType) e.nextElement())
448: .updateParameter(st, project);
449: }
450: project.log("Transforming with stylesheet " + url,
451: Project.MSG_DEBUG);
452:
453: // Possibly keep a cache of the style, for performance reasons.
454: s.setTransformer(st);
455: return st;
456: }
457:
458: // ----------------------------------------------------------------
459:
460: protected String getStylesheetSystemIdForFile(File f)
461: throws IOException {
462: if (f == null || !f.exists()) {
463: throw new java.io.FileNotFoundException(
464: "Could not find file '" + f + "'");
465: }
466: URL url = new URL("file", "", f.getAbsolutePath());
467: return url.toExternalForm();
468: }
469:
470: protected String getStylesheetSystemIdForClass(String name)
471: throws IOException {
472: URL url = getClass().getResource(name);
473: if (url == null) {
474: throw new java.io.FileNotFoundException(
475: "Could not find jar resource " + name);
476: }
477: return url.toExternalForm();
478: }
479:
480: private static DocumentBuilder getDocumentBuilder() {
481: try {
482: return DocumentBuilderFactory.newInstance()
483: .newDocumentBuilder();
484: } catch (Exception ex) {
485: throw new ExceptionInInitializerError(ex);
486: }
487: }
488: }
|