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