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}