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