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