001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.ant;
005
006 import java.io.File;
007 import java.io.IOException;
008 import java.io.PrintWriter;
009 import java.io.StringWriter;
010 import java.util.ArrayList;
011 import java.util.Collection;
012 import java.util.Iterator;
013 import java.util.LinkedList;
014 import java.util.List;
015 import java.util.concurrent.atomic.AtomicInteger;
016 import java.util.logging.Handler;
017 import java.util.logging.Level;
018
019 import net.sourceforge.pmd.Configuration;
020 import net.sourceforge.pmd.PMD;
021 import net.sourceforge.pmd.Report;
022 import net.sourceforge.pmd.Rule;
023 import net.sourceforge.pmd.RuleContext;
024 import net.sourceforge.pmd.RulePriority;
025 import net.sourceforge.pmd.RuleSet;
026 import net.sourceforge.pmd.RuleSetFactory;
027 import net.sourceforge.pmd.RuleSetNotFoundException;
028 import net.sourceforge.pmd.RuleSets;
029 import net.sourceforge.pmd.lang.Language;
030 import net.sourceforge.pmd.lang.LanguageVersion;
031 import net.sourceforge.pmd.renderers.AbstractRenderer;
032 import net.sourceforge.pmd.renderers.Renderer;
033 import net.sourceforge.pmd.util.datasource.DataSource;
034 import net.sourceforge.pmd.util.datasource.FileDataSource;
035 import net.sourceforge.pmd.util.log.AntLogHandler;
036 import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
037
038 import org.apache.tools.ant.AntClassLoader;
039 import org.apache.tools.ant.BuildException;
040 import org.apache.tools.ant.DirectoryScanner;
041 import org.apache.tools.ant.Project;
042 import org.apache.tools.ant.Task;
043 import org.apache.tools.ant.types.FileSet;
044 import org.apache.tools.ant.types.Path;
045 import org.apache.tools.ant.types.Reference;
046
047 public class PMDTask extends Task {
048
049 private Path classpath;
050 private Path auxClasspath;
051 private final List<Formatter> formatters = new ArrayList<Formatter>();
052 private final List<FileSet> filesets = new ArrayList<FileSet>();
053 private final Configuration configuration = new Configuration();
054 private boolean failOnError;
055 private boolean failOnRuleViolation;
056 private int maxRuleViolations = 0;
057 private String failuresPropertyName;
058 private final Collection<RuleSetWrapper> nestedRules = new ArrayList<RuleSetWrapper>();
059
060 public void setShortFilenames(boolean reportShortNames) {
061 configuration.setReportShortNames(reportShortNames);
062 }
063
064 public void setSuppressMarker(String suppressMarker) {
065 configuration.setSuppressMarker(suppressMarker);
066 }
067
068 public void setFailOnError(boolean fail) {
069 this.failOnError = fail;
070 }
071
072 public void setFailOnRuleViolation(boolean fail) {
073 this.failOnRuleViolation = fail;
074 }
075
076 public void setMaxRuleViolations(int max) {
077 if (max >= 0) {
078 this.maxRuleViolations = max;
079 this.failOnRuleViolation = true;
080 }
081 }
082
083 public void setRuleSetFiles(String ruleSets) {
084 configuration.setRuleSets(ruleSets);
085 }
086
087 public void setEncoding(String sourceEncoding) {
088 configuration.setSourceEncoding(sourceEncoding);
089 }
090
091 public void setThreads(int threads) {
092 configuration.setThreads(threads);
093 }
094
095 public void setFailuresPropertyName(String failuresPropertyName) {
096 this.failuresPropertyName = failuresPropertyName;
097 }
098
099 public void setMinimumPriority(int minPriority) {
100 configuration.setMinimumPriority(RulePriority.valueOf(minPriority));
101 }
102
103 public void addFileset(FileSet set) {
104 filesets.add(set);
105 }
106
107 public void addFormatter(Formatter f) {
108 formatters.add(f);
109 }
110
111 public void addConfiguredVersion(Version version) {
112 LanguageVersion languageVersion = LanguageVersion.findByTerseName(version.getTerseName());
113 if (languageVersion == null) {
114 StringBuilder buf = new StringBuilder();
115 buf.append("The <version> element, if used, must be one of ");
116 boolean first = true;
117 for (Language language : Language.values()) {
118 if (language.getVersions().size() > 2) {
119 for (LanguageVersion v : language.getVersions()) {
120 if (!first) {
121 buf.append(", ");
122 }
123 buf.append('\'');
124 buf.append(v.getTerseName());
125 buf.append('\'');
126 first = false;
127 }
128 }
129 }
130 buf.append('.');
131 throw new BuildException(buf.toString());
132 }
133 configuration.setDefaultLanguageVersion(languageVersion);
134 }
135
136 public void setClasspath(Path classpath) {
137 this.classpath = classpath;
138 }
139
140 public Path getClasspath() {
141 return classpath;
142 }
143
144 public Path createClasspath() {
145 if (classpath == null) {
146 classpath = new Path(getProject());
147 }
148 return classpath.createPath();
149 }
150
151 public void setClasspathRef(Reference r) {
152 createClasspath().setRefid(r);
153 }
154
155 public void setAuxClasspath(Path auxClasspath) {
156 this.auxClasspath = auxClasspath;
157 }
158
159 public Path getAuxClasspath() {
160 return auxClasspath;
161 }
162
163 public Path createAuxClasspath() {
164 if (auxClasspath == null) {
165 auxClasspath = new Path(getProject());
166 }
167 return auxClasspath.createPath();
168 }
169
170 public void setAuxClasspathRef(Reference r) {
171 createAuxClasspath().setRefid(r);
172 }
173
174 private void doTask() {
175 // Setup ClassLoader
176 if (classpath == null) {
177 log("Using the normal ClassLoader", Project.MSG_VERBOSE);
178 } else {
179 log("Using the AntClassLoader", Project.MSG_VERBOSE);
180 configuration.setClassLoader(new AntClassLoader(getProject(), classpath));
181 }
182 try {
183 /*
184 * 'basedir' is added to the path to make sure that relative paths
185 * such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
186 * work when ant is invoked from a different directory using "-f"
187 */
188 configuration.prependClasspath(getProject().getBaseDir().toString());
189 if (auxClasspath != null) {
190 log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
191 configuration.prependClasspath(auxClasspath.toString());
192 }
193 } catch (IOException ioe) {
194 throw new BuildException(ioe.getMessage(), ioe);
195 }
196
197 // Setup RuleSetFactory and validate RuleSets
198 RuleSetFactory ruleSetFactory = new RuleSetFactory();
199 ruleSetFactory.setClassLoader(configuration.getClassLoader());
200 try {
201 // This is just used to validate and display rules. Each thread will create its own ruleset
202 RuleSets rules;
203 ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
204 ruleSetFactory.setWarnDeprecated(true);
205 String ruleSets = configuration.getRuleSets();
206 if (ruleSets != null) {
207 // Substitute env variables/properties
208 configuration.setRuleSets(getProject().replaceProperties(ruleSets));
209 }
210 rules = ruleSetFactory.createRuleSets(configuration.getRuleSets());
211 ruleSetFactory.setWarnDeprecated(false);
212 logRulesUsed(rules);
213 } catch (RuleSetNotFoundException e) {
214 throw new BuildException(e.getMessage(), e);
215 }
216
217 if (configuration.getSuppressMarker() != null) {
218 log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE);
219 }
220
221 // Start the Formatters
222 for (Formatter formatter : formatters) {
223 log("Sending a report to " + formatter, Project.MSG_VERBOSE);
224 formatter.start(getProject().getBaseDir().toString());
225 }
226
227 //log("Setting Language Version " + languageVersion.getShortName(), Project.MSG_VERBOSE);
228
229 // TODO Do we really need all this in a loop over each FileSet? Seems like a lot of redundancy
230 RuleContext ctx = new RuleContext();
231 Report errorReport = new Report();
232 final AtomicInteger reportSize = new AtomicInteger();
233 for (FileSet fs : filesets) {
234 List<DataSource> files = new LinkedList<DataSource>();
235 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
236 String[] srcFiles = ds.getIncludedFiles();
237 for (String srcFile : srcFiles) {
238 File file = new File(ds.getBasedir() + System.getProperty("file.separator") + srcFile);
239 files.add(new FileDataSource(file));
240 }
241
242 final String inputPaths = ds.getBasedir().getPath();
243 configuration.setInputPaths(inputPaths);
244
245 Renderer logRenderer = new AbstractRenderer("log", "Logging renderer", null) {
246 public void start() {
247 // Nothing to do
248 }
249
250 public void startFileAnalysis(DataSource dataSource) {
251 log("Processing file " + dataSource.getNiceFileName(false, inputPaths), Project.MSG_VERBOSE);
252 }
253
254 public void renderFileReport(Report r) {
255 int size = r.size();
256 if (size > 0) {
257 reportSize.addAndGet(size);
258 }
259 }
260
261 public void end() {
262 // Nothing to do
263 }
264 };
265 List<Renderer> renderers = new LinkedList<Renderer>();
266 renderers.add(logRenderer);
267 for (Formatter formatter : formatters) {
268 renderers.add(formatter.getRenderer());
269 }
270 try {
271 PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers);
272 } catch (RuntimeException pmde) {
273 pmde.printStackTrace();
274 log(pmde.toString(), Project.MSG_VERBOSE);
275 if (pmde.getCause() != null) {
276 StringWriter strWriter = new StringWriter();
277 PrintWriter printWriter = new PrintWriter(strWriter);
278 pmde.getCause().printStackTrace(printWriter);
279 log(strWriter.toString(), Project.MSG_VERBOSE);
280 }
281 if (pmde.getCause() != null && pmde.getCause().getMessage() != null) {
282 log(pmde.getCause().getMessage(), Project.MSG_VERBOSE);
283 }
284 if (failOnError) {
285 throw new BuildException(pmde);
286 }
287 errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename()));
288 }
289 }
290
291 int problemCount = reportSize.get();
292 log(problemCount + " problems found", Project.MSG_VERBOSE);
293
294 for (Formatter formatter : formatters) {
295 formatter.end(errorReport);
296 }
297
298 if (failuresPropertyName != null && problemCount > 0) {
299 getProject().setProperty(failuresPropertyName, String.valueOf(problemCount));
300 log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE);
301 }
302
303 if (failOnRuleViolation && problemCount > maxRuleViolations) {
304 throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code");
305 }
306 }
307
308 @Override
309 public void execute() throws BuildException {
310 validate();
311 final Handler antLogHandler = new AntLogHandler(this);
312 final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST, antLogHandler);
313 try {
314 doTask();
315 } finally {
316 logManager.close();
317 }
318 }
319
320 private void logRulesUsed(RuleSets rules) {
321 log("Using these rulesets: " + configuration.getRuleSets(), Project.MSG_VERBOSE);
322
323 RuleSet[] ruleSets = rules.getAllRuleSets();
324 for (RuleSet ruleSet : ruleSets) {
325 for (Rule rule : ruleSet.getRules()) {
326 log("Using rule " + rule.getName(), Project.MSG_VERBOSE);
327 }
328 }
329 }
330
331 private void validate() throws BuildException {
332 // TODO - check for empty Formatters List here?
333 for (Formatter f : formatters) {
334 if (f.isNoOutputSupplied()) {
335 throw new BuildException("toFile or toConsole needs to be specified in Formatter");
336 }
337 }
338
339 if (configuration.getRuleSets() == null) {
340 if (nestedRules.isEmpty()) {
341 throw new BuildException("No rulesets specified");
342 }
343 configuration.setRuleSets(getNestedRuleSetFiles());
344 }
345 }
346
347 private String getNestedRuleSetFiles() {
348 final StringBuffer sb = new StringBuffer();
349 for (Iterator<RuleSetWrapper> it = nestedRules.iterator(); it.hasNext();) {
350 RuleSetWrapper rs = it.next();
351 sb.append(rs.getFile());
352 if (it.hasNext()) {
353 sb.append(',');
354 }
355 }
356 return sb.toString();
357 }
358
359 public void addRuleset(RuleSetWrapper r) {
360 nestedRules.add(r);
361 }
362
363 }
|