View Javadoc

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