View Javadoc

1   package org.codehaus.mojo.macker;
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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.handler.ArtifactHandler;
24  import org.apache.maven.artifact.repository.ArtifactRepository;
25  import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
26  import org.apache.maven.artifact.resolver.ArtifactResolver;
27  import org.apache.maven.plugin.AbstractMojo;
28  import org.apache.maven.plugin.MojoExecutionException;
29  import org.apache.maven.plugin.MojoFailureException;
30  import org.apache.maven.project.MavenProject;
31  
32  import java.io.File;
33  import java.io.IOException;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.LinkedHashSet;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.Map;
43  
44  import org.codehaus.plexus.resource.ResourceManager;
45  import org.codehaus.plexus.resource.loader.FileResourceCreationException;
46  import org.codehaus.plexus.resource.loader.FileResourceLoader;
47  import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
48  import org.codehaus.plexus.util.FileUtils;
49  import org.codehaus.plexus.util.StringUtils;
50  
51  /**
52   * Runs Macker against the compiled classes of the project.
53   *
54   * @goal macker
55   * @execute phase="compile"
56   * @requiresDependencyResolution compile
57   * @requiresProject
58   * @author <a href="http://www.codehaus.org/~wfay/">Wayne Fay</a>
59   * @author <a href="http://people.apache.org/~bellingard/">Fabrice Bellingard</a>
60   * @author <a href="http://www.code-cop.org/">Peter Kofler</a>
61   */
62  public class MackerMojo
63      extends AbstractMojo
64  {
65      /**
66       * Directory containing the class files for Macker to analyze.
67       *
68       * @parameter expression="${project.build.outputDirectory}"
69       * @required
70       */
71      private File classesDirectory;
72  
73      /**
74       * The directories containing the test-classes to be analyzed.
75       *
76       * @parameter expression="${project.build.testOutputDirectory}"
77       * @required
78       */
79      private File testClassesDirectory;
80  
81      /**
82       * A list of files to exclude from checking. Can contain Ant-style wildcards and double wildcards. Note that these
83       * exclusion patterns only operate on the path of a source file relative to its source root directory. In other
84       * words, files are excluded based on their package and/or class name. If you want to exclude entire root
85       * directories, use the parameter <code>excludeRoots</code> instead.
86       *
87       * @parameter
88       */
89      private String[] excludes;
90  
91      /**
92       * A list of files to include from checking. Can contain Ant-style wildcards and double wildcards.
93       * Defaults to **\/*.class.
94       *
95       * @parameter
96       */
97      private String[] includes;
98  
99      /**
100      * Run Macker on the tests.
101      *
102      * @parameter default-value="false"
103      */
104     private boolean includeTests;
105 
106     /**
107      * Directory containing the rules files for Macker.
108      *
109      * @parameter expression="${basedir}/src/main/config"
110      * @required
111      */
112     private File rulesDirectory;
113 
114     /**
115      * Directory where the Macker output file will be generated.
116      *
117      * @parameter default-value="${project.build.directory}"
118      * @required
119      */
120     private File outputDirectory;
121 
122     /**
123      * Name of the Macker output file.
124      *
125      * @parameter expression="${outputName}" default-value="macker-out.xml"
126      * @required
127      */
128     private String outputName;
129 
130     /**
131      * Print max messages.
132      *
133      * @parameter expression="${maxmsg}" default-value="0"
134      */
135     private int maxmsg;
136 
137     /**
138      * Print threshold. Valid options are error, warning, info, and debug.
139      *
140      * @parameter expression="${print}"
141      */
142     private String print;
143 
144     /**
145      * Anger threshold. Valid options are error, warning, info, and debug.
146      *
147      * @parameter expression="${anger}"
148      */
149     private String anger;
150 
151     /**
152      * Name of the Macker rules file.
153      *
154      * @parameter expression="${rule}" default-value="macker-rules.xml"
155      */
156     private String rule;
157 
158     /**
159      * Name of the Macker rules files.
160      *
161      * @parameter expression="${rules}"
162      */
163     private String[] rules = new String[0];
164 
165     /**
166      * @component
167      * @required
168      * @readonly
169      */
170     private ResourceManager locator;
171 
172     /**
173      * Variables map that will be passed to Macker.
174      *
175      * @parameter expression="${variables}"
176      */
177     private Map/*<String, String>*/variables = new HashMap/*<String, String>*/();
178 
179     /**
180      * Verbose setting for Macker tool execution.
181      *
182      * @parameter expression="${verbose}" default-value="false"
183      */
184     private boolean verbose;
185 
186     /**
187      * Fail the build on an error.
188      *
189      * @parameter default-value="true"
190      */
191     private boolean failOnError;
192 
193     /**
194      * Skip the checks.  Most useful on the command line
195      * via "-Dmacker.skip=true".
196      *
197      * @parameter expression="${macker.skip}" default-value="false"
198      */
199     private boolean skip;
200 
201     /**
202      * <i>Maven Internal</i>: Project to interact with.
203      *
204      * @parameter expression="${project}"
205      * @required
206      * @readonly
207      */
208     private MavenProject project;
209 
210     /**
211      * Maximum memory to pass JVM of Macker processes.
212      *
213      * @parameter expression="${macker.maxmem}" default-value="64m"
214      */
215     private String maxmem;
216 
217     /**
218      * <i>Maven Internal</i>: List of artifacts for the plugin.
219      *
220      * @parameter expression="${plugin.artifacts}"
221      * @required
222      * @readonly
223      */
224     private List/*<Artifact>*/ pluginClasspathList;
225 
226     /**
227      * Only output Macker errors, avoid info messages.
228      *
229      * @parameter expression="${quiet}" default-value="false"
230      */
231     private boolean quiet;
232 
233     /**
234      * @parameter default-value="${localRepository}"
235      * @required
236      * @readonly
237      */
238     private ArtifactRepository localRepository;
239 
240     /**
241      * @parameter default-value="${project.remoteArtifactRepositories}"
242      * @required
243      * @readonly
244      */
245     private List remoteRepositories;
246 
247     /**
248      * @component
249      * @required
250      * @readonly
251      */
252     private ArtifactResolver resolver;
253 
254     /**
255      * @throws MojoExecutionException if a error occurs during Macker execution
256      * @throws MojoFailureException if Macker detects a failure.
257      * @see org.apache.maven.plugin.Mojo#execute()
258      */
259     public void execute()
260         throws MojoExecutionException, MojoFailureException
261     {
262         if ( skip )
263         {
264             return;
265         }
266 
267         ArtifactHandler artifactHandler = project.getArtifact().getArtifactHandler();
268         if ( !"java".equals( artifactHandler.getLanguage() ) )
269         {
270             if ( !quiet )
271             {
272                 getLog().info( "Not executing macker as the project is not a Java classpath-capable package" );
273             }
274             return;
275         }
276 
277         //configure ResourceManager
278         locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
279         locator.addSearchPath( "url", "" );
280         locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
281 
282         // check if rules were specified
283         if ( null == rules || 0 == rules.length )
284         {
285             rules = new String[1]; // at least the default name
286             rules[0] = rule;
287         }
288 
289         // check if there are class files to analyze
290         List/*<File>*/files;
291         try
292         {
293             files = getFilesToProcess();
294         }
295         catch ( IOException e )
296         {
297             throw new MojoExecutionException( "Error during Macker execution: error in file selection", e );
298         }
299         if ( files == null || files.size() == 0 )
300         {
301             // no class file, we can't do anything
302             if ( !quiet )
303             {
304                 if ( includeTests )
305                 {
306                     getLog().info( "No class files in directories " + classesDirectory + ", " + testClassesDirectory );
307                 }
308                 else
309                 {
310                     getLog().info( "No class files in specified directory " + classesDirectory );
311                 }
312             }
313         }
314         else
315         {
316             if ( !outputDirectory.exists() )
317             {
318                 if ( !outputDirectory.mkdirs() )
319                 {
320                     throw new MojoExecutionException( "Error during Macker execution: Could not create directory "
321                             + outputDirectory.getAbsolutePath() );
322                 }
323             }
324 
325             // let's go!
326             File outputFile = new File( outputDirectory, outputName );
327             launchMacker( outputFile, files );
328         }
329     }
330 
331     /**
332      * Executes Macker as requested.
333      *
334      * @param outputFile the result file that will should produced by macker
335      * @param files classes files that should be analysed
336      * @throws MojoExecutionException if a error occurs during Macker execution
337      * @throws MojoFailureException if Macker detects a failure.
338      */
339     private void launchMacker( File outputFile, List/*<File>*/files )
340         throws MojoExecutionException, MojoFailureException
341     {
342         try
343         {
344             Macker macker = createMacker( outputFile );
345             configureRules( macker );
346             initMackerVariables( macker );
347             specifyClassFilesToAnalyse( files, macker );
348             // we're OK with configuration, let's run Macker
349             macker.check();
350             // if we're here, then everything went fine
351             if ( !quiet )
352             {
353                 getLog().info( "Macker has not found any violation." );
354             }
355         }
356         catch ( MojoExecutionException ex )
357         {
358             throw ex;
359         }
360         catch ( MojoFailureException ex )
361         {
362             getLog().warn( "Macker has detected violations. Please refer to the XML report for more information." );
363             if ( failOnError )
364             {
365                 throw ex;
366             }
367         }
368         catch ( Exception ex )
369         {
370             throw new MojoExecutionException( "Error during Macker execution: " + ex.getMessage(), ex );
371         }
372     }
373 
374     /**
375      * Tell Macker where to look for Class files to analyze.
376      *
377      * @param files the ".class" files to analyze
378      * @param macker the Macker instance
379      * @throws IOException if there's a problem reading a file
380      * @throws MojoExecutionException if there's a problem parsing a class
381      */
382     private void specifyClassFilesToAnalyse( List/*<File>*/files, Macker macker )
383         throws IOException, MojoExecutionException
384     {
385         for ( Iterator/*<File>*/i = files.iterator(); i.hasNext(); )
386         {
387             macker.addClass( (File) i.next() );
388         }
389     }
390 
391     /**
392      * If specific variables are set in the POM, give them to Macker.
393      *
394      * @param macker the Macker isntance
395      */
396     private void initMackerVariables( Macker macker )
397     {
398         if ( variables != null && variables.size() > 0 )
399         {
400             Iterator/*<String>*/it = variables.keySet().iterator();
401             while ( it.hasNext() )
402             {
403                 String key = (String) it.next();
404                 macker.setVariable( key, (String) variables.get( key ) );
405             }
406         }
407     }
408 
409     /**
410      * Configure Macker with the rule files specified in the POM.
411      *
412      * @param macker the Macker instance
413      * @throws IOException if there's a problem reading a file
414      * @throws MojoExecutionException if there's a problem parsing a rule file
415      */
416     private void configureRules( Macker macker )
417         throws IOException, MojoExecutionException
418     {
419         try
420         {
421             for ( int i = 0; i < rules.length; i++ )
422             {
423                 String set = rules[i];
424                 File ruleFile = new File( rulesDirectory, set );
425                 if ( ruleFile.exists() )
426                 {
427                     getLog().debug( "Add rules file: " + rulesDirectory + File.separator + rules[i] );
428                 }
429                 else
430                 {
431                     getLog().debug( "Preparing ruleset: " + set );
432                     ruleFile = locator.getResourceAsFile( set, getLocationTemp( set ) );
433 
434                     if ( null == ruleFile )
435                     {
436                         throw new MojoExecutionException( "Could not resolve rules file: " + set );
437                     }
438                 }
439                 macker.addRulesFile( ruleFile );
440             }
441         }
442         catch ( ResourceNotFoundException e )
443         {
444             throw new MojoExecutionException( e.getMessage(), e );
445         }
446         catch ( FileResourceCreationException e )
447         {
448             throw new MojoExecutionException( e.getMessage(), e );
449         }
450     }
451 
452     /**
453      * Convenience method to get the location of the specified file name.
454      *
455      * @param name the name of the file whose location is to be resolved
456      * @return a String that contains the absolute file name of the file
457      */
458     private String getLocationTemp( String name )
459     {
460         String loc = name;
461         if ( loc.indexOf( '/' ) != -1 )
462         {
463             loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
464         }
465         if ( loc.indexOf( '\\' ) != -1 )
466         {
467             loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
468         }
469         getLog().debug( "Before: " + name + " After: " + loc );
470         return loc;
471     }
472 
473     /**
474      * Prepares Macker for the analysis.
475      *
476      * @param outputFile the result file that will should produced by Macker
477      * @return the new instance of Macker
478      * @throws IOException if there's a problem with the report file
479      * @throws MojoExecutionException if there's a creating the classpath for forking
480      */
481     private Macker createMacker( File outputFile )
482         throws IOException, MojoExecutionException
483     {
484         // Macker macker = new LinkedMacker();
485         ForkedMacker macker = new ForkedMacker( );
486         macker.setLog( getLog() );
487         macker.setMaxmem( maxmem );
488         macker.setPluginClasspathList( collectArtifactList() );
489         macker.setQuiet( quiet );
490 
491         macker.setVerbose( verbose );
492         macker.setXmlReportFile( outputFile );
493         if ( maxmsg > 0 )
494         {
495             macker.setPrintMaxMessages( maxmsg );
496         }
497         if ( print != null )
498         {
499             macker.setPrintThreshold( print );
500         }
501         if ( anger != null )
502         {
503             macker.setAngerThreshold( anger );
504         }
505         return macker;
506     }
507 
508     /**
509      * Get the full classpath of this plugin including the plugin itself.
510      * @throws MojoExecutionException if there's a creating the classpath for forking
511      */
512     private List collectArtifactList()
513         throws MojoExecutionException
514     {
515         // look up myself, it must be here
516         Artifact myself = (Artifact) getProject().getPluginArtifactMap().get( "org.codehaus.mojo:macker-maven-plugin" );
517         try
518         {
519             resolver.resolve( myself, remoteRepositories, localRepository );
520         }
521         catch ( AbstractArtifactResolutionException e )
522         {
523             throw new MojoExecutionException( e.getMessage(), e );
524         }
525 
526         List/*<Artifact>*/ classpath = new ArrayList/*<Artifact>*/();
527         classpath.add( myself );
528         classpath.addAll( pluginClasspathList );
529         return classpath;
530     }
531 
532     /**
533      * Returns the MavenProject object.
534      *
535      * @return MavenProject
536      */
537     public MavenProject getProject()
538     {
539         return this.project;
540     }
541 
542     /**
543      * Convenience method to get the list of files where the PMD tool will be executed
544      *
545      * @return a List of the files where the MACKER tool will be executed
546      * @throws IOException if there's a problem scanning the directories
547      */
548     private List/*<File>*/getFilesToProcess()
549         throws IOException
550     {
551         List/*<File>*/directories = new ArrayList/*<File>*/();
552 
553         if ( classesDirectory != null && classesDirectory.isDirectory() )
554         {
555             directories.add( classesDirectory );
556         }
557         if ( includeTests )
558         {
559             if ( testClassesDirectory != null && testClassesDirectory.isDirectory() )
560             {
561                 directories.add( testClassesDirectory );
562             }
563             else
564             {
565                 getLog().info( "No class files in test directory: " + testClassesDirectory );
566             }
567         }
568 
569         String excluding = getExcludes();
570         getLog().debug( "Exclusions: " + excluding );
571         String including = getIncludes();
572         getLog().debug( "Inclusions: " + including );
573 
574         List/*<File>*/files = new LinkedList/*<File>*/();
575 
576         for ( Iterator/*<File>*/i = directories.iterator(); i.hasNext(); )
577         {
578             File sourceDirectory = (File) i.next();
579             if ( sourceDirectory.isDirectory() )
580             {
581                 List/*<File>*/newfiles = FileUtils.getFiles( sourceDirectory, including, excluding );
582                 files.addAll( newfiles );
583             }
584         }
585 
586         return files;
587     }
588 
589     /**
590      * Gets the comma separated list of effective include patterns.
591      *
592      * @return The comma separated list of effective include patterns, never <code>null</code>.
593      */
594     private String getIncludes()
595     {
596         Collection/*<String>*/patterns = new LinkedHashSet/*<String>*/();
597         if ( includes != null )
598         {
599             patterns.addAll( Arrays.asList( includes ) );
600         }
601         if ( patterns.isEmpty() )
602         {
603             patterns.add( "**/*.class" );
604         }
605         return StringUtils.join( patterns.iterator(), "," );
606     }
607 
608     /**
609      * Gets the comma separated list of effective exclude patterns.
610      *
611      * @return The comma separated list of effective exclude patterns, never <code>null</code>.
612      */
613     private String getExcludes()
614     {
615         Collection/*<String>*/patterns = new LinkedHashSet/*<String>*/( FileUtils.getDefaultExcludesAsList() );
616         if ( excludes != null )
617         {
618             patterns.addAll( Arrays.asList( excludes ) );
619         }
620         return StringUtils.join( patterns.iterator(), "," );
621     }
622 
623     /**
624      * For test purposes only.
625      */
626     void setRules( String[] ruleSets )
627     {
628         rules = (String[]) ruleSets.clone();
629     }
630 
631 }