View Javadoc
1   /*
2    * The MIT License (MIT)
3    *
4    * Copyright (c) 2013-2026 The Coveralls Maven Plugin Project Contributors:
5    *     https://github.com/hazendaz/coveralls-maven-plugin/graphs/contributors
6    *
7    * Permission is hereby granted, free of charge, to any person obtaining a copy
8    * of this software and associated documentation files (the "Software"), to deal
9    * in the Software without restriction, including without limitation the rights
10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11   * copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in
15   * all copies or substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23   * THE SOFTWARE.
24   */
25  package org.eluder.coveralls.maven.plugin.util;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.nio.file.Path;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.List;
33  
34  import org.apache.maven.project.MavenProject;
35  import org.eluder.coveralls.maven.plugin.CoverageParser;
36  import org.eluder.coveralls.maven.plugin.parser.CloverParser;
37  import org.eluder.coveralls.maven.plugin.parser.CoberturaParser;
38  import org.eluder.coveralls.maven.plugin.parser.JaCoCoParser;
39  import org.eluder.coveralls.maven.plugin.parser.SagaParser;
40  import org.eluder.coveralls.maven.plugin.source.SourceLoader;
41  
42  /**
43   * A factory for creating CoverageParsers objects.
44   */
45  public class CoverageParsersFactory {
46  
47      /** The Constant JACOCO_FILE. */
48      private static final String JACOCO_FILE = "jacoco.xml";
49  
50      /** The Constant DEFAULT_JACOCO_DIRECTORY. */
51      static final String DEFAULT_JACOCO_DIRECTORY = "jacoco";
52  
53      /** The Constant DEFAULT_JACOCO_IT_DIRECTORY. */
54      static final String DEFAULT_JACOCO_IT_DIRECTORY = "jacoco-it";
55  
56      /** The Constant DEFAULT_JACOCO_MERGED_DIRECTORY. */
57      static final String DEFAULT_JACOCO_MERGED_DIRECTORY = "jacoco-merged-report";
58  
59      /** The Constant COBERTURA_FILE. */
60      private static final String COBERTURA_FILE = "coverage.xml";
61  
62      /** The Constant COBERTURA_DIRECTORY. */
63      private static final String COBERTURA_DIRECTORY = "cobertura";
64  
65      /** The Constant CLOVER_FILE. */
66      private static final String CLOVER_FILE = "clover.xml";
67  
68      /** The Constant CLOVER_DIRECTORY. */
69      private static final String CLOVER_DIRECTORY = "clover";
70  
71      /** The Constant SAGA_FILE. */
72      private static final String SAGA_FILE = "total-coverage.xml";
73  
74      /** The Constant SAGA_DIRECTORY. */
75      private static final String SAGA_DIRECTORY = "saga-coverage";
76  
77      /** The project. */
78      private final MavenProject project;
79  
80      /** The source loader. */
81      private final SourceLoader sourceLoader;
82  
83      /**
84       * This new property can be used in a Maven Multi-Module project that has a JaCoCo aggregate project.
85       *
86       * @since 5.0.0
87       */
88      private File jacocoAggregateReport;
89  
90      /**
91       * The jacoco reports option is used to add additional paths. By default the plugin already looks in standard
92       * locations.
93       */
94      private List<File> jacocoReports;
95  
96      /** The cobertura reports. */
97      private List<File> coberturaReports;
98  
99      /** The saga reports. */
100     private List<File> sagaReports;
101 
102     /** The clover reports. */
103     private List<File> cloverReports;
104 
105     /** The relative report dirs. */
106     private List<String> relativeReportDirs;
107 
108     /**
109      * Instantiates a new coverage parsers factory.
110      *
111      * @param project
112      *            the project
113      * @param sourceLoader
114      *            the source loader
115      */
116     public CoverageParsersFactory(final MavenProject project, final SourceLoader sourceLoader) {
117         this.project = project;
118         this.sourceLoader = sourceLoader;
119     }
120 
121     /**
122      * With JaCoCo aggregate report.
123      * <p>
124      * This new property is for Maven multi-module projects
125      *
126      * @param jacocoAggregateReport
127      *            A single JaCoCo report file in an aggregated report
128      *
129      * @return the coverage parsers factory
130      *
131      * @since 5.0.0
132      */
133     public CoverageParsersFactory withJacocoAggregateReport(final File jacocoAggregateReport) {
134         this.jacocoAggregateReport = jacocoAggregateReport;
135         return this;
136     }
137 
138     /**
139      * With jacoco reports.
140      * <p>
141      * For Maven multi-module projects, configure an aggregate project and use
142      * {@link CoverageParsersFactory#withJacocoAggregateReport}
143      *
144      * @param jacocoReports
145      *            the jacoco reports
146      *
147      * @return the coverage parsers factory
148      */
149     public CoverageParsersFactory withJaCoCoReports(final List<File> jacocoReports) {
150         this.jacocoReports = jacocoReports;
151         return this;
152     }
153 
154     /**
155      * With cobertura reports.
156      *
157      * @param coberturaReports
158      *            the cobertura reports
159      *
160      * @return the coverage parsers factory
161      */
162     public CoverageParsersFactory withCoberturaReports(final List<File> coberturaReports) {
163         this.coberturaReports = coberturaReports;
164         return this;
165     }
166 
167     /**
168      * With saga reports.
169      *
170      * @param sagaReports
171      *            the saga reports
172      *
173      * @return the coverage parsers factory
174      */
175     public CoverageParsersFactory withSagaReports(final List<File> sagaReports) {
176         this.sagaReports = sagaReports;
177         return this;
178     }
179 
180     /**
181      * With clover reports.
182      *
183      * @param cloverReports
184      *            the clover reports
185      *
186      * @return the coverage parsers factory
187      */
188     public CoverageParsersFactory withCloverReports(final List<File> cloverReports) {
189         this.cloverReports = cloverReports;
190         return this;
191     }
192 
193     /**
194      * With relative report dirs.
195      *
196      * @param relativeReportDirs
197      *            the relative report dirs
198      *
199      * @return the coverage parsers factory
200      */
201     public CoverageParsersFactory withRelativeReportDirs(final List<String> relativeReportDirs) {
202         this.relativeReportDirs = relativeReportDirs;
203         return this;
204     }
205 
206     /**
207      * Creates a new CoverageParsers object.
208      *
209      * @return the list of coverage parsers
210      *
211      * @throws IOException
212      *             Signals that an I/O exception has occurred.
213      */
214     public List<CoverageParser> createParsers() throws IOException {
215         final List<CoverageParser> parsers = new ArrayList<>();
216         final var projects = new MavenProjectCollector(this.project).collect();
217 
218         final var jacocoFiles = this.jacocoAggregateReport != null
219                 ? ExistingFiles.create(List.of(this.jacocoAggregateReport))
220                 : ExistingFiles.create(this.jacocoReports);
221         final var coberturaFiles = ExistingFiles.create(this.coberturaReports);
222         final var sagaFiles = ExistingFiles.create(this.sagaReports);
223         final var cloverFiles = ExistingFiles.create(this.cloverReports);
224         for (final MavenProject p : projects) {
225             final var reportingDirectory = Path.of(p.getModel().getReporting().getOutputDirectory());
226             final var buildDirectory = Path.of(p.getBuild().getDirectory());
227 
228             final var jacocoMergedReport = reportingDirectory
229                     .resolve(CoverageParsersFactory.DEFAULT_JACOCO_MERGED_DIRECTORY)
230                     .resolve(CoverageParsersFactory.JACOCO_FILE).toFile();
231 
232             // If a JaCoCo merged report exists there is no need to individually add reports for unit tests and IT.
233             // Note that in a Maven multi-module project JaCoCo can also be configured to aggregate all reports to a
234             // single module. In which case there is no need to gather reports from individual Maven projects
235             // as it's already done. Therefore, we only need to add to jacocoFiles if jacocoAggregateReport is null.
236             if (this.jacocoAggregateReport == null) {
237                 if (jacocoMergedReport.exists() && jacocoMergedReport.canRead()) {
238                     jacocoFiles.add(jacocoMergedReport);
239                 } else {
240                     jacocoFiles.add(reportingDirectory.resolve(CoverageParsersFactory.DEFAULT_JACOCO_DIRECTORY)
241                             .resolve(CoverageParsersFactory.JACOCO_FILE).toFile());
242                     jacocoFiles.add(reportingDirectory.resolve(CoverageParsersFactory.DEFAULT_JACOCO_IT_DIRECTORY)
243                             .resolve(CoverageParsersFactory.JACOCO_FILE).toFile());
244                 }
245             }
246 
247             coberturaFiles.add(reportingDirectory.resolve(CoverageParsersFactory.COBERTURA_DIRECTORY)
248                     .resolve(CoverageParsersFactory.COBERTURA_FILE).toFile());
249             sagaFiles.add(buildDirectory.resolve(CoverageParsersFactory.SAGA_DIRECTORY)
250                     .resolve(CoverageParsersFactory.SAGA_FILE).toFile());
251             cloverFiles.add(reportingDirectory.resolve(CoverageParsersFactory.CLOVER_DIRECTORY)
252                     .resolve(CoverageParsersFactory.CLOVER_FILE).toFile());
253             cloverFiles.add(buildDirectory.resolve(CoverageParsersFactory.CLOVER_DIRECTORY)
254                     .resolve(CoverageParsersFactory.CLOVER_FILE).toFile());
255 
256             this.setupRelativeReportDirs(jacocoFiles, coberturaFiles, sagaFiles, cloverFiles, reportingDirectory,
257                     buildDirectory);
258         }
259 
260         // Use ExistingFiles.toParsers to create parser instances
261         parsers.addAll(jacocoFiles.toParsers(file -> new JaCoCoParser(file, this.sourceLoader)));
262         parsers.addAll(coberturaFiles.toParsers(file -> new CoberturaParser(file, this.sourceLoader)));
263         parsers.addAll(sagaFiles.toParsers(file -> new SagaParser(file, this.sourceLoader)));
264         parsers.addAll(cloverFiles.toParsers(file -> new CloverParser(file, this.sourceLoader)));
265 
266         if (parsers.isEmpty()) {
267             throw new IOException("No coverage report files found");
268         }
269 
270         return Collections.unmodifiableList(parsers);
271     }
272 
273     /**
274      * Setup relative report dirs.
275      *
276      * @param jacocoFiles
277      *            the jacoco files
278      * @param coberturaFiles
279      *            the cobertura files
280      * @param sagaFiles
281      *            the saga files
282      * @param cloverFiles
283      *            the clover files
284      * @param reportingDirectory
285      *            the reporting directory
286      * @param buildDirectory
287      *            the build directory
288      */
289     private void setupRelativeReportDirs(final ExistingFiles jacocoFiles, final ExistingFiles coberturaFiles,
290             final ExistingFiles sagaFiles, final ExistingFiles cloverFiles, final Path reportingDirectory,
291             final Path buildDirectory) {
292         if (this.relativeReportDirs == null) {
293             return;
294         }
295 
296         for (final String relativeReportPath : this.relativeReportDirs) {
297             var relativeReportingDirectory = reportingDirectory;
298             var relativeBuildDirectory = buildDirectory;
299             if (!relativeReportPath.isEmpty() && !File.separator.equals(relativeReportPath)) {
300                 relativeReportingDirectory = reportingDirectory.resolve(relativeReportPath);
301                 relativeBuildDirectory = buildDirectory.resolve(relativeReportPath);
302             }
303 
304             jacocoFiles.add(relativeReportingDirectory.resolve(CoverageParsersFactory.JACOCO_FILE).toFile());
305             jacocoFiles.add(relativeBuildDirectory.resolve(CoverageParsersFactory.JACOCO_FILE).toFile());
306             coberturaFiles.add(relativeReportingDirectory.resolve(CoverageParsersFactory.COBERTURA_FILE).toFile());
307             coberturaFiles.add(relativeBuildDirectory.resolve(CoverageParsersFactory.COBERTURA_FILE).toFile());
308             sagaFiles.add(relativeReportingDirectory.resolve(CoverageParsersFactory.SAGA_FILE).toFile());
309             sagaFiles.add(relativeBuildDirectory.resolve(CoverageParsersFactory.SAGA_FILE).toFile());
310             cloverFiles.add(relativeReportingDirectory.resolve(CoverageParsersFactory.CLOVER_FILE).toFile());
311             cloverFiles.add(relativeBuildDirectory.resolve(CoverageParsersFactory.CLOVER_FILE).toFile());
312         }
313     }
314 
315 }