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