PMDTask.java
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 }