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