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