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