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.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.util.ArrayList;
29  import java.util.Calendar;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.ResourceBundle;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.doxia.siterenderer.Renderer;
36  import org.apache.maven.doxia.tools.SiteTool;
37  import org.apache.maven.model.Dependency;
38  import org.apache.maven.model.Plugin;
39  import org.apache.maven.model.ReportPlugin;
40  import org.apache.maven.model.Resource;
41  import org.apache.maven.plugin.checkstyle.rss.CheckstyleRssGenerator;
42  import org.apache.maven.plugin.checkstyle.rss.CheckstyleRssGeneratorRequest;
43  import org.apache.maven.plugin.descriptor.PluginDescriptor;
44  import org.apache.maven.plugins.annotations.Component;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.reporting.AbstractMavenReport;
48  import org.apache.maven.reporting.MavenReportException;
49  import org.codehaus.plexus.resource.ResourceManager;
50  import org.codehaus.plexus.resource.loader.FileResourceLoader;
51  import org.codehaus.plexus.util.PathTool;
52  import org.codehaus.plexus.util.StringUtils;
53  
54  import com.puppycrawl.tools.checkstyle.DefaultLogger;
55  import com.puppycrawl.tools.checkstyle.XMLLogger;
56  import com.puppycrawl.tools.checkstyle.api.AuditListener;
57  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
58  
59  /**
60   * Base abstract class for Checkstyle reports.
61   *
62   * @version $Id: AbstractCheckstyleReport.java 1586844 2014-04-12 09:53:39Z rfscholte $
63   * @since 2.8
64   */
65  public abstract class AbstractCheckstyleReport
66      extends AbstractMavenReport
67  {
68      public static final String PLUGIN_RESOURCES = "org/apache/maven/plugin/checkstyle";
69  
70      protected static final String JAVA_FILES = "**\\/*.java";
71  
72      /**
73       * Specifies the cache file used to speed up Checkstyle on successive runs.
74       */
75      @Parameter( defaultValue = "${project.build.directory}/checkstyle-cachefile" )
76      protected String cacheFile;
77  
78      /**
79       * <p>
80       * Specifies the location of the XML configuration to use.
81       * </p>
82       * <p/>
83       * <p>
84       * Potential values are a filesystem path, a URL, or a classpath resource.
85       * This parameter expects that the contents of the location conform to the
86       * xml format (Checkstyle <a
87       * href="http://checkstyle.sourceforge.net/config.html#Modules">Checker
88       * module</a>) configuration of rulesets.
89       * </p>
90       * <p/>
91       * <p>
92       * This parameter is resolved as resource, URL, then file. If successfully
93       * resolved, the contents of the configuration is copied into the
94       * <code>${project.build.directory}/checkstyle-configuration.xml</code>
95       * file before being passed to Checkstyle as a configuration.
96       * </p>
97       * <p/>
98       * <p>
99       * There are 4 predefined rulesets.
100      * </p>
101      * <p/>
102      * <ul>
103      * <li><code>config/sun_checks.xml</code>: Sun Checks.</li>
104      * <li><code>config/turbine_checks.xml</code>: Turbine Checks.</li>
105      * <li><code>config/avalon_checks.xml</code>: Avalon Checks.</li>
106      * <li><code>config/maven_checks.xml</code>: Maven Source Checks.</li>
107      * </ul>
108      */
109     @Parameter( property = "checkstyle.config.location", defaultValue = "config/sun_checks.xml" )
110     protected String configLocation;
111 
112     /**
113      * Output errors to console.
114      */
115     @Parameter( property = "checkstyle.consoleOutput", defaultValue = "false" )
116     protected boolean consoleOutput;
117 
118     /**
119      * The file encoding to use when reading the source files. If the property <code>project.build.sourceEncoding</code>
120      * is not set, the platform default encoding is used. <strong>Note:</strong> This parameter always overrides the
121      * property <code>charset</code> from Checkstyle's <code>TreeWalker</code> module.
122      *
123      * @since 2.2
124      */
125     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
126     protected String encoding;
127 
128     /**
129      * Specifies if the build should fail upon a violation.
130      */
131     @Parameter( defaultValue = "false" )
132     protected boolean failsOnError;
133 
134     /**
135      * <p>
136      * Specifies the location of the License file (a.k.a. the header file) that
137      * can be used by Checkstyle to verify that source code has the correct
138      * license header.
139      * </p>
140      * <p>
141      * You need to use ${checkstyle.header.file} in your Checkstyle xml
142      * configuration to reference the name of this header file.
143      * </p>
144      * <p>
145      * For instance:
146      * </p>
147      * <p>
148      * <code>
149      * &lt;module name="RegexpHeader">
150      * &lt;property name="headerFile" value="${checkstyle.header.file}"/>
151      * &lt;/module>
152      * </code>
153      * </p>
154      *
155      * @since 2.0-beta-2
156      */
157     @Parameter( property = "checkstyle.header.file", defaultValue = "LICENSE.txt" )
158     protected String headerLocation;
159 
160     /**
161      * Skip entire check.
162      *
163      * @since 2.2
164      */
165     @Parameter( property = "checkstyle.skip", defaultValue = "false" )
166     protected boolean skip;
167 
168     /**
169      * The output directory for the report. Note that this parameter is only
170      * evaluated if the goal is run directly from the command line. If the goal
171      * is run indirectly as part of a site generation, the output directory
172      * configured in Maven Site Plugin is used instead.
173      */
174     @Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true )
175     private File outputDirectory;
176 
177     /**
178      * Specifies the path and filename to save the checkstyle output. The format
179      * of the output file is determined by the <code>outputFileFormat</code>
180      * parameter.
181      */
182     @Parameter( property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml" )
183     private File outputFile;
184 
185     /**
186      * <p>
187      * Specifies the location of the properties file.
188      * </p>
189      * <p/>
190      * <p>
191      * This parameter is resolved as URL, File then resource. If successfully
192      * resolved, the contents of the properties location is copied into the
193      * <code>${project.build.directory}/checkstyle-checker.properties</code>
194      * file before being passed to Checkstyle for loading.
195      * </p>
196      * <p/>
197      * <p>
198      * The contents of the <code>propertiesLocation</code> will be made
199      * available to Checkstyle for specifying values for parameters within the
200      * xml configuration (specified in the <code>configLocation</code>
201      * parameter).
202      * </p>
203      *
204      * @since 2.0-beta-2
205      */
206     @Parameter( property = "checkstyle.properties.location" )
207     protected String propertiesLocation;
208 
209     /**
210      * Allows for specifying raw property expansion information.
211      */
212     @Parameter
213     protected String propertyExpansion;
214 
215     /**
216      * Specifies the location of the resources to be used for Checkstyle.
217      *
218      * @since 2.10
219      */
220     @Parameter( defaultValue = "${project.resources}", readonly = true )
221     protected List<Resource> resources;
222 
223     /**
224      * Specifies the location of the test resources to be used for Checkstyle.
225      *
226      * @since 2.11
227      */
228     @Parameter( defaultValue = "${project.testResources}", readonly = true )
229     protected List<Resource> testResources;
230 
231     /**
232      * Specifies the names filter of the source files to be used for Checkstyle.
233      */
234     @Parameter( property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true )
235     protected String includes;
236 
237     /**
238      * Specifies the names filter of the source files to be excluded for
239      * Checkstyle.
240      */
241     @Parameter( property = "checkstyle.excludes" )
242     protected String excludes;
243 
244     /**
245      * Specifies the names filter of the resource files to be used for Checkstyle.
246      * @since 2.11
247      */
248     @Parameter( property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true )
249     protected String resourceIncludes;
250 
251     /**
252      * Specifies the names filter of the resource files to be excluded for
253      * Checkstyle.
254      * @since 2.11
255      */
256     @Parameter( property = "checkstyle.resourceExcludes" )
257     protected String resourceExcludes;
258 
259     /**
260      * Specifies whether to include the resource directories in the check.
261      * @since 2.11
262      */
263     @Parameter( property = "checkstyle.includeResources", defaultValue = "true", required = true )
264     protected boolean includeResources;
265 
266     /**
267      * Specifies whether to include the test resource directories in the check.
268      * @since 2.11
269      */
270     @Parameter( property = "checkstyle.includeTestResources", defaultValue = "true", required = true )
271     protected boolean includeTestResources;
272 
273     /**
274      * Specifies the location of the source directory to be used for Checkstyle.
275      */
276     @Parameter( defaultValue = "${project.build.sourceDirectory}", required = true )
277     protected File sourceDirectory;
278 
279     /**
280      * Specifies the location of the test source directory to be used for
281      * Checkstyle.
282      *
283      * @since 2.2
284      */
285     @Parameter( defaultValue = "${project.build.testSourceDirectory}" )
286     protected File testSourceDirectory;
287 
288     /**
289      * Include or not the test source directory to be used for Checkstyle.
290      *
291      * @since 2.2
292      */
293     @Parameter( defaultValue = "false" )
294     protected boolean includeTestSourceDirectory;
295 
296     /**
297      * The key to be used in the properties for the suppressions file.
298      *
299      * @since 2.1
300      */
301     @Parameter( property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file" )
302     protected String suppressionsFileExpression;
303 
304     /**
305      * <p>
306      * Specifies the location of the suppressions XML file to use.
307      * </p>
308      * <p/>
309      * <p>
310      * This parameter is resolved as resource, URL, then file. If successfully
311      * resolved, the contents of the suppressions XML is copied into the
312      * <code>${project.build.directory}/checkstyle-supressions.xml</code> file
313      * before being passed to Checkstyle for loading.
314      * </p>
315      * <p/>
316      * <p>
317      * See <code>suppressionsFileExpression</code> for the property that will
318      * be made available to your checkstyle configuration.
319      * </p>
320      *
321      * @since 2.0-beta-2
322      */
323     @Parameter( property = "checkstyle.suppressions.location" )
324     protected String suppressionsLocation;
325 
326     /**
327      * If <code>null</code>, the Checkstyle plugin will display violations on stdout.
328      * Otherwise, a text file will be created with the violations.
329      */
330     @Parameter
331     private File useFile;
332 
333     /**
334      * Specifies the format of the output to be used when writing to the output
335      * file. Valid values are "plain" and "xml".
336      */
337     @Parameter( property = "checkstyle.output.format", defaultValue = "xml" )
338     private String outputFileFormat;
339 
340     /**
341      * Specifies if the Rules summary should be enabled or not.
342      */
343     @Parameter( property = "checkstyle.enable.rules.summary", defaultValue = "true" )
344     private boolean enableRulesSummary;
345 
346     /**
347      * Specifies if the Severity summary should be enabled or not.
348      */
349     @Parameter( property = "checkstyle.enable.severity.summary", defaultValue = "true" )
350     private boolean enableSeveritySummary;
351 
352     /**
353      * Specifies if the Files summary should be enabled or not.
354      */
355     @Parameter( property = "checkstyle.enable.files.summary", defaultValue = "true" )
356     private boolean enableFilesSummary;
357 
358     /**
359      * Specifies if the RSS should be enabled or not.
360      */
361     @Parameter( property = "checkstyle.enable.rss", defaultValue = "true" )
362     private boolean enableRSS;
363 
364     /**
365      * SiteTool.
366      *
367      * @since 2.2
368      */
369     @Component( role = SiteTool.class )
370     protected SiteTool siteTool;
371 
372     /**
373      * The Maven Project Object.
374      */
375     @Parameter( defaultValue = "${project}" )
376     protected MavenProject project;
377     
378     
379     /**
380      * The Plugin Descriptor
381      */
382     @Parameter( defaultValue= "${plugin}" )
383     private PluginDescriptor plugin;
384 
385     /**
386      * Link the violation line numbers to the source xref. Will link
387      * automatically if Maven JXR plugin is being used.
388      *
389      * @since 2.1
390      */
391     @Parameter( property = "linkXRef", defaultValue = "true" )
392     private boolean linkXRef;
393 
394     /**
395      * Location of the Xrefs to link to.
396      */
397     @Parameter( defaultValue = "${project.reporting.outputDirectory}/xref" )
398     private File xrefLocation;
399 
400     /**
401      * When using custom treeWalkers, specify their names here so the checks
402      * inside the treeWalker end up the the rule-summary.
403      * 
404      * @since 2.11
405      */
406     @Parameter
407     private List<String> treeWalkerNames;
408 
409     /**
410      */
411     @Component
412     private Renderer siteRenderer;
413 
414     /**
415      */
416     @Component
417     protected ResourceManager locator;
418 
419     /**
420      * CheckstyleRssGenerator.
421      *
422      * @since 2.4
423      */
424     @Component( role = CheckstyleRssGenerator.class, hint = "default" )
425     protected CheckstyleRssGenerator checkstyleRssGenerator;
426 
427     /**
428      * @since 2.5
429      */
430     @Component( role = CheckstyleExecutor.class, hint = "default" )
431     protected CheckstyleExecutor checkstyleExecutor;
432 
433     protected ByteArrayOutputStream stringOutputStream;
434 
435     /** {@inheritDoc} */
436     public String getName( Locale locale )
437     {
438         return getBundle( locale ).getString( "report.checkstyle.name" );
439     }
440 
441     /** {@inheritDoc} */
442     public String getDescription( Locale locale )
443     {
444         return getBundle( locale ).getString( "report.checkstyle.description" );
445     }
446 
447     /** {@inheritDoc} */
448     protected String getOutputDirectory()
449     {
450         return outputDirectory.getAbsolutePath();
451     }
452 
453     /** {@inheritDoc} */
454     protected MavenProject getProject()
455     {
456         return project;
457     }
458 
459     /** {@inheritDoc} */
460     protected Renderer getSiteRenderer()
461     {
462         return siteRenderer;
463     }
464 
465     /** {@inheritDoc} */
466     public void executeReport( Locale locale )
467         throws MavenReportException
468     {
469         locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
470         locator.addSearchPath( "url", "" );
471 
472         locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
473 
474         // for when we start using maven-shared-io and
475         // maven-shared-monitor...
476         // locator = new Locator( new MojoLogMonitorAdaptor( getLog() ) );
477 
478         // locator = new Locator( getLog(), new File(
479         // project.getBuild().getDirectory() ) );
480 
481         ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
482 
483         try
484         {
485             CheckstyleExecutorRequest request = createRequest().setLicenseArtifacts( collectArtifacts( "license" ) )
486                             .setConfigurationArtifacts( collectArtifacts( "configuration" ) );
487 
488             CheckstyleResults results = checkstyleExecutor.executeCheckstyle( request );
489 
490             ResourceBundle bundle = getBundle( locale );
491             generateReportStatics();
492             generateMainReport( results, bundle );
493             if ( enableRSS )
494             {
495                 CheckstyleRssGeneratorRequest checkstyleRssGeneratorRequest =
496                     new CheckstyleRssGeneratorRequest( this.project, this.getCopyright(), outputDirectory, getLog() );
497                 checkstyleRssGenerator.generateRSS( results, checkstyleRssGeneratorRequest );
498             }
499 
500         }
501         catch ( CheckstyleException e )
502         {
503             throw new MavenReportException( "Failed during checkstyle configuration", e );
504         }
505         catch ( CheckstyleExecutorException e )
506         {
507             throw new MavenReportException( "Failed during checkstyle execution", e );
508         }
509         finally
510         {
511             //be sure to restore original context classloader
512             Thread.currentThread().setContextClassLoader( currentClassLoader );
513         }
514     }
515 
516     /**
517      * Create the Checkstyle executor request.
518      *
519      * @return The executor request.
520      * @throws MavenReportException If something goes wrong during creation.
521      */
522     protected abstract CheckstyleExecutorRequest createRequest()
523             throws MavenReportException;
524 
525     private List<Artifact> collectArtifacts( String hint )
526     {
527         List<Artifact> artifacts = new ArrayList<Artifact>();
528 
529         Plugin checkstylePlugin =
530             (Plugin) project.getBuild().getPluginsAsMap().get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
531         if ( checkstylePlugin != null )
532         {
533             for ( Dependency dep : checkstylePlugin.getDependencies() )
534             {
535              // @todo if we can filter on hints, it should be done here...
536                 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
537                 artifacts.add( (Artifact) plugin.getArtifactMap().get( depKey ) );
538             }
539         }
540 
541         return artifacts;
542     }
543     
544     /**
545      * Creates and returns the report generation listener.
546      *
547      * @return The audit listener.
548      * @throws MavenReportException If something goes wrong.
549      */
550     protected AuditListener getListener()
551         throws MavenReportException
552     {
553         AuditListener listener = null;
554 
555         if ( StringUtils.isNotEmpty( outputFileFormat ) )
556         {
557             File resultFile = outputFile;
558 
559             OutputStream out = getOutputStream( resultFile );
560 
561             if ( "xml".equals( outputFileFormat ) )
562             {
563                 listener = new XMLLogger( out, true );
564             }
565             else if ( "plain".equals( outputFileFormat ) )
566             {
567                 listener = new DefaultLogger( out, true );
568             }
569             else
570             {
571                 // TODO: failure if not a report
572                 throw new MavenReportException( "Invalid output file format: (" + outputFileFormat
573                     + "). Must be 'plain' or 'xml'." );
574             }
575         }
576 
577         return listener;
578     }
579 
580     private OutputStream getOutputStream( File file )
581         throws MavenReportException
582     {
583         File parentFile = file.getAbsoluteFile().getParentFile();
584 
585         if ( !parentFile.exists() )
586         {
587             parentFile.mkdirs();
588         }
589 
590         FileOutputStream fileOutputStream;
591         try
592         {
593             fileOutputStream = new FileOutputStream( file );
594         }
595         catch ( FileNotFoundException e )
596         {
597             throw new MavenReportException( "Unable to create output stream: " + file, e );
598         }
599         return fileOutputStream;
600     }
601 
602     /**
603      * Creates and returns the console listener.
604      *
605      * @return The console listener.
606      * @throws MavenReportException If something goes wrong.
607      */
608     protected DefaultLogger getConsoleListener()
609         throws MavenReportException
610     {
611         DefaultLogger consoleListener;
612 
613         if ( useFile == null )
614         {
615             stringOutputStream = new ByteArrayOutputStream();
616             consoleListener = new DefaultLogger( stringOutputStream, false );
617         }
618         else
619         {
620             OutputStream out = getOutputStream( useFile );
621 
622             consoleListener = new DefaultLogger( out, true );
623         }
624 
625         return consoleListener;
626     }
627 
628     private void generateReportStatics()
629         throws MavenReportException
630     {
631         ReportResource rresource = new ReportResource( PLUGIN_RESOURCES, outputDirectory );
632         try
633         {
634             rresource.copy( "images/rss.png" );
635         }
636         catch ( IOException e )
637         {
638             throw new MavenReportException( "Unable to copy static resources.", e );
639         }
640     }
641 
642 
643     private String getCopyright()
644     {
645         String copyright;
646         int currentYear = Calendar.getInstance().get( Calendar.YEAR );
647         if ( StringUtils.isNotEmpty( project.getInceptionYear() )
648             && !String.valueOf( currentYear ).equals( project.getInceptionYear() ) )
649         {
650             copyright = project.getInceptionYear() + " - " + currentYear;
651         }
652         else
653         {
654             copyright = String.valueOf( currentYear );
655         }
656 
657         if ( ( project.getOrganization() != null ) && StringUtils.isNotEmpty( project.getOrganization().getName() ) )
658         {
659             copyright = copyright + " " + project.getOrganization().getName();
660         }
661         return copyright;
662     }
663 
664     private void generateMainReport( CheckstyleResults results, ResourceBundle bundle )
665     {
666         CheckstyleReportGenerator generator =
667             new CheckstyleReportGenerator( getSink(), bundle, project.getBasedir(), siteTool );
668 
669         generator.setLog( getLog() );
670         generator.setEnableRulesSummary( enableRulesSummary );
671         generator.setEnableSeveritySummary( enableSeveritySummary );
672         generator.setEnableFilesSummary( enableFilesSummary );
673         generator.setEnableRSS( enableRSS );
674         generator.setCheckstyleConfig( results.getConfiguration() );
675         if ( linkXRef )
676         {
677             String relativePath = PathTool.getRelativePath( getOutputDirectory(), xrefLocation.getAbsolutePath() );
678             if ( StringUtils.isEmpty( relativePath ) )
679             {
680                 relativePath = ".";
681             }
682             relativePath = relativePath + "/" + xrefLocation.getName();
683             if ( xrefLocation.exists() )
684             {
685                 // XRef was already generated by manual execution of a lifecycle
686                 // binding
687                 generator.setXrefLocation( relativePath );
688             }
689             else
690             {
691                 // Not yet generated - check if the report is on its way
692                 for ( ReportPlugin report : (Iterable<ReportPlugin>) getProject().getReportPlugins() )
693                 {
694                     String artifactId = report.getArtifactId();
695                     if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
696                     {
697                         generator.setXrefLocation( relativePath );
698                     }
699                 }
700             }
701 
702             if ( generator.getXrefLocation() == null && results.getFileCount() > 0 )
703             {
704                 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
705             }
706         }
707         if ( treeWalkerNames != null )
708         {
709             generator.setTreeWalkerNames( treeWalkerNames );
710         }
711         generator.generateReport( results );
712     }
713 
714     private static ResourceBundle getBundle( Locale locale )
715     {
716         return ResourceBundle.getBundle( "checkstyle-report", locale, AbstractCheckstyleReport.class.getClassLoader() );
717     }
718 
719     /** {@inheritDoc} */
720     public void setReportOutputDirectory( File reportOutputDirectory )
721     {
722         super.setReportOutputDirectory( reportOutputDirectory );
723         this.outputDirectory = reportOutputDirectory;
724     }
725     
726     
727 }