1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.ant;
5
6 import java.io.IOException;
7 import java.io.File;
8 import java.io.PrintWriter;
9 import java.io.StringWriter;
10 import java.io.Writer;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Iterator;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.concurrent.atomic.AtomicInteger;
17 import java.util.logging.Handler;
18 import java.util.logging.Level;
19
20 import net.sourceforge.pmd.DataSource;
21 import net.sourceforge.pmd.FileDataSource;
22 import net.sourceforge.pmd.PMD;
23 import net.sourceforge.pmd.Report;
24 import net.sourceforge.pmd.Rule;
25 import net.sourceforge.pmd.RuleContext;
26 import net.sourceforge.pmd.RuleSet;
27 import net.sourceforge.pmd.RuleSetFactory;
28 import net.sourceforge.pmd.RuleSetNotFoundException;
29 import net.sourceforge.pmd.RuleSets;
30 import net.sourceforge.pmd.SimpleRuleSetNameMapper;
31 import net.sourceforge.pmd.SourceType;
32 import net.sourceforge.pmd.renderers.AbstractRenderer;
33 import net.sourceforge.pmd.renderers.Renderer;
34 import net.sourceforge.pmd.ScopedLogHandlersManager;
35 import net.sourceforge.pmd.util.AntLogHandler;
36 import net.sourceforge.pmd.util.ClasspathClassLoader;
37
38 import org.apache.tools.ant.AntClassLoader;
39 import org.apache.tools.ant.BuildException;
40 import org.apache.tools.ant.DirectoryScanner;
41 import org.apache.tools.ant.Project;
42 import org.apache.tools.ant.Task;
43 import org.apache.tools.ant.types.FileSet;
44 import org.apache.tools.ant.types.Path;
45 import org.apache.tools.ant.types.Reference;
46
47 public class PMDTask extends Task {
48
49 private Path classpath;
50 private Path auxClasspath;
51 private List<Formatter> formatters = new ArrayList<Formatter>();
52 private List<FileSet> filesets = new ArrayList<FileSet>();
53 private int minPriority = Rule.LOWEST_PRIORITY;
54 private boolean shortFilenames;
55 private String ruleSetFiles;
56 private String encoding = System.getProperty("file.encoding");
57 private boolean failOnError;
58 private boolean failOnRuleViolation;
59 private int maxRuleViolations = 0;
60 private String targetJDK = "1.5";
61 private String failuresPropertyName;
62 private String excludeMarker = PMD.EXCLUDE_MARKER;
63 private int cpus = Runtime.getRuntime().availableProcessors();
64 private final Collection<RuleSetWrapper> nestedRules = new ArrayList<RuleSetWrapper>();
65
66 public void setShortFilenames(boolean value) {
67 this.shortFilenames = value;
68 }
69
70 public void setTargetJDK(String value) {
71 this.targetJDK = value;
72 }
73
74 public void setExcludeMarker(String value) {
75 this.excludeMarker = value;
76 }
77
78 public void setFailOnError(boolean fail) {
79 this.failOnError = fail;
80 }
81
82 public void setFailOnRuleViolation(boolean fail) {
83 this.failOnRuleViolation = fail;
84 }
85
86 public void setMaxRuleViolations(int max) {
87 if (max >= 0) {
88 this.maxRuleViolations = max;
89 this.failOnRuleViolation = true;
90 }
91 }
92
93
94 public void setRuleSetFiles(String ruleSetFiles) {
95 this.ruleSetFiles = ruleSetFiles;
96 }
97
98 public void setEncoding(String encoding) {
99 this.encoding = encoding;
100 }
101
102 public void setCpus(int cpus) {
103 this.cpus = cpus;
104 }
105
106 public void setFailuresPropertyName(String failuresPropertyName) {
107 this.failuresPropertyName = failuresPropertyName;
108 }
109
110 public void setMinimumPriority(int minPriority) {
111 this.minPriority = minPriority;
112 }
113
114 public void addFileset(FileSet set) {
115 filesets.add(set);
116 }
117
118 public void addFormatter(Formatter f) {
119 formatters.add(f);
120 }
121
122 public void setClasspath(Path classpath) {
123 this.classpath = classpath;
124 }
125
126 public Path getClasspath() {
127 return classpath;
128 }
129
130 public Path createClasspath() {
131 if (classpath == null) {
132 classpath = new Path(getProject());
133 }
134 return classpath.createPath();
135 }
136
137 public void setClasspathRef(Reference r) {
138 createLongClasspath().setRefid(r);
139 }
140
141 public void setAuxClasspath(Path auxClasspath) {
142 this.auxClasspath = auxClasspath;
143 }
144
145 public Path getAuxClasspath() {
146 return auxClasspath;
147 }
148
149 public Path createAuxClasspath() {
150 if (auxClasspath == null) {
151 auxClasspath = new Path(getProject());
152 }
153 return auxClasspath.createPath();
154 }
155
156 public void setAuxClasspathRef(Reference r) {
157 createLongAuxClasspath().setRefid(r);
158 }
159
160 private class AntTaskNameMapper extends SimpleRuleSetNameMapper {
161 public AntTaskNameMapper(String s) {
162 super(s);
163 }
164
165 protected void check(String name) {
166 if (name.indexOf("rulesets") == -1 && nameMap.containsKey(name)) {
167 append(nameMap.get(name));
168 } else {
169
170 append(getProject().replaceProperties(name));
171 }
172 }
173
174 }
175
176 private void doTask(){
177 ruleSetFiles = new AntTaskNameMapper(ruleSetFiles).getRuleSets();
178
179 ClassLoader cl;
180 if (classpath == null) {
181 log("Using the normal ClassLoader", Project.MSG_VERBOSE);
182 cl = getClass().getClassLoader();
183 } else {
184 log("Using the AntClassLoader", Project.MSG_VERBOSE);
185 cl = new AntClassLoader(getProject(), classpath);
186 }
187
188
189
190
191
192 String extraPath = getProject().getBaseDir().toString();
193
194 if (auxClasspath != null) {
195 log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
196 extraPath = auxClasspath.toString() + File.pathSeparator + extraPath;
197 }
198
199 try {
200 cl = new ClasspathClassLoader(extraPath, cl);
201 } catch (IOException ioe) {
202 throw new BuildException(ioe.getMessage());
203 }
204
205 final ClassLoader classLoader = cl;
206 RuleSetFactory ruleSetFactory = new RuleSetFactory() {
207 public RuleSets createRuleSets(String ruleSetFileNames) throws RuleSetNotFoundException {
208 return createRuleSets(ruleSetFiles, classLoader);
209 }
210 };
211 for (Formatter formatter: formatters) {
212 log("Sending a report to " + formatter, Project.MSG_VERBOSE);
213 formatter.start(getProject().getBaseDir().toString());
214 }
215
216 try {
217
218 RuleSets rules;
219 ruleSetFactory.setMinimumPriority(minPriority);
220 rules = ruleSetFactory.createRuleSets(ruleSetFiles, classLoader);
221 logRulesUsed(rules);
222 } catch (RuleSetNotFoundException e) {
223 throw new BuildException(e.getMessage());
224 }
225
226 SourceType sourceType;
227 if (targetJDK.equals("1.3")) {
228 log("Targeting Java language version 1.3", Project.MSG_VERBOSE);
229 sourceType = SourceType.JAVA_13;
230 } else if (targetJDK.equals("1.5")) {
231 log("Targeting Java language version 1.5", Project.MSG_VERBOSE);
232 sourceType = SourceType.JAVA_15;
233 } else if (targetJDK.equals("1.6")) {
234 log("Targeting Java language version 1.6", Project.MSG_VERBOSE);
235 sourceType = SourceType.JAVA_16;
236 } else if (targetJDK.equals("1.7")) {
237 log("Targeting Java language version 1.7", Project.MSG_VERBOSE);
238 sourceType = SourceType.JAVA_17;
239 } else if(targetJDK.equals("jsp")){
240 log("Targeting JSP", Project.MSG_VERBOSE);
241 sourceType = SourceType.JSP;
242 } else {
243 log("Targeting Java language version 1.4", Project.MSG_VERBOSE);
244 sourceType = SourceType.JAVA_14;
245 }
246
247 if (excludeMarker != null) {
248 log("Setting exclude marker to be " + excludeMarker, Project.MSG_VERBOSE);
249 }
250
251 RuleContext ctx = new RuleContext();
252 Report errorReport = new Report();
253 final AtomicInteger reportSize = new AtomicInteger();
254 for (FileSet fs: filesets) {
255 List<DataSource> files = new LinkedList<DataSource>();
256 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
257 String[] srcFiles = ds.getIncludedFiles();
258 for (int j = 0; j < srcFiles.length; j++) {
259 File file = new File(ds.getBasedir() + System.getProperty("file.separator") + srcFiles[j]);
260 files.add(new FileDataSource(file));
261 }
262
263 final String inputPath = ds.getBasedir().getPath();
264
265 Renderer logRenderer = new AbstractRenderer() {
266 public void start() {}
267
268 public void startFileAnalysis(DataSource dataSource) {
269 log("Processing file " + dataSource.getNiceFileName(false, inputPath), Project.MSG_VERBOSE);
270 }
271
272 public void renderFileReport(Report r) {
273 int size = r.size();
274 if (size > 0) {
275 reportSize.addAndGet(size);
276 }
277 }
278
279 public void end() {}
280
281 public void render(Writer writer, Report r) {}
282 };
283 List<Renderer> renderers = new LinkedList<Renderer>();
284 renderers.add(logRenderer);
285 for (Formatter formatter: formatters) {
286 renderers.add(formatter.getRenderer());
287 }
288 try {
289 PMD.processFiles(cpus, ruleSetFactory, sourceType, files, ctx,
290 renderers, ruleSetFiles,
291 shortFilenames, inputPath,
292 encoding, excludeMarker, classLoader);
293 } catch (RuntimeException pmde) {
294 pmde.printStackTrace();
295 log(pmde.toString(), Project.MSG_VERBOSE);
296 if (pmde.getCause() != null) {
297 StringWriter strWriter = new StringWriter();
298 PrintWriter printWriter = new PrintWriter(strWriter);
299 pmde.getCause().printStackTrace(printWriter);
300 log(strWriter.toString(), Project.MSG_VERBOSE);
301 }
302 if (pmde.getCause() != null && pmde.getCause().getMessage() != null) {
303 log(pmde.getCause().getMessage(), Project.MSG_VERBOSE);
304 }
305 if (failOnError) {
306 throw new BuildException(pmde);
307 }
308 errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename()));
309 }
310 }
311
312 int problemCount = reportSize.get();
313 log(problemCount + " problems found", Project.MSG_VERBOSE);
314
315 for (Formatter formatter: formatters) {
316 formatter.end(errorReport);
317 }
318
319 if (failuresPropertyName != null && problemCount > 0) {
320 getProject().setProperty(failuresPropertyName, String.valueOf(problemCount));
321 log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE);
322 }
323
324 if (failOnRuleViolation && problemCount > maxRuleViolations) {
325 throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code");
326 }
327 }
328
329 public void execute() throws BuildException {
330 validate();
331 final Handler antLogHandler = new AntLogHandler(this);
332 final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST,antLogHandler);
333 try{
334 doTask();
335 }finally{
336 logManager.close();
337 }
338 }
339
340 private void logRulesUsed(RuleSets rules) {
341 log("Using these rulesets: " + ruleSetFiles, Project.MSG_VERBOSE);
342
343 RuleSet[] ruleSets = rules.getAllRuleSets();
344 for (int j = 0; j < ruleSets.length; j++) {
345 RuleSet ruleSet = ruleSets[j];
346
347 for (Rule rule: ruleSet.getRules()) {
348 log("Using rule " + rule.getName(), Project.MSG_VERBOSE);
349 }
350 }
351 }
352
353 private void validate() throws BuildException {
354
355 for (Formatter f: formatters) {
356 if (f.isNoOutputSupplied()) {
357 throw new BuildException("toFile or toConsole needs to be specified in Formatter");
358 }
359 }
360
361 if (ruleSetFiles == null) {
362 if (nestedRules.isEmpty()) {
363 throw new BuildException("No rulesets specified");
364 }
365 ruleSetFiles = getNestedRuleSetFiles();
366 }
367
368 if (!targetJDK.equals("1.3") && !targetJDK.equals("1.4") && !targetJDK.equals("1.5") && !targetJDK.equals("1.6") && !targetJDK.equals("1.7") && !targetJDK.equals("jsp")) {
369 throw new BuildException("The targetjdk attribute, if used, must be set to either '1.3', '1.4', '1.5', '1.6', '1.7' or 'jsp'");
370 }
371 }
372
373 private String getNestedRuleSetFiles() {
374 final StringBuffer sb = new StringBuffer();
375 for (Iterator<RuleSetWrapper> it = nestedRules.iterator(); it.hasNext();) {
376 RuleSetWrapper rs = it.next();
377 sb.append(rs.getFile());
378 if (it.hasNext()) {
379 sb.append(',');
380 }
381 }
382 return sb.toString();
383 }
384
385 private Path createLongClasspath() {
386 if (classpath == null) {
387 classpath = new Path(getProject());
388 }
389 return classpath.createPath();
390 }
391
392 private Path createLongAuxClasspath() {
393 if (auxClasspath == null) {
394 auxClasspath = new Path(getProject());
395 }
396 return auxClasspath.createPath();
397 }
398
399 public void addRuleset(RuleSetWrapper r) {
400 nestedRules.add(r);
401 }
402
403 }