1 package org.apache.maven.plugin.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.ByteArrayInputStream;
23 import java.io.Closeable;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Properties;
34
35 import org.apache.commons.io.IOUtils;
36 import org.apache.maven.artifact.Artifact;
37 import org.apache.maven.artifact.DependencyResolutionRequiredException;
38 import org.apache.maven.model.Resource;
39 import org.apache.maven.project.MavenProject;
40 import org.codehaus.plexus.component.annotations.Component;
41 import org.codehaus.plexus.component.annotations.Requirement;
42 import org.codehaus.plexus.logging.AbstractLogEnabled;
43 import org.codehaus.plexus.resource.ResourceManager;
44 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
45 import org.codehaus.plexus.resource.loader.FileResourceLoader;
46 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
47 import org.codehaus.plexus.util.FileUtils;
48 import org.codehaus.plexus.util.StringUtils;
49
50 import com.puppycrawl.tools.checkstyle.Checker;
51 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
52 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
53 import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
54 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
55 import com.puppycrawl.tools.checkstyle.api.AuditListener;
56 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
57 import com.puppycrawl.tools.checkstyle.api.Configuration;
58 import com.puppycrawl.tools.checkstyle.api.FilterSet;
59 import com.puppycrawl.tools.checkstyle.filters.SuppressionsLoader;
60
61
62
63
64
65
66 @Component( role = CheckstyleExecutor.class, hint = "default", instantiationStrategy = "per-lookup" )
67 public class DefaultCheckstyleExecutor
68 extends AbstractLogEnabled
69 implements CheckstyleExecutor
70 {
71 @Requirement( hint = "default" )
72 private ResourceManager locator;
73
74 @Requirement( hint = "license" )
75 private ResourceManager licenseLocator;
76
77 private static final File[] EMPTY_FILE_ARRAY = new File[0];
78
79 public CheckstyleResults executeCheckstyle( CheckstyleExecutorRequest request )
80 throws CheckstyleExecutorException, CheckstyleException
81 {
82
83
84
85
86 ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
87 Thread.currentThread().setContextClassLoader( checkstyleClassLoader );
88
89 if ( getLogger().isDebugEnabled() )
90 {
91 getLogger().debug( "executeCheckstyle start headerLocation : " + request.getHeaderLocation() );
92 }
93
94 MavenProject project = request.getProject();
95
96 configureResourceLocator( locator, request, null );
97
98 configureResourceLocator( licenseLocator, request, request.getLicenseArtifacts() );
99
100
101
102
103 File[] files;
104 try
105 {
106 files = getFilesToProcess( request );
107 }
108 catch ( IOException e )
109 {
110 throw new CheckstyleExecutorException( "Error getting files to process", e );
111 }
112
113 final String suppressionsFilePath = getSuppressionsFilePath( request );
114 FilterSet filterSet = getSuppressionsFilterSet( suppressionsFilePath );
115
116 Checker checker = new Checker();
117
118
119
120 List<String> classPathStrings = new ArrayList<String>();
121 List<String> outputDirectories = new ArrayList<String>();
122 File sourceDirectory = request.getSourceDirectory();
123 File testSourceDirectory = request.getTestSourceDirectory();
124 if ( request.isAggregate() )
125 {
126 for ( MavenProject childProject : request.getReactorProjects() )
127 {
128 prepareCheckstylePaths( request, childProject, classPathStrings, outputDirectories,
129 new File( childProject.getBuild().getSourceDirectory() ),
130 new File( childProject.getBuild().getTestSourceDirectory() ) );
131 }
132 }
133 else
134 {
135 prepareCheckstylePaths( request, project, classPathStrings, outputDirectories, sourceDirectory,
136 testSourceDirectory );
137 }
138
139 List<URL> urls = new ArrayList<URL>( classPathStrings.size() );
140
141 for ( String path : classPathStrings )
142 {
143 try
144 {
145 urls.add( new File( path ).toURL() );
146 }
147 catch ( MalformedURLException e )
148 {
149 throw new CheckstyleExecutorException( e.getMessage(), e );
150 }
151 }
152
153 for ( String outputDirectoryString : outputDirectories )
154 {
155 try
156 {
157 if ( outputDirectoryString != null )
158 {
159 File outputDirectoryFile = new File( outputDirectoryString );
160 if ( outputDirectoryFile.exists() )
161 {
162 URL outputDirectoryUrl = outputDirectoryFile.toURL();
163 request.getLog().debug(
164 "Adding the outputDirectory " + outputDirectoryUrl.toString()
165 + " to the Checkstyle class path" );
166 urls.add( outputDirectoryUrl );
167 }
168 }
169 }
170 catch ( MalformedURLException e )
171 {
172 throw new CheckstyleExecutorException( e.getMessage(), e );
173 }
174 }
175
176 URLClassLoader projectClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null );
177 checker.setClassloader( projectClassLoader );
178
179 checker.setModuleClassLoader( Thread.currentThread().getContextClassLoader() );
180
181 if ( filterSet != null )
182 {
183 checker.addFilter( filterSet );
184 }
185 Configuration configuration = getConfiguration( request );
186 checker.configure( configuration );
187
188 AuditListener listener = request.getListener();
189
190 if ( listener != null )
191 {
192 checker.addListener( listener );
193 }
194
195 if ( request.isConsoleOutput() )
196 {
197 checker.addListener( request.getConsoleListener() );
198 }
199
200 CheckstyleReportListener sinkListener = new CheckstyleReportListener( configuration );
201 if ( request.isAggregate() )
202 {
203 for ( MavenProject childProject : request.getReactorProjects() )
204 {
205 addSourceDirectory( sinkListener, new File( childProject.getBuild().getSourceDirectory() ),
206 new File( childProject.getBuild().getTestSourceDirectory() ),
207 childProject.getResources(), request );
208 }
209 }
210 else
211 {
212 addSourceDirectory( sinkListener, sourceDirectory, testSourceDirectory, request.getResources(), request );
213 }
214
215 checker.addListener( sinkListener );
216
217 List<File> filesList = Arrays.asList( files );
218 int nbErrors = checker.process( filesList );
219
220 checker.destroy();
221
222 if ( projectClassLoader instanceof Closeable )
223 {
224 try
225 {
226 ( ( Closeable ) projectClassLoader ).close();
227 }
228 catch ( IOException ex )
229 {
230
231 getLogger().info( "Failed to close custom Classloader - this indicated a bug in the code.", ex );
232 }
233 }
234
235 if ( request.getStringOutputStream() != null )
236 {
237 request.getLog().info( request.getStringOutputStream().toString() );
238 }
239
240 if ( request.isFailsOnError() && nbErrors > 0 )
241 {
242
243
244
245 throw new CheckstyleExecutorException( "There are " + nbErrors + " checkstyle errors." );
246 }
247 else if ( nbErrors > 0 )
248 {
249 request.getLog().info( "There are " + nbErrors + " checkstyle errors." );
250 }
251
252 return sinkListener.getResults();
253 }
254
255 protected void addSourceDirectory( CheckstyleReportListener sinkListener, File sourceDirectory,
256 File testSourceDirectory, List<Resource> resources,
257 CheckstyleExecutorRequest request )
258 {
259 if ( sourceDirectory != null )
260 {
261 sinkListener.addSourceDirectory( sourceDirectory );
262 }
263
264 if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
265 && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
266 {
267 sinkListener.addSourceDirectory( testSourceDirectory );
268 }
269
270 if ( resources != null )
271 {
272 for ( Resource resource : resources )
273 {
274 if ( resource.getDirectory() != null )
275 {
276 File resourcesDirectory = new File( resource.getDirectory() );
277 if ( resourcesDirectory.exists() && resourcesDirectory.isDirectory() )
278 {
279 sinkListener.addSourceDirectory( resourcesDirectory );
280 getLogger().debug( "Added '" + resourcesDirectory.getAbsolutePath()
281 + "' as a source directory." );
282 }
283 }
284 }
285 }
286 }
287
288 public Configuration getConfiguration( CheckstyleExecutorRequest request )
289 throws CheckstyleExecutorException
290 {
291 try
292 {
293
294
295
296 ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
297 Thread.currentThread().setContextClassLoader( checkstyleClassLoader );
298 String configFile = getConfigFile( request );
299 Properties overridingProperties = getOverridingProperties( request );
300 Configuration config = ConfigurationLoader
301 .loadConfiguration( configFile, new PropertiesExpander( overridingProperties ) );
302 String effectiveEncoding = StringUtils.isNotEmpty( request.getEncoding() ) ? request.getEncoding() : System
303 .getProperty( "file.encoding", "UTF-8" );
304
305 if ( StringUtils.isEmpty( request.getEncoding() ) )
306 {
307 request.getLog().warn(
308 "File encoding has not been set, using platform encoding " + effectiveEncoding
309 + ", i.e. build is platform dependent!" );
310 }
311
312 if ( "Checker".equals( config.getName() )
313 || "com.puppycrawl.tools.checkstyle.Checker".equals( config.getName() ) )
314 {
315 if ( config instanceof DefaultConfiguration )
316 {
317
318 try
319 {
320 if ( config.getAttribute( "charset" ) == null )
321 {
322 ( (DefaultConfiguration) config ).addAttribute( "charset", effectiveEncoding );
323 }
324 }
325 catch ( CheckstyleException ex )
326 {
327
328 ( (DefaultConfiguration) config ).addAttribute( "charset", effectiveEncoding );
329 }
330 }
331 else
332 {
333 request.getLog().warn( "Failed to configure file encoding on module " + config );
334 }
335 }
336 Configuration[] modules = config.getChildren();
337 for ( Configuration module : modules )
338 {
339 if ( "TreeWalker".equals( module.getName() )
340 || "com.puppycrawl.tools.checkstyle.TreeWalker".equals( module.getName() ) )
341 {
342 if ( module instanceof DefaultConfiguration )
343 {
344
345 try
346 {
347 if ( module.getAttribute( "cacheFile" ) == null )
348 {
349 ( (DefaultConfiguration) module ).addAttribute( "cacheFile", request.getCacheFile() );
350 }
351 }
352 catch ( CheckstyleException ex )
353 {
354
355
356
357 ( (DefaultConfiguration) module ).addAttribute( "cacheFile", request.getCacheFile() );
358 }
359 }
360 else
361 {
362 request.getLog().warn( "Failed to configure cache file on module " + module );
363 }
364 }
365 }
366 return config;
367 }
368 catch ( CheckstyleException e )
369 {
370 throw new CheckstyleExecutorException( "Failed during checkstyle configuration", e );
371 }
372 }
373
374 private void prepareCheckstylePaths( CheckstyleExecutorRequest request, MavenProject project,
375 List<String> classPathStrings, List<String> outputDirectories,
376 File sourceDirectory, File testSourceDirectory )
377 throws CheckstyleExecutorException
378 {
379 try
380 {
381 outputDirectories.add( project.getBuild().getOutputDirectory() );
382
383 if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
384 && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
385 {
386 classPathStrings.addAll( project.getTestClasspathElements() );
387 outputDirectories.add( project.getBuild().getTestOutputDirectory() );
388 }
389 else
390 {
391 classPathStrings.addAll( project.getCompileClasspathElements() );
392 }
393 }
394 catch ( DependencyResolutionRequiredException e )
395 {
396 throw new CheckstyleExecutorException( e.getMessage(), e );
397 }
398 }
399
400 private Properties getOverridingProperties( CheckstyleExecutorRequest request )
401 throws CheckstyleExecutorException
402 {
403 Properties p = new Properties();
404
405 try
406 {
407 if ( request.getPropertiesLocation() != null )
408 {
409 if ( getLogger().isDebugEnabled() )
410 {
411 getLogger().debug( "request.getPropertiesLocation() " + request.getPropertiesLocation() );
412 }
413
414 File propertiesFile = locator.getResourceAsFile( request.getPropertiesLocation(),
415 "checkstyle-checker.properties" );
416
417 FileInputStream properties = new FileInputStream( propertiesFile );
418 try
419 {
420 if ( propertiesFile != null )
421 {
422 p.load( properties );
423 }
424 }
425 finally
426 {
427 IOUtils.closeQuietly( properties );
428 }
429 }
430
431 if ( StringUtils.isNotEmpty( request.getPropertyExpansion() ) )
432 {
433 String propertyExpansion = request.getPropertyExpansion();
434
435 propertyExpansion = StringUtils.replace( propertyExpansion, "\\", "\\\\" );
436 p.load( new ByteArrayInputStream( propertyExpansion.getBytes() ) );
437 }
438
439
440
441
442 String headerLocation = request.getHeaderLocation();
443 if ( "config/maven_checks.xml".equals( request.getConfigLocation() ) )
444 {
445
446 if ( "LICENSE.txt".equals( request.getHeaderLocation() ) )
447 {
448 headerLocation = "config/maven-header.txt";
449 }
450 }
451 if ( getLogger().isDebugEnabled() )
452 {
453 getLogger().debug( "headerLocation " + headerLocation );
454 }
455
456 if ( StringUtils.isNotEmpty( headerLocation ) )
457 {
458 try
459 {
460 File headerFile = licenseLocator.getResourceAsFile( headerLocation, "checkstyle-header.txt" );
461
462 if ( headerFile != null )
463 {
464 p.setProperty( "checkstyle.header.file", headerFile.getAbsolutePath() );
465 }
466 }
467 catch ( FileResourceCreationException e )
468 {
469 getLogger().debug( "Unable to process header location: " + headerLocation );
470 getLogger().debug( "Checkstyle will throw exception if ${checkstyle.header.file} is used" );
471 }
472 catch ( ResourceNotFoundException e )
473 {
474 getLogger().debug( "Unable to process header location: " + headerLocation );
475 getLogger().debug( "Checkstyle will throw exception if ${checkstyle.header.file} is used" );
476 }
477 }
478
479 if ( request.getCacheFile() != null )
480 {
481 p.setProperty( "checkstyle.cache.file", request.getCacheFile() );
482 }
483 }
484 catch ( IOException e )
485 {
486 throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
487 }
488 catch ( FileResourceCreationException e )
489 {
490 throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
491 }
492 catch ( ResourceNotFoundException e )
493 {
494 throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
495 }
496 if ( request.getSuppressionsFileExpression() != null )
497 {
498 String suppressionsFilePath = getSuppressionsFilePath( request );
499
500 if ( suppressionsFilePath != null )
501 {
502 p.setProperty( request.getSuppressionsFileExpression(), suppressionsFilePath );
503 }
504 }
505
506 return p;
507 }
508
509 private File[] getFilesToProcess( CheckstyleExecutorRequest request )
510 throws IOException
511 {
512 StringBuilder excludesStr = new StringBuilder();
513
514 if ( StringUtils.isNotEmpty( request.getExcludes() ) )
515 {
516 excludesStr.append( request.getExcludes() );
517 }
518
519 String[] defaultExcludes = FileUtils.getDefaultExcludes();
520 for ( String defaultExclude : defaultExcludes )
521 {
522 if ( excludesStr.length() > 0 )
523 {
524 excludesStr.append( "," );
525 }
526
527 excludesStr.append( defaultExclude );
528 }
529
530 File sourceDirectory = request.getSourceDirectory();
531
532 List<File> files = new ArrayList<File>();
533 if ( request.isAggregate() )
534 {
535 for ( MavenProject project : request.getReactorProjects() )
536 {
537 addFilesToProcess( request, new File( project.getBuild().getSourceDirectory() ),
538 project.getResources(), project.getTestResources(),
539 files, new File( project.getBuild().getTestSourceDirectory() )
540 );
541 }
542 }
543 else
544 {
545 addFilesToProcess( request, sourceDirectory, request.getResources(),
546 request.getTestResources(), files, request.getTestSourceDirectory() );
547 }
548
549 getLogger().debug( "Added " + files.size() + " files to process." );
550
551 return files.toArray( new File[files.size()] );
552 }
553
554 private void addFilesToProcess( CheckstyleExecutorRequest request, File sourceDirectory, List<Resource> resources,
555 List<Resource> testResources, List<File> files, File testSourceDirectory )
556 throws IOException
557 {
558 if ( sourceDirectory != null && sourceDirectory.exists() )
559 {
560 final List<File> sourceFiles =
561 FileUtils.getFiles( sourceDirectory, request.getIncludes(), request.getExcludes() );
562 files.addAll( sourceFiles );
563 getLogger().debug( "Added " + sourceFiles.size() + " source files found in '"
564 + sourceDirectory.getAbsolutePath() + "'." );
565 }
566
567 if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
568 && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
569 {
570 final List<File> testSourceFiles =
571 FileUtils.getFiles( testSourceDirectory, request.getIncludes(), request.getExcludes() );
572 files.addAll( testSourceFiles );
573 getLogger().debug( "Added " + testSourceFiles.size() + " test source files found in '"
574 + testSourceDirectory.getAbsolutePath() + "'." );
575 }
576
577 if ( resources != null && request.isIncludeResources() )
578 {
579 addResourceFilesToProcess( request, resources, files );
580 }
581 else
582 {
583 getLogger().debug( "No resources found in this project." );
584 }
585
586 if ( testResources != null && request.isIncludeTestResources() )
587 {
588 addResourceFilesToProcess( request, testResources, files );
589 }
590 else
591 {
592 getLogger().debug( "No test resources found in this project." );
593 }
594 }
595
596 private void addResourceFilesToProcess( CheckstyleExecutorRequest request, List<Resource> resources,
597 List<File> files )
598 throws IOException
599 {
600 for ( Resource resource : resources )
601 {
602 if ( resource.getDirectory() != null )
603 {
604 File resourcesDirectory = new File( resource.getDirectory() );
605 if ( resourcesDirectory.exists() && resourcesDirectory.isDirectory() )
606 {
607 List<File> resourceFiles =
608 FileUtils.getFiles( resourcesDirectory, request.getResourceIncludes(),
609 request.getResourceExcludes() );
610 files.addAll( resourceFiles );
611 getLogger().debug( "Added " + resourceFiles.size() + " resource files found in '"
612 + resourcesDirectory.getAbsolutePath() + "'." );
613 }
614 else
615 {
616 getLogger().debug( "The resources directory '" + resourcesDirectory.getAbsolutePath()
617 + "' does not exist or is not a directory." );
618 }
619 }
620 }
621 }
622
623 private FilterSet getSuppressionsFilterSet( final String suppressionsFilePath )
624 throws CheckstyleExecutorException
625 {
626
627 if ( suppressionsFilePath == null )
628 {
629 return null;
630 }
631
632 try
633 {
634 return SuppressionsLoader.loadSuppressions( suppressionsFilePath );
635 }
636 catch ( CheckstyleException ce )
637 {
638 throw new CheckstyleExecutorException( "Failed to load suppressions file from: "
639 + suppressionsFilePath, ce );
640 }
641 }
642
643 private String getSuppressionsFilePath( final CheckstyleExecutorRequest request ) throws CheckstyleExecutorException
644 {
645 final String suppressionsLocation = request.getSuppressionsLocation();
646 if ( StringUtils.isEmpty( suppressionsLocation ) )
647 {
648 return null;
649 }
650
651 try
652 {
653 File suppressionsFile = locator.getResourceAsFile( suppressionsLocation, "checkstyle-suppressions.xml" );
654 return suppressionsFile == null ? null : suppressionsFile.getAbsolutePath();
655 }
656 catch ( ResourceNotFoundException e )
657 {
658 throw new CheckstyleExecutorException( "Unable to find suppressions file at location: "
659 + suppressionsLocation, e );
660 }
661 catch ( FileResourceCreationException e )
662 {
663 throw new CheckstyleExecutorException( "Unable to process suppressions file location: "
664 + suppressionsLocation, e );
665 }
666 }
667
668 private String getConfigFile( CheckstyleExecutorRequest request )
669 throws CheckstyleExecutorException
670 {
671 try
672 {
673 if ( getLogger().isDebugEnabled() )
674 {
675 getLogger().debug( "request.getConfigLocation() " + request.getConfigLocation() );
676 }
677
678 File configFile = locator.getResourceAsFile( request.getConfigLocation(), "checkstyle-checker.xml" );
679 if ( configFile == null )
680 {
681 throw new CheckstyleExecutorException( "Unable to process config location: "
682 + request.getConfigLocation() );
683 }
684 return configFile.getAbsolutePath();
685 }
686 catch ( ResourceNotFoundException e )
687 {
688 throw new CheckstyleExecutorException( "Unable to find configuration file at location: "
689 + request.getConfigLocation(), e );
690 }
691 catch ( FileResourceCreationException e )
692 {
693 throw new CheckstyleExecutorException( "Unable to process configuration file at location: "
694 + request.getConfigLocation(), e );
695 }
696
697 }
698
699
700
701
702
703
704
705 private void configureResourceLocator( final ResourceManager resourceManager,
706 final CheckstyleExecutorRequest request,
707 final List<Artifact> additionalArtifacts )
708 {
709 final MavenProject project = request.getProject();
710 resourceManager.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
711
712
713 MavenProject parent = project;
714 while ( parent != null && parent.getFile() != null )
715 {
716
717
718
719 File dir = parent.getFile().getParentFile();
720 resourceManager.addSearchPath( FileResourceLoader.ID, dir.getAbsolutePath() );
721 parent = parent.getParent();
722 }
723 resourceManager.addSearchPath( "url", "" );
724
725
726 if ( additionalArtifacts != null )
727 {
728 for ( Artifact licenseArtifact : additionalArtifacts )
729 {
730 try
731 {
732 resourceManager.addSearchPath( "jar", "jar:" + licenseArtifact.getFile().toURI().toURL() );
733 }
734 catch ( MalformedURLException e )
735 {
736
737 }
738 }
739 }
740 }
741 }