/******************************************************************************* * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Evgeny Mandrikov - initial API and implementation * Kyle Lieber - implementation of CheckMojo * *******************************************************************************/ package org.jacoco.maven; import static java.lang.String.format; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.jacoco.core.analysis.Analyzer; import org.jacoco.core.analysis.CoverageBuilder; import org.jacoco.core.analysis.IBundleCoverage; import org.jacoco.core.analysis.IClassCoverage; import org.jacoco.core.tools.ExecFileLoader; import org.jacoco.report.FileMultiReportOutput; import org.jacoco.report.IReportGroupVisitor; import org.jacoco.report.IReportVisitor; import org.jacoco.report.ISourceFileLocator; import org.jacoco.report.MultiReportVisitor; import org.jacoco.report.check.IViolationsOutput; import org.jacoco.report.check.Rule; import org.jacoco.report.check.RulesChecker; import org.jacoco.report.csv.CSVFormatter; import org.jacoco.report.html.HTMLFormatter; import org.jacoco.report.xml.XMLFormatter; /** * Encapsulates the tasks to create reports for Maven projects. Instances are * supposed to be used in the following sequence: * *
    *
  1. Create an instance
  2. *
  3. Load one or multiple exec files with loadExecutionData()
  4. *
  5. Add one or multiple formatters with addXXX() methods
  6. *
  7. Create the root visitor with initRootVisitor()
  8. *
  9. Process one or multiple projects with processProject()
  10. *
*/ final class ReportSupport { private final Log log; private final ExecFileLoader loader; private final List formatters; /** * Construct a new instance with the given log output. * * @param log * for log output */ public ReportSupport(final Log log) { this.log = log; this.loader = new ExecFileLoader(); this.formatters = new ArrayList(); } /** * Loads the given execution data file. * * @param execFile * execution data file to load * @throws IOException * if the file can't be loaded */ public void loadExecutionData(final File execFile) throws IOException { log.info("Loading execution data file " + execFile); loader.load(execFile); } public void addXmlFormatter(final File targetfile, final String encoding) throws IOException { final XMLFormatter xml = new XMLFormatter(); xml.setOutputEncoding(encoding); formatters.add(xml.createVisitor(new FileOutputStream(targetfile))); } public void addCsvFormatter(final File targetfile, final String encoding) throws IOException { final CSVFormatter csv = new CSVFormatter(); csv.setOutputEncoding(encoding); formatters.add(csv.createVisitor(new FileOutputStream(targetfile))); } public void addHtmlFormatter(final File targetdir, final String encoding, final String footer, final Locale locale) throws IOException { final HTMLFormatter htmlFormatter = new HTMLFormatter(); htmlFormatter.setOutputEncoding(encoding); htmlFormatter.setLocale(locale); if (footer != null) { htmlFormatter.setFooterText(footer); } formatters.add(htmlFormatter.createVisitor(new FileMultiReportOutput( targetdir))); } public void addAllFormatters(final File targetdir, final String encoding, final String footer, final Locale locale) throws IOException { targetdir.mkdirs(); addXmlFormatter(new File(targetdir, "jacoco.xml"), encoding); addCsvFormatter(new File(targetdir, "jacoco.csv"), encoding); addHtmlFormatter(targetdir, encoding, footer, locale); } public void addRulesChecker(final List rules, final IViolationsOutput output) { final RulesChecker checker = new RulesChecker(); checker.setRules(rules); formatters.add(checker.createVisitor(output)); } public IReportVisitor initRootVisitor() throws IOException { final IReportVisitor visitor = new MultiReportVisitor(formatters); visitor.visitInfo(loader.getSessionInfoStore().getInfos(), loader .getExecutionDataStore().getContents()); return visitor; } /** * Calculates coverage for the given project and emits it to the report * group without source references * * @param visitor * group visitor to emit the project's coverage to * @param project * the MavenProject * @param includes * list of includes patterns * @param excludes * list of excludes patterns * @throws IOException * if class files can't be read */ public void processProject(final IReportGroupVisitor visitor, final MavenProject project, final List includes, final List excludes) throws IOException { processProject(visitor, project.getArtifactId(), project, includes, excludes, new NoSourceLocator()); } /** * Calculates coverage for the given project and emits it to the report * group including source references * * @param visitor * group visitor to emit the project's coverage to * @param bundeName * name for this project in the report * @param project * the MavenProject * @param includes * list of includes patterns * @param excludes * list of excludes patterns * @param srcEncoding * encoding of the source files within this project * @throws IOException * if class files can't be read */ public void processProject(final IReportGroupVisitor visitor, final String bundeName, final MavenProject project, final List includes, final List excludes, final String srcEncoding) throws IOException { processProject(visitor, bundeName, project, includes, excludes, new SourceFileCollection(project, srcEncoding)); } private void processProject(final IReportGroupVisitor visitor, final String bundeName, final MavenProject project, final List includes, final List excludes, final ISourceFileLocator locator) throws IOException { final CoverageBuilder builder = new CoverageBuilder(); final File classesDir = new File(project.getBuild() .getOutputDirectory()); if (classesDir.isDirectory()) { final Analyzer analyzer = new Analyzer( loader.getExecutionDataStore(), builder); final FileFilter filter = new FileFilter(includes, excludes); for (final File file : filter.getFiles(classesDir)) { analyzer.analyzeAll(file); } } final IBundleCoverage bundle = builder.getBundle(bundeName); logBundleInfo(bundle, builder.getNoMatchClasses()); visitor.visitBundle(bundle, locator); } private void logBundleInfo(final IBundleCoverage bundle, final Collection nomatch) { log.info(format("Analyzed bundle '%s' with %s classes", bundle.getName(), Integer.valueOf(bundle.getClassCounter().getTotalCount()))); if (!nomatch.isEmpty()) { log.warn(format( "Classes in bundle '%s' do no match with execution data. " + "For report generation the same class files must be used as at runtime.", bundle.getName())); for (final IClassCoverage c : nomatch) { log.warn(format("Execution data for class %s does not match.", c.getName())); } } if (bundle.getClassCounter().getTotalCount() > 0 && bundle.getLineCounter().getTotalCount() == 0) { log.warn("To enable source code annotation class files have to be compiled with debug information."); } } private class NoSourceLocator implements ISourceFileLocator { public Reader getSourceFile(final String packageName, final String fileName) { return null; } public int getTabWidth() { return 0; } } private class SourceFileCollection implements ISourceFileLocator { private final List sourceRoots; private final String encoding; public SourceFileCollection(final MavenProject project, final String encoding) { this.sourceRoots = getCompileSourceRoots(project); this.encoding = encoding; } public Reader getSourceFile(final String packageName, final String fileName) throws IOException { final String r; if (packageName.length() > 0) { r = packageName + '/' + fileName; } else { r = fileName; } for (final File sourceRoot : sourceRoots) { final File file = new File(sourceRoot, r); if (file.exists() && file.isFile()) { return new InputStreamReader(new FileInputStream(file), encoding); } } return null; } public int getTabWidth() { return 4; } } private static List getCompileSourceRoots(final MavenProject project) { final List result = new ArrayList(); for (final Object path : project.getCompileSourceRoots()) { result.add(resolvePath(project, (String) path)); } return result; } private static File resolvePath(final MavenProject project, final String path) { File file = new File(path); if (!file.isAbsolute()) { file = new File(project.getBasedir(), path); } return file; } }