View Javadoc

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