content: /*******************************************************************************
   * Copyright (c) 2009, 2019 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:
   *    John Oliver, Marc R. Hoffmann, Jan Wloka - initial API and implementation
   *
   *******************************************************************************/
  package org.jacoco.maven;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Locale;
  
  import org.apache.maven.artifact.Artifact;
  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
  import org.apache.maven.artifact.versioning.VersionRange;
  import org.apache.maven.model.Dependency;
  import org.apache.maven.plugins.annotations.Mojo;
  import org.apache.maven.plugins.annotations.Parameter;
  import org.apache.maven.project.MavenProject;
  import org.jacoco.report.IReportGroupVisitor;
  
  /**
   * <p>
   * Creates a structured code coverage report (HTML, XML, and CSV) from multiple
   * projects within reactor. The report is created from all modules this project
   * depends on. From those projects class and source files as well as JaCoCo
   * execution data files will be collected. In addition execution data is
   * collected from the project itself. This also allows to create coverage
   * reports when tests are in separate projects than the code under test, for
   * example in case of integration tests.
   * </p>
   * 
   * <p>
   * Using the dependency scope allows to distinguish projects which contribute
   * execution data but should not become part of the report:
   * </p>
   * 
   * <ul>
   * <li><code>compile</code>, <code>runtime</code>, <code>provided</code>:
   * Project source and execution data is included in the report.</li>
   * <li><code>test</code>: Only execution data is considered for the report.</li>
   * </ul>
   * 
   * @since 0.7.7
   */
  @Mojo(name = "report-aggregate", threadSafe = true)
  public class ReportAggregateMojo extends AbstractReportMojo {
  
  	/**
  	 * A list of execution data files to include in the report from each
  	 * project. May use wildcard characters (* and ?). When not specified all
  	 * *.exec files from the target folder will be included.
  	 */
  	@Parameter
  	List<String> dataFileIncludes;
  
  	/**
  	 * A list of execution data files to exclude from the report. May use
  	 * wildcard characters (* and ?). When not specified nothing will be
  	 * excluded.
  	 */
  	@Parameter
  	List<String> dataFileExcludes;
  
  	/**
  	 * Output directory for the reports. Note that this parameter is only
  	 * relevant if the goal is run from the command line or from the default
  	 * build lifecycle. If the goal is run indirectly as part of a site
  	 * generation, the output directory configured in the Maven Site Plugin is
  	 * used instead.
  	 */
  	@Parameter(defaultValue = "${project.reporting.outputDirectory}/jacoco-aggregate")
  	private File outputDirectory;
  
  	/**
  	 * The projects in the reactor.
  	 */
  	@Parameter(property = "reactorProjects", readonly = true)
  	private List<MavenProject> reactorProjects;
  
  	@Override
  	boolean canGenerateReportRegardingDataFiles() {
  		return true;
  	}
  
  	@Override
  	boolean canGenerateReportRegardingClassesDirectory() {
  		return true;
  	}
  
  	@Override
  	void loadExecutionData(final ReportSupport support) throws IOException {
  		// https://issues.apache.org/jira/browse/MNG-5440
  		if (dataFileIncludes == null) {
  			dataFileIncludes = Arrays.asList("target/*.exec");
  		}
  
  		final FileFilter filter = new FileFilter(dataFileIncludes,
  				dataFileExcludes);
  		loadExecutionData(support, filter, getProject().getBasedir());
  		for (final MavenProject dependency : findDependencies(
  				Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME,
  				Artifact.SCOPE_PROVIDED, Artifact.SCOPE_TEST)) {
  			loadExecutionData(support, filter, dependency.getBasedir());
  		}
  	}
  
  	private void loadExecutionData(final ReportSupport support,
  			final FileFilter filter, final File basedir) throws IOException {
  		for (final File execFile : filter.getFiles(basedir)) {
  			support.loadExecutionData(execFile);
  		}
  	}
  
  	@Override
  	void addFormatters(final ReportSupport support, final Locale locale)
  			throws IOException {
  		support.addAllFormatters(outputDirectory, outputEncoding, footer,
  				locale);
  	}
  
  	@Override
  	void createReport(final IReportGroupVisitor visitor,
  			final ReportSupport support) throws IOException {
  		final IReportGroupVisitor group = visitor.visitGroup(title);
  		for (final MavenProject dependency : findDependencies(
  				Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME,
  				Artifact.SCOPE_PROVIDED)) {
  			support.processProject(group, dependency.getArtifactId(),
  					dependency, getIncludes(), getExcludes(), sourceEncoding);
  		}
  	}
  
  	@Override
  	protected String getOutputDirectory() {
  		return outputDirectory.getAbsolutePath();
  	}
  
  	@Override
  	public void setReportOutputDirectory(final File reportOutputDirectory) {
  		if (reportOutputDirectory != null
  				&& !reportOutputDirectory.getAbsolutePath().endsWith(
  						"jacoco-aggregate")) {
  			outputDirectory = new File(reportOutputDirectory,
  					"jacoco-aggregate");
  		} else {
  			outputDirectory = reportOutputDirectory;
  		}
  	}
  
  	public String getOutputName() {
  		return "jacoco-aggregate/index";
  	}
  
  	public String getName(final Locale locale) {
  		return "JaCoCo Aggregate";
  	}
  
  	private List<MavenProject> findDependencies(final String... scopes) {
  		final List<MavenProject> result = new ArrayList<MavenProject>();
  		final List<String> scopeList = Arrays.asList(scopes);
  		for (final Object dependencyObject : getProject().getDependencies()) {
  			final Dependency dependency = (Dependency) dependencyObject;
  			if (scopeList.contains(dependency.getScope())) {
  				final MavenProject project = findProjectFromReactor(dependency);
  				if (project != null) {
  					result.add(project);
  				}
  			}
  		}
  		return result;
  	}
  
  	/**
  	 * Note that if dependency specified using version range and reactor
  	 * contains multiple modules with same artifactId and groupId but of
  	 * different versions, then first dependency which matches range will be
  	 * selected. For example in case of range <code>[0,2]</code> if version 1 is
  	 * before version 2 in reactor, then version 1 will be selected.
  	 */
  	private MavenProject findProjectFromReactor(final Dependency d) {
  		final VersionRange depVersionAsRange;
  		try {
  			depVersionAsRange = VersionRange
  					.createFromVersionSpec(d.getVersion());
  		} catch (InvalidVersionSpecificationException e) {
  			throw new AssertionError(e);
  		}
  
  		for (final MavenProject p : reactorProjects) {
  			final DefaultArtifactVersion pv = new DefaultArtifactVersion(
  					p.getVersion());
  			if (p.getGroupId().equals(d.getGroupId())
  					&& p.getArtifactId().equals(d.getArtifactId())
  					&& depVersionAsRange.containsVersion(pv)) {
  				return p;
  			}
  		}
  		return null;
  	}
  
  }
  