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