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