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