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