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.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.net.URLClassLoader;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.List;
32  import java.util.Properties;
33  
34  import org.apache.commons.io.IOUtils;
35  import org.apache.maven.artifact.DependencyResolutionRequiredException;
36  import org.apache.maven.model.Resource;
37  import org.apache.maven.project.MavenProject;
38  import org.codehaus.plexus.component.annotations.Component;
39  import org.codehaus.plexus.component.annotations.Requirement;
40  import org.codehaus.plexus.logging.AbstractLogEnabled;
41  import org.codehaus.plexus.resource.ResourceManager;
42  import org.codehaus.plexus.resource.loader.FileResourceCreationException;
43  import org.codehaus.plexus.resource.loader.FileResourceLoader;
44  import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
45  import org.codehaus.plexus.util.FileUtils;
46  import org.codehaus.plexus.util.StringUtils;
47  
48  import com.puppycrawl.tools.checkstyle.Checker;
49  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
50  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
51  import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
52  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
53  import com.puppycrawl.tools.checkstyle.api.AuditListener;
54  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
55  import com.puppycrawl.tools.checkstyle.api.Configuration;
56  import com.puppycrawl.tools.checkstyle.api.FilterSet;
57  import com.puppycrawl.tools.checkstyle.filters.SuppressionsLoader;
58  
59  /**
60   * @author Olivier Lamy
61   * @since 2.5
62   * @version $Id: DefaultCheckstyleExecutor.java 1441973 2013-02-03 21:44:20Z dennisl $
63   */
64  @Component( role = CheckstyleExecutor.class, hint = "default", instantiationStrategy = "per-lookup" )
65  public class DefaultCheckstyleExecutor
66      extends AbstractLogEnabled
67      implements CheckstyleExecutor
68  {
69      @Requirement
70      private ResourceManager locator;
71  
72      private static final File[] EMPTY_FILE_ARRAY = new File[0];
73  
74      public CheckstyleResults executeCheckstyle( CheckstyleExecutorRequest request )
75          throws CheckstyleExecutorException, CheckstyleException
76      {
77          // checkstyle will always use the context classloader in order
78          // to load resources (dtds),
79          // so we have to fix it
80          // olamy this hack is not anymore needed in maven 3.x
81          ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
82          Thread.currentThread().setContextClassLoader( checkstyleClassLoader );
83  
84          if ( getLogger().isDebugEnabled() )
85          {
86              getLogger().debug( "executeCheckstyle start headerLocation : " + request.getHeaderLocation() );
87          }
88          MavenProject project = request.getProject();
89          locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
90          File[] files;
91          try
92          {
93              files = getFilesToProcess( request );
94          }
95          catch ( IOException e )
96          {
97              throw new CheckstyleExecutorException( "Error getting files to process", e );
98          }
99  
100         FilterSet filterSet = getSuppressions( request );
101 
102         Checker checker = new Checker();
103 
104         // setup classloader, needed to avoid "Unable to get class information
105         // for ..." errors
106         List<String> classPathStrings = new ArrayList<String>();
107         List<String> outputDirectories = new ArrayList<String>();
108         File sourceDirectory = request.getSourceDirectory();
109         File testSourceDirectory = request.getTestSourceDirectory();
110         if ( request.isAggregate() )
111         {
112             for ( MavenProject childProject : request.getReactorProjects() )
113             {
114                 prepareCheckstylePaths( request, childProject, classPathStrings, outputDirectories,
115                                         new File( childProject.getBuild().getSourceDirectory() ),
116                                         new File( childProject.getBuild().getTestSourceDirectory() ) );
117             }
118         }
119         else
120         {
121             prepareCheckstylePaths( request, project, classPathStrings, outputDirectories, sourceDirectory,
122                                     testSourceDirectory );
123         }
124 
125         List<URL> urls = new ArrayList<URL>( classPathStrings.size() );
126 
127         for ( String path : classPathStrings )
128         {
129             try
130             {
131                 urls.add( new File( path ).toURL() );
132             }
133             catch ( MalformedURLException e )
134             {
135                 throw new CheckstyleExecutorException( e.getMessage(), e );
136             }
137         }
138 
139         for ( String outputDirectoryString : outputDirectories )
140         {
141             try
142             {
143                 if ( outputDirectoryString != null )
144                 {
145                     File outputDirectoryFile = new File( outputDirectoryString );
146                     if ( outputDirectoryFile.exists() )
147                     {
148                         URL outputDirectoryUrl = outputDirectoryFile.toURL();
149                         request.getLog().debug(
150                                                 "Adding the outputDirectory " + outputDirectoryUrl.toString()
151                                                     + " to the Checkstyle class path" );
152                         urls.add( outputDirectoryUrl );
153                     }
154                 }
155             }
156             catch ( MalformedURLException e )
157             {
158                 throw new CheckstyleExecutorException( e.getMessage(), e );
159             }
160         }
161 
162         URLClassLoader projectClassLoader = new URLClassLoader( (URL[]) urls.toArray( new URL[urls.size()] ), null );
163         checker.setClassloader( projectClassLoader );
164 
165         checker.setModuleClassLoader( Thread.currentThread().getContextClassLoader() );
166 
167         if ( filterSet != null )
168         {
169             checker.addFilter( filterSet );
170         }
171         Configuration configuration = getConfiguration( request );
172         checker.configure( configuration );
173 
174         AuditListener listener = request.getListener();
175 
176         if ( listener != null )
177         {
178             checker.addListener( listener );
179         }
180 
181         if ( request.isConsoleOutput() )
182         {
183             checker.addListener( request.getConsoleListener() );
184         }
185 
186         CheckstyleReportListener sinkListener = new CheckstyleReportListener( configuration );
187         if ( request.isAggregate() )
188         {
189             for ( MavenProject childProject : request.getReactorProjects() )
190             {
191                 addSourceDirectory( sinkListener, new File( childProject.getBuild().getSourceDirectory() ),
192                                     new File( childProject.getBuild().getTestSourceDirectory() ),
193                                     childProject.getResources(), request );
194             }
195         }
196         else
197         {
198             addSourceDirectory( sinkListener, sourceDirectory, testSourceDirectory, request.getResources(), request );
199         }
200 
201         checker.addListener( sinkListener );
202 
203         List<File> filesList = Arrays.asList( files );
204         int nbErrors = checker.process( filesList );
205 
206         checker.destroy();
207 
208         if ( request.getStringOutputStream() != null )
209         {
210             request.getLog().info( request.getStringOutputStream().toString() );
211         }
212 
213         if ( request.isFailsOnError() && nbErrors > 0 )
214         {
215             // TODO: should be a failure, not an error. Report is not meant to
216             // throw an exception here (so site would
217             // work regardless of config), but should record this information
218             throw new CheckstyleExecutorException( "There are " + nbErrors + " checkstyle errors." );
219         }
220         else if ( nbErrors > 0 )
221         {
222             request.getLog().info( "There are " + nbErrors + " checkstyle errors." );
223         }
224 
225         return sinkListener.getResults();
226     }
227 
228     protected void addSourceDirectory( CheckstyleReportListener sinkListener, File sourceDirectory,
229                                        File testSourceDirectory, List<Resource> resources,
230                                        CheckstyleExecutorRequest request )
231     {
232         if ( sourceDirectory != null )
233         {
234             sinkListener.addSourceDirectory( sourceDirectory );
235         }
236 
237         if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
238             && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
239         {
240             sinkListener.addSourceDirectory( testSourceDirectory );
241         }
242 
243         if ( resources != null )
244         {
245             for ( Resource resource : resources )
246             {
247                 if ( resource.getDirectory() != null )
248                 {
249                     File resourcesDirectory = new File( resource.getDirectory() );
250                     if ( resourcesDirectory.exists() && resourcesDirectory.isDirectory() )
251                     {
252                         sinkListener.addSourceDirectory( resourcesDirectory );
253                         getLogger().debug( "Added '" + resourcesDirectory.getAbsolutePath()
254                                 + "' as a source directory." );
255                     }
256                 }
257             }
258         }
259     }
260 
261     public Configuration getConfiguration( CheckstyleExecutorRequest request )
262         throws CheckstyleExecutorException
263     {
264         try
265         {
266             // checkstyle will always use the context classloader in order
267             // to load resources (dtds),
268             // so we have to fix it
269             ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
270             Thread.currentThread().setContextClassLoader( checkstyleClassLoader );
271             String configFile = getConfigFile( request );
272             Properties overridingProperties = getOverridingProperties( request );
273             Configuration config = ConfigurationLoader
274                 .loadConfiguration( configFile, new PropertiesExpander( overridingProperties ) );
275             String effectiveEncoding = StringUtils.isNotEmpty( request.getEncoding() ) ? request.getEncoding() : System
276                 .getProperty( "file.encoding", "UTF-8" );
277             if ( StringUtils.isEmpty( request.getEncoding() ) )
278             {
279                 request.getLog().warn(
280                                        "File encoding has not been set, using platform encoding " + effectiveEncoding
281                                            + ", i.e. build is platform dependent!" );
282             }
283 
284             if ( "Checker".equals( config.getName() )
285                     || "com.puppycrawl.tools.checkstyle.Checker".equals( config.getName() ) )
286             {
287                 if ( config instanceof DefaultConfiguration )
288                 {
289                     // MCHECKSTYLE-173 Only add the "charset" attribute if it has not been set
290                     try
291                     {
292                         if ( ( (DefaultConfiguration) config ).getAttribute( "charset" ) == null )
293                         {
294                             ( (DefaultConfiguration) config ).addAttribute( "charset", effectiveEncoding );
295                         }
296                     }
297                     catch ( CheckstyleException ex )
298                     {
299                         // Checkstyle 5.4+ throws an exception when trying to access an attribute that doesn't exist
300                         ( (DefaultConfiguration) config ).addAttribute( "charset", effectiveEncoding );
301                     }
302                 }
303                 else
304                 {
305                     request.getLog().warn( "Failed to configure file encoding on module " + config );
306                 }
307             }
308             Configuration[] modules = config.getChildren();
309             for ( int i = 0; i < modules.length; i++ )
310             {
311                 Configuration module = modules[i];
312                 if ( "TreeWalker".equals( module.getName() )
313                     || "com.puppycrawl.tools.checkstyle.TreeWalker".equals( module.getName() ) )
314                 {
315                     if ( module instanceof DefaultConfiguration )
316                     {
317                         //MCHECKSTYLE-132 DefaultConfiguration addAttribute has changed in checkstyle 5.3
318                         try
319                         {
320                             if ( ( (DefaultConfiguration) module ).getAttribute( "cacheFile" ) == null )
321                             {
322                                 ( (DefaultConfiguration) module ).addAttribute( "cacheFile", request.getCacheFile() );
323                             }
324                         }
325                         catch ( CheckstyleException ex )
326                         {
327                             //MCHECKSTYLE-159 - checkstyle 5.4 throws an exception instead of return null if "cacheFile"
328                             // doesn't exist
329                             ( (DefaultConfiguration) module ).addAttribute( "cacheFile", request.getCacheFile() );
330                         }
331                     }
332                     else
333                     {
334                         request.getLog().warn( "Failed to configure cache file on module " + module );
335                     }
336                 }
337             }
338             return config;
339         }
340         catch ( CheckstyleException e )
341         {
342             throw new CheckstyleExecutorException( "Failed during checkstyle configuration", e );
343         }
344     }
345 
346     private void prepareCheckstylePaths( CheckstyleExecutorRequest request, MavenProject project,
347                                          List<String> classPathStrings, List<String> outputDirectories,
348                                          File sourceDirectory, File testSourceDirectory )
349         throws CheckstyleExecutorException
350     {
351         try
352         {
353             outputDirectories.add( project.getBuild().getOutputDirectory() );
354 
355             if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
356                 && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
357             {
358                 classPathStrings.addAll( project.getTestClasspathElements() );
359                 outputDirectories.add( project.getBuild().getTestOutputDirectory() );
360             }
361             else
362             {
363                 classPathStrings.addAll( project.getCompileClasspathElements() );
364             }
365         }
366         catch ( DependencyResolutionRequiredException e )
367         {
368             throw new CheckstyleExecutorException( e.getMessage(), e );
369         }
370     }
371 
372     private Properties getOverridingProperties( CheckstyleExecutorRequest request )
373         throws CheckstyleExecutorException
374     {
375         Properties p = new Properties();
376 
377         try
378         {
379             if ( request.getPropertiesLocation() != null )
380             {
381                 if ( getLogger().isDebugEnabled() )
382                 {
383                     getLogger().debug( "request.getPropertiesLocation() " + request.getPropertiesLocation() );
384                 }
385 
386                 File propertiesFile = locator.getResourceAsFile( request.getPropertiesLocation(),
387                                                                  "checkstyle-checker.properties" );
388 
389                 FileInputStream properties = new FileInputStream( propertiesFile );
390                 try
391                 {
392                     if ( propertiesFile != null )
393                     {
394                         p.load( properties );
395                     }
396                 }
397                 finally
398                 {
399                     IOUtils.closeQuietly( properties );
400                 }
401             }
402 
403             if ( StringUtils.isNotEmpty( request.getPropertyExpansion() ) )
404             {
405                 String propertyExpansion = request.getPropertyExpansion();
406                 // Convert \ to \\, so that p.load will convert it back properly
407                 propertyExpansion = StringUtils.replace( propertyExpansion, "\\", "\\\\" );
408                 p.load( new ByteArrayInputStream( propertyExpansion.getBytes() ) );
409             }
410 
411             // Workaround for MCHECKSTYLE-48
412             // Make sure that "config/maven-header.txt" is the default value
413             // for headerLocation, if configLocation="config/maven_checks.xml"
414             String headerLocation = request.getHeaderLocation();
415             if ( "config/maven_checks.xml".equals( request.getConfigLocation() ) )
416             {
417 
418                 if ( "LICENSE.txt".equals( request.getHeaderLocation() ) )
419                 {
420                     headerLocation = "config/maven-header.txt";
421                 }
422             }
423             if ( getLogger().isDebugEnabled() )
424             {
425                 getLogger().debug( "headerLocation " + headerLocation );
426             }
427 
428             if ( StringUtils.isNotEmpty( headerLocation ) )
429             {
430                 try
431                 {
432                     File headerFile = locator.getResourceAsFile( headerLocation, "checkstyle-header.txt" );
433 
434                     if ( headerFile != null )
435                     {
436                         p.setProperty( "checkstyle.header.file", headerFile.getAbsolutePath() );
437                     }
438                 }
439                 catch ( FileResourceCreationException e )
440                 {
441                     throw new CheckstyleExecutorException( "Unable to process header location: " + headerLocation, e );
442                 }
443                 catch ( ResourceNotFoundException e )
444                 {
445                     throw new CheckstyleExecutorException( "Unable to process header location: " + headerLocation, e );
446                 }
447             }
448 
449             if ( request.getCacheFile() != null )
450             {
451                 p.setProperty( "checkstyle.cache.file", request.getCacheFile() );
452             }
453         }
454         catch ( IOException e )
455         {
456             throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
457         }
458         catch ( FileResourceCreationException e )
459         {
460             throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
461         }
462         catch ( ResourceNotFoundException e )
463         {
464             throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
465         }
466         if ( request.getSuppressionsFileExpression() != null )
467         {
468             String suppresionFile = request.getSuppressionsLocation();
469 
470             if ( suppresionFile != null )
471             {
472                 p.setProperty( request.getSuppressionsFileExpression(), suppresionFile );
473             }
474         }
475 
476         return p;
477     }
478 
479     private File[] getFilesToProcess( CheckstyleExecutorRequest request )
480         throws IOException
481     {
482         StringBuilder excludesStr = new StringBuilder();
483 
484         if ( StringUtils.isNotEmpty( request.getExcludes() ) )
485         {
486             excludesStr.append( request.getExcludes() );
487         }
488 
489         String[] defaultExcludes = FileUtils.getDefaultExcludes();
490         for ( int i = 0; i < defaultExcludes.length; i++ )
491         {
492             if ( excludesStr.length() > 0 )
493             {
494                 excludesStr.append( "," );
495             }
496 
497             excludesStr.append( defaultExcludes[i] );
498         }
499 
500         File sourceDirectory = request.getSourceDirectory();
501 
502         List<File> files = new ArrayList<File>();
503         if ( request.isAggregate() )
504         {
505             for ( MavenProject project : request.getReactorProjects() )
506             {
507                 addFilesToProcess( request, excludesStr, new File( project.getBuild().getSourceDirectory() ),
508                                    project.getResources(), new File( project.getBuild().getTestSourceDirectory() ),
509                                    files );
510             }
511         }
512         else
513         {
514             addFilesToProcess( request, excludesStr, sourceDirectory, request.getResources(),
515                                request.getTestSourceDirectory(), files );
516         }
517 
518         getLogger().debug( "Added " + files.size() + " files to process." );
519 
520         return (File[]) files.toArray( EMPTY_FILE_ARRAY );
521     }
522 
523     private void addFilesToProcess( CheckstyleExecutorRequest request, StringBuilder excludesStr, File sourceDirectory,
524                                     List<Resource> resources, File testSourceDirectory, List<File> files )
525         throws IOException
526     {
527         if ( sourceDirectory != null && sourceDirectory.exists() )
528         {
529             final List sourceFiles = FileUtils.getFiles( sourceDirectory,
530                                                          request.getIncludes(),
531                                                          excludesStr.toString() );
532             files.addAll( sourceFiles );
533             getLogger().debug( "Added " + sourceFiles.size() + " source files found in '"
534                     + sourceDirectory.getAbsolutePath() + "'." );
535         }
536 
537         if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
538             && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
539         {
540             final List testSourceFiles = FileUtils.getFiles( testSourceDirectory, request.getIncludes(),
541                                                              excludesStr.toString() );
542             files.addAll( testSourceFiles );
543             getLogger().debug( "Added " + testSourceFiles.size() + " test source files found in '"
544                     + testSourceDirectory.getAbsolutePath() + "'." );
545         }
546 
547         // @todo Should we add a check to see if resources should be included or not, similar to request.isIncludeTestSourceDirectory()?
548         if ( resources != null )
549         {
550             for ( Resource resource : resources )
551             {
552                 if ( resource.getDirectory() != null )
553                 {
554                     File resourcesDirectory = new File( resource.getDirectory() );
555                     if ( resourcesDirectory.exists() && resourcesDirectory.isDirectory() )
556                     {
557                         // @todo Perhaps extend the functionality in the future so that the included types of files can be configured. For now it is hard-coded to properties files.
558                         List resourceFiles = FileUtils.getFiles( resourcesDirectory, "**/*.properties", null );
559                         files.addAll( resourceFiles );
560                         getLogger().debug( "Added " + resourceFiles.size() + " resource files found in '"
561                                 + resourcesDirectory.getAbsolutePath() + "'." );
562                     }
563                     else
564                     {
565                         getLogger().debug( "The resources directory '" + resourcesDirectory.getAbsolutePath()
566                                 + "' does not exist or is not a directory." );
567                     }
568                 }
569             }
570         }
571         else
572         {
573             getLogger().debug( "No resources found in this project." );
574         }
575     }
576 
577     private FilterSet getSuppressions( CheckstyleExecutorRequest request )
578         throws CheckstyleExecutorException
579     {
580         try
581         {
582             File suppressionsFile = locator.resolveLocation( request.getSuppressionsLocation(),
583                                                              "checkstyle-suppressions.xml" );
584 
585             if ( suppressionsFile == null )
586             {
587                 return null;
588             }
589 
590             return SuppressionsLoader.loadSuppressions( suppressionsFile.getAbsolutePath() );
591         }
592         catch ( CheckstyleException ce )
593         {
594             throw new CheckstyleExecutorException( "failed to load suppressions location: "
595                 + request.getSuppressionsLocation(), ce );
596         }
597         catch ( IOException e )
598         {
599             throw new CheckstyleExecutorException( "Failed to process supressions location: "
600                 + request.getSuppressionsLocation(), e );
601         }
602     }
603 
604     private String getConfigFile( CheckstyleExecutorRequest request )
605         throws CheckstyleExecutorException
606     {
607         try
608         {
609             if ( getLogger().isDebugEnabled() )
610             {
611                 getLogger().debug( "request.getConfigLocation() " + request.getConfigLocation() );
612             }
613 
614             MavenProject parent = request.getProject();
615             while ( parent != null && parent.getFile() != null )
616             {
617                 // MCHECKSTYLE-131 ( olamy ) I don't like this hack.
618                 // (dkulp) Me either.   It really pollutes the location stuff
619                 // by allowing searches of stuff outside the current module.
620                 File dir = parent.getFile().getParentFile();
621                 locator.addSearchPath( FileResourceLoader.ID, dir.getAbsolutePath() );
622                 parent = parent.getParent();
623             }
624             locator.addSearchPath( "url", "" );
625 
626             File configFile = locator.getResourceAsFile( request.getConfigLocation(), "checkstyle-checker.xml" );
627             if ( configFile == null )
628             {
629                 throw new CheckstyleExecutorException( "Unable to process config location: "
630                     + request.getConfigLocation() );
631             }
632             return configFile.getAbsolutePath();
633         }
634         catch ( org.codehaus.plexus.resource.loader.ResourceNotFoundException e )
635         {
636             throw new CheckstyleExecutorException( "Unable to find configuration file at location "
637                 + request.getConfigLocation(), e );
638         }
639         catch ( FileResourceCreationException e )
640         {
641             throw new CheckstyleExecutorException( "Unable to process configuration file location "
642                 + request.getConfigLocation(), e );
643         }
644 
645     }
646 }