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