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