View Javadoc

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                 // substitute env variables/properties
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          * 'basedir' is added to the path to make sure that relative paths
189          * such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
190          * work when ant is invoked from a different directory using "-f"
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             // This is just used to validate and display rules. Each thread will create its own ruleset
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         // TODO - check for empty Formatters List here?
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 }