View Javadoc
1   package org.codehaus.mojo.javancss;
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.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.ResourceBundle;
28  
29  import org.apache.maven.model.ReportPlugin;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.project.MavenProject;
33  import org.apache.maven.reporting.AbstractMavenReport;
34  import org.apache.maven.reporting.MavenReportException;
35  import org.codehaus.plexus.util.DirectoryScanner;
36  import org.codehaus.plexus.util.PathTool;
37  import org.codehaus.plexus.util.ReaderFactory;
38  import org.codehaus.plexus.util.StringUtils;
39  import org.dom4j.Document;
40  import org.dom4j.DocumentException;
41  import org.dom4j.io.SAXReader;
42  
43  /**
44   * Generates a JavaNCSS report based on this module's source code.
45   *
46   * @author <a href="jeanlaurentATgmail.com">Jean-Laurent de Morlhon</a>
47   * @version $Id$
48   */
49  @Mojo( name = "report" )
50  public class NcssReportMojo
51      extends AbstractMavenReport
52  {
53      private static final String OUTPUT_NAME = "javancss";
54  
55      /**
56       * Specifies the directory where the XML report will be generated.
57       */
58      @Parameter( defaultValue = "${project.build.directory}", readonly = true, required = true )
59      private File xmlOutputDirectory;
60  
61      /**
62       * Specifies the location of the source files to be used.
63       */
64      @Parameter( defaultValue = "${project.build.sourceDirectory}", readonly = true, required = true )
65      private File sourceDirectory;
66  
67      /**
68       * Specifies the encoding of the source files.
69       */
70      @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
71      private String sourceEncoding;
72  
73      /**
74       * Specifies the maximum number of lines to take into account into the reports.
75       */
76      @Parameter( defaultValue = "30" )
77      private int lineThreshold;
78  
79      /**
80       * Specified the name of the temporary file generated by JavaNCSS prior report generation.
81       */
82      @Parameter( defaultValue = "javancss-raw-report.xml" )
83      private String tempFileName;
84  
85      /**
86       * The projects in the reactor for aggregation report.
87       */
88      @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
89      private List<MavenProject> reactorProjects;
90  
91      /**
92       * Link the violation line numbers to the source xref. Defaults to true and will link automatically if jxr plugin is
93       * being used.
94       */
95      @Parameter( property = "linkXRef", defaultValue = "true" )
96      private boolean linkXRef;
97  
98      /**
99       * Location of the Xrefs to link to.
100      */
101     @Parameter( defaultValue = "${project.build.directory}/site/xref" )
102     private File xrefLocation;
103 
104     /**
105      * List of ant-style patterns used to specify the java sources that should be included when running JavaNCSS. If
106      * this is not specified, all .java files in the project source directories are included.
107      */
108     @Parameter
109     private String[] includes;
110 
111     /**
112      * List of ant-style patterns used to specify the java sources that should be excluded when running JavaNCSS. If
113      * this is not specified, no files in the project source directories are excluded.
114      */
115     @Parameter
116     private String[] excludes;
117 
118     /**
119      * Skip the report.
120      *
121      * @since 2.1
122      */
123     @Parameter( property = "ncss.skip", defaultValue = "false" )
124     private boolean skip;
125 
126     /**
127      * Gets the source files encoding.
128      *
129      * @return The source file encoding.
130      */
131     protected String getSourceEncoding()
132     {
133         return sourceEncoding;
134     }
135 
136     /**
137      * @see org.apache.maven.reporting.MavenReport#executeReport(java.util.Locale)
138      */
139     public void executeReport( Locale locale )
140         throws MavenReportException
141     {
142         if ( !canGenerateReport() )
143         {
144             return;
145         }
146 
147         if ( canGenerateSingleReport() )
148         {
149             generateSingleReport( locale );
150         }
151         if ( canGenerateAggregateReport() )
152         {
153             generateAggregateReport( locale );
154         }
155     }
156 
157     private void generateAggregateReport( Locale locale )
158         throws MavenReportException
159     {
160         // All this work just to get "target" so that we can scan the filesystem for
161         // child javancss xml files...
162         String basedir = project.getBasedir().toString();
163         String output = xmlOutputDirectory.toString();
164         if ( getLog().isDebugEnabled() )
165         {
166             getLog().debug( "basedir: " + basedir );
167             getLog().debug( "output: " + output );
168         }
169         String relative = null;
170         if ( output.startsWith( basedir ) )
171         {
172             relative = output.substring( basedir.length() + 1 );
173         }
174         else
175         {
176             getLog().error(
177                             "Unable to aggregate report because I can't "
178                                 + "determine the relative location of the XML report" );
179             return;
180         }
181         getLog().debug( "relative: " + relative );
182         List<ModuleReport> reports = new ArrayList<ModuleReport>();
183         for ( MavenProject child : reactorProjects )
184         {
185             File xmlReport = new File( child.getBasedir() + File.separator + relative, tempFileName );
186             if ( xmlReport.exists() )
187             {
188                 reports.add( new ModuleReport( child, loadDocument( xmlReport ) ) );
189             }
190             else
191             {
192                 getLog().debug( "xml file not found: " + xmlReport );
193             }
194         }
195         getLog().debug( "Aggregating " + reports.size() + " JavaNCSS reports" );
196 
197         // parse the freshly generated file and write the report
198         NcssAggregateReportGenerator reportGenerator =
199             new NcssAggregateReportGenerator( getSink(), getBundle( locale ), getLog() );
200         reportGenerator.doReport( locale, reports, lineThreshold );
201     }
202 
203     private boolean isIncludeExcludeUsed()
204     {
205         return ( ( excludes != null ) || ( includes != null ) );
206     }
207 
208     private void generateSingleReport( Locale locale )
209         throws MavenReportException
210     {
211         getLog().info( "Running JavaNCSS " + NcssExecuter.getJavaNCSSVersion() );
212         if ( getLog().isDebugEnabled() )
213         {
214             getLog().debug( "Calling NcssExecuter with src: " + sourceDirectory );
215             getLog().debug( "                       output: " + buildOutputFileName() );
216             getLog().debug( "                     includes: " + includes );
217             getLog().debug( "                     excludes: " + excludes );
218             getLog().debug( "                     encoding: " + getSourceEncoding() );
219         }
220 
221         // run javaNCss and produce an temp xml file
222         NcssExecuter ncssExecuter;
223         if ( isIncludeExcludeUsed() )
224         {
225             ncssExecuter = new NcssExecuter( scanForSources(), buildOutputFileName() );
226         }
227         else
228         {
229             ncssExecuter = new NcssExecuter( sourceDirectory, buildOutputFileName() );
230         }
231         ncssExecuter.setEncoding( getSourceEncoding() ); // in case of null value, JavaNCSS uses platform encoding, as
232         // expected
233 
234         ncssExecuter.execute();
235         if ( !isTempReportGenerated() )
236         {
237             throw new MavenReportException( "Can't process temp ncss xml file." );
238         }
239         // parse the freshly generated file and write the report
240         NcssReportGenerator reportGenerator =
241             new NcssReportGenerator( getSink(), getBundle( locale ), getLog(), constructXRefLocation() );
242         reportGenerator.doReport( loadDocument(), lineThreshold );
243     }
244 
245     /**
246      * Load the xml file generated by javancss.
247      */
248     private Document loadDocument( File file )
249         throws MavenReportException
250     {
251         try
252         {
253             SAXReader saxReader = new SAXReader();
254             return saxReader.read( ReaderFactory.newXmlReader( file ) );
255         }
256         catch ( DocumentException de )
257         {
258             throw new MavenReportException( de.getMessage(), de );
259         }
260         catch ( IOException ioe )
261         {
262             throw new MavenReportException( ioe.getMessage(), ioe );
263         }
264     }
265 
266     private Document loadDocument()
267         throws MavenReportException
268     {
269         return loadDocument( new File( buildOutputFileName() ) );
270     }
271 
272     /**
273      * Check that the expected temporary file generated by JavaNCSS exists.
274      *
275      * @return <code>true</code> if the temporary report exists, <code>false</code> otherwise.
276      */
277     private boolean isTempReportGenerated()
278     {
279         return new File( buildOutputFileName() ).exists();
280     }
281 
282     /**
283      * @see org.apache.maven.reporting.MavenReport#canGenerateReport()
284      */
285     public boolean canGenerateReport()
286     {
287         return !skip && ( canGenerateSingleReport() || canGenerateAggregateReport() );
288     }
289 
290     private boolean canGenerateAggregateReport()
291     {
292         if ( project.getModules().size() == 0 )
293         {
294             // no child modules
295             return false;
296         }
297         if ( sourceDirectory != null && sourceDirectory.exists() )
298         {
299             // only non-source projects can aggregate
300             String[] sources = scanForSources();
301             return !( ( sources != null ) && ( sources.length > 0 ) );
302         }
303         return true;
304     }
305 
306     private boolean canGenerateSingleReport()
307     {
308         if ( sourceDirectory == null || !sourceDirectory.exists() )
309         {
310             return false;
311         }
312         // now that we know we have a valid existing source directory
313         // we check if any *.java files are existing.
314         String[] sources = scanForSources();
315         return ( sources != null ) && ( sources.length > 0 );
316     }
317 
318     /**
319      * gets a list of all files in the source directory.
320      *
321      * @return the list of all files in the source directory;
322      */
323     private String[] scanForSources()
324     {
325         String[] defaultIncludes = { "**\\*.java" };
326         DirectoryScanner ds = new DirectoryScanner();
327         if ( includes == null )
328         {
329             ds.setIncludes( defaultIncludes );
330         }
331         else
332         {
333             ds.setIncludes( includes );
334         }
335         if ( excludes != null )
336         {
337             ds.setExcludes( excludes );
338         }
339         ds.setBasedir( sourceDirectory );
340         getLog().debug( "Scanning base directory " + sourceDirectory );
341         ds.scan();
342         int maxFiles = ds.getIncludedFiles().length;
343         String[] result = new String[maxFiles];
344         for ( int i = 0; i < maxFiles; i++ )
345         {
346             result[i] = sourceDirectory + File.separator + ds.getIncludedFiles()[i];
347         }
348         return result;
349     }
350 
351     /**
352      * Build a path for the output filename.
353      *
354      * @return A String representation of the output filename.
355      */
356     /* package */String buildOutputFileName()
357     {
358         return getXmlOutputDirectory() + File.separator + tempFileName;
359     }
360 
361     /**
362      * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
363      */
364     public String getName( Locale locale )
365     {
366         return getBundle( locale ).getString( "report.javancss.name" );
367     }
368 
369     /**
370      * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
371      */
372     public String getDescription( Locale locale )
373     {
374         return getBundle( locale ).getString( "report.javancss.description" );
375     }
376 
377     protected String getXmlOutputDirectory()
378     {
379         return xmlOutputDirectory.getAbsolutePath();
380     }
381 
382     /**
383      * @see org.apache.maven.reporting.MavenReport#getOutputName()
384      */
385     public String getOutputName()
386     {
387         return OUTPUT_NAME;
388     }
389 
390     /**
391      * Getter for the source directory
392      *
393      * @return the source directory as a File object.
394      */
395     protected File getSourceDirectory()
396     {
397         return sourceDirectory;
398     }
399 
400     // helper to retrieve the right bundle
401     private static ResourceBundle getBundle( Locale locale )
402     {
403         return ResourceBundle.getBundle( "javancss-report", locale, NcssReportMojo.class.getClassLoader() );
404     }
405 
406     // blatantly copied from maven pmd plugin
407     protected String constructXRefLocation()
408     {
409         String location = null;
410         if ( linkXRef )
411         {
412             String relativePath =
413                 PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLocation.getAbsolutePath() );
414             if ( StringUtils.isEmpty( relativePath ) )
415             {
416                 relativePath = ".";
417             }
418             relativePath = relativePath + "/" + xrefLocation.getName();
419             if ( xrefLocation.exists() )
420             {
421                 // XRef was already generated by manual execution of a lifecycle binding
422                 location = relativePath;
423             }
424             else
425             {
426                 // Not yet generated - check if the report is on its way
427                 for ( ReportPlugin plugin : (List<ReportPlugin>) project.getReportPlugins() )
428                 {
429                     String artifactId = plugin.getArtifactId();
430                     if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
431                     {
432                         location = relativePath;
433                     }
434                 }
435             }
436 
437             if ( location == null )
438             {
439                 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
440             }
441         }
442         return location;
443     }
444 }