View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.checkstyle;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import com.puppycrawl.tools.checkstyle.DefaultLogger;
35  import com.puppycrawl.tools.checkstyle.SarifLogger;
36  import com.puppycrawl.tools.checkstyle.XMLLogger;
37  import com.puppycrawl.tools.checkstyle.api.AuditListener;
38  import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
39  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40  import org.apache.maven.artifact.Artifact;
41  import org.apache.maven.execution.MavenSession;
42  import org.apache.maven.model.Dependency;
43  import org.apache.maven.model.Plugin;
44  import org.apache.maven.model.PluginManagement;
45  import org.apache.maven.model.Resource;
46  import org.apache.maven.plugin.descriptor.PluginDescriptor;
47  import org.apache.maven.plugins.annotations.Component;
48  import org.apache.maven.plugins.annotations.Parameter;
49  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
50  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
51  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
52  import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
53  import org.apache.maven.project.MavenProject;
54  import org.apache.maven.reporting.AbstractMavenReport;
55  import org.apache.maven.reporting.MavenReportException;
56  import org.codehaus.plexus.configuration.PlexusConfiguration;
57  import org.codehaus.plexus.i18n.I18N;
58  import org.codehaus.plexus.resource.ResourceManager;
59  import org.codehaus.plexus.resource.loader.FileResourceLoader;
60  import org.codehaus.plexus.util.FileUtils;
61  import org.codehaus.plexus.util.PathTool;
62  
63  /**
64   * Base abstract class for Checkstyle reports.
65   *
66   *
67   */
68  public abstract class AbstractCheckstyleReport extends AbstractMavenReport {
69      protected static final String JAVA_FILES = "**\\/*.java";
70  
71      private static final String DEFAULT_CONFIG_LOCATION = "sun_checks.xml";
72  
73      @Parameter(defaultValue = "${session}", readonly = true, required = true)
74      private MavenSession session;
75  
76      /**
77       * Specifies the cache file used to speed up Checkstyle on successive runs.
78       */
79      @Parameter(defaultValue = "${project.build.directory}/checkstyle-cachefile")
80      protected String cacheFile;
81  
82      /**
83       * <p>
84       * Specifies the location of the XML configuration to use.
85       * <p>
86       * Potential values are a filesystem path, a URL, or a classpath resource.
87       * This parameter expects that the contents of the location conform to the
88       * xml format (Checkstyle <a
89       * href="https://checkstyle.org/config.html#Modules">Checker
90       * module</a>) configuration of rulesets.
91       * <p>
92       * This parameter is resolved as resource, URL, then file. If successfully
93       * resolved, the contents of the configuration is copied into the
94       * <code>${project.build.directory}/checkstyle-configuration.xml</code>
95       * file before being passed to Checkstyle as a configuration.
96       * <p>
97       * There are 2 predefined rulesets included in Maven Checkstyle Plugin:
98       * <ul>
99       * <li><code>sun_checks.xml</code>: Sun Checks.</li>
100      * <li><code>google_checks.xml</code>: Google Checks.</li>
101      * </ul>
102      */
103     @Parameter(property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION)
104     protected String configLocation;
105 
106     /**
107      * Output errors to console.
108      */
109     @Parameter(property = "checkstyle.consoleOutput", defaultValue = "false")
110     protected boolean consoleOutput;
111 
112     /**
113      * Specifies if the build should fail upon a violation.
114      */
115     @Parameter(defaultValue = "false")
116     protected boolean failsOnError;
117 
118     /**
119      * <p>
120      * Specifies the location of the License file (a.k.a. the header file) that
121      * can be used by Checkstyle to verify that source code has the correct
122      * license header.
123      * <p>
124      * You need to use <code>${checkstyle.header.file}</code> in your Checkstyle xml
125      * configuration to reference the name of this header file.
126      * <p>
127      * For instance:
128      * <pre>
129      * &lt;module name="RegexpHeader"&gt;
130      *   &lt;property name="headerFile" value="${checkstyle.header.file}"/&gt;
131      * &lt;/module&gt;
132      * </pre>
133      *
134      * @since 2.0-beta-2
135      */
136     @Parameter(property = "checkstyle.header.file", defaultValue = "LICENSE.txt")
137     protected String headerLocation;
138 
139     /**
140      * Skip entire check.
141      *
142      * @since 2.2
143      */
144     @Parameter(property = "checkstyle.skip", defaultValue = "false")
145     protected boolean skip;
146 
147     /**
148      * Specifies the path and filename to save the Checkstyle output. The format
149      * of the output file is determined by the <code>outputFileFormat</code>
150      * parameter.
151      */
152     @Parameter(property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml")
153     private File outputFile;
154 
155     /**
156      * <p>
157      * Specifies the location of the properties file.
158      * <p>
159      * This parameter is resolved as URL, File then resource. If successfully
160      * resolved, the contents of the properties location is copied into the
161      * <code>${project.build.directory}/checkstyle-checker.properties</code>
162      * file before being passed to Checkstyle for loading.
163      * <p>
164      * The contents of the <code>propertiesLocation</code> will be made
165      * available to Checkstyle for specifying values for parameters within the
166      * xml configuration (specified in the <code>configLocation</code>
167      * parameter).
168      *
169      * @since 2.0-beta-2
170      */
171     @Parameter(property = "checkstyle.properties.location")
172     protected String propertiesLocation;
173 
174     /**
175      * Allows for specifying raw property expansion information.
176      */
177     @Parameter
178     protected String propertyExpansion;
179 
180     /**
181      * Specifies the location of the resources to be used for Checkstyle.
182      *
183      * @since 2.10
184      */
185     @Parameter(defaultValue = "${project.resources}", readonly = true)
186     protected List<Resource> resources;
187 
188     /**
189      * Specifies the location of the test resources to be used for Checkstyle.
190      *
191      * @since 2.11
192      */
193     @Parameter(defaultValue = "${project.testResources}", readonly = true)
194     protected List<Resource> testResources;
195 
196     /**
197      * Specifies the names filter of the source files to be used for Checkstyle.
198      */
199     @Parameter(property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true)
200     protected String includes;
201 
202     /**
203      * Specifies the names filter of the source files to be excluded for
204      * Checkstyle.
205      */
206     @Parameter(property = "checkstyle.excludes")
207     protected String excludes;
208 
209     /**
210      * Specifies the names filter of the resource files to be used for Checkstyle.
211      * @since 2.11
212      */
213     @Parameter(property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true)
214     protected String resourceIncludes;
215 
216     /**
217      * Specifies the names filter of the resource files to be excluded for
218      * Checkstyle.
219      * @since 2.11
220      */
221     @Parameter(property = "checkstyle.resourceExcludes")
222     protected String resourceExcludes;
223 
224     /**
225      * Specifies whether to include the resource directories in the check.
226      * @since 2.11
227      */
228     @Parameter(property = "checkstyle.includeResources", defaultValue = "true", required = true)
229     protected boolean includeResources;
230 
231     /**
232      * Specifies whether to include the test resource directories in the check.
233      * @since 2.11
234      */
235     @Parameter(property = "checkstyle.includeTestResources", defaultValue = "true", required = true)
236     protected boolean includeTestResources;
237 
238     /**
239      * Specifies the location of the source directory to be used for Checkstyle.
240      *
241      * @deprecated instead use {@link #sourceDirectories}. For version 3.0.0, this parameter is only defined to break
242      *             the build if you use it!
243      */
244     @Deprecated
245     @Parameter
246     private File sourceDirectory;
247 
248     /**
249      * Specifies the location of the source directories to be used for Checkstyle.
250      * Default value is <code>${project.compileSourceRoots}</code>.
251      * @since 2.13
252      */
253     // Compatibility with all Maven 3: default of 'project.compileSourceRoots' is done manually because of MNG-5440
254     @Parameter
255     private List<String> sourceDirectories;
256 
257     /**
258      * Specifies the location of the test source directory to be used for Checkstyle.
259      *
260      * @since 2.2
261      * @deprecated instead use {@link #testSourceDirectories}. For version 3.0.0, this parameter is only defined to
262      *             break the build if you use it!
263      */
264     @Parameter
265     @Deprecated
266     private File testSourceDirectory;
267 
268     /**
269      * Specifies the location of the test source directories to be used for Checkstyle.
270      * Default value is <code>${project.testCompileSourceRoots}</code>.
271      * @since 2.13
272      */
273     // Compatibility with all Maven 3: default of 'project.testCompileSourceRoots' is done manually because of MNG-5440
274     @Parameter
275     private List<String> testSourceDirectories;
276 
277     /**
278      * Include or not the test source directory/directories to be used for Checkstyle.
279      *
280      * @since 2.2
281      */
282     @Parameter(defaultValue = "false")
283     protected boolean includeTestSourceDirectory;
284 
285     /**
286      * The key to be used in the properties for the suppressions file.
287      *
288      * @since 2.1
289      */
290     @Parameter(property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file")
291     protected String suppressionsFileExpression;
292 
293     /**
294      * <p>
295      * Specifies the location of the suppressions XML file to use.
296      * <p>
297      * This parameter is resolved as resource, URL, then file. If successfully
298      * resolved, the contents of the suppressions XML is copied into the
299      * <code>${project.build.directory}/checkstyle-supressions.xml</code> file
300      * before being passed to Checkstyle for loading.
301      * <p>
302      * See <code>suppressionsFileExpression</code> for the property that will
303      * be made available to your Checkstyle configuration.
304      *
305      * @since 2.0-beta-2
306      */
307     @Parameter(property = "checkstyle.suppressions.location")
308     protected String suppressionsLocation;
309 
310     /**
311      * If <code>null</code>, the Checkstyle plugin will display violations on stdout.
312      * Otherwise, a text file will be created with the violations.
313      */
314     @Parameter
315     private File useFile;
316 
317     /**
318      * Specifies the format of the output to be used when writing to the output
319      * file. Valid values are "<code>plain</code>", "<code>sarif</code>" and "<code>xml</code>".
320      */
321     @Parameter(property = "checkstyle.output.format", defaultValue = "xml")
322     private String outputFileFormat;
323 
324     /**
325      * Specifies if the Rules summary should be enabled or not.
326      */
327     @Parameter(property = "checkstyle.enable.rules.summary", defaultValue = "true")
328     private boolean enableRulesSummary;
329 
330     /**
331      * Specifies if the Severity summary should be enabled or not.
332      */
333     @Parameter(property = "checkstyle.enable.severity.summary", defaultValue = "true")
334     private boolean enableSeveritySummary;
335 
336     /**
337      * Specifies if the Files summary should be enabled or not.
338      */
339     @Parameter(property = "checkstyle.enable.files.summary", defaultValue = "true")
340     private boolean enableFilesSummary;
341 
342     /**
343      * The Plugin Descriptor
344      */
345     @Parameter(defaultValue = "${plugin}", readonly = true, required = true)
346     private PluginDescriptor plugin;
347 
348     /**
349      * Link the violation line numbers to the (Test) Source XRef. Links will be created automatically if the JXR plugin is
350      * being used.
351      *
352      * @since 2.1
353      */
354     @Parameter(property = "linkXRef", defaultValue = "true")
355     private boolean linkXRef;
356 
357     /**
358      * Location where Source XRef is generated for this project.
359      * <br>
360      * <strong>Default</strong>: {@link #getReportOutputDirectory()} + {@code /xref}
361      */
362     @Parameter
363     private File xrefLocation;
364 
365     /**
366      * Location where Test Source XRef is generated for this project.
367      * <br>
368      * <strong>Default</strong>: {@link #getReportOutputDirectory()} + {@code /xref-test}
369      */
370     @Parameter
371     private File xrefTestLocation;
372 
373     /**
374      * When using custom treeWalkers, specify their names here so the checks
375      * inside the treeWalker end up the the rule-summary.
376      *
377      * @since 2.11
378      */
379     @Parameter
380     private List<String> treeWalkerNames;
381 
382     /**
383      * Specifies whether modules with a configured severity of <code>ignore</code> should be omitted during Checkstyle
384      * invocation.
385      *
386      * @since 3.0.0
387      */
388     @Parameter(defaultValue = "false")
389     private boolean omitIgnoredModules;
390 
391     /**
392      * By using this property, you can specify the whole Checkstyle rules
393      * inline directly inside this pom.
394      *
395      * <pre>
396      * &lt;plugin&gt;
397      *   ...
398      *   &lt;configuration&gt;
399      *     &lt;checkstyleRules&gt;
400      *       &lt;module name="Checker"&gt;
401      *         &lt;module name="FileTabCharacter"&gt;
402      *           &lt;property name="eachLine" value="true" /&gt;
403      *         &lt;/module&gt;
404      *         &lt;module name="TreeWalker"&gt;
405      *           &lt;module name="EmptyBlock"/&gt;
406      *         &lt;/module&gt;
407      *       &lt;/module&gt;
408      *     &lt;/checkstyleRules&gt;
409      *   &lt;/configuration&gt;
410      *   ...
411      * </pre>
412      *
413      * @since 2.12
414      */
415     @Parameter
416     private PlexusConfiguration checkstyleRules;
417 
418     /**
419      * Dump file for inlined Checkstyle rules.
420      */
421     @Parameter(
422             property = "checkstyle.output.rules.file",
423             defaultValue = "${project.build.directory}/checkstyle-rules.xml")
424     private File rulesFiles;
425 
426     /**
427      * The header to use for the inline configuration.
428      * Only used when you specify {@code checkstyleRules}.
429      */
430     @Parameter(
431             defaultValue = "<?xml version=\"1.0\"?>\n"
432                     + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
433                     + "        \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n")
434     private String checkstyleRulesHeader;
435 
436     /**
437      * Specifies whether generated source files should be excluded from Checkstyle.
438      *
439      * @since 3.3.1
440      */
441     @Parameter(property = "checkstyle.excludeGeneratedSources", defaultValue = "false")
442     private boolean excludeGeneratedSources;
443 
444     /**
445      */
446     @Component
447     protected ResourceManager locator;
448 
449     /**
450      * @since 2.5
451      */
452     @Component(role = CheckstyleExecutor.class, hint = "default")
453     protected CheckstyleExecutor checkstyleExecutor;
454 
455     /**
456      * Internationalization component
457      */
458     @Component
459     private I18N i18n;
460 
461     protected ByteArrayOutputStream stringOutputStream;
462 
463     /** {@inheritDoc} */
464     public String getName(Locale locale) {
465         return getI18nString(locale, "name");
466     }
467 
468     /** {@inheritDoc} */
469     public String getDescription(Locale locale) {
470         return getI18nString(locale, "description");
471     }
472 
473     /**
474      * @param locale The locale
475      * @param key The key to search for
476      * @return The text appropriate for the locale.
477      */
478     protected String getI18nString(Locale locale, String key) {
479         return i18n.getString("checkstyle-report", locale, "report.checkstyle." + key);
480     }
481 
482     protected MavenProject getProject() {
483         return project;
484     }
485 
486     protected List<MavenProject> getReactorProjects() {
487         return reactorProjects;
488     }
489 
490     /** {@inheritDoc} */
491     public void executeReport(Locale locale) throws MavenReportException {
492         checkDeprecatedParameterUsage(sourceDirectory, "sourceDirectory", "sourceDirectories");
493         checkDeprecatedParameterUsage(testSourceDirectory, "testSourceDirectory", "testSourceDirectories");
494 
495         locator.addSearchPath(
496                 FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath());
497         locator.addSearchPath("url", "");
498 
499         locator.setOutputDirectory(new File(project.getBuild().getDirectory()));
500 
501         // for when we start using maven-shared-io and maven-shared-monitor...
502         // locator = new Locator( new MojoLogMonitorAdaptor( getLog() ) );
503 
504         // locator = new Locator( getLog(), new File( project.getBuild().getDirectory() ) );
505         String effectiveConfigLocation = configLocation;
506         if (checkstyleRules != null) {
507             if (!DEFAULT_CONFIG_LOCATION.equals(configLocation)) {
508                 throw new MavenReportException(
509                         "If you use inline configuration for rules, don't specify " + "a configLocation");
510             }
511             if (checkstyleRules.getChildCount() > 1) {
512                 throw new MavenReportException("Currently only one root module is supported");
513             }
514             PlexusConfiguration checkerModule = checkstyleRules.getChild(0);
515 
516             try {
517                 FileUtils.forceMkdir(rulesFiles.getParentFile());
518                 FileUtils.fileWrite(rulesFiles, checkstyleRulesHeader + checkerModule.toString());
519             } catch (final IOException e) {
520                 throw new MavenReportException(e.getMessage(), e);
521             }
522             effectiveConfigLocation = rulesFiles.getAbsolutePath();
523         }
524         ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
525 
526         try {
527             CheckstyleExecutorRequest request = createRequest()
528                     .setLicenseArtifacts(collectArtifacts("license"))
529                     .setConfigurationArtifacts(collectArtifacts("configuration"))
530                     .setOmitIgnoredModules(omitIgnoredModules)
531                     .setConfigLocation(effectiveConfigLocation);
532 
533             CheckstyleResults results = checkstyleExecutor.executeCheckstyle(request);
534 
535             CheckstyleReportRenderer r = new CheckstyleReportRenderer(
536                     getSink(),
537                     i18n,
538                     locale,
539                     project,
540                     siteTool,
541                     effectiveConfigLocation,
542                     linkXRef ? constructXrefLocation(xrefLocation, false) : null,
543                     linkXRef ? constructXrefLocation(xrefTestLocation, true) : null,
544                     linkXRef ? getTestSourceDirectories() : Collections.emptyList(),
545                     enableRulesSummary,
546                     enableSeveritySummary,
547                     enableFilesSummary,
548                     results);
549             if (treeWalkerNames != null) {
550                 r.setTreeWalkerNames(treeWalkerNames);
551             }
552             r.render();
553         } catch (CheckstyleException e) {
554             throw new MavenReportException("Failed during checkstyle configuration", e);
555         } catch (CheckstyleExecutorException e) {
556             throw new MavenReportException("Failed during checkstyle execution", e);
557         } finally {
558             // be sure to restore original context classloader
559             Thread.currentThread().setContextClassLoader(currentClassLoader);
560         }
561     }
562 
563     private void checkDeprecatedParameterUsage(Object parameter, String name, String replacement)
564             throws MavenReportException {
565         if (parameter != null) {
566             throw new MavenReportException("You are using '" + name + "' which has been removed"
567                     + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
568                     + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site.");
569         }
570     }
571 
572     /**
573      * Create the Checkstyle executor request.
574      *
575      * @return The executor request.
576      * @throws MavenReportException If something goes wrong during creation.
577      */
578     protected abstract CheckstyleExecutorRequest createRequest() throws MavenReportException;
579 
580     private List<Artifact> collectArtifacts(String hint) {
581         List<Artifact> artifacts = new ArrayList<>();
582 
583         PluginManagement pluginManagement = project.getBuild().getPluginManagement();
584         if (pluginManagement != null) {
585             artifacts.addAll(getCheckstylePluginDependenciesAsArtifacts(pluginManagement.getPluginsAsMap(), hint));
586         }
587 
588         artifacts.addAll(
589                 getCheckstylePluginDependenciesAsArtifacts(project.getBuild().getPluginsAsMap(), hint));
590 
591         return artifacts;
592     }
593 
594     private List<Artifact> getCheckstylePluginDependenciesAsArtifacts(Map<String, Plugin> plugins, String hint) {
595         List<Artifact> artifacts = new ArrayList<>();
596 
597         Plugin checkstylePlugin = plugins.get(plugin.getGroupId() + ":" + plugin.getArtifactId());
598         if (checkstylePlugin != null) {
599             for (Dependency dep : checkstylePlugin.getDependencies()) {
600                 // @todo if we can filter on hints, it should be done here...
601                 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
602                 artifacts.add(plugin.getArtifactMap().get(depKey));
603             }
604         }
605         return artifacts;
606     }
607 
608     /**
609      * Creates and returns the report generation listener.
610      *
611      * @return The audit listener.
612      * @throws MavenReportException If something goes wrong.
613      */
614     protected AuditListener getListener() throws MavenReportException {
615         AuditListener listener = null;
616 
617         if (outputFileFormat != null && !outputFileFormat.isEmpty()) {
618             File resultFile = outputFile;
619 
620             OutputStream out = getOutputStream(resultFile);
621 
622             if ("xml".equals(outputFileFormat)) {
623                 listener = new XMLLogger(out, OutputStreamOptions.CLOSE);
624             } else if ("plain".equals(outputFileFormat)) {
625                 listener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
626             } else if ("sarif".equals(outputFileFormat)) {
627                 try {
628                     listener = new SarifLogger(out, OutputStreamOptions.CLOSE);
629                 } catch (IOException e) {
630                     throw new MavenReportException("Failed to create SarifLogger", e);
631                 }
632             } else {
633                 // TODO: failure if not a report
634                 throw new MavenReportException(
635                         "Invalid output file format: (" + outputFileFormat + "). Must be 'plain', 'sarif' or 'xml'.");
636             }
637         }
638 
639         return listener;
640     }
641 
642     private OutputStream getOutputStream(File file) throws MavenReportException {
643         File parentFile = file.getAbsoluteFile().getParentFile();
644 
645         if (!parentFile.exists()) {
646             parentFile.mkdirs();
647         }
648 
649         FileOutputStream fileOutputStream;
650         try {
651             fileOutputStream = new FileOutputStream(file);
652         } catch (FileNotFoundException e) {
653             throw new MavenReportException("Unable to create output stream: " + file, e);
654         }
655         return fileOutputStream;
656     }
657 
658     /**
659      * Creates and returns the console listener.
660      *
661      * @return The console listener.
662      * @throws MavenReportException If something goes wrong.
663      */
664     protected DefaultLogger getConsoleListener() throws MavenReportException {
665         DefaultLogger consoleListener;
666 
667         if (useFile == null) {
668             stringOutputStream = new ByteArrayOutputStream();
669             consoleListener = new DefaultLogger(stringOutputStream, OutputStreamOptions.NONE);
670         } else {
671             OutputStream out = getOutputStream(useFile);
672 
673             consoleListener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
674         }
675 
676         return consoleListener;
677     }
678 
679     private String determineRelativePath(File location) {
680         String relativePath =
681                 PathTool.getRelativePath(getReportOutputDirectory().getAbsolutePath(), location.getAbsolutePath());
682         if (relativePath == null || relativePath.trim().isEmpty()) {
683             relativePath = ".";
684         }
685 
686         return relativePath + "/" + location.getName();
687     }
688 
689     protected List<File> getSourceDirectories() {
690         if (sourceDirectories == null) {
691             sourceDirectories = filterBuildTarget(project.getCompileSourceRoots());
692         }
693         List<File> sourceDirs = new ArrayList<>(sourceDirectories.size());
694         for (String sourceDir : sourceDirectories) {
695             sourceDirs.add(FileUtils.resolveFile(project.getBasedir(), sourceDir));
696         }
697         return sourceDirs;
698     }
699 
700     protected List<File> getTestSourceDirectories() {
701         if (testSourceDirectories == null) {
702             testSourceDirectories = filterBuildTarget(project.getTestCompileSourceRoots());
703         }
704         List<File> testSourceDirs = new ArrayList<>(testSourceDirectories.size());
705         for (String testSourceDir : testSourceDirectories) {
706             testSourceDirs.add(FileUtils.resolveFile(project.getBasedir(), testSourceDir));
707         }
708         return testSourceDirs;
709     }
710 
711     private List<String> filterBuildTarget(List<String> sourceDirectories) {
712         if (!excludeGeneratedSources) {
713             return sourceDirectories;
714         }
715 
716         List<String> filtered = new ArrayList<>(sourceDirectories.size());
717         Path buildTarget = FileUtils.resolveFile(
718                         project.getBasedir(), project.getBuild().getDirectory())
719                 .toPath();
720 
721         for (String sourceDir : sourceDirectories) {
722             Path src = FileUtils.resolveFile(project.getBasedir(), sourceDir).toPath();
723             if (!src.startsWith(buildTarget)) {
724                 filtered.add(sourceDir);
725             }
726         }
727         return filtered;
728     }
729 }