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$
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 
259             if ( "Checker".equals( config.getName() )
260                     || "com.puppycrawl.tools.checkstyle.Checker".equals( config.getName() ) )
261             {
262                 if ( config instanceof DefaultConfiguration )
263                 {
264                     ( (DefaultConfiguration) config ).addAttribute( "charset", effectiveEncoding );
265                 }
266                 else
267                 {
268                     request.getLog().warn( "Failed to configure file encoding on module " + config );
269                 }
270             }
271             Configuration[] modules = config.getChildren();
272             for ( int i = 0; i < modules.length; i++ )
273             {
274                 Configuration module = modules[i];
275                 if ( "TreeWalker".equals( module.getName() )
276                     || "com.puppycrawl.tools.checkstyle.TreeWalker".equals( module.getName() ) )
277                 {
278                     if ( module instanceof DefaultConfiguration )
279                     {
280                         //MCHECKSTYLE-132 DefaultConfiguration addAttribute has changed in checkstyle 5.3
281                         try
282                         {
283                             if ( ( (DefaultConfiguration) module ).getAttribute( "cacheFile" ) == null )
284                             {
285                                 ( (DefaultConfiguration) module ).addAttribute( "cacheFile", request.getCacheFile() );
286                             }
287                         }
288                         catch ( CheckstyleException ex )
289                         {
290                             //MCHECKSTYLE-159 - checkstyle 5.4 throws an exception instead of return null if "cacheFile"
291                             // doesn't exist
292                             ( (DefaultConfiguration) module ).addAttribute( "cacheFile", request.getCacheFile() );
293                         }
294                     }
295                     else
296                     {
297                         request.getLog().warn( "Failed to configure cache file on module " + module );
298                     }
299                 }
300             }
301             return config;
302         }
303         catch ( CheckstyleException e )
304         {
305             throw new CheckstyleExecutorException( "Failed during checkstyle configuration", e );
306         }
307     }
308 
309     private void prepareCheckstylePaths( CheckstyleExecutorRequest request, MavenProject project,
310                                          List<String> classPathStrings, List<String> outputDirectories,
311                                          File sourceDirectory, File testSourceDirectory )
312         throws CheckstyleExecutorException
313     {
314         try
315         {
316             outputDirectories.add( project.getBuild().getOutputDirectory() );
317 
318             if ( request.isIncludeTestSourceDirectory() && ( sourceDirectory != null )
319                 && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
320             {
321                 classPathStrings.addAll( project.getTestClasspathElements() );
322                 outputDirectories.add( project.getBuild().getTestOutputDirectory() );
323             }
324             else
325             {
326                 classPathStrings.addAll( project.getCompileClasspathElements() );
327             }
328         }
329         catch ( DependencyResolutionRequiredException e )
330         {
331             throw new CheckstyleExecutorException( e.getMessage(), e );
332         }
333     }
334 
335     private Properties getOverridingProperties( CheckstyleExecutorRequest request )
336         throws CheckstyleExecutorException
337     {
338         Properties p = new Properties();
339 
340         try
341         {
342             if ( request.getPropertiesLocation() != null )
343             {
344                 if ( getLogger().isDebugEnabled() )
345                 {
346                     getLogger().debug( "request.getPropertiesLocation() " + request.getPropertiesLocation() );
347                 }
348 
349                 File propertiesFile = locator.getResourceAsFile( request.getPropertiesLocation(),
350                                                                  "checkstyle-checker.properties" );
351 
352                 FileInputStream properties = new FileInputStream( propertiesFile );
353                 try
354                 {
355                     if ( propertiesFile != null )
356                     {
357                         p.load( properties );
358                     }
359                 }
360                 finally
361                 {
362                     IOUtils.closeQuietly( properties );
363                 }
364             }
365 
366             if ( StringUtils.isNotEmpty( request.getPropertyExpansion() ) )
367             {
368                 String propertyExpansion = request.getPropertyExpansion();
369                 // Convert \ to \\, so that p.load will convert it back properly
370                 propertyExpansion = StringUtils.replace( propertyExpansion, "\\", "\\\\" );
371                 p.load( new ByteArrayInputStream( propertyExpansion.getBytes() ) );
372             }
373 
374             // Workaround for MCHECKSTYLE-48
375             // Make sure that "config/maven-header.txt" is the default value
376             // for headerLocation, if configLocation="config/maven_checks.xml"
377             String headerLocation = request.getHeaderLocation();
378             if ( "config/maven_checks.xml".equals( request.getConfigLocation() ) )
379             {
380 
381                 if ( "LICENSE.txt".equals( request.getHeaderLocation() ) )
382                 {
383                     headerLocation = "config/maven-header.txt";
384                 }
385             }
386             if ( getLogger().isDebugEnabled() )
387             {
388                 getLogger().debug( "headerLocation " + headerLocation );
389             }
390 
391             if ( StringUtils.isNotEmpty( headerLocation ) )
392             {
393                 try
394                 {
395                     File headerFile = locator.getResourceAsFile( headerLocation, "checkstyle-header.txt" );
396 
397                     if ( headerFile != null )
398                     {
399                         p.setProperty( "checkstyle.header.file", headerFile.getAbsolutePath() );
400                     }
401                 }
402                 catch ( FileResourceCreationException e )
403                 {
404                     throw new CheckstyleExecutorException( "Unable to process header location: " + headerLocation, e );
405                 }
406                 catch ( ResourceNotFoundException e )
407                 {
408                     throw new CheckstyleExecutorException( "Unable to process header location: " + headerLocation, e );
409                 }
410             }
411 
412             if ( request.getCacheFile() != null )
413             {
414                 p.setProperty( "checkstyle.cache.file", request.getCacheFile() );
415             }
416         }
417         catch ( IOException e )
418         {
419             throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
420         }
421         catch ( FileResourceCreationException e )
422         {
423             throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
424         }
425         catch ( ResourceNotFoundException e )
426         {
427             throw new CheckstyleExecutorException( "Failed to get overriding properties", e );
428         }
429         if ( request.getSuppressionsFileExpression() != null )
430         {
431             String suppresionFile = request.getSuppressionsLocation();
432 
433             if ( suppresionFile != null )
434             {
435                 p.setProperty( request.getSuppressionsFileExpression(), suppresionFile );
436             }
437         }
438 
439         return p;
440     }
441 
442     private File[] getFilesToProcess( CheckstyleExecutorRequest request )
443         throws IOException
444     {
445         StringBuffer excludesStr = new StringBuffer();
446 
447         if ( StringUtils.isNotEmpty( request.getExcludes() ) )
448         {
449             excludesStr.append( request.getExcludes() );
450         }
451 
452         String[] defaultExcludes = FileUtils.getDefaultExcludes();
453         for ( int i = 0; i < defaultExcludes.length; i++ )
454         {
455             if ( excludesStr.length() > 0 )
456             {
457                 excludesStr.append( "," );
458             }
459 
460             excludesStr.append( defaultExcludes[i] );
461         }
462 
463         File sourceDirectory = request.getSourceDirectory();
464 
465         List<File> files = new ArrayList<File>();
466         addFilesToProcess( request, excludesStr, sourceDirectory, files );
467         if ( request.isAggregate() )
468         {
469             for ( MavenProject project : request.getReactorProjects() )
470             {
471                 addFilesToProcess( request, excludesStr, new File( project.getBuild().getSourceDirectory() ), files );
472             }
473         }
474 
475         return (File[]) files.toArray( EMPTY_FILE_ARRAY );
476     }
477 
478     private void addFilesToProcess( CheckstyleExecutorRequest request, StringBuffer excludesStr, File sourceDirectory,
479                                     List<File> files )
480         throws IOException
481     {
482         if ( sourceDirectory == null || !sourceDirectory.exists() )
483         {
484             return;
485         }
486         files.addAll(
487             FileUtils.getFiles( sourceDirectory, request.getIncludes(), excludesStr.toString() ) );
488         File testSourceDirectory = request.getTestSourceDirectory();
489         if ( request.isIncludeTestSourceDirectory() && ( testSourceDirectory != null )
490             && ( testSourceDirectory.exists() ) && ( testSourceDirectory.isDirectory() ) )
491         {
492             files.addAll( FileUtils.getFiles( testSourceDirectory, request.getIncludes(),
493                                               excludesStr.toString() ) );
494         }
495     }
496 
497     private FilterSet getSuppressions( CheckstyleExecutorRequest request )
498         throws CheckstyleExecutorException
499     {
500         try
501         {
502             File suppressionsFile = locator.resolveLocation( request.getSuppressionsLocation(),
503                                                              "checkstyle-suppressions.xml" );
504 
505             if ( suppressionsFile == null )
506             {
507                 return null;
508             }
509 
510             return SuppressionsLoader.loadSuppressions( suppressionsFile.getAbsolutePath() );
511         }
512         catch ( CheckstyleException ce )
513         {
514             throw new CheckstyleExecutorException( "failed to load suppressions location: "
515                 + request.getSuppressionsLocation(), ce );
516         }
517         catch ( IOException e )
518         {
519             throw new CheckstyleExecutorException( "Failed to process supressions location: "
520                 + request.getSuppressionsLocation(), e );
521         }
522     }
523 
524     private String getConfigFile( CheckstyleExecutorRequest request )
525         throws CheckstyleExecutorException
526     {
527         try
528         {
529             if ( getLogger().isDebugEnabled() )
530             {
531                 getLogger().debug( "request.getConfigLocation() " + request.getConfigLocation() );
532             }
533 
534             MavenProject parent = request.getProject();
535             while ( parent != null && parent.getFile() != null )
536             {
537                 // MCHECKSTYLE-131 ( olamy ) I don't like this hack.
538                 // (dkulp) Me either.   It really pollutes the location stuff
539                 // by allowing searches of stuff outside the current module.
540                 File dir = parent.getFile().getParentFile();
541                 locator.addSearchPath( FileResourceLoader.ID, dir.getAbsolutePath() );
542                 parent = parent.getParent();
543             }
544             locator.addSearchPath( "url", "" );
545 
546             File configFile = locator.getResourceAsFile( request.getConfigLocation(), "checkstyle-checker.xml" );
547             if ( configFile == null )
548             {
549                 throw new CheckstyleExecutorException( "Unable to process config location: "
550                     + request.getConfigLocation() );
551             }
552             return configFile.getAbsolutePath();
553         }
554         catch ( org.codehaus.plexus.resource.loader.ResourceNotFoundException e )
555         {
556             throw new CheckstyleExecutorException( "Unable to find configuration file at location "
557                 + request.getConfigLocation(), e );
558         }
559         catch ( FileResourceCreationException e )
560         {
561             throw new CheckstyleExecutorException( "Unable to process configuration file location "
562                 + request.getConfigLocation(), e );
563         }
564 
565     }
566 }