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.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.OutputStream;
30  import java.net.MalformedURLException;
31  import java.net.URL;
32  import java.net.URLClassLoader;
33  import java.util.ArrayList;
34  import java.util.Calendar;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Locale;
40  import java.util.Map;
41  import java.util.Properties;
42  import java.util.ResourceBundle;
43  
44  import org.apache.maven.artifact.DependencyResolutionRequiredException;
45  import org.apache.maven.doxia.siterenderer.Renderer;
46  import org.apache.maven.doxia.tools.SiteTool;
47  import org.apache.maven.model.ReportPlugin;
48  import org.apache.maven.plugin.checkstyle.rss.CheckstyleRssGenerator;
49  import org.apache.maven.plugin.checkstyle.rss.CheckstyleRssGeneratorRequest;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.reporting.AbstractMavenReport;
52  import org.apache.maven.reporting.MavenReportException;
53  import org.codehaus.plexus.resource.ResourceManager;
54  import org.codehaus.plexus.resource.loader.FileResourceCreationException;
55  import org.codehaus.plexus.resource.loader.FileResourceLoader;
56  import org.codehaus.plexus.util.FileUtils;
57  import org.codehaus.plexus.util.PathTool;
58  import org.codehaus.plexus.util.StringUtils;
59  
60  import com.puppycrawl.tools.checkstyle.Checker;
61  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
62  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
63  import com.puppycrawl.tools.checkstyle.DefaultLogger;
64  import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
65  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
66  import com.puppycrawl.tools.checkstyle.XMLLogger;
67  import com.puppycrawl.tools.checkstyle.api.AuditListener;
68  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
69  import com.puppycrawl.tools.checkstyle.api.Configuration;
70  import com.puppycrawl.tools.checkstyle.api.FilterSet;
71  import com.puppycrawl.tools.checkstyle.filters.SuppressionsLoader;
72  
73  /**
74   * Perform a Checkstyle analysis, and generate a report on violations.
75   *
76   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
77   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
78   * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
79   * @version $Id: CheckstyleReport.html 816654 2012-05-08 13:54:20Z hboutemy $
80   * @goal checkstyle
81   * @requiresDependencyResolution compile
82   */
83  public class CheckstyleReport
84      extends AbstractMavenReport
85  {
86      public static final String PLUGIN_RESOURCES = "org/apache/maven/plugin/checkstyle";
87  
88      /**
89       * @deprecated Remove with format parameter.
90       */
91      private static final Map FORMAT_TO_CONFIG_LOCATION;
92  
93      static
94      {
95          Map fmt2Cfg = new HashMap();
96  
97          fmt2Cfg.put( "sun", "config/sun_checks.xml" );
98          fmt2Cfg.put( "turbine", "config/turbine_checks.xml" );
99          fmt2Cfg.put( "avalon", "config/avalon_checks.xml" );
100         fmt2Cfg.put( "maven", "config/maven_checks.xml" );
101 
102         FORMAT_TO_CONFIG_LOCATION = Collections.unmodifiableMap( fmt2Cfg );
103     }
104 
105     /**
106      * Skip entire check.
107      *
108      * @parameter expression="${checkstyle.skip}" default-value="false"
109      * @since 2.2
110      */
111     private boolean skip;
112 
113     /**
114      * The output directory for the report. Note that this parameter is only
115      * evaluated if the goal is run directly from the command line. If the goal
116      * is run indirectly as part of a site generation, the output directory
117      * configured in Maven Site Plugin is used instead.
118      *
119      * @parameter default-value="${project.reporting.outputDirectory}"
120      * @required
121      */
122     private File outputDirectory;
123 
124     /**
125      * Specifies if the Rules summary should be enabled or not.
126      *
127      * @parameter expression="${checkstyle.enable.rules.summary}"
128      *            default-value="true"
129      */
130     private boolean enableRulesSummary;
131 
132     /**
133      * Specifies if the Severity summary should be enabled or not.
134      *
135      * @parameter expression="${checkstyle.enable.severity.summary}"
136      *            default-value="true"
137      */
138     private boolean enableSeveritySummary;
139 
140     /**
141      * Specifies if the Files summary should be enabled or not.
142      *
143      * @parameter expression="${checkstyle.enable.files.summary}"
144      *            default-value="true"
145      */
146     private boolean enableFilesSummary;
147 
148     /**
149      * Specifies if the RSS should be enabled or not.
150      *
151      * @parameter expression="${checkstyle.enable.rss}" default-value="true"
152      */
153     private boolean enableRSS;
154 
155     /**
156      * Specifies the names filter of the source files to be used for Checkstyle.
157      *
158      * @parameter expression="${checkstyle.includes}" default-value="**\/*.java"
159      * @required
160      */
161     private String includes;
162 
163     /**
164      * Specifies the names filter of the source files to be excluded for
165      * Checkstyle.
166      *
167      * @parameter expression="${checkstyle.excludes}"
168      */
169     private String excludes;
170 
171     /**
172      * <p>
173      * Specifies the location of the XML configuration to use.
174      * </p>
175      *
176      * <p>
177      * Potential values are a filesystem path, a URL, or a classpath resource.
178      * This parameter expects that the contents of the location conform to the
179      * xml format (Checkstyle <a
180      * href="http://checkstyle.sourceforge.net/config.html#Modules">Checker
181      * module</a>) configuration of rulesets.
182      * </p>
183      *
184      * <p>
185      * This parameter is resolved as resource, URL, then file. If successfully
186      * resolved, the contents of the configuration is copied into the
187      * <code>${project.build.directory}/checkstyle-configuration.xml</code>
188      * file before being passed to Checkstyle as a configuration.
189      * </p>
190      *
191      * <p>
192      * There are 4 predefined rulesets.
193      * </p>
194      *
195      * <ul>
196      * <li><code>config/sun_checks.xml</code>: Sun Checks.</li>
197      * <li><code>config/turbine_checks.xml</code>: Turbine Checks.</li>
198      * <li><code>config/avalon_checks.xml</code>: Avalon Checks.</li>
199      * <li><code>config/maven_checks.xml</code>: Maven Source Checks.</li>
200      * </ul>
201      *
202      * @parameter expression="${checkstyle.config.location}"
203      *            default-value="config/sun_checks.xml"
204      */
205     private String configLocation;
206 
207     /**
208      * Specifies what predefined check set to use. Available sets are "sun" (for
209      * the Sun coding conventions), "turbine", and "avalon".
210      *
211      * @parameter default-value="sun"
212      * @deprecated Use configLocation instead.
213      */
214     private String format;
215 
216     /**
217      * <p>
218      * Specifies the location of the properties file.
219      * </p>
220      *
221      * <p>
222      * This parameter is resolved as URL, File then resource. If successfully
223      * resolved, the contents of the properties location is copied into the
224      * <code>${project.build.directory}/checkstyle-checker.properties</code>
225      * file before being passed to Checkstyle for loading.
226      * </p>
227      *
228      * <p>
229      * The contents of the <code>propertiesLocation</code> will be made
230      * available to Checkstyle for specifying values for parameters within the
231      * xml configuration (specified in the <code>configLocation</code>
232      * parameter).
233      * </p>
234      *
235      * @parameter expression="${checkstyle.properties.location}"
236      * @since 2.0-beta-2
237      */
238     private String propertiesLocation;
239 
240     /**
241      * Specifies the location of the Checkstyle properties file that will be used to
242      * check the source.
243      *
244      * @parameter
245      * @deprecated Use propertiesLocation instead.
246      */
247     private File propertiesFile;
248 
249     /**
250      * Specifies the URL of the Checkstyle properties that will be used to check
251      * the source.
252      *
253      * @parameter
254      * @deprecated Use propertiesLocation instead.
255      */
256     private URL propertiesURL;
257 
258     /**
259      * Allows for specifying raw property expansion information.
260      *
261      * @parameter
262      */
263     private String propertyExpansion;
264 
265     /**
266      * <p>
267      * Specifies the location of the License file (a.k.a. the header file) that
268      * can be used by Checkstyle to verify that source code has the correct
269      * license header.
270      * </p>
271      * <p>
272      * You need to use ${checkstyle.header.file} in your Checkstyle xml
273      * configuration to reference the name of this header file.
274      * </p>
275      * <p>
276      * For instance:
277      * </p>
278      * <p>
279      * <code>
280      * &lt;module name="RegexpHeader">
281      *   &lt;property name="headerFile" value="${checkstyle.header.file}"/>
282      * &lt;/module>
283      * </code>
284      * </p>
285      *
286      * @parameter expression="${checkstyle.header.file}"
287      *            default-value="LICENSE.txt"
288      * @since 2.0-beta-2
289      */
290     private String headerLocation;
291 
292     /**
293      * Specifies the location of the License file (a.k.a. the header file) that
294      * is used by Checkstyle to verify that source code has the correct
295      * license header.
296      *
297      * @parameter expression="${basedir}/LICENSE.txt"
298      * @deprecated Use headerLocation instead.
299      */
300     private File headerFile;
301 
302     /**
303      * Specifies the cache file used to speed up Checkstyle on successive runs.
304      *
305      * @parameter default-value="${project.build.directory}/checkstyle-cachefile"
306      */
307     private String cacheFile;
308 
309     /**
310      * If <code>null</code>, the Checkstyle plugin will display violations on stdout.
311      * Otherwise, a text file will be created with the violations.
312      *
313      * @parameter
314      */
315     private File useFile;
316 
317     /**
318      * SiteTool.
319      *
320      * @since 2.2
321      * @component role="org.apache.maven.doxia.tools.SiteTool"
322      * @required
323      * @readonly
324      */
325     protected SiteTool siteTool;
326 
327     /**
328      * <p>
329      * Specifies the location of the suppressions XML file to use.
330      * </p>
331      *
332      * <p>
333      * This parameter is resolved as resource, URL, then file. If successfully
334      * resolved, the contents of the suppressions XML is copied into the
335      * <code>${project.build.directory}/checkstyle-supressions.xml</code> file
336      * before being passed to Checkstyle for loading.
337      * </p>
338      *
339      * <p>
340      * See <code>suppressionsFileExpression</code> for the property that will
341      * be made available to your checkstyle configuration.
342      * </p>
343      *
344      * @parameter expression="${checkstyle.suppressions.location}"
345      * @since 2.0-beta-2
346      */
347     private String suppressionsLocation;
348 
349     /**
350      * The key to be used in the properties for the suppressions file.
351      *
352      * @parameter expression="${checkstyle.suppression.expression}"
353      *            default-value="checkstyle.suppressions.file"
354      * @since 2.1
355      */
356     private String suppressionsFileExpression;
357 
358     /**
359      * Specifies the location of the suppressions XML file to use. The plugin
360      * defines a Checkstyle property named
361      * <code>checkstyle.suppressions.file</code> with the value of this
362      * property. This allows using the Checkstyle property in your own custom
363      * checkstyle configuration file when specifying a suppressions file.
364      *
365      * @parameter
366      * @deprecated Use suppressionsLocation instead.
367      */
368     private String suppressionsFile;
369 
370     /**
371      * Specifies the path and filename to save the checkstyle output. The format
372      * of the output file is determined by the <code>outputFileFormat</code>
373      * parameter.
374      *
375      * @parameter expression="${checkstyle.output.file}"
376      *            default-value="${project.build.directory}/checkstyle-result.xml"
377      */
378     private File outputFile;
379 
380     /**
381      * Specifies the format of the output to be used when writing to the output
382      * file. Valid values are "plain" and "xml".
383      *
384      * @parameter expression="${checkstyle.output.format}" default-value="xml"
385      */
386     private String outputFileFormat;
387 
388     /**
389      * <p>
390      * Specifies the location of the package names XML to be used to configure
391      * the Checkstyle <a
392      * href="http://checkstyle.sourceforge.net/config.html#Packages">Packages</a>.
393      * </p>
394      *
395      * <p>
396      * This parameter is resolved as resource, URL, then file. If resolved to a
397      * resource, or a URL, the contents of the package names XML is copied into
398      * the <code>${project.build.directory}/checkstyle-packagenames.xml</code>
399      * file before being passed to Checkstyle for loading.
400      * </p>
401      *
402      * @parameter
403      * @since 2.0-beta-2
404      */
405     private String packageNamesLocation;
406 
407     /**
408      * Specifies the location of the package names XML to be used to configure
409      * Checkstyle.
410      *
411      * @parameter
412      * @deprecated Use packageNamesLocation instead.
413      */
414     private String packageNamesFile;
415 
416     /**
417      * Specifies if the build should fail upon a violation.
418      *
419      * @parameter default-value="false"
420      */
421     private boolean failsOnError;
422 
423     /**
424      * Specifies the location of the source directory to be used for Checkstyle.
425      *
426      * @parameter default-value="${project.build.sourceDirectory}"
427      * @required
428      */
429     private File sourceDirectory;
430 
431     /**
432      * Specifies the location of the test source directory to be used for
433      * Checkstyle.
434      *
435      * @parameter default-value="${project.build.testSourceDirectory}"
436      * @since 2.2
437      */
438     private File testSourceDirectory;
439 
440     /**
441      * Include or not the test source directory to be used for Checkstyle.
442      *
443      * @parameter default-value="${false}"
444      * @since 2.2
445      */
446     private boolean includeTestSourceDirectory;
447 
448     /**
449      * The Maven Project Object.
450      *
451      * @parameter default-value="${project}"
452      * @required
453      * @readonly
454      */
455     private MavenProject project;
456 
457     /**
458      * Output errors to console.
459      *
460      * @parameter default-value="false"
461      */
462     private boolean consoleOutput;
463 
464     /**
465      * Link the violation line numbers to the source xref. Will link
466      * automatically if Maven JXR plugin is being used.
467      *
468      * @parameter expression="${linkXRef}" default-value="true"
469      * @since 2.1
470      */
471     private boolean linkXRef;
472 
473     /**
474      * Location of the Xrefs to link to.
475      *
476      * @parameter default-value="${project.reporting.outputDirectory}/xref"
477      */
478     private File xrefLocation;
479 
480     /**
481      * The file encoding to use when reading the source files. If the property <code>project.build.sourceEncoding</code>
482      * is not set, the platform default encoding is used. <strong>Note:</strong> This parameter always overrides the
483      * property <code>charset</code> from Checkstyle's <code>TreeWalker</code> module.
484      *
485      * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
486      * @since 2.2
487      */
488     private String encoding;
489 
490     /**
491      * @component
492      * @required
493      * @readonly
494      */
495     private Renderer siteRenderer;
496     
497     private static final File[] EMPTY_FILE_ARRAY = new File[0];
498 
499     private ByteArrayOutputStream stringOutputStream;
500 
501     /**
502      * @component
503      * @required
504      * @readonly
505      */
506     private ResourceManager locator;
507     
508     /**
509      * CheckstyleRssGenerator.
510      *
511      * @since 2.4
512      * @component role="org.apache.maven.plugin.checkstyle.rss.CheckstyleRssGenerator" role-hint="default"
513      * @required
514      * @readonly
515      */
516     protected CheckstyleRssGenerator checkstyleRssGenerator;    
517 
518     /** {@inheritDoc} */
519     public String getName( Locale locale )
520     {
521         return getBundle( locale ).getString( "report.checkstyle.name" );
522     }
523 
524     /** {@inheritDoc} */
525     public String getDescription( Locale locale )
526     {
527         return getBundle( locale ).getString( "report.checkstyle.description" );
528     }
529 
530     /** {@inheritDoc} */
531     protected String getOutputDirectory()
532     {
533         return outputDirectory.getAbsolutePath();
534     }
535 
536     /** {@inheritDoc} */
537     protected MavenProject getProject()
538     {
539         return project;
540     }
541 
542     /** {@inheritDoc} */
543     protected Renderer getSiteRenderer()
544     {
545         return siteRenderer;
546     }
547 
548     /** {@inheritDoc} */
549     public void executeReport( Locale locale )
550         throws MavenReportException
551     {
552         if ( !skip )
553         {
554             mergeDeprecatedInfo();
555 
556             locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
557             locator.addSearchPath( "url", "" );
558 
559             locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );
560 
561             if ( !canGenerateReport() )
562             {
563                 getLog().info( "Source directory does not exist - skipping report." );
564                 return;
565             }
566 
567             // for when we start using maven-shared-io and
568             // maven-shared-monitor...
569             // locator = new Locator( new MojoLogMonitorAdaptor( getLog() ) );
570 
571             // locator = new Locator( getLog(), new File(
572             // project.getBuild().getDirectory() ) );
573 
574             ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
575 
576             try
577             {
578                 // checkstyle will always use the context classloader in order
579                 // to load resources (dtds),
580                 // so we have to fix it
581                 ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
582                 Thread.currentThread().setContextClassLoader( checkstyleClassLoader );
583 
584 
585                 String configFile = getConfigFile();
586                 Properties overridingProperties = getOverridingProperties();
587                 Configuration config;
588                 CheckstyleResults results;
589 
590                 config = ConfigurationLoader.loadConfiguration( configFile,
591                                                                 new PropertiesExpander( overridingProperties ) );
592                 String effectiveEncoding =
593                     StringUtils.isNotEmpty( encoding ) ? encoding : System.getProperty( "file.encoding", "UTF-8" );
594                 if ( StringUtils.isEmpty( encoding ) )
595                 {
596                     getLog().warn(
597                                    "File encoding has not been set, using platform encoding " + effectiveEncoding
598                                        + ", i.e. build is platform dependent!" );
599                 }
600                 Configuration[] modules = config.getChildren();
601                 for ( int i = 0; i < modules.length; i++ )
602                 {
603                     Configuration module = modules[i];
604                     if ( "Checker".equals( module.getName() )
605                         || "com.puppycrawl.tools.checkstyle.Checker".equals( module.getName() ) )
606                     {
607                         if ( module instanceof DefaultConfiguration )
608                         {
609                             ( (DefaultConfiguration) module ).addAttribute( "charset", effectiveEncoding );
610                         }
611                         else
612                         {
613                             getLog().warn( "Failed to configure file encoding on module " + module );
614                         }
615                     }
616                     if ("TreeWalker".equals(module.getName())
617                         || "com.puppycrawl.tools.checkstyle.TreeWalker".equals(module.getName()))
618                     {
619                         if (module instanceof DefaultConfiguration)
620                         {
621                             ((DefaultConfiguration) module).addAttribute("cacheFile", cacheFile);
622                         }
623                         else
624                         {
625                             getLog().warn("Failed to configure cache file on module " + module);
626                         }
627                     }
628                 }
629 
630                 results = executeCheckstyle( config );
631 
632                 ResourceBundle bundle = getBundle( locale );
633                 generateReportStatics();
634                 generateMainReport( results, config, bundle );
635                 if ( enableRSS )
636                 {
637                     CheckstyleRssGeneratorRequest request =
638                         new CheckstyleRssGeneratorRequest( this.project, this.getCopyright(), outputDirectory, getLog() );
639                     checkstyleRssGenerator.generateRSS( results, request );
640                 }
641 
642             }
643             catch ( CheckstyleException e )
644             {
645                 throw new MavenReportException( "Failed during checkstyle configuration", e );
646             }
647             finally
648             {
649                 //be sure to restore original context classloader
650                 Thread.currentThread().setContextClassLoader( currentClassLoader );
651             }
652         }
653     }
654 
655     private void generateReportStatics()
656         throws MavenReportException
657     {
658         ReportResource rresource = new ReportResource( PLUGIN_RESOURCES, outputDirectory );
659         try
660         {
661             rresource.copy( "images/rss.png" );
662         }
663         catch ( IOException e )
664         {
665             throw new MavenReportException( "Unable to copy static resources.", e );
666         }
667     }
668 
669     
670     private String getCopyright()
671     {
672         String copyright;
673         int currentYear = Calendar.getInstance().get( Calendar.YEAR );
674         if ( StringUtils.isNotEmpty( project.getInceptionYear() )
675             && !String.valueOf( currentYear ).equals( project.getInceptionYear() ) )
676         {
677             copyright = project.getInceptionYear() + " - " + currentYear;
678         }
679         else
680         {
681             copyright = String.valueOf( currentYear );
682         }
683 
684         if ( ( project.getOrganization() != null ) && StringUtils.isNotEmpty( project.getOrganization().getName() ) )
685         {
686             copyright = copyright + " " + project.getOrganization().getName();
687         }
688         return copyright;
689     }
690 
691     private void generateMainReport( CheckstyleResults results, Configuration config, ResourceBundle bundle )
692     {
693         CheckstyleReportGenerator generator = new CheckstyleReportGenerator( getSink(), bundle, project.getBasedir(), siteTool );
694 
695         generator.setLog( getLog() );
696         generator.setEnableRulesSummary( enableRulesSummary );
697         generator.setEnableSeveritySummary( enableSeveritySummary );
698         generator.setEnableFilesSummary( enableFilesSummary );
699         generator.setEnableRSS( enableRSS );
700         generator.setCheckstyleConfig( config );
701         if ( linkXRef )
702         {
703             String relativePath = PathTool.getRelativePath( getOutputDirectory(), xrefLocation.getAbsolutePath() );
704             if ( StringUtils.isEmpty( relativePath ) )
705             {
706                 relativePath = ".";
707             }
708             relativePath = relativePath + "/" + xrefLocation.getName();
709             if ( xrefLocation.exists() )
710             {
711                 // XRef was already generated by manual execution of a lifecycle
712                 // binding
713                 generator.setXrefLocation( relativePath );
714             }
715             else
716             {
717                 // Not yet generated - check if the report is on its way
718                 for ( Iterator reports = getProject().getReportPlugins().iterator(); reports.hasNext(); )
719                 {
720                     ReportPlugin report = (ReportPlugin) reports.next();
721 
722                     String artifactId = report.getArtifactId();
723                     if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
724                     {
725                         generator.setXrefLocation( relativePath );
726                     }
727                 }
728             }
729 
730             if ( generator.getXrefLocation() == null )
731             {
732                 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
733             }
734         }
735         generator.generateReport( results );
736     }
737 
738     /**
739      * Merge in the deprecated parameters to the new ones, unless the new
740      * parameters have values.
741      *
742      * @deprecated Remove when deprecated params are removed.
743      */
744     private void mergeDeprecatedInfo()
745     {
746         if ( "config/sun_checks.xml".equals( configLocation ) && !"sun".equals( format ) )
747         {
748             configLocation = (String) FORMAT_TO_CONFIG_LOCATION.get( format );
749         }
750 
751         if ( StringUtils.isEmpty( propertiesLocation ) )
752         {
753             if ( propertiesFile != null )
754             {
755                 propertiesLocation = propertiesFile.getPath();
756             }
757             else if ( propertiesURL != null )
758             {
759                 propertiesLocation = propertiesURL.toExternalForm();
760             }
761         }
762 
763         if ( "LICENSE.txt".equals( headerLocation ) )
764         {
765             File defaultHeaderFile = new File( project.getBasedir(), "LICENSE.txt" );
766             if ( !defaultHeaderFile.equals( headerFile ) )
767             {
768                 headerLocation = headerFile.getPath();
769             }
770         }
771 
772         if ( StringUtils.isEmpty( suppressionsLocation ) )
773         {
774             suppressionsLocation = suppressionsFile;
775         }
776 
777         if ( StringUtils.isEmpty( packageNamesLocation ) )
778         {
779             packageNamesLocation = packageNamesFile;
780         }
781     }
782 
783     private CheckstyleResults executeCheckstyle( Configuration config )
784         throws MavenReportException, CheckstyleException
785     {
786         File[] files;
787         try
788         {
789             files = getFilesToProcess( includes, excludes );
790         }
791         catch ( IOException e )
792         {
793             throw new MavenReportException( "Error getting files to process", e );
794         }
795 
796         FilterSet filterSet = getSuppressions();
797 
798         Checker checker = new Checker();
799 
800         // setup classloader, needed to avoid "Unable to get class information
801         // for ..." errors
802         List classPathStrings;
803         List outputDirectories = new ArrayList();
804         try
805         {
806             classPathStrings = this.project.getCompileClasspathElements();
807             outputDirectories.add( this.project.getBuild().getOutputDirectory() );
808 
809             if ( includeTestSourceDirectory && ( testSourceDirectory != null ) && ( testSourceDirectory.exists() )
810                 && ( testSourceDirectory.isDirectory() ) )
811             {
812                 classPathStrings = this.project.getTestClasspathElements();
813                 outputDirectories.add( this.project.getBuild().getTestOutputDirectory() );
814             }
815         }
816         catch ( DependencyResolutionRequiredException e )
817         {
818             throw new MavenReportException( e.getMessage(), e );
819         }
820 
821         List urls = new ArrayList( classPathStrings.size() );
822 
823         Iterator iter = classPathStrings.iterator();
824         while ( iter.hasNext() )
825         {
826             try
827             {
828                 urls.add( new File( ( (String) iter.next() ) ).toURL() );
829             }
830             catch ( MalformedURLException e )
831             {
832                 throw new MavenReportException( e.getMessage(), e );
833             }
834         }
835 
836         Iterator iterator = outputDirectories.iterator();
837         while ( iterator.hasNext() )
838         {
839             try
840             {
841                 String outputDirectoryString = (String) iterator.next();
842                 if ( outputDirectoryString != null )
843                 {
844                     File outputDirectoryFile = new File( outputDirectoryString );
845                     if ( outputDirectoryFile.exists() )
846                     {
847                         URL outputDirectoryUrl = outputDirectoryFile.toURL();
848                         getLog().debug( "Adding the outputDirectory " + outputDirectoryUrl.toString()
849                             + " to the Checkstyle class path" );
850                         urls.add( outputDirectoryUrl );
851                     }
852                 }
853             }
854             catch ( MalformedURLException e )
855             {
856                 throw new MavenReportException( e.getMessage(), e );
857             }
858         }
859 
860         URLClassLoader projectClassLoader = new URLClassLoader( (URL[]) urls.toArray( new URL[urls.size()] ), null );
861         checker.setClassloader( projectClassLoader );
862 
863         checker.setModuleClassLoader( Thread.currentThread().getContextClassLoader() );
864 
865         if ( filterSet != null )
866         {
867             checker.addFilter( filterSet );
868         }
869 
870         checker.configure( config );
871 
872         AuditListener listener = getListener();
873 
874         if ( listener != null )
875         {
876             checker.addListener( listener );
877         }
878 
879         if ( consoleOutput )
880         {
881             checker.addListener( getConsoleListener() );
882         }
883 
884         CheckstyleReportListener sinkListener = new CheckstyleReportListener( sourceDirectory );
885         if ( includeTestSourceDirectory && ( testSourceDirectory != null ) && ( testSourceDirectory.exists() )
886             && ( testSourceDirectory.isDirectory() ) )
887         {
888             sinkListener.addSourceDirectory( testSourceDirectory );
889         }
890 
891         checker.addListener( sinkListener );
892 
893         ArrayList filesList = new ArrayList();
894         for (int i = 0; i < files.length; i++) {
895             filesList.add(files[i]);
896         }
897         int nbErrors = checker.process( filesList );
898 
899         checker.destroy();
900 
901         if ( stringOutputStream != null )
902         {
903             getLog().info( stringOutputStream.toString() );
904         }
905 
906         if ( failsOnError && nbErrors > 0 )
907         {
908             // TODO: should be a failure, not an error. Report is not meant to
909             // throw an exception here (so site would
910             // work regardless of config), but should record this information
911             throw new MavenReportException( "There are " + nbErrors + " checkstyle errors." );
912         }
913         else if ( nbErrors > 0 )
914         {
915             getLog().info( "There are " + nbErrors + " checkstyle errors." );
916         }
917 
918         return sinkListener.getResults();
919     }
920 
921     /** {@inheritDoc} */
922     public String getOutputName()
923     {
924         return "checkstyle";
925     }
926 
927     private AuditListener getListener()
928         throws MavenReportException
929     {
930         AuditListener listener = null;
931 
932         if ( StringUtils.isNotEmpty( outputFileFormat ) )
933         {
934             File resultFile = outputFile;
935 
936             OutputStream out = getOutputStream( resultFile );
937 
938             if ( "xml".equals( outputFileFormat ) )
939             {
940                 listener = new XMLLogger( out, true );
941             }
942             else if ( "plain".equals( outputFileFormat ) )
943             {
944                 listener = new DefaultLogger( out, true );
945             }
946             else
947             {
948                 // TODO: failure if not a report
949                 throw new MavenReportException( "Invalid output file format: (" + outputFileFormat
950                     + "). Must be 'plain' or 'xml'." );
951             }
952         }
953 
954         return listener;
955     }
956 
957     private OutputStream getOutputStream( File file )
958         throws MavenReportException
959     {
960         File parentFile = file.getAbsoluteFile().getParentFile();
961 
962         if ( !parentFile.exists() )
963         {
964             parentFile.mkdirs();
965         }
966 
967         FileOutputStream fileOutputStream;
968         try
969         {
970             fileOutputStream = new FileOutputStream( file );
971         }
972         catch ( FileNotFoundException e )
973         {
974             throw new MavenReportException( "Unable to create output stream: " + file, e );
975         }
976         return fileOutputStream;
977     }
978 
979     private File[] getFilesToProcess( String includes, String excludes )
980         throws IOException
981     {
982         StringBuffer excludesStr = new StringBuffer();
983 
984         if ( StringUtils.isNotEmpty( excludes ) )
985         {
986             excludesStr.append( excludes );
987         }
988 
989         String[] defaultExcludes = FileUtils.getDefaultExcludes();
990         for ( int i = 0; i < defaultExcludes.length; i++ )
991         {
992             if ( excludesStr.length() > 0 )
993             {
994                 excludesStr.append( "," );
995             }
996 
997             excludesStr.append( defaultExcludes[i] );
998         }
999 
1000         List files = FileUtils.getFiles( sourceDirectory, includes, excludesStr.toString() );
1001         if ( includeTestSourceDirectory && ( testSourceDirectory != null ) && ( testSourceDirectory.exists() )
1002             && ( testSourceDirectory.isDirectory() ) )
1003         {
1004             files.addAll( FileUtils.getFiles( testSourceDirectory, includes, excludesStr.toString() ) );
1005         }
1006 
1007         return (File[]) files.toArray( EMPTY_FILE_ARRAY );
1008     }
1009 
1010     private Properties getOverridingProperties()
1011         throws MavenReportException
1012     {
1013         Properties p = new Properties();
1014 
1015         try
1016         {
1017             File propertiesFile = locator.resolveLocation( propertiesLocation, "checkstyle-checker.properties" );
1018 
1019             if ( propertiesFile != null )
1020             {
1021                 p.load( new FileInputStream( propertiesFile ) );
1022             }
1023 
1024             if ( StringUtils.isNotEmpty( propertyExpansion ) )
1025             {
1026                 // Convert \ to \\, so that p.load will convert it back properly
1027                 propertyExpansion = StringUtils.replace( propertyExpansion, "\\", "\\\\" );
1028                 p.load( new ByteArrayInputStream( propertyExpansion.getBytes() ) );
1029             }
1030 
1031             // Workaround for MCHECKSTYLE-48
1032             // Make sure that "config/maven-header.txt" is the default value
1033             // for headerLocation, if configLocation="config/maven_checks.xml"
1034             if ( "config/maven_checks.xml".equals( configLocation ) )
1035             {
1036                 if ( "LICENSE.txt".equals( headerLocation ) )
1037                 {
1038                     headerLocation = "config/maven-header.txt";
1039                 }
1040             }
1041             if ( StringUtils.isNotEmpty( headerLocation ) )
1042             {
1043                 try
1044                 {
1045                     File headerFile = locator.resolveLocation( headerLocation, "checkstyle-header.txt" );
1046 
1047                     if ( headerFile != null )
1048                     {
1049                         p.setProperty( "checkstyle.header.file", headerFile.getAbsolutePath() );
1050                     }
1051                 }
1052                 catch ( IOException e )
1053                 {
1054                     throw new MavenReportException( "Unable to process header location: " + headerLocation, e );
1055                 }
1056             }
1057 
1058             if ( cacheFile != null )
1059             {
1060                 p.setProperty( "checkstyle.cache.file", cacheFile );
1061             }
1062         }
1063         catch ( IOException e )
1064         {
1065             throw new MavenReportException( "Failed to get overriding properties", e );
1066         }
1067 
1068         if ( suppressionsFileExpression != null )
1069         {
1070             String suppresionFile = getSuppressionLocation();
1071 
1072             if ( suppresionFile != null )
1073             {
1074                 p.setProperty( suppressionsFileExpression, suppresionFile );
1075             }
1076         }
1077 
1078         return p;
1079     }
1080 
1081     private String getConfigFile()
1082         throws MavenReportException
1083     {
1084         try
1085         {
1086             File configFile = locator.getResourceAsFile( configLocation, "checkstyle-checker.xml" );
1087 
1088             if ( configFile == null )
1089             {
1090                 throw new MavenReportException( "Unable to process config location: " + configLocation );
1091             }
1092             return configFile.getAbsolutePath();
1093         }
1094         catch ( org.codehaus.plexus.resource.loader.ResourceNotFoundException e )
1095         {
1096             throw new MavenReportException( "Unable to find configuration file at location "
1097                                             + configLocation, e );
1098         }
1099         catch ( FileResourceCreationException e )
1100         {
1101             throw new MavenReportException( "Unable to process configuration file location "
1102                                             + configLocation, e );
1103         }
1104 
1105     }
1106 
1107     private String getSuppressionLocation()
1108         throws MavenReportException
1109     {
1110         try
1111         {
1112             File suppressionsFile = locator.resolveLocation( suppressionsLocation, "checkstyle-suppressions.xml" );
1113 
1114             if ( suppressionsFile == null )
1115             {
1116                 return null;
1117             }
1118 
1119             return suppressionsFile.getAbsolutePath();
1120         }
1121         catch ( IOException e )
1122         {
1123             throw new MavenReportException( "Failed to process supressions location: " + suppressionsLocation, e );
1124         }
1125     }
1126 
1127     private FilterSet getSuppressions()
1128         throws MavenReportException
1129     {
1130         try
1131         {
1132             File suppressionsFile = locator.resolveLocation( suppressionsLocation, "checkstyle-suppressions.xml" );
1133 
1134             if ( suppressionsFile == null )
1135             {
1136                 return null;
1137             }
1138 
1139             return SuppressionsLoader.loadSuppressions( suppressionsFile.getAbsolutePath() );
1140         }
1141         catch ( CheckstyleException ce )
1142         {
1143             throw new MavenReportException( "failed to load suppressions location: " + suppressionsLocation, ce );
1144         }
1145         catch ( IOException e )
1146         {
1147             throw new MavenReportException( "Failed to process supressions location: " + suppressionsLocation, e );
1148         }
1149     }
1150 
1151     private DefaultLogger getConsoleListener()
1152         throws MavenReportException
1153     {
1154         DefaultLogger consoleListener;
1155 
1156         if ( useFile == null )
1157         {
1158             stringOutputStream = new ByteArrayOutputStream();
1159             consoleListener = new DefaultLogger( stringOutputStream, false );
1160         }
1161         else
1162         {
1163             OutputStream out = getOutputStream( useFile );
1164 
1165             consoleListener = new DefaultLogger( out, true );
1166         }
1167 
1168         return consoleListener;
1169     }
1170 
1171     private static ResourceBundle getBundle( Locale locale )
1172     {
1173         return ResourceBundle.getBundle( "checkstyle-report", locale, CheckstyleReport.class.getClassLoader() );
1174     }
1175 
1176     /** {@inheritDoc} */
1177     public boolean canGenerateReport()
1178     {
1179         // TODO: would be good to scan the files here
1180         return sourceDirectory.exists();
1181     }
1182 
1183     /** {@inheritDoc} */
1184     public void setReportOutputDirectory( File reportOutputDirectory )
1185     {
1186         super.setReportOutputDirectory( reportOutputDirectory );
1187         this.outputDirectory = reportOutputDirectory;
1188     }
1189 }