001: /*
002: * Hammurapi
003: * Automated Java code review system.
004: * Copyright (C) 2004 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.org
021: * e-Mail: support@hammurapi.biz
022: */
023:
024: package org.hammurapi;
025:
026: import java.io.File;
027: import java.io.FileInputStream;
028: import java.io.FileNotFoundException;
029: import java.io.FileOutputStream;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.Map;
035: import java.util.StringTokenizer;
036:
037: import org.apache.tools.ant.BuildException;
038: import org.apache.tools.ant.Project;
039: import org.apache.tools.ant.util.FileUtils;
040: import org.hammurapi.render.dom.CompositeResultsRenderer;
041: import org.hammurapi.render.dom.HammurapiMetricRenderer;
042: import org.hammurapi.render.dom.InspectorDescriptorRenderer;
043: import org.hammurapi.render.dom.InspectorSetRenderer;
044: import org.hammurapi.render.dom.InspectorSummaryRenderer;
045: import org.hammurapi.render.dom.ReportRenderer;
046: import org.hammurapi.render.dom.ReviewResultsRenderer;
047: import org.hammurapi.results.AggregatedResults;
048: import org.hammurapi.results.Annotation;
049: import org.hammurapi.results.AnnotationConfig;
050: import org.hammurapi.results.AnnotationContext;
051: import org.hammurapi.results.CompositeResults;
052: import org.hammurapi.results.InspectorSummary;
053: import org.hammurapi.results.ReportMixer;
054: import org.hammurapi.results.ReviewResults;
055:
056: import com.pavelvlasov.config.ConfigurationException;
057: import com.pavelvlasov.metrics.MeasurementCategoryFactory;
058: import com.pavelvlasov.metrics.Metric;
059: import com.pavelvlasov.metrics.TimeIntervalCategory;
060: import com.pavelvlasov.render.RenderRequest;
061: import com.pavelvlasov.render.RenderingException;
062: import com.pavelvlasov.render.dom.AbstractRenderer;
063:
064: /**
065: * Outputs review results to XML or HTML.
066: * @ant.element parent="hammurapi" name="output" display-name="Output subelement"
067: * @ant.non-required
068: * @author Pavel Vlasov
069: * @version $Revision: 1.12 $
070: */
071: public class Output implements Listener /*, ReviewUnitFilter*/{
072: private String dir;
073: private boolean embeddedStyle = true;
074: private String extension = ".html";
075: private TaskBase task;
076:
077: public Output(TaskBase task) {
078: this .task = task;
079: }
080:
081: /**
082: * Output directory
083: * @ant.required
084: */
085: public void setDir(String dir) {
086: this .dir = dir;
087: }
088:
089: private Map styleSheets = new HashMap();
090:
091: {
092: styleSheets.put("compilation-unit", new StyleSheetEntry());
093: styleSheets.put("summary", new StyleSheetEntry());
094: styleSheets.put("left-panel", new StyleSheetEntry());
095: styleSheets.put("waived-violations", new StyleSheetEntry());
096: styleSheets.put("package", new StyleSheetEntry());
097: styleSheets.put("inspector-set", new StyleSheetEntry());
098: styleSheets.put("inspector-descriptor", new StyleSheetEntry());
099: styleSheets.put("inspector-summary", new StyleSheetEntry());
100: styleSheets.put("metric-details", new StyleSheetEntry());
101: }
102:
103: public void addConfiguredStyleSheet(StyleSheetEntry styleSheet) {
104: if (styleSheet.getName() == null) {
105: throw new BuildException("Unnamed stylesheet");
106: }
107:
108: StyleSheetEntry existing = (StyleSheetEntry) styleSheets
109: .get(styleSheet.getName());
110: if (existing == null) {
111: throw new BuildException("Invalid stylesheet name: "
112: + styleSheet.getName());
113: }
114:
115: if (styleSheet.getFile() != null) {
116: existing.setFile(styleSheet.getFile());
117: }
118:
119: if (styleSheet.getUrl() != null) {
120: existing.setUrl(styleSheet.getUrl());
121: }
122:
123: existing.setParameters(styleSheet.getParameters());
124: }
125:
126: /**
127: * Use embedded stylesheets if no stylesheets has been set explicitly.
128: * Default is true.
129: * @ant.non-required
130: */
131: public void setEmbeddedStyle(boolean embeddedStyle) {
132: this .embeddedStyle = embeddedStyle;
133: }
134:
135: /**
136: * Extension for output files. Defaults to ".html"
137: * @ant.non-required
138: */
139: public void setExtension(String extension) {
140: this .extension = extension;
141: }
142:
143: private File javaDocDir;
144:
145: /**
146: * JavaDoc directory to generate links.
147: * @ant.non-required
148: */
149: public void setJavaDocDir(File javaDocDir) {
150: this .javaDocDir = javaDocDir;
151: }
152:
153: private File getOutDir() throws HammurapiException {
154: File outDir = FileUtils.newFileUtils().resolveFile(
155: task.getProject().getBaseDir(), dir);
156: if (!outDir.exists()) {
157: throw new HammurapiException(
158: "Output directory does not exist: "
159: + outDir.getAbsolutePath());
160: }
161:
162: if (!outDir.isDirectory()) {
163: throw new HammurapiException("Not a directory: "
164: + outDir.getAbsolutePath());
165: }
166:
167: return outDir;
168: }
169:
170: private String getFileName(ReviewResults reviewResult) {
171: String packageName = reviewResult.getCompilationUnit()
172: .getPackage().getName();
173: String unitName = reviewResult.getName();
174: return "source/" + packageName.replace('.', '/') + '/'
175: + unitName;
176: }
177:
178: private File getFile(ReviewResults reviewResult)
179: throws HammurapiException {
180: return new File(getOutDir(), getFileName(reviewResult)
181: + extension);
182: }
183:
184: private static final Object mkDirSynchronizationMonitor = new Object();
185:
186: public void onReview(ReviewResults reviewResult)
187: throws HammurapiException {
188: if (dir == null) {
189: throw new HammurapiException("dir attribute is mandatory");
190: }
191:
192: final File outFile = getFile(reviewResult);
193: final File outFileParent = outFile.getParentFile();
194: synchronized (mkDirSynchronizationMonitor) {
195: if (!outFileParent.exists()) {
196: if (!outFileParent.mkdirs()) {
197: throw new HammurapiException("Can't create "
198: + outFileParent.getAbsolutePath());
199: }
200: }
201: }
202:
203: String packageName = reviewResult.getCompilationUnit()
204: .getPackage().getName();
205: int count = 0;
206: for (int i = 0; i < packageName.length(); i++) {
207: if ('.' == packageName.charAt(i)) {
208: count++;
209: }
210: }
211:
212: StringBuffer inspectorsPath = new StringBuffer("../");
213: while (count-- >= 0) {
214: inspectorsPath.append("../");
215: }
216:
217: renderAnnotations(reviewResult, outFile);
218:
219: writeUnitDoc(reviewResult, outFile, inspectorsPath.toString(),
220: getJavaDocPath(reviewResult));
221: }
222:
223: /**
224: * @param reviewResult
225: * @param outFile
226: * @throws HammurapiException
227: */
228: private void renderAnnotations(AggregatedResults reviewResult,
229: final File outFile) throws HammurapiException {
230: Iterator ait = reviewResult.getAnnotations().iterator();
231: while (ait.hasNext()) {
232: final Annotation annotation = (Annotation) ait.next();
233: annotation.render(new AnnotationContext() {
234: public FileEntry getNextFile(String extension)
235: throws HammurapiException {
236: try {
237: final File ret = File.createTempFile(outFile
238: .getName()
239: + "_" + annotation.getName(),
240: extension, outFile.getParentFile());
241: return new AnnotationContext.FileEntry() {
242: public File getFile() {
243: return ret;
244: }
245:
246: public String getPath() {
247: return ret.getName();
248: }
249: };
250: } catch (IOException e) {
251: throw new HammurapiException(e);
252: }
253: }
254:
255: public String getExtension() {
256: return Output.this .getExtension();
257: }
258:
259: public Object getParameter(String name)
260: throws BuildException {
261: AnnotationConfig ac = (AnnotationConfig) annotationConfigs
262: .get(annotation.getName());
263: if (ac == null) {
264: return null;
265: }
266:
267: return ac.getParameter(name);
268: }
269:
270: public boolean isEmbeddedStyle() {
271: return Output.this .isEmbeddedStyle();
272: }
273: });
274: }
275: }
276:
277: private String getRelativePath(String packageName)
278: throws HammurapiException {
279: if (javaDocDir == null) {
280: return null;
281: }
282:
283: try {
284: StringTokenizer ost = new StringTokenizer(getOutDir()
285: .getCanonicalPath(), File.separator);
286: StringTokenizer jst = new StringTokenizer(javaDocDir
287: .getCanonicalPath(), File.separator);
288: StringBuffer upPath = new StringBuffer("../");
289: StringBuffer downPath = new StringBuffer();
290: while (ost.hasMoreTokens() && jst.hasMoreTokens()) {
291: String ot = ost.nextToken();
292: String jt = jst.nextToken();
293: if (!ot.equals(jt)) {
294: upPath.append("../");
295: downPath.append(jt);
296: downPath.append("/");
297: break;
298: }
299: }
300:
301: while (ost.hasMoreTokens()) {
302: upPath.append("../");
303: ost.nextToken();
304: }
305:
306: while (jst.hasMoreTokens()) {
307: downPath.append(jst.nextToken());
308: downPath.append("/");
309: }
310:
311: StringTokenizer pst = new StringTokenizer(packageName, ".");
312: while (pst.hasMoreTokens()) {
313: upPath.append("../");
314: downPath.append(pst.nextToken());
315: downPath.append("/");
316: }
317:
318: upPath.append(downPath);
319: return upPath.toString();
320: } catch (IOException e) {
321: return null;
322: }
323: }
324:
325: private String getRelativePath() throws HammurapiException {
326: if (javaDocDir == null) {
327: return null;
328: }
329:
330: try {
331: StringTokenizer ost = new StringTokenizer(getOutDir()
332: .getCanonicalPath(), File.separator);
333: StringTokenizer jst = new StringTokenizer(javaDocDir
334: .getCanonicalPath(), File.separator);
335: StringBuffer upPath = new StringBuffer();
336: StringBuffer downPath = new StringBuffer();
337: while (ost.hasMoreTokens() && jst.hasMoreTokens()) {
338: String ot = ost.nextToken();
339: String jt = jst.nextToken();
340: if (!ot.equals(jt)) {
341: upPath.append("../");
342: downPath.append(jt);
343: downPath.append("/");
344: break;
345: }
346: }
347:
348: while (ost.hasMoreTokens()) {
349: upPath.append("../");
350: ost.nextToken();
351: }
352:
353: while (jst.hasMoreTokens()) {
354: downPath.append(jst.nextToken());
355: downPath.append("/");
356: }
357:
358: upPath.append(downPath);
359: return upPath.toString();
360: } catch (IOException e) {
361: return null;
362: }
363: }
364:
365: /**
366: * @pag:todo Should calculate relative path to JavaDoc dir.
367: */
368: private String getJavaDocPath(ReviewResults ru)
369: throws HammurapiException {
370: if (javaDocDir == null) {
371: return null;
372: }
373:
374: if (!javaDocDir.exists()) {
375: throw new HammurapiException(
376: "JavaDoc directory does not exist");
377: }
378:
379: String packageName = ru.getCompilationUnit().getPackage()
380: .getName();
381: String path = getRelativePath(packageName);
382: if (path == null) {
383: path = javaDocDir.getAbsolutePath() + '/'
384: + packageName.replace('.', '/') + "/";
385: }
386:
387: return path;
388: }
389:
390: /**
391: * @pag:todo Should calculate relative path to JavaDoc dir.
392: */
393: private String getJavaDocPath(CompositeResults packageResults)
394: throws HammurapiException {
395: if (javaDocDir == null) {
396: return null;
397: }
398:
399: if (!javaDocDir.exists()) {
400: throw new HammurapiException(
401: "JavaDoc directory does not exist");
402: }
403:
404: String path = getRelativePath(packageResults.getName());
405: if (path == null) {
406: path = javaDocDir.getAbsolutePath() + '/'
407: + packageResults.getName().replace('.', '/') + '/';
408: }
409:
410: return path + "package-summary.html";
411: }
412:
413: private static TimeIntervalCategory tic = MeasurementCategoryFactory
414: .getTimeIntervalCategory(Output.class);
415:
416: private void render(AbstractRenderer renderer, String styleName,
417: String inspectorsPath, String javaDocPath, File outFile)
418: throws HammurapiException {
419: long start = tic.getTime();
420: File outFileParent = outFile.getParentFile();
421: if (!outFileParent.exists()) {
422: if (!outFileParent.mkdirs()) {
423: throw new HammurapiException("Can't create "
424: + outFileParent.getAbsolutePath());
425: }
426: }
427:
428: renderer.setEmbeddedStyle(embeddedStyle);
429:
430: StyleSheetEntry sse = (StyleSheetEntry) styleSheets
431: .get(styleName);
432: if (sse == null) {
433: throw new HammurapiException("Stylesheet entry with name '"
434: + styleName + "' not found");
435: }
436:
437: try {
438: sse.setParameters(task.getProject(), renderer);
439: } catch (ConfigurationException ce) {
440: throw new HammurapiException("setParameters() failed", ce);
441: }
442:
443: if (inspectorsPath != null) {
444: renderer.setParameter("inspectorsPath", inspectorsPath);
445: }
446:
447: if (javaDocPath != null) {
448: renderer.setParameter("javaDocPath", javaDocPath);
449: }
450:
451: try {
452: if (sse.getFile() == null) {
453: renderer.render(new FileOutputStream(outFile));
454: } else {
455: renderer.render(new FileInputStream(sse.getFile()),
456: new FileOutputStream(outFile));
457: }
458: } catch (FileNotFoundException e) {
459: throw new HammurapiException(e.toString(), e);
460: } catch (RenderingException e) {
461: throw new HammurapiException(e.toString(), e);
462: }
463: tic.addInterval("render", start);
464: }
465:
466: private void writeUnitDoc(ReviewResults reviewResult, File outFile,
467: String inspectorsPath, String javaDocPath)
468: throws HammurapiException {
469: task.getProject().log("Writing " + outFile.getAbsolutePath(),
470: Project.MSG_VERBOSE);
471: render(new ReviewResultsRenderer(
472: new RenderRequest(reviewResult)), "compilation-unit",
473: inspectorsPath, javaDocPath, outFile);
474: }
475:
476: public void onSummary(CompositeResults summary,
477: InspectorSet inspectorSet) throws HammurapiException {
478: if (dir == null) {
479: throw new HammurapiException("dir attribute is mandatory");
480: }
481:
482: File outDir = FileUtils.newFileUtils().resolveFile(
483: task.getProject().getBaseDir(), dir);
484: if (!outDir.exists()) {
485: throw new HammurapiException(
486: "Output directory does not exist: "
487: + outDir.getAbsolutePath());
488: }
489:
490: if (!outDir.isDirectory()) {
491: throw new HammurapiException("Not a directory: "
492: + outDir.getAbsolutePath());
493: }
494:
495: String javaDocPath = javaDocDir == null ? null
496: : getRelativePath() + "index.html";
497: File summaryFile = new File(outDir, "summary" + extension);
498: renderAnnotations(summary, summaryFile);
499: RenderRequest summaryRenderRequest = new RenderRequest(
500: ReportMixer.mix(summary, task.reviewDescription));
501: render(new ReportRenderer(summaryRenderRequest, null),
502: "summary", null, javaDocPath, summaryFile);
503: render(new CompositeResultsRenderer(summaryRenderRequest,
504: "leftPanel"), "left-panel", null, javaDocPath,
505: new File(outDir, "leftPanel" + extension));
506: render(new CompositeResultsRenderer(summaryRenderRequest,
507: "waivedViolations"), "waived-violations", null,
508: javaDocPath, new File(outDir, "waivedViolations"
509: + extension));
510: render(
511: new InspectorSetRenderer(
512: new RenderRequest(inspectorSet)),
513: "inspector-set", null, null, new File(outDir,
514: "inspectors" + extension));
515:
516: Iterator descriptors = inspectorSet.getDescriptors().iterator();
517: while (descriptors.hasNext()) {
518: InspectorDescriptor d = (InspectorDescriptor) descriptors
519: .next();
520: render(
521: new InspectorDescriptorRenderer(
522: new RenderRequest(d)),
523: "inspector-descriptor", null, null, new File(
524: outDir, "inspectors/inspector_"
525: + d.getName() + extension));
526: }
527:
528: if (".HTML".equalsIgnoreCase(extension)) {
529: writeFrame(outDir);
530: }
531:
532: Iterator it = summary.getSeveritySummary().values().iterator();
533: while (it.hasNext()) {
534: Iterator iit = ((Map) it.next()).values().iterator();
535: while (iit.hasNext()) {
536: InspectorSummary is = (InspectorSummary) iit.next();
537: if (is.getLocations() != null) {
538: render(new InspectorSummaryRenderer(
539: new RenderRequest(is)),
540: "inspector-summary", "source/", null,
541: new File(outDir, "summary_" + is.getName()
542: + extension));
543: }
544: }
545: }
546:
547: it = summary.getMetrics().values().iterator();
548: while (it.hasNext()) {
549: Metric metric = (Metric) it.next();
550: if (metric.getMeasurements() != null) {
551: render(new HammurapiMetricRenderer(new RenderRequest(
552: metric)), "metric-details", "source/", null,
553: new File(outDir, "metric_details_"
554: + metric.getName() + extension));
555: }
556: }
557: }
558:
559: private void writeFrame(File outDir) throws HammurapiException {
560: try {
561: byte[] buf = new byte[4096];
562: String resourceName = "/"
563: + getClass().getName().replace('.', '/')
564: + "!report.html";
565: InputStream in = getClass().getResourceAsStream(
566: resourceName);
567: if (in != null) {
568: FileOutputStream out = new FileOutputStream(new File(
569: outDir, "report.html"));
570: int l;
571: while ((l = in.read(buf)) != -1) {
572: out.write(buf, 0, l);
573: }
574: out.close();
575: in.close();
576: }
577: } catch (IOException e) {
578: throw new HammurapiException("Can't write report.html: "
579: + e);
580: }
581: }
582:
583: public void onPackage(CompositeResults packageResults)
584: throws HammurapiException {
585: if (dir == null) {
586: throw new HammurapiException("dir attribute is mandatory");
587: }
588:
589: File outDir = FileUtils.newFileUtils().resolveFile(
590: task.getProject().getBaseDir(), dir);
591: if (!outDir.exists()) {
592: throw new HammurapiException(
593: "Output directory does not exist: "
594: + outDir.getAbsolutePath());
595: }
596:
597: if (!outDir.isDirectory()) {
598: throw new HammurapiException("Not a directory: "
599: + outDir.getAbsolutePath());
600: }
601:
602: String summaryPath = "source/"
603: + packageResults.getName().replace('.', '/')
604: + "/.summary";
605:
606: int count = 0;
607: for (int i = 0; i < summaryPath.length(); i++) {
608: if ('/' == summaryPath.charAt(i)) {
609: count++;
610: }
611: }
612:
613: StringBuffer inspectorsPath = new StringBuffer();
614: while (count-- > 0) {
615: inspectorsPath.append("../");
616: }
617:
618: File packageSummaryFile = new File(outDir, summaryPath
619: + extension);
620: renderAnnotations(packageResults, packageSummaryFile);
621: render(new CompositeResultsRenderer(new RenderRequest(
622: packageResults), "packageSummary"), "package",
623: inspectorsPath.toString(),
624: getJavaDocPath(packageResults), packageSummaryFile);
625:
626: Iterator it = packageResults.getSeveritySummary().values()
627: .iterator();
628: while (it.hasNext()) {
629: Iterator iit = ((Map) it.next()).values().iterator();
630: while (iit.hasNext()) {
631: InspectorSummary is = (InspectorSummary) iit.next();
632: if (is.getLocations() != null) {
633: render(new InspectorSummaryRenderer(
634: new RenderRequest(is)),
635: "inspector-summary", inspectorsPath
636: .toString()
637: + "source/", null, new File(outDir,
638: summaryPath + "_" + is.getName()
639: + extension));
640: }
641: }
642: }
643:
644: it = packageResults.getMetrics().values().iterator();
645: while (it.hasNext()) {
646: Metric metric = (Metric) it.next();
647: if (metric.getMeasurements() != null) {
648: render(new HammurapiMetricRenderer(new RenderRequest(
649: metric)), "metric-details", inspectorsPath
650: .toString()
651: + "source/", null, new File(outDir, summaryPath
652: + "_metric_details_" + metric.getName()
653: + extension));
654: }
655: }
656: }
657:
658: private final Map annotationConfigs = new HashMap();
659:
660: /**
661: * Annotation configuration
662: * @ant.non-required
663: * @param annotationConfig
664: */
665: public void addConfiguredAnnotationConfig(
666: AnnotationConfig annotationConfig) {
667: annotationConfigs.put(annotationConfig.getName(),
668: annotationConfig);
669: }
670:
671: /**
672: * @return Returns the extension.
673: */
674: private String getExtension() {
675: return extension;
676: }
677:
678: /**
679: * @return Returns the embeddedStyle.
680: */
681: private boolean isEmbeddedStyle() {
682: return embeddedStyle;
683: }
684:
685: public void onBegin(InspectorSet inspectorSet)
686: throws HammurapiException {
687: File outDir = FileUtils.newFileUtils().resolveFile(
688: task.getProject().getBaseDir(), dir);
689: if (!outDir.exists()) {
690: if (!outDir.mkdirs()) {
691: throw new HammurapiException(
692: "Output directory cannot be created: "
693: + outDir.getAbsolutePath());
694: }
695: }
696:
697: if (!outDir.isDirectory()) {
698: throw new HammurapiException("Not a directory: "
699: + outDir.getAbsolutePath());
700: }
701: }
702: }
|