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