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.commons.lang3.StringUtils;
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.execution.MavenSession;
38  import org.apache.maven.model.Dependency;
39  import org.apache.maven.model.Plugin;
40  import org.apache.maven.model.PluginManagement;
41  import org.apache.maven.model.ReportPlugin;
42  import org.apache.maven.model.Resource;
43  import org.apache.maven.plugin.descriptor.PluginDescriptor;
44  import org.apache.maven.plugins.annotations.Component;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
47  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
48  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
49  import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
50  import org.apache.maven.plugins.checkstyle.rss.CheckstyleRssGenerator;
51  import org.apache.maven.plugins.checkstyle.rss.CheckstyleRssGeneratorRequest;
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.resource.ResourceManager;
56  import org.codehaus.plexus.resource.loader.FileResourceLoader;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.PathTool;
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         String effectiveConfigLocation = configLocation;
497         if ( checkstyleRules != null )
498         {
499             if ( !DEFAULT_CONFIG_LOCATION.equals( configLocation ) )
500             {
501                 throw new MavenReportException( "If you use inline configuration for rules, don't specify "
502                         + "a configLocation" );
503             }
504             if ( checkstyleRules.getChildCount() > 1 )
505             {
506                 throw new MavenReportException( "Currently only one root module is supported" );
507             }
508             PlexusConfiguration checkerModule = checkstyleRules.getChild( 0 );
509 
510             try
511             {
512                 FileUtils.forceMkdir( rulesFiles.getParentFile() );
513                 FileUtils.fileWrite( rulesFiles, checkstyleRulesHeader + checkerModule.toString() );
514             }
515             catch ( final IOException e )
516             {
517                 throw new MavenReportException( e.getMessage(), e );
518             }
519             effectiveConfigLocation = rulesFiles.getAbsolutePath();
520         }
521         ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
522 
523         try
524         {
525             CheckstyleExecutorRequest request = createRequest().setLicenseArtifacts( collectArtifacts( "license" ) )
526                             .setConfigurationArtifacts( collectArtifacts( "configuration" ) )
527                             .setOmitIgnoredModules( omitIgnoredModules )
528                             .setConfigLocation( effectiveConfigLocation );
529 
530             CheckstyleResults results = checkstyleExecutor.executeCheckstyle( request );
531 
532             ResourceBundle bundle = getBundle( locale );
533             generateReportStatics();
534             generateMainReport( results, bundle, effectiveConfigLocation );
535             if ( enableRSS )
536             {
537                 CheckstyleRssGeneratorRequest checkstyleRssGeneratorRequest =
538                     new CheckstyleRssGeneratorRequest( this.project, this.getCopyright(), outputDirectory, getLog() );
539                 checkstyleRssGenerator.generateRSS( results, checkstyleRssGeneratorRequest );
540             }
541 
542         }
543         catch ( CheckstyleException e )
544         {
545             throw new MavenReportException( "Failed during checkstyle configuration", e );
546         }
547         catch ( CheckstyleExecutorException e )
548         {
549             throw new MavenReportException( "Failed during checkstyle execution", e );
550         }
551         finally
552         {
553             //be sure to restore original context classloader
554             Thread.currentThread().setContextClassLoader( currentClassLoader );
555         }
556     }
557 
558     private void checkDeprecatedParameterUsage( Object parameter, String name, String replacement )
559         throws MavenReportException
560     {
561         if ( parameter != null )
562         {
563             throw new MavenReportException( "You are using '" + name + "' which has been removed"
564                 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
565                 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site." );
566         }
567     }
568 
569     /**
570      * Create the Checkstyle executor request.
571      *
572      * @return The executor request.
573      * @throws MavenReportException If something goes wrong during creation.
574      */
575     protected abstract CheckstyleExecutorRequest createRequest()
576             throws MavenReportException;
577 
578     private List<Artifact> collectArtifacts( String hint )
579     {
580         List<Artifact> artifacts = new ArrayList<>();
581 
582         PluginManagement pluginManagement = project.getBuild().getPluginManagement();
583         if ( pluginManagement != null )
584         {
585             artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( pluginManagement.getPluginsAsMap(), hint ) );
586         }
587 
588         artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( project.getBuild().getPluginsAsMap(), hint ) );
589 
590         return artifacts;
591     }
592 
593     private List<Artifact> getCheckstylePluginDependenciesAsArtifacts( Map<String, Plugin> plugins, String hint )
594     {
595         List<Artifact> artifacts = new ArrayList<>();
596 
597         Plugin checkstylePlugin = plugins.get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
598         if ( checkstylePlugin != null )
599         {
600             for ( Dependency dep : checkstylePlugin.getDependencies() )
601             {
602              // @todo if we can filter on hints, it should be done here...
603                 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
604                 artifacts.add( plugin.getArtifactMap().get( depKey ) );
605             }
606         }
607         return artifacts;
608     }
609 
610     /**
611      * Creates and returns the report generation listener.
612      *
613      * @return The audit listener.
614      * @throws MavenReportException If something goes wrong.
615      */
616     protected AuditListener getListener()
617         throws MavenReportException
618     {
619         AuditListener listener = null;
620 
621         if ( StringUtils.isNotEmpty( outputFileFormat ) )
622         {
623             File resultFile = outputFile;
624 
625             OutputStream out = getOutputStream( resultFile );
626 
627             if ( "xml".equals( outputFileFormat ) )
628             {
629                 listener = new XMLLogger( out, OutputStreamOptions.CLOSE );
630             }
631             else if ( "plain".equals( outputFileFormat ) )
632             {
633                 listener = new DefaultLogger( out, OutputStreamOptions.CLOSE );
634             }
635             else
636             {
637                 // TODO: failure if not a report
638                 throw new MavenReportException( "Invalid output file format: (" + outputFileFormat
639                     + "). Must be 'plain' or 'xml'." );
640             }
641         }
642 
643         return listener;
644     }
645 
646     private OutputStream getOutputStream( File file )
647         throws MavenReportException
648     {
649         File parentFile = file.getAbsoluteFile().getParentFile();
650 
651         if ( !parentFile.exists() )
652         {
653             parentFile.mkdirs();
654         }
655 
656         FileOutputStream fileOutputStream;
657         try
658         {
659             fileOutputStream = new FileOutputStream( file );
660         }
661         catch ( FileNotFoundException e )
662         {
663             throw new MavenReportException( "Unable to create output stream: " + file, e );
664         }
665         return fileOutputStream;
666     }
667 
668     /**
669      * Creates and returns the console listener.
670      *
671      * @return The console listener.
672      * @throws MavenReportException If something goes wrong.
673      */
674     protected DefaultLogger getConsoleListener()
675         throws MavenReportException
676     {
677         DefaultLogger consoleListener;
678 
679         if ( useFile == null )
680         {
681             stringOutputStream = new ByteArrayOutputStream();
682             consoleListener = new DefaultLogger( stringOutputStream, OutputStreamOptions.NONE );
683         }
684         else
685         {
686             OutputStream out = getOutputStream( useFile );
687 
688             consoleListener = new DefaultLogger( out, OutputStreamOptions.CLOSE );
689         }
690 
691         return consoleListener;
692     }
693 
694     private void generateReportStatics()
695         throws MavenReportException
696     {
697         ReportResource rresource = new ReportResource( PLUGIN_RESOURCES, outputDirectory );
698         try
699         {
700             rresource.copy( "images/rss.png" );
701         }
702         catch ( IOException e )
703         {
704             throw new MavenReportException( "Unable to copy static resources.", e );
705         }
706     }
707 
708 
709     private String getCopyright()
710     {
711         String copyright;
712         int currentYear = Calendar.getInstance().get( Calendar.YEAR );
713         if ( StringUtils.isNotEmpty( project.getInceptionYear() )
714             && !String.valueOf( currentYear ).equals( project.getInceptionYear() ) )
715         {
716             copyright = project.getInceptionYear() + " - " + currentYear;
717         }
718         else
719         {
720             copyright = String.valueOf( currentYear );
721         }
722 
723         if ( ( project.getOrganization() != null ) && StringUtils.isNotEmpty( project.getOrganization().getName() ) )
724         {
725             copyright = copyright + " " + project.getOrganization().getName();
726         }
727         return copyright;
728     }
729 
730     private void generateMainReport( CheckstyleResults results, ResourceBundle bundle, String configLocation )
731     {
732         CheckstyleReportGenerator generator =
733             new CheckstyleReportGenerator( getSink(), bundle, project.getBasedir(), siteTool, configLocation );
734 
735         generator.setLog( getLog() );
736         generator.setEnableRulesSummary( enableRulesSummary );
737         generator.setEnableSeveritySummary( enableSeveritySummary );
738         generator.setEnableFilesSummary( enableFilesSummary );
739         generator.setEnableRSS( enableRSS );
740         generator.setCheckstyleConfig( results.getConfiguration() );
741         if ( linkXRef )
742         {
743             initializeXrefLocation( generator );
744             if ( generator.getXrefLocation() == null && results.getFileCount() > 0 )
745             {
746                 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
747             }
748 
749             initializeXrefTestLocation( generator );
750             if ( generator.getXrefTestLocation() == null && results.getFileCount() > 0 )
751             {
752                 getLog().warn( "Unable to locate Test Source XRef to link to - DISABLED" );
753             }
754 
755             generator.setTestSourceDirectories( getTestSourceDirectories() );
756         }
757         if ( treeWalkerNames != null )
758         {
759             generator.setTreeWalkerNames( treeWalkerNames );
760         }
761         generator.generateReport( results );
762     }
763 
764     private void initializeXrefLocation( CheckstyleReportGenerator generator )
765     {
766       String relativePath = determineRelativePath( xrefLocation );
767       if ( xrefLocation.exists() || checkMavenJxrPluginIsConfigured() )
768       {
769           // XRef was already generated by manual execution of a lifecycle binding
770           // the report is on its way
771           generator.setXrefLocation( relativePath );
772       }
773     }
774 
775     private void initializeXrefTestLocation( CheckstyleReportGenerator generator )
776     {
777       String relativePath = determineRelativePath( xrefTestLocation );
778       if ( xrefTestLocation.exists() || checkMavenJxrPluginIsConfigured() )
779       {
780           // XRef was already generated by manual execution of a lifecycle binding
781           // the report is on its way
782           generator.setXrefTestLocation( relativePath );
783       }
784     }
785 
786     private String determineRelativePath( File location )
787     {
788       String relativePath = PathTool.getRelativePath( getOutputDirectory(), location.getAbsolutePath() );
789       if ( relativePath == null || relativePath.trim().isEmpty() )
790       {
791           relativePath = ".";
792       }
793 
794       return relativePath + "/" + location.getName();
795     }
796 
797     private boolean checkMavenJxrPluginIsConfigured()
798     {
799         for ( ReportPlugin report : (Iterable<ReportPlugin>) getProject().getReportPlugins() )
800         {
801             String artifactId = report.getArtifactId();
802             if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
803             {
804                 return true;
805             }
806         }
807 
808         return false;
809     }
810 
811     private static ResourceBundle getBundle( Locale locale )
812     {
813         return ResourceBundle.getBundle( "checkstyle-report", locale, AbstractCheckstyleReport.class.getClassLoader() );
814     }
815 
816     protected List<File> getSourceDirectories()
817     {
818         if ( sourceDirectories == null )
819         {
820             sourceDirectories = project.getCompileSourceRoots();
821         }
822         List<File> sourceDirs = new ArrayList<>( sourceDirectories.size() );
823         for ( String sourceDir : sourceDirectories )
824         {
825             sourceDirs.add( FileUtils.resolveFile( project.getBasedir(), sourceDir ) );
826         }
827         return sourceDirs;
828     }
829 
830     protected List<File> getTestSourceDirectories()
831     {
832         if ( testSourceDirectories == null )
833         {
834             testSourceDirectories = project.getTestCompileSourceRoots();
835         }
836         List<File> testSourceDirs = new ArrayList<>( testSourceDirectories.size() );
837         for ( String testSourceDir : testSourceDirectories )
838         {
839             testSourceDirs.add( FileUtils.resolveFile( project.getBasedir(), testSourceDir ) );
840         }
841         return testSourceDirs;
842     }
843 }