1   package org.apache.maven.plugins.checkstyle;
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  import java.io.BufferedReader;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileNotFoundException;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.io.Reader;
30  import java.nio.file.Files;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Map;
35  
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.maven.artifact.Artifact;
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.Resource;
42  import org.apache.maven.plugin.AbstractMojo;
43  import org.apache.maven.plugin.MojoExecutionException;
44  import org.apache.maven.plugin.MojoFailureException;
45  import org.apache.maven.plugin.descriptor.PluginDescriptor;
46  import org.apache.maven.plugins.annotations.Component;
47  import org.apache.maven.plugins.annotations.LifecyclePhase;
48  import org.apache.maven.plugins.annotations.Mojo;
49  import org.apache.maven.plugins.annotations.Parameter;
50  import org.apache.maven.plugins.annotations.ResolutionScope;
51  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
52  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
53  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
54  import org.apache.maven.project.MavenProject;
55  import org.codehaus.plexus.configuration.PlexusConfiguration;
56  import org.codehaus.plexus.util.FileUtils;
57  import org.codehaus.plexus.util.PathTool;
58  import org.codehaus.plexus.util.ReaderFactory;
59  import org.codehaus.plexus.util.xml.pull.MXParser;
60  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
61  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62  
63  import com.puppycrawl.tools.checkstyle.DefaultLogger;
64  import com.puppycrawl.tools.checkstyle.XMLLogger;
65  import com.puppycrawl.tools.checkstyle.api.AuditListener;
66  import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
67  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  @Mojo( name = "check", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.NONE,
78         threadSafe = true )
79  public class CheckstyleViolationCheckMojo
80      extends AbstractMojo
81  {
82  
83      private static final String JAVA_FILES = "**\\/*.java";
84      private static final String DEFAULT_CONFIG_LOCATION = "sun_checks.xml";
85  
86      
87  
88  
89  
90  
91      @Parameter( property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml" )
92      private File outputFile;
93  
94      
95  
96  
97  
98      @Parameter( property = "checkstyle.output.format", defaultValue = "xml" )
99      private String outputFileFormat;
100 
101     
102 
103 
104 
105 
106 
107     @Parameter( property = "checkstyle.failOnViolation", defaultValue = "true" )
108     private boolean failOnViolation;
109 
110     
111 
112 
113 
114 
115 
116     @Parameter( property = "checkstyle.maxAllowedViolations", defaultValue = "0" )
117     private int maxAllowedViolations;
118 
119     
120 
121 
122 
123 
124 
125     @Parameter( property = "checkstyle.violationSeverity", defaultValue = "error" )
126     private String violationSeverity = "error";
127 
128     
129 
130 
131 
132 
133 
134     @Parameter( property = "checkstyle.violation.ignore" )
135     private String violationIgnore;
136 
137     
138 
139 
140 
141 
142     @Parameter( property = "checkstyle.skip", defaultValue = "false" )
143     private boolean skip;
144 
145     
146 
147 
148 
149 
150     @Parameter( property = "checkstyle.skipExec", defaultValue = "false" )
151     private boolean skipExec;
152 
153     
154 
155 
156 
157 
158     @Parameter( property = "checkstyle.console", defaultValue = "true" )
159     private boolean logViolationsToConsole;
160 
161     
162 
163 
164 
165 
166     @Parameter( property = "checkstyle.logViolationCount", defaultValue = "true" )
167     private boolean logViolationCountToConsole;
168 
169     
170 
171 
172 
173 
174     @Parameter( defaultValue = "${project.resources}", readonly = true )
175     protected List<Resource> resources;
176 
177     
178 
179 
180 
181 
182     @Parameter( defaultValue = "${project.testResources}", readonly = true )
183     protected List<Resource> testResources;
184 
185     
186 
187 
188 
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199 
200 
201 
202 
203 
204 
205 
206 
207 
208     @Parameter( property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION )
209     private String configLocation;
210 
211     
212 
213 
214 
215 
216 
217 
218 
219 
220 
221 
222 
223 
224 
225 
226 
227     @Parameter( property = "checkstyle.properties.location" )
228     private String propertiesLocation;
229 
230     
231 
232 
233     @Parameter
234     private String propertyExpansion;
235 
236     
237 
238 
239 
240 
241 
242 
243 
244 
245 
246 
247 
248 
249 
250 
251 
252 
253 
254     @Parameter( property = "checkstyle.header.file", defaultValue = "LICENSE.txt" )
255     private String headerLocation;
256 
257     
258 
259 
260     @Parameter( defaultValue = "${project.build.directory}/checkstyle-cachefile" )
261     private String cacheFile;
262 
263     
264 
265 
266 
267 
268     @Parameter( property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file" )
269     private String suppressionsFileExpression;
270 
271     
272 
273 
274 
275 
276 
277 
278 
279 
280 
281 
282 
283 
284 
285     @Parameter( property = "checkstyle.suppressions.location" )
286     private String suppressionsLocation;
287 
288     
289 
290 
291 
292 
293 
294 
295     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
296     private String inputEncoding;
297 
298     
299 
300 
301     @Component( role = CheckstyleExecutor.class, hint = "default" )
302     protected CheckstyleExecutor checkstyleExecutor;
303 
304     
305 
306 
307     @Parameter( property = "checkstyle.consoleOutput", defaultValue = "false" )
308     private boolean consoleOutput;
309 
310     
311 
312 
313     @Parameter ( defaultValue = "${project}", readonly = true, required = true )
314     protected MavenProject project;
315 
316     
317 
318 
319     @Parameter( defaultValue = "${plugin}", readonly = true, required = true )
320     private PluginDescriptor plugin;
321 
322     
323 
324 
325 
326     @Parameter
327     private File useFile;
328 
329     
330 
331 
332 
333     @Parameter( property = "checkstyle.excludes" )
334     private String excludes;
335 
336     
337 
338 
339     @Parameter( property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true )
340     private String includes;
341 
342     
343 
344 
345 
346 
347     @Parameter( property = "checkstyle.resourceExcludes" )
348     private String resourceExcludes;
349 
350     
351 
352 
353 
354     @Parameter( property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true )
355     private String resourceIncludes;
356 
357     
358 
359 
360 
361 
362 
363     @Parameter( defaultValue = "false" )
364     private boolean failsOnError;
365 
366     
367 
368 
369 
370 
371 
372 
373     @Deprecated
374     @Parameter
375     private File testSourceDirectory;
376 
377     
378 
379 
380 
381 
382     
383     @Parameter
384     private List<String> testSourceDirectories;
385 
386     
387 
388 
389 
390 
391     @Parameter( defaultValue = "false" )
392     private boolean includeTestSourceDirectory;
393 
394     
395 
396 
397 
398 
399 
400     @Deprecated
401     @Parameter
402     private File sourceDirectory;
403 
404     
405 
406 
407 
408 
409     
410     @Parameter
411     private List<String> sourceDirectories;
412 
413     
414 
415 
416 
417     @Parameter( property = "checkstyle.includeResources", defaultValue = "true", required = true )
418     private boolean includeResources = true;
419 
420     
421 
422 
423 
424     @Parameter( property = "checkstyle.includeTestResources", defaultValue = "true", required = true )
425     private boolean includeTestResources = true;
426 
427     
428 
429 
430 
431 
432 
433 
434 
435 
436 
437 
438 
439 
440 
441 
442 
443 
444 
445 
446 
447 
448 
449 
450 
451     @Parameter
452     private PlexusConfiguration checkstyleRules;
453 
454     
455 
456 
457     @Parameter( property = "checkstyle.output.rules.file",
458                     defaultValue = "${project.build.directory}/checkstyle-rules.xml" )
459     private File rulesFiles;
460 
461     
462 
463 
464 
465     @Parameter( defaultValue = "<?xml version=\"1.0\"?>\n"
466             + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
467             + "        \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n" )
468     private String checkstyleRulesHeader;
469 
470     
471 
472 
473 
474 
475 
476     @Parameter( defaultValue = "false" )
477     private boolean omitIgnoredModules;
478 
479     private ByteArrayOutputStream stringOutputStream;
480 
481     private File outputXmlFile;
482 
483     
484     public void execute()
485         throws MojoExecutionException, MojoFailureException
486     {
487         checkDeprecatedParameterUsage( sourceDirectory, "sourceDirectory", "sourceDirectories" );
488         checkDeprecatedParameterUsage( testSourceDirectory, "testSourceDirectory", "testSourceDirectories" );
489         if ( skip )
490         {
491             return;
492         }
493 
494         outputXmlFile = outputFile;
495 
496         if ( !skipExec )
497         {
498             String effectiveConfigLocation = configLocation;
499             if ( checkstyleRules != null )
500             {
501                 if ( !DEFAULT_CONFIG_LOCATION.equals( configLocation ) )
502                 {
503                     throw new MojoExecutionException( "If you use inline configuration for rules, don't specify "
504                         + "a configLocation" );
505                 }
506                 if ( checkstyleRules.getChildCount() > 1 )
507                 {
508                     throw new MojoExecutionException( "Currently only one root module is supported" );
509                 }
510 
511                 PlexusConfiguration checkerModule = checkstyleRules.getChild( 0 );
512 
513                 try
514                 {
515                     FileUtils.forceMkdir( rulesFiles.getParentFile() );
516                     FileUtils.fileWrite( rulesFiles, checkstyleRulesHeader + checkerModule.toString() );
517                 }
518                 catch ( final IOException e )
519                 {
520                     throw new MojoExecutionException( e.getMessage(), e );
521                 }
522                 effectiveConfigLocation = rulesFiles.getAbsolutePath();
523             }
524 
525             ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
526 
527             try
528             {
529                 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
530                 request.setConsoleListener( getConsoleListener() ).setConsoleOutput( consoleOutput )
531                     .setExcludes( excludes ).setFailsOnError( failsOnError ).setIncludes( includes )
532                     .setResourceIncludes( resourceIncludes )
533                     .setResourceExcludes( resourceExcludes )
534                     .setIncludeResources( includeResources )
535                     .setIncludeTestResources( includeTestResources )
536                     .setIncludeTestSourceDirectory( includeTestSourceDirectory ).setListener( getListener() )
537                     .setProject( project ).setSourceDirectories( getSourceDirectories() )
538                     .setResources( resources ).setTestResources( testResources )
539                     .setStringOutputStream( stringOutputStream ).setSuppressionsLocation( suppressionsLocation )
540                     .setTestSourceDirectories( getTestSourceDirectories() ).setConfigLocation( effectiveConfigLocation )
541                     .setConfigurationArtifacts( collectArtifacts( "config" ) )
542                     .setPropertyExpansion( propertyExpansion )
543                     .setHeaderLocation( headerLocation ).setLicenseArtifacts( collectArtifacts( "license" ) )
544                     .setCacheFile( cacheFile ).setSuppressionsFileExpression( suppressionsFileExpression )
545                     .setEncoding( inputEncoding ).setPropertiesLocation( propertiesLocation )
546                     .setOmitIgnoredModules( omitIgnoredModules );
547                 checkstyleExecutor.executeCheckstyle( request );
548 
549             }
550             catch ( CheckstyleException e )
551             {
552                 throw new MojoExecutionException( "Failed during checkstyle configuration", e );
553             }
554             catch ( CheckstyleExecutorException e )
555             {
556                 throw new MojoExecutionException( "Failed during checkstyle execution", e );
557             }
558             finally
559             {
560                 
561                 Thread.currentThread().setContextClassLoader( currentClassLoader );
562             }
563         }
564 
565         if ( !"xml".equals( outputFileFormat ) && skipExec )
566         {
567             throw new MojoExecutionException( "Output format is '" + outputFileFormat
568                 + "', checkstyle:check requires format to be 'xml' when using skipExec." );
569         }
570 
571         if ( !outputXmlFile.exists() )
572         {
573             getLog().info( "Unable to perform checkstyle:check, unable to find checkstyle:checkstyle outputFile." );
574             return;
575         }
576 
577         try ( Reader reader = new BufferedReader( ReaderFactory.newXmlReader( outputXmlFile ) ) )
578         {
579             XmlPullParser xpp = new MXParser();
580             xpp.setInput( reader );
581 
582             final List<Violation> violationsList = getViolations( xpp );
583             long violationCount = countViolations( violationsList );
584             printViolations( violationsList );
585 
586             String msg = "You have " + violationCount + " Checkstyle violation"
587                 + ( ( violationCount > 1 || violationCount == 0 ) ? "s" : "" ) + ".";
588 
589             if ( violationCount > maxAllowedViolations )
590             {
591                 if ( failOnViolation )
592                 {
593                     if ( maxAllowedViolations > 0 )
594                     {
595                         msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
596                     }
597                     throw new MojoFailureException( msg );
598                 }
599 
600                 getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
601             }
602             if ( logViolationCountToConsole )
603             {
604                 if ( maxAllowedViolations > 0 )
605                 {
606                   msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
607                 }
608                 getLog().info( msg );
609             }
610         }
611         catch ( IOException | XmlPullParserException e )
612         {
613             throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
614                 + outputXmlFile.getAbsolutePath(), e );
615         }
616     }
617 
618     private void checkDeprecatedParameterUsage( Object parameter, String name, String replacement )
619         throws MojoFailureException
620     {
621         if ( parameter != null )
622         {
623             throw new MojoFailureException( "You are using '" + name + "' which has been removed"
624                 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
625                 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site." );
626         }
627     }
628 
629     private List<Violation> getViolations( XmlPullParser xpp )
630         throws XmlPullParserException, IOException
631     {
632         List<Violation> violations = new ArrayList<>();
633 
634         String basedir = project.getBasedir().getAbsolutePath();
635         String file = "";
636 
637         for ( int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next() )
638         {
639             if ( eventType != XmlPullParser.START_TAG )
640             {
641                 continue;
642             }
643             else if ( "file".equals( xpp.getName() ) )
644             {
645                 file = PathTool.getRelativeFilePath( basedir, xpp.getAttributeValue( "", "name" ) );
646                 continue;
647             }
648             else if ( ! "error".equals( xpp.getName() ) )
649             {
650                 continue;
651             }
652 
653             String severity = xpp.getAttributeValue( "", "severity" );
654             String source = xpp.getAttributeValue( "", "source" );
655             String line = xpp.getAttributeValue( "", "line" );
656             
657             String column = xpp.getAttributeValue( "", "column" );
658             String message = xpp.getAttributeValue( "", "message" );
659             String rule = RuleUtil.getName( source );
660             String category = RuleUtil.getCategory( source );
661 
662             Violation violation = new Violation(
663                 source,
664                 file,
665                 line,
666                 severity,
667                 message,
668                 rule,
669                 category
670             );
671             if ( column != null )
672             {
673                 violation.setColumn( column );
674             }
675 
676             violations.add( violation );
677         }
678 
679         return violations;
680     }
681 
682     private int countViolations( List<Violation> violations )
683     {
684         List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
685             : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
686 
687         int ignored = 0;
688         int countedViolations = 0;
689 
690         for ( Violation violation : violations )
691         {
692             if ( ! isViolation( violation.getSeverity() ) )
693             {
694                 continue;
695             }
696 
697             if ( ignore( ignores, violation.getSource() ) )
698             {
699                 ignored++;
700                 continue;
701             }
702 
703             countedViolations++;
704         }
705 
706         if ( ignored > 0 )
707         {
708             getLog().info( "Ignored " + ignored + " error" + ( ( ignored > 1L ) ? "s" : "" ) + ", " + countedViolations
709                 + " violation" + ( ( countedViolations > 1 ) ? "s" : "" ) + " remaining." );
710         }
711 
712         return countedViolations;
713     }
714 
715     private void printViolations( List<Violation> violations )
716     {
717         if ( ! logViolationsToConsole )
718         {
719             return;
720         }
721 
722         List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
723             : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
724 
725         violations.stream()
726             .filter( violation -> isViolation( violation.getSeverity() ) )
727             .filter( violation -> !ignore( ignores, violation.getSource() ) )
728             .forEach( violation ->
729             {
730                 final String message = String.format( "%s:[%s%s] (%s) %s: %s",
731                     violation.getFile(),
732                     violation.getLine(),
733                     ( Violation.NO_COLUMN.equals( violation.getColumn() ) ) ? "" : ( ',' + violation.getColumn() ),
734                     violation.getCategory(),
735                     violation.getRuleName(),
736                     violation.getMessage() );
737                 log( violation.getSeverity(), message );
738             } );
739     }
740 
741     private void log( String severity, String message )
742     {
743         if ( "info".equals( severity ) )
744         {
745             getLog().info( message );
746         }
747         else if ( "warning".equals( severity ) )
748         {
749             getLog().warn( message );
750         }
751         else
752         {
753             getLog().error( message );
754         }
755     }
756 
757     
758 
759 
760 
761 
762 
763     private boolean isViolation( String severity )
764     {
765         if ( "error".equals( severity ) )
766         {
767             return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
768                 || "info".equals( violationSeverity );
769         }
770         else if ( "warning".equals( severity ) )
771         {
772             return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
773         }
774         else if ( "info".equals( severity ) )
775         {
776             return "info".equals( violationSeverity );
777         }
778         else
779         {
780             return false;
781         }
782     }
783 
784     private boolean ignore( List<RuleUtil.Matcher> ignores, String source )
785     {
786         for ( RuleUtil.Matcher ignore : ignores )
787         {
788             if ( ignore.match( source ) )
789             {
790                 return true;
791             }
792         }
793         return false;
794     }
795 
796     private DefaultLogger getConsoleListener()
797         throws MojoExecutionException
798     {
799         DefaultLogger consoleListener;
800 
801         if ( useFile == null )
802         {
803             stringOutputStream = new ByteArrayOutputStream();
804             consoleListener = new DefaultLogger( stringOutputStream, OutputStreamOptions.NONE );
805         }
806         else
807         {
808             OutputStream out = getOutputStream( useFile );
809 
810             consoleListener = new DefaultLogger( out, OutputStreamOptions.CLOSE );
811         }
812 
813         return consoleListener;
814     }
815 
816     private OutputStream getOutputStream( File file )
817         throws MojoExecutionException
818     {
819         File parentFile = file.getAbsoluteFile().getParentFile();
820 
821         if ( !parentFile.exists() )
822         {
823             parentFile.mkdirs();
824         }
825 
826         FileOutputStream fileOutputStream;
827         try
828         {
829             fileOutputStream = new FileOutputStream( file );
830         }
831         catch ( FileNotFoundException e )
832         {
833             throw new MojoExecutionException( "Unable to create output stream: " + file, e );
834         }
835         return fileOutputStream;
836     }
837 
838     private AuditListener getListener()
839         throws MojoFailureException, MojoExecutionException
840     {
841         AuditListener listener = null;
842 
843         if ( StringUtils.isNotEmpty( outputFileFormat ) )
844         {
845             File resultFile = outputFile;
846 
847             OutputStream out = getOutputStream( resultFile );
848 
849             if ( "xml".equals( outputFileFormat ) )
850             {
851                 listener = new XMLLogger( out, OutputStreamOptions.CLOSE );
852             }
853             else if ( "plain".equals( outputFileFormat ) )
854             {
855                 try
856                 {
857                     
858                     
859                     outputXmlFile = Files.createTempFile( "checkstyle-result", ".xml" ).toFile();
860                     outputXmlFile.deleteOnExit();
861                     OutputStream xmlOut = getOutputStream( outputXmlFile );
862                     CompositeAuditListener compoundListener = new CompositeAuditListener();
863                     compoundListener.addListener( new XMLLogger( xmlOut, OutputStreamOptions.CLOSE ) );
864                     compoundListener.addListener( new DefaultLogger( out, OutputStreamOptions.CLOSE ) );
865                     listener = compoundListener;
866                 }
867                 catch ( IOException e )
868                 {
869                     throw new MojoExecutionException( "Unable to create temporary file", e );
870                 }
871             }
872             else
873             {
874                 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
875                     + "). Must be 'plain' or 'xml'." );
876             }
877         }
878 
879         return listener;
880     }
881 
882     private List<Artifact> collectArtifacts( String hint )
883     {
884         List<Artifact> artifacts = new ArrayList<>();
885 
886         PluginManagement pluginManagement = project.getBuild().getPluginManagement();
887         if ( pluginManagement != null )
888         {
889             artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( pluginManagement.getPluginsAsMap(), hint ) );
890         }
891 
892         artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( project.getBuild().getPluginsAsMap(), hint ) );
893 
894         return artifacts;
895     }
896 
897     private List<Artifact> getCheckstylePluginDependenciesAsArtifacts( Map<String, Plugin> plugins, String hint )
898     {
899         List<Artifact> artifacts = new ArrayList<>();
900 
901         Plugin checkstylePlugin = plugins.get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
902         if ( checkstylePlugin != null )
903         {
904             for ( Dependency dep : checkstylePlugin.getDependencies() )
905             {
906              
907                 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
908                 artifacts.add( plugin.getArtifactMap().get( depKey ) );
909             }
910         }
911         return artifacts;
912     }
913 
914     private List<File> getSourceDirectories()
915     {
916         if ( sourceDirectories == null )
917         {
918             sourceDirectories = project.getCompileSourceRoots();
919         }
920         List<File> sourceDirs = new ArrayList<>( sourceDirectories.size() );
921         for ( String sourceDir : sourceDirectories )
922         {
923             sourceDirs.add( FileUtils.resolveFile( project.getBasedir(), sourceDir ) );
924         }
925         return sourceDirs;
926     }
927 
928     private List<File> getTestSourceDirectories()
929     {
930         if ( testSourceDirectories == null )
931         {
932             testSourceDirectories = project.getTestCompileSourceRoots();
933         }
934         List<File> testSourceDirs = new ArrayList<>( testSourceDirectories.size() );
935         for ( String testSourceDir : testSourceDirectories )
936         {
937             testSourceDirs.add( FileUtils.resolveFile( project.getBasedir(), testSourceDir ) );
938         }
939         return testSourceDirs;
940     }
941 
942 }