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