1
2
3
4 package net.sourceforge.pmd.ant;
5
6 import java.io.File;
7 import java.io.IOException;
8 import java.io.PrintWriter;
9 import java.io.StringWriter;
10 import java.util.ArrayList;
11 import java.util.Collection;
12 import java.util.Iterator;
13 import java.util.LinkedList;
14 import java.util.List;
15 import java.util.concurrent.atomic.AtomicInteger;
16 import java.util.logging.Handler;
17 import java.util.logging.Level;
18
19 import net.sourceforge.pmd.PMDConfiguration;
20 import net.sourceforge.pmd.PMD;
21 import net.sourceforge.pmd.Report;
22 import net.sourceforge.pmd.Rule;
23 import net.sourceforge.pmd.RuleContext;
24 import net.sourceforge.pmd.RulePriority;
25 import net.sourceforge.pmd.RuleSet;
26 import net.sourceforge.pmd.RuleSetFactory;
27 import net.sourceforge.pmd.RuleSetNotFoundException;
28 import net.sourceforge.pmd.RuleSets;
29 import net.sourceforge.pmd.lang.Language;
30 import net.sourceforge.pmd.lang.LanguageVersion;
31 import net.sourceforge.pmd.renderers.AbstractRenderer;
32 import net.sourceforge.pmd.renderers.Renderer;
33 import net.sourceforge.pmd.util.IOUtil;
34 import net.sourceforge.pmd.util.StringUtil;
35 import net.sourceforge.pmd.util.datasource.DataSource;
36 import net.sourceforge.pmd.util.datasource.FileDataSource;
37 import net.sourceforge.pmd.util.log.AntLogHandler;
38 import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
39
40 import org.apache.tools.ant.AntClassLoader;
41 import org.apache.tools.ant.BuildException;
42 import org.apache.tools.ant.DirectoryScanner;
43 import org.apache.tools.ant.Project;
44 import org.apache.tools.ant.Task;
45 import org.apache.tools.ant.types.FileSet;
46 import org.apache.tools.ant.types.Path;
47 import org.apache.tools.ant.types.Reference;
48
49 public class PMDTask extends Task {
50
51 private Path classpath;
52 private Path auxClasspath;
53 private final List<Formatter> formatters = new ArrayList<Formatter>();
54 private final List<FileSet> filesets = new ArrayList<FileSet>();
55 private final PMDConfiguration configuration = new PMDConfiguration();
56 private boolean failOnError;
57 private boolean failOnRuleViolation;
58 private int maxRuleViolations = 0;
59 private String failuresPropertyName;
60 private final Collection<RuleSetWrapper> nestedRules = new ArrayList<RuleSetWrapper>();
61
62 public void setShortFilenames(boolean reportShortNames) {
63 configuration.setReportShortNames(reportShortNames);
64 }
65
66 public void setSuppressMarker(String suppressMarker) {
67 configuration.setSuppressMarker(suppressMarker);
68 }
69
70 public void setFailOnError(boolean fail) {
71 this.failOnError = fail;
72 }
73
74 public void setFailOnRuleViolation(boolean fail) {
75 this.failOnRuleViolation = fail;
76 }
77
78 public void setMaxRuleViolations(int max) {
79 if (max >= 0) {
80 this.maxRuleViolations = max;
81 this.failOnRuleViolation = true;
82 }
83 }
84
85 public void setRuleSetFiles(String ruleSets) {
86 configuration.setRuleSets(ruleSets);
87 }
88
89 public void setEncoding(String sourceEncoding) {
90 configuration.setSourceEncoding(sourceEncoding);
91 }
92
93 public void setThreads(int threads) {
94 configuration.setThreads(threads);
95 }
96
97 public void setFailuresPropertyName(String failuresPropertyName) {
98 this.failuresPropertyName = failuresPropertyName;
99 }
100
101 public void setMinimumPriority(int minPriority) {
102 configuration.setMinimumPriority(RulePriority.valueOf(minPriority));
103 }
104
105 public void addFileset(FileSet set) {
106 filesets.add(set);
107 }
108
109 public void addFormatter(Formatter f) {
110 formatters.add(f);
111 }
112
113 public void addConfiguredVersion(Version version) {
114 LanguageVersion languageVersion = LanguageVersion.findByTerseName(version.getTerseName());
115 if (languageVersion == null) {
116 StringBuilder buf = new StringBuilder("The <version> element, if used, must be one of ");
117 boolean first = true;
118 for (Language language : Language.values()) {
119 if (language.getVersions().size() > 2) {
120 for (LanguageVersion v : language.getVersions()) {
121 if (!first) {
122 buf.append(", ");
123 }
124 buf.append('\'').append(v.getTerseName()).append('\'');
125 first = false;
126 }
127 }
128 }
129 buf.append('.');
130 throw new BuildException(buf.toString());
131 }
132 configuration.setDefaultLanguageVersion(languageVersion);
133 }
134
135 public void setClasspath(Path classpath) {
136 this.classpath = classpath;
137 }
138
139 public Path getClasspath() {
140 return classpath;
141 }
142
143 public Path createClasspath() {
144 if (classpath == null) {
145 classpath = new Path(getProject());
146 }
147 return classpath.createPath();
148 }
149
150 public void setClasspathRef(Reference r) {
151 createClasspath().setRefid(r);
152 }
153
154 public void setAuxClasspath(Path auxClasspath) {
155 this.auxClasspath = auxClasspath;
156 }
157
158 public Path getAuxClasspath() {
159 return auxClasspath;
160 }
161
162 public Path createAuxClasspath() {
163 if (auxClasspath == null) {
164 auxClasspath = new Path(getProject());
165 }
166 return auxClasspath.createPath();
167 }
168
169 public void setAuxClasspathRef(Reference r) {
170 createAuxClasspath().setRefid(r);
171 }
172
173 private void doTask() {
174 setupClassLoader();
175
176
177 RuleSetFactory ruleSetFactory = new RuleSetFactory();
178 ruleSetFactory.setClassLoader(configuration.getClassLoader());
179 try {
180
181 ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
182 ruleSetFactory.setWarnDeprecated(true);
183 String ruleSets = configuration.getRuleSets();
184 if (StringUtil.isNotEmpty(ruleSets)) {
185
186 configuration.setRuleSets(getProject().replaceProperties(ruleSets));
187 }
188 RuleSets rules = ruleSetFactory.createRuleSets(configuration.getRuleSets());
189 ruleSetFactory.setWarnDeprecated(false);
190 logRulesUsed(rules);
191 } catch (RuleSetNotFoundException e) {
192 throw new BuildException(e.getMessage(), e);
193 }
194
195 if (configuration.getSuppressMarker() != null) {
196 log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE);
197 }
198
199
200 for (Formatter formatter : formatters) {
201 log("Sending a report to " + formatter, Project.MSG_VERBOSE);
202 formatter.start(getProject().getBaseDir().toString());
203 }
204
205
206
207
208 RuleContext ctx = new RuleContext();
209 Report errorReport = new Report();
210 final AtomicInteger reportSize = new AtomicInteger();
211 final String separator = System.getProperty("file.separator");
212
213 for (FileSet fs : filesets) {
214 List<DataSource> files = new LinkedList<DataSource>();
215 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
216 String[] srcFiles = ds.getIncludedFiles();
217 for (String srcFile : srcFiles) {
218 File file = new File(ds.getBasedir() + separator + srcFile);
219 files.add(new FileDataSource(file));
220 }
221
222 final String inputPaths = ds.getBasedir().getPath();
223 configuration.setInputPaths(inputPaths);
224
225 Renderer logRenderer = new AbstractRenderer("log", "Logging renderer") {
226 public void start() {
227
228 }
229
230 public void startFileAnalysis(DataSource dataSource) {
231 log("Processing file " + dataSource.getNiceFileName(false, inputPaths), Project.MSG_VERBOSE);
232 }
233
234 public void renderFileReport(Report r) {
235 int size = r.size();
236 if (size > 0) {
237 reportSize.addAndGet(size);
238 }
239 }
240
241 public void end() {
242
243 }
244
245 public String defaultFileExtension() { return null; }
246 };
247 List<Renderer> renderers = new LinkedList<Renderer>();
248 renderers.add(logRenderer);
249 for (Formatter formatter : formatters) {
250 renderers.add(formatter.getRenderer());
251 }
252 try {
253 PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers);
254 } catch (RuntimeException pmde) {
255 handleError(ctx, errorReport, pmde);
256 }
257 }
258
259 int problemCount = reportSize.get();
260 log(problemCount + " problems found", Project.MSG_VERBOSE);
261
262 for (Formatter formatter : formatters) {
263 formatter.end(errorReport);
264 }
265
266 if (failuresPropertyName != null && problemCount > 0) {
267 getProject().setProperty(failuresPropertyName, String.valueOf(problemCount));
268 log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE);
269 }
270
271 if (failOnRuleViolation && problemCount > maxRuleViolations) {
272 throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code");
273 }
274 }
275
276 private void handleError(RuleContext ctx, Report errorReport, RuntimeException pmde) {
277
278 pmde.printStackTrace();
279 log(pmde.toString(), Project.MSG_VERBOSE);
280
281 Throwable cause = pmde.getCause();
282
283 if (cause != null) {
284 StringWriter strWriter = new StringWriter();
285 PrintWriter printWriter = new PrintWriter(strWriter);
286 cause.printStackTrace(printWriter);
287 log(strWriter.toString(), Project.MSG_VERBOSE);
288 IOUtil.closeQuietly(printWriter);
289
290 if (StringUtil.isNotEmpty(cause.getMessage())) {
291 log(cause.getMessage(), Project.MSG_VERBOSE);
292 }
293 }
294
295 if (failOnError) {
296 throw new BuildException(pmde);
297 }
298 errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename()));
299 }
300
301 private void setupClassLoader() {
302
303 if (classpath == null) {
304 log("Using the normal ClassLoader", Project.MSG_VERBOSE);
305 } else {
306 log("Using the AntClassLoader", Project.MSG_VERBOSE);
307 configuration.setClassLoader(new AntClassLoader(getProject(), classpath));
308 }
309 try {
310
311
312
313
314
315 configuration.prependClasspath(getProject().getBaseDir().toString());
316 if (auxClasspath != null) {
317 log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
318 configuration.prependClasspath(auxClasspath.toString());
319 }
320 } catch (IOException ioe) {
321 throw new BuildException(ioe.getMessage(), ioe);
322 }
323 }
324
325 @Override
326 public void execute() throws BuildException {
327 validate();
328 final Handler antLogHandler = new AntLogHandler(this);
329 final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST, antLogHandler);
330 try {
331 doTask();
332 } finally {
333 logManager.close();
334 }
335 }
336
337 private void logRulesUsed(RuleSets rules) {
338 log("Using these rulesets: " + configuration.getRuleSets(), Project.MSG_VERBOSE);
339
340 RuleSet[] ruleSets = rules.getAllRuleSets();
341 for (RuleSet ruleSet : ruleSets) {
342 for (Rule rule : ruleSet.getRules()) {
343 log("Using rule " + rule.getName(), Project.MSG_VERBOSE);
344 }
345 }
346 }
347
348 private void validate() throws BuildException {
349 if (formatters.isEmpty()) {
350 Formatter defaultFormatter = new Formatter();
351 defaultFormatter.setType("text");
352 defaultFormatter.setToConsole(true);
353 formatters.add(defaultFormatter);
354 } else {
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
362 if (configuration.getRuleSets() == null) {
363 if (nestedRules.isEmpty()) {
364 throw new BuildException("No rulesets specified");
365 }
366 configuration.setRuleSets(getNestedRuleSetFiles());
367 }
368 }
369
370 private String getNestedRuleSetFiles() {
371 final StringBuilder sb = new StringBuilder();
372 for (Iterator<RuleSetWrapper> it = nestedRules.iterator(); it.hasNext();) {
373 RuleSetWrapper rs = it.next();
374 sb.append(rs.getFile());
375 if (it.hasNext()) {
376 sb.append(',');
377 }
378 }
379 return sb.toString();
380 }
381
382 public void addRuleset(RuleSetWrapper r) {
383 nestedRules.add(r);
384 }
385
386 }