1 package org.apache.maven.plugin.pmd;
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.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.TreeMap;
34
35 import net.sourceforge.pmd.PMD;
36
37 import org.apache.maven.doxia.siterenderer.Renderer;
38 import org.apache.maven.model.ReportPlugin;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.reporting.AbstractMavenReport;
41 import org.codehaus.plexus.util.FileUtils;
42 import org.codehaus.plexus.util.PathTool;
43 import org.codehaus.plexus.util.ReaderFactory;
44 import org.codehaus.plexus.util.StringUtils;
45
46 /**
47 * Base class for the PMD reports.
48 *
49 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
50 * @version $Id: AbstractPmdReport.html 816688 2012-05-08 15:14:44Z hboutemy $
51 */
52 public abstract class AbstractPmdReport
53 extends AbstractMavenReport
54 {
55 /**
56 * The output directory for the intermediate XML report.
57 *
58 * @parameter expression="${project.build.directory}"
59 * @required
60 */
61 protected File targetDirectory;
62
63 /**
64 * The output directory for the final HTML report. Note that this parameter is only evaluated if the goal is run
65 * directly from the command line or during the default lifecycle. If the goal is run indirectly as part of a site
66 * generation, the output directory configured in the Maven Site Plugin is used instead.
67 *
68 * @parameter expression="${project.reporting.outputDirectory}"
69 * @required
70 */
71 protected File outputDirectory;
72
73 /**
74 * Site rendering component for generating the HTML report.
75 *
76 * @component
77 */
78 private Renderer siteRenderer;
79
80 /**
81 * The project to analyse.
82 *
83 * @parameter expression="${project}"
84 * @required
85 * @readonly
86 */
87 protected MavenProject project;
88
89 /**
90 * Set the output format type, in addition to the HTML report. Must be one of: "none",
91 * "csv", "xml", "txt" or the full class name of the PMD renderer to use.
92 * See the net.sourceforge.pmd.renderers package javadoc for available renderers.
93 * XML is required if the pmd:check goal is being used.
94 *
95 * @parameter expression="${format}" default-value="xml"
96 */
97 protected String format = "xml";
98
99 /**
100 * Link the violation line numbers to the source xref. Links will be created
101 * automatically if the jxr plugin is being used.
102 *
103 * @parameter expression="${linkXRef}" default-value="true"
104 */
105 private boolean linkXRef;
106
107 /**
108 * Location of the Xrefs to link to.
109 *
110 * @parameter default-value="${project.reporting.outputDirectory}/xref"
111 */
112 private File xrefLocation;
113
114 /**
115 * Location of the Test Xrefs to link to.
116 *
117 * @parameter default-value="${project.reporting.outputDirectory}/xref-test"
118 */
119 private File xrefTestLocation;
120
121 /**
122 * A list of files to exclude from checking. Can contain Ant-style wildcards and double wildcards. Note that these
123 * exclusion patterns only operate on the path of a source file relative to its source root directory. In other
124 * words, files are excluded based on their package and/or class name. If you want to exclude entire source root
125 * directories, use the parameter <code>excludeRoots</code> instead.
126 *
127 * @parameter
128 * @since 2.2
129 */
130 private String[] excludes;
131
132 /**
133 * A list of files to include from checking. Can contain Ant-style wildcards and double wildcards.
134 * Defaults to **\/*.java.
135 *
136 * @parameter
137 * @since 2.2
138 */
139 private String[] includes;
140
141 /**
142 * The directories containing the sources to be compiled.
143 *
144 * @parameter expression="${project.compileSourceRoots}"
145 * @required
146 * @readonly
147 */
148 private List compileSourceRoots;
149
150 /**
151 * The directories containing the test-sources to be compiled.
152 *
153 * @parameter expression="${project.testCompileSourceRoots}"
154 * @required
155 * @readonly
156 */
157 private List testSourceRoots;
158
159 /**
160 * The project source directories that should be excluded.
161 *
162 * @parameter
163 * @since 2.2
164 */
165 private File[] excludeRoots;
166
167 /**
168 * Run PMD on the tests.
169 *
170 * @parameter default-value="false"
171 * @since 2.2
172 */
173 protected boolean includeTests;
174
175 /**
176 * Whether to build an aggregated report at the root, or build individual reports.
177 *
178 * @parameter expression="${aggregate}" default-value="false"
179 * @since 2.2
180 */
181 protected boolean aggregate;
182
183 /**
184 * The file encoding to use when reading the Java sources.
185 *
186 * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
187 * @since 2.3
188 */
189 private String sourceEncoding;
190
191 /**
192 * The file encoding when writing non-HTML reports.
193 *
194 * @parameter expression="${outputEncoding}" default-value="${project.reporting.outputEncoding}"
195 * @since 2.5
196 */
197 private String outputEncoding;
198
199 /**
200 * The projects in the reactor for aggregation report.
201 *
202 * @parameter expression="${reactorProjects}"
203 * @readonly
204 */
205 protected List reactorProjects;
206
207 /** {@inheritDoc} */
208 protected MavenProject getProject()
209 {
210 return project;
211 }
212
213 /** {@inheritDoc} */
214 protected Renderer getSiteRenderer()
215 {
216 return siteRenderer;
217 }
218
219 protected String constructXRefLocation( boolean test )
220 {
221 String location = null;
222 if ( linkXRef )
223 {
224 File xrefLoc = test ? xrefTestLocation : xrefLocation;
225
226 String relativePath = PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLoc.getAbsolutePath() );
227 if ( StringUtils.isEmpty( relativePath ) )
228 {
229 relativePath = ".";
230 }
231 relativePath = relativePath + "/" + xrefLoc.getName();
232 if ( xrefLoc.exists() )
233 {
234 // XRef was already generated by manual execution of a lifecycle binding
235 location = relativePath;
236 }
237 else
238 {
239 // Not yet generated - check if the report is on its way
240 for ( Iterator reports = project.getReportPlugins().iterator(); reports.hasNext(); )
241 {
242 ReportPlugin plugin = (ReportPlugin) reports.next();
243
244 String artifactId = plugin.getArtifactId();
245 if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
246 {
247 location = relativePath;
248 }
249 }
250 }
251
252 if ( location == null )
253 {
254 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
255 }
256 }
257 return location;
258 }
259
260 /**
261 * Convenience method to get the list of files where the PMD tool will be executed
262 *
263 * @return a List of the files where the PMD tool will be executed
264 * @throws java.io.IOException
265 */
266 protected Map getFilesToProcess()
267 throws IOException
268 {
269 String sourceXref = constructXRefLocation( false );
270 String testXref = includeTests ? constructXRefLocation( true ) : "";
271
272 if ( aggregate && !project.isExecutionRoot() )
273 {
274 return Collections.EMPTY_MAP;
275 }
276
277 if ( excludeRoots == null )
278 {
279 excludeRoots = new File[0];
280 }
281 Collection excludeRootFiles = new HashSet( excludeRoots.length );
282
283 for ( int i = 0; i < excludeRoots.length; i++ )
284 {
285 File file = excludeRoots[i];
286 if ( file.isDirectory() )
287 {
288 excludeRootFiles.add( file );
289 }
290 }
291
292 List directories = new ArrayList();
293
294 for ( Iterator i = compileSourceRoots.iterator(); i.hasNext(); )
295 {
296 String root = (String) i.next();
297 File sroot = new File( root );
298 directories.add( new PmdFileInfo( project, sroot, sourceXref ) );
299 }
300
301 if ( includeTests )
302 {
303 for ( Iterator i = testSourceRoots.iterator(); i.hasNext(); )
304 {
305 String root = (String) i.next();
306 File sroot = new File( root );
307 directories.add( new PmdFileInfo( project, sroot, testXref ) );
308 }
309 }
310 if ( aggregate )
311 {
312 for ( Iterator i = reactorProjects.iterator(); i.hasNext(); )
313 {
314 MavenProject localProject = (MavenProject) i.next();
315 for ( Iterator i2 = localProject.getCompileSourceRoots().iterator(); i2.hasNext(); )
316 {
317 String root = (String) i2.next();
318 File sroot = new File( root );
319 directories.add( new PmdFileInfo( localProject, sroot, sourceXref ) );
320 }
321 if ( includeTests )
322 {
323 for ( Iterator i2 = localProject.getTestCompileSourceRoots().iterator(); i2.hasNext(); )
324 {
325 String root = (String) i2.next();
326 File sroot = new File( root );
327 directories.add( new PmdFileInfo( localProject, sroot, testXref ) );
328 }
329 }
330 }
331
332 }
333
334 String excluding = getExcludes();
335 getLog().debug( "Exclusions: " + excluding );
336 String including = getIncludes();
337 getLog().debug( "Inclusions: " + including );
338
339 Map files = new TreeMap();
340
341 for ( Iterator it = directories.iterator(); it.hasNext(); )
342 {
343 PmdFileInfo finfo = (PmdFileInfo) it.next();
344 File sourceDirectory = finfo.getSourceDirectory();
345 if ( sourceDirectory.isDirectory() && !excludeRootFiles.contains( sourceDirectory ) )
346 {
347 List newfiles = FileUtils.getFiles( sourceDirectory, including, excluding );
348 for ( Iterator it2 = newfiles.iterator(); it2.hasNext(); )
349 {
350 files.put( it2.next(), finfo );
351 }
352 }
353 }
354
355 return files;
356 }
357
358 /**
359 * Gets the comma separated list of effective include patterns.
360 *
361 * @return The comma separated list of effective include patterns, never <code>null</code>.
362 */
363 private String getIncludes()
364 {
365 Collection patterns = new LinkedHashSet();
366 if ( includes != null )
367 {
368 patterns.addAll( Arrays.asList( includes ) );
369 }
370 if ( patterns.isEmpty() )
371 {
372 patterns.add( "**/*.java" );
373 }
374 return StringUtils.join( patterns.iterator(), "," );
375 }
376
377 /**
378 * Gets the comma separated list of effective exclude patterns.
379 *
380 * @return The comma separated list of effective exclude patterns, never <code>null</code>.
381 */
382 private String getExcludes()
383 {
384 Collection patterns = new LinkedHashSet( FileUtils.getDefaultExcludesAsList() );
385 if ( excludes != null )
386 {
387 patterns.addAll( Arrays.asList( excludes ) );
388 }
389 return StringUtils.join( patterns.iterator(), "," );
390 }
391
392 protected boolean isHtml()
393 {
394 return "html".equals( format );
395 }
396
397 /** {@inheritDoc} */
398 public boolean canGenerateReport()
399 {
400 if ( aggregate && !project.isExecutionRoot() )
401 {
402 return false;
403 }
404
405 if ( "pom".equals( project.getPackaging() ) && !aggregate )
406 {
407 return false;
408 }
409
410 // if format is XML, we need to output it even if the file list is empty
411 // so the "check" goals can check for failures
412 if ( "xml".equals( format ) )
413 {
414 return true;
415 }
416 try
417 {
418 Map filesToProcess = getFilesToProcess();
419 if ( filesToProcess.isEmpty() )
420 {
421 return false;
422 }
423 }
424 catch ( IOException e )
425 {
426 getLog().error( e );
427 }
428 return true;
429 }
430
431 /** {@inheritDoc} */
432 protected String getOutputDirectory()
433 {
434 return outputDirectory.getAbsolutePath();
435 }
436
437 protected String getSourceEncoding()
438 {
439 return sourceEncoding;
440 }
441
442 /**
443 * Gets the effective reporting output files encoding.
444 *
445 * @return The effective reporting output file encoding, never <code>null</code>.
446 * @since 2.5
447 */
448 protected String getOutputEncoding()
449 {
450 return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
451 }
452
453 static String getPmdVersion()
454 {
455 try
456 {
457 return (String) PMD.class.getField( "VERSION" ).get( null );
458 }
459 catch ( IllegalAccessException e )
460 {
461 throw new RuntimeException( "PMD VERSION field not accessible", e );
462 }
463 catch ( NoSuchFieldException e )
464 {
465 throw new RuntimeException( "PMD VERSION field not found", e );
466 }
467 }
468 }