/*******************************************************************************
* Copyright (c) 2009, 2014 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
*
*******************************************************************************/
package org.jacoco.maven;
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.List;
import java.util.Locale;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.jacoco.core.analysis.IBundleCoverage;
import org.jacoco.core.analysis.ICoverageNode;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.SessionInfoStore;
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.csv.CSVFormatter;
import org.jacoco.report.html.HTMLFormatter;
import org.jacoco.report.xml.XMLFormatter;
/**
* Base class for creating a code coverage report for tests of a single project
* in multiple formats (HTML, XML, and CSV).
*/
public abstract class AbstractReportMojo extends AbstractMavenReport {
/**
* Encoding of the generated reports.
*
* @parameter property="project.reporting.outputEncoding"
* default-value="UTF-8"
*/
String outputEncoding;
/**
* Encoding of the source files.
*
* @parameter property="project.build.sourceEncoding"
* default-value="UTF-8"
*/
String sourceEncoding;
/**
* A list of class files to include in the report. May use wildcard
* characters (* and ?). When not specified everything will be included.
*
* @parameter
*/
List<String> includes;
/**
* A list of class files to exclude from the report. May use wildcard
* characters (* and ?). When not specified nothing will be excluded.
*
* @parameter
*/
List<String> excludes;
/**
* Flag used to suppress execution.
*
* @parameter property="jacoco.skip" default-value="false"
*/
boolean skip;
/**
* Maven project.
*
* @parameter property="project"
* @readonly
*/
MavenProject project;
/**
* Doxia Site Renderer.
*
* @component
*/
Renderer siteRenderer;
SessionInfoStore sessionInfoStore;
ExecutionDataStore executionDataStore;
public abstract String getOutputName();
public abstract String getName(final Locale locale);
public String getDescription(final Locale locale) {
return getName(locale) + " Coverage Report.";
}
@Override
public boolean isExternalReport() {
return true;
}
@Override
protected MavenProject getProject() {
return project;
}
@Override
protected Renderer getSiteRenderer() {
return siteRenderer;
}
/**
* Returns the list of class files to include in the report.
*
* @return class files to include, may contain wildcard characters
*/
List<String> getIncludes() {
return includes;
}
/**
* Returns the list of class files to exclude from the report.
*
* @return class files to exclude, may contain wildcard characters
*/
List<String> getExcludes() {
return excludes;
}
@Override
public abstract void setReportOutputDirectory(
final File reportOutputDirectory);
@Override
public boolean canGenerateReport() {
if (skip) {
getLog().info(
"Skipping JaCoCo execution because property jacoco.skip is set.");
return false;
}
if (!getDataFile().exists()) {
getLog().info(
"Skipping JaCoCo execution due to missing execution data file:"
+ getDataFile());
return false;
}
final File classesDirectory = new File(getProject().getBuild()
.getOutputDirectory());
if (!classesDirectory.exists()) {
getLog().info(
"Skipping JaCoCo execution due to missing classes directory:"
+ classesDirectory);
return false;
}
return true;
}
/**
* This method is called when the report generation is invoked directly as a
* standalone Mojo.
*/
@Override
public void execute() throws MojoExecutionException {
if (!canGenerateReport()) {
return;
}
try {
executeReport(Locale.getDefault());
} catch (final MavenReportException e) {
throw new MojoExecutionException("An error has occurred in "
+ getName(Locale.ENGLISH) + " report generation.", e);
}
}
@Override
protected void executeReport(final Locale locale)
throws MavenReportException {
loadExecutionData();
try {
final IReportVisitor visitor = createVisitor(locale);
visitor.visitInfo(sessionInfoStore.getInfos(),
executionDataStore.getContents());
createReport(visitor);
visitor.visitEnd();
} catch (final IOException e) {
throw new MavenReportException("Error while creating report: "
+ e.getMessage(), e);
}
}
void loadExecutionData() throws MavenReportException {
final ExecFileLoader loader = new ExecFileLoader();
try {
loader.load(getDataFile());
} catch (final IOException e) {
throw new MavenReportException(
"Unable to read execution data file " + getDataFile()
+ ": " + e.getMessage(), e);
}
sessionInfoStore = loader.getSessionInfoStore();
executionDataStore = loader.getExecutionDataStore();
}
void createReport(final IReportGroupVisitor visitor) throws IOException {
final FileFilter fileFilter = new FileFilter(this.getIncludes(),
this.getExcludes());
final BundleCreator creator = new BundleCreator(this.getProject(),
fileFilter, getLog());
final IBundleCoverage bundle = creator.createBundle(executionDataStore);
final SourceFileCollection locator = new SourceFileCollection(
getCompileSourceRoots(), sourceEncoding);
checkForMissingDebugInformation(bundle);
visitor.visitBundle(bundle, locator);
}
void checkForMissingDebugInformation(final ICoverageNode node) {
if (node.getClassCounter().getTotalCount() > 0
&& node.getLineCounter().getTotalCount() == 0) {
getLog().warn(
"To enable source code annotation class files have to be compiled with debug information.");
}
}
IReportVisitor createVisitor(final Locale locale) throws IOException {
final List<IReportVisitor> visitors = new ArrayList<IReportVisitor>();
getOutputDirectoryFile().mkdirs();
final XMLFormatter xmlFormatter = new XMLFormatter();
xmlFormatter.setOutputEncoding(outputEncoding);
visitors.add(xmlFormatter.createVisitor(new FileOutputStream(new File(
getOutputDirectoryFile(), "jacoco.xml"))));
final CSVFormatter csvFormatter = new CSVFormatter();
csvFormatter.setOutputEncoding(outputEncoding);
visitors.add(csvFormatter.createVisitor(new FileOutputStream(new File(
getOutputDirectoryFile(), "jacoco.csv"))));
final HTMLFormatter htmlFormatter = new HTMLFormatter();
htmlFormatter.setOutputEncoding(outputEncoding);
htmlFormatter.setLocale(locale);
visitors.add(htmlFormatter.createVisitor(new FileMultiReportOutput(
getOutputDirectoryFile())));
return new MultiReportVisitor(visitors);
}
File resolvePath(final String path) {
File file = new File(path);
if (!file.isAbsolute()) {
file = new File(getProject().getBasedir(), path);
}
return file;
}
List<File> getCompileSourceRoots() {
final List<File> result = new ArrayList<File>();
for (final Object path : getProject().getCompileSourceRoots()) {
result.add(resolvePath((String) path));
}
return result;
}
private static class SourceFileCollection implements ISourceFileLocator {
private final List<File> sourceRoots;
private final String encoding;
public SourceFileCollection(final List<File> sourceRoots,
final String encoding) {
this.sourceRoots = sourceRoots;
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;
}
}
abstract File getDataFile();
abstract File getOutputDirectoryFile();
}