ReportTask.java
1/*******************************************************************************
2 * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Marc R. Hoffmann - initial API and implementation
10 *
11 * $Id: $
12 *******************************************************************************/
13package org.jacoco.ant;
14
15import java.io.BufferedInputStream;
16import java.io.File;
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.InputStreamReader;
20import java.io.Reader;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.HashMap;
24import java.util.Iterator;
25import java.util.List;
26import java.util.Map;
27
28import org.apache.tools.ant.BuildException;
29import org.apache.tools.ant.Task;
30import org.apache.tools.ant.types.Resource;
31import org.apache.tools.ant.types.resources.FileResource;
32import org.apache.tools.ant.types.resources.Union;
33import org.apache.tools.ant.util.FileUtils;
34import org.jacoco.core.analysis.BundleCoverage;
35import org.jacoco.core.analysis.ClassCoverage;
36import org.jacoco.core.analysis.CoverageBuilder;
37import org.jacoco.core.analysis.CoverageNodeImpl;
38import org.jacoco.core.analysis.ICoverageNode;
39import org.jacoco.core.analysis.PackageCoverage;
40import org.jacoco.core.analysis.ICoverageNode.ElementType;
41import org.jacoco.core.data.ExecutionDataReader;
42import org.jacoco.core.data.ExecutionDataStore;
43import org.jacoco.core.instr.Analyzer;
44import org.jacoco.report.FileMultiReportOutput;
45import org.jacoco.report.FileSingleReportOutput;
46import org.jacoco.report.IReportFormatter;
47import org.jacoco.report.IReportVisitor;
48import org.jacoco.report.ISourceFileLocator;
49import org.jacoco.report.MultiFormatter;
50import org.jacoco.report.csv.CsvFormatter;
51import org.jacoco.report.html.HTMLFormatter;
52import org.jacoco.report.xml.XMLFormatter;
53
54/**
55 * Task for coverage report generation. Experimental implementation that needs
56 * refinement.
57 *
58 * @author Marc R. Hoffmann
59 * @version $Revision: $
60 */
61public class ReportTask extends Task {
62
63 /**
64 * The source files are specified in a resource collection with additional
65 * attributes.
66 */
67 public static class SourceFilesElement extends Union {
68
69 String encoding;
70
71 /**
72 * Defines the optional source file encoding. If not set the platform
73 * default is used.
74 *
75 * @param encoding
76 * source file encoding
77 */
78 public void setEncoding(final String encoding) {
79 this.encoding = encoding;
80 }
81
82 }
83
84 /**
85 * Container element for class file groups.
86 */
87 public static class GroupElement {
88
89 final List<GroupElement> children = new ArrayList<GroupElement>();
90
91 final Union classfiles = new Union();
92
93 final SourceFilesElement sourcefiles = new SourceFilesElement();
94
95 String name;
96
97 /**
98 * Sets the name of the group.
99 *
100 * @param name
101 * name of the group
102 */
103 public void setName(final String name) {
104 this.name = name;
105 }
106
107 /**
108 * Creates a new child group.
109 *
110 * @return new child group
111 */
112 public GroupElement createGroup() {
113 final GroupElement group = new GroupElement();
114 children.add(group);
115 return group;
116 }
117
118 /**
119 * Returns the nested resource collection for class files.
120 *
121 * @return resource collection for class files
122 */
123 public Union createClassfiles() {
124 return classfiles;
125 }
126
127 /**
128 * Returns the nested resource collection for source files.
129 *
130 * @return resource collection for source files
131 */
132 public SourceFilesElement createSourcefiles() {
133 return sourcefiles;
134 }
135
136 }
137
138 /**
139 * Interface for child elements that define formatters.
140 */
141 private interface IFormatterElement {
142
143 IReportFormatter createFormatter();
144
145 }
146
147 /**
148 * Formatter Element for HTML reports.
149 */
150 public static class HTMLFormatterElement implements IFormatterElement {
151
152 private File destdir;
153
154 private String footer = "";
155
156 private String encoding = "UTF-8";
157
158 /**
159 * Sets the output directory for the report.
160 *
161 * @param destdir
162 * output directory
163 */
164 public void setDestdir(final File destdir) {
165 this.destdir = destdir;
166 }
167
168 /**
169 * Sets an optional footer text that will be displayed on every report
170 * page.
171 *
172 * @param text
173 * footer text
174 */
175 public void setFooter(final String text) {
176 this.footer = text;
177 }
178
179 /**
180 * Sets the output encoding for generated HTML files. Default is UTF-8.
181 *
182 * @param encoding
183 * output encoding
184 */
185 public void setEncoding(final String encoding) {
186 this.encoding = encoding;
187 }
188
189 public IReportFormatter createFormatter() {
190 final HTMLFormatter formatter = new HTMLFormatter();
191 formatter.setReportOutput(new FileMultiReportOutput(destdir));
192 formatter.setFooterText(footer);
193 formatter.setOutputEncoding(encoding);
194 return formatter;
195 }
196
197 }
198
199 /**
200 * Formatter Element for CSV reports.
201 */
202 public static class CsvFormatterElement implements IFormatterElement {
203
204 private File destfile;
205
206 private String encoding = "UTF-8";
207
208 /**
209 * Sets the output file for the report.
210 *
211 * @param destfile
212 * output file
213 */
214 public void setDestfile(final File destfile) {
215 this.destfile = destfile;
216 }
217
218 public IReportFormatter createFormatter() {
219 final CsvFormatter formatter = new CsvFormatter();
220 formatter.setReportOutput(new FileSingleReportOutput(destfile));
221 formatter.setOutputEncoding(encoding);
222 return formatter;
223 }
224
225 /**
226 * Sets the output encoding for generated XML file. Default is UTF-8.
227 *
228 * @param encoding
229 * output encoding
230 */
231 public void setEncoding(final String encoding) {
232 this.encoding = encoding;
233 }
234 }
235
236 /**
237 * Formatter Element for XML reports.
238 */
239 public static class XMLFormatterElement implements IFormatterElement {
240
241 private File destfile;
242
243 private String encoding = "UTF-8";
244
245 /**
246 * Sets the output file for the report.
247 *
248 * @param destfile
249 * output file
250 */
251 public void setDestfile(final File destfile) {
252 this.destfile = destfile;
253 }
254
255 /**
256 * Sets the output encoding for generated XML file. Default is UTF-8.
257 *
258 * @param encoding
259 * output encoding
260 */
261 public void setEncoding(final String encoding) {
262 this.encoding = encoding;
263 }
264
265 public IReportFormatter createFormatter() {
266 final XMLFormatter formatter = new XMLFormatter();
267 formatter.setReportOutput(new FileSingleReportOutput(destfile));
268 formatter.setOutputEncoding(encoding);
269 return formatter;
270 }
271 }
272
273 private final Union executiondataElement = new Union();
274
275 private final GroupElement structure = new GroupElement();
276
277 private final List<IFormatterElement> formatters = new ArrayList<IFormatterElement>();
278
279 /**
280 * Returns the nested resource collection for execution data files.
281 *
282 * @return resource collection for execution files
283 */
284 public Union createExecutiondata() {
285 return executiondataElement;
286 }
287
288 /**
289 * Returns the root group element that defines the report structure.
290 *
291 * @return root group element
292 */
293 public GroupElement createStructure() {
294 return structure;
295 }
296
297 /**
298 * Creates a new HTML report element.
299 *
300 * @return HTML report element
301 */
302 public HTMLFormatterElement createHtml() {
303 final HTMLFormatterElement element = new HTMLFormatterElement();
304 formatters.add(element);
305 return element;
306 }
307
308 /**
309 * Creates a new CSV report element.
310 *
311 * @return CSV report element
312 */
313 public CsvFormatterElement createCsv() {
314 final CsvFormatterElement element = new CsvFormatterElement();
315 formatters.add(element);
316 return element;
317 }
318
319 /**
320 * Creates a new XML report element.
321 *
322 * @return CSV report element
323 */
324 public XMLFormatterElement createXml() {
325 final XMLFormatterElement element = new XMLFormatterElement();
326 formatters.add(element);
327 return element;
328 }
329
330 @Override
331 public void execute() throws BuildException {
332 final ExecutionDataStore executionData = loadExecutionData();
333 final IReportFormatter formatter = createFormatter();
334 try {
335 createReport(structure, formatter, executionData);
336 } catch (final IOException e) {
337 throw new BuildException("Error while creating report.", e);
338 }
339 }
340
341 private ExecutionDataStore loadExecutionData() {
342 final ExecutionDataStore data = new ExecutionDataStore();
343 for (final Iterator<?> i = executiondataElement.iterator(); i.hasNext();) {
344 final Resource resource = (Resource) i.next();
345 InputStream in = null;
346 try {
347 in = new BufferedInputStream(resource.getInputStream());
348 final ExecutionDataReader reader = new ExecutionDataReader(in);
349 reader.setExecutionDataVisitor(data);
350 reader.read();
351 } catch (final IOException e) {
352 throw new BuildException("Unable to read execution data file "
353 + resource.getName(), e);
354 } finally {
355 FileUtils.close(in);
356 }
357 }
358 return data;
359 }
360
361 private IReportFormatter createFormatter() {
362 final MultiFormatter multi = new MultiFormatter();
363 for (final IFormatterElement f : formatters) {
364 multi.add(f.createFormatter());
365 }
366 return multi;
367 }
368
369 private void createReport(final GroupElement group,
370 final IReportFormatter formatter,
371 final ExecutionDataStore executionData) throws IOException {
372 final CoverageNodeImpl node = createNode(group, executionData);
373 final IReportVisitor visitor = formatter.createReportVisitor(node);
374 final SourceFileCollection sourceFileLocator = new SourceFileCollection(
375 group.sourcefiles);
376 if (node instanceof BundleCoverage) {
377 visitBundle(visitor, (BundleCoverage) node, sourceFileLocator);
378 } else {
379 for (final GroupElement g : group.children) {
380 createReport(g, visitor, node, executionData);
381 }
382 }
383 visitor.visitEnd(sourceFileLocator);
384 }
385
386 private void createReport(final GroupElement group,
387 final IReportVisitor parentVisitor,
388 final CoverageNodeImpl parentNode,
389 final ExecutionDataStore executionData) throws IOException {
390 final CoverageNodeImpl node = createNode(group, executionData);
391 final IReportVisitor visitor = parentVisitor.visitChild(node);
392 final SourceFileCollection sourceFileLocator = new SourceFileCollection(
393 group.sourcefiles);
394 if (node instanceof BundleCoverage) {
395 visitBundle(visitor, (BundleCoverage) node, sourceFileLocator);
396 } else {
397 for (final GroupElement g : group.children) {
398 createReport(g, visitor, node, executionData);
399 }
400 }
401 parentNode.increment(node);
402 visitor.visitEnd(sourceFileLocator);
403 }
404
405 private CoverageNodeImpl createNode(final GroupElement group,
406 final ExecutionDataStore executionData) throws IOException {
407 if (group.children.size() > 0) {
408 return new CoverageNodeImpl(ElementType.GROUP, group.name, false);
409 } else {
410 final CoverageBuilder builder = new CoverageBuilder(executionData);
411 final Analyzer analyzer = new Analyzer(builder);
412 for (final Iterator<?> i = group.classfiles.iterator(); i.hasNext();) {
413 final Resource resource = (Resource) i.next();
414 if (resource.isDirectory() && resource instanceof FileResource) {
415 analyzer.analyzeAll(((FileResource) resource).getFile());
416 continue;
417 }
418 if (resource.getName().toLowerCase().endsWith(".jar")) {
419 final InputStream in = resource.getInputStream();
420 analyzer.analyzeJAR(in);
421 in.close();
422 continue;
423 }
424 if (resource.getName().toLowerCase().endsWith(".class")) {
425 final InputStream in = resource.getInputStream();
426 analyzer.analyze(in);
427 in.close();
428 }
429 }
430 return builder.getBundle(group.name);
431 }
432 }
433
434 private static class SourceFileCollection implements ISourceFileLocator {
435
436 private final String encoding;
437
438 private final Map<String, Resource> resources = new HashMap<String, Resource>();
439
440 SourceFileCollection(final SourceFilesElement sourceFiles) {
441 encoding = sourceFiles.encoding;
442 for (final Iterator<?> i = sourceFiles.iterator(); i.hasNext();) {
443 final Resource r = (Resource) i.next();
444 resources.put(r.getName().replace(File.separatorChar, '/'), r);
445 }
446 }
447
448 public Reader getSourceFile(final String packageName,
449 final String fileName) throws IOException {
450 final Resource r = resources.get(packageName + '/' + fileName);
451 if (r == null) {
452 return null;
453 }
454 if (encoding == null) {
455 return new InputStreamReader(r.getInputStream());
456 } else {
457 return new InputStreamReader(r.getInputStream(), encoding);
458 }
459 }
460 }
461
462 private static void visitBundle(final IReportVisitor visitor,
463 final BundleCoverage bundledata,
464 final ISourceFileLocator sourceFileLocator) throws IOException {
465 for (final PackageCoverage p : bundledata.getPackages()) {
466 visitPackage(visitor.visitChild(p), p, sourceFileLocator);
467 }
468 }
469
470 private static void visitPackage(final IReportVisitor visitor,
471 final PackageCoverage packagedata,
472 final ISourceFileLocator sourceFileLocator) throws IOException {
473 visitLeafs(visitor, packagedata.getSourceFiles(), sourceFileLocator);
474 for (final ClassCoverage c : packagedata.getClasses()) {
475 visitClass(visitor.visitChild(c), c, sourceFileLocator);
476 }
477 visitor.visitEnd(sourceFileLocator);
478 }
479
480 private static void visitClass(final IReportVisitor visitor,
481 final ClassCoverage classdata,
482 final ISourceFileLocator sourceFileLocator) throws IOException {
483 visitLeafs(visitor, classdata.getMethods(), sourceFileLocator);
484 visitor.visitEnd(sourceFileLocator);
485 }
486
487 private static void visitLeafs(final IReportVisitor visitor,
488 final Collection<? extends ICoverageNode> leafs,
489 final ISourceFileLocator sourceFileLocator) throws IOException {
490 for (final ICoverageNode l : leafs) {
491 final IReportVisitor child = visitor.visitChild(l);
492 child.visitEnd(sourceFileLocator);
493 }
494 }
495
496}