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.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 		// Setup RuleSetFactory and validate RuleSets
177 		RuleSetFactory ruleSetFactory = new RuleSetFactory();
178 		ruleSetFactory.setClassLoader(configuration.getClassLoader());
179 		try {
180 			// This is just used to validate and display rules. Each thread will create its own ruleset
181 			ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
182 			ruleSetFactory.setWarnDeprecated(true);
183 			String ruleSets = configuration.getRuleSets();
184 			if (StringUtil.isNotEmpty(ruleSets)) {
185 				// Substitute env variables/properties
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 		// Start the Formatters
200 		for (Formatter formatter : formatters) {
201 			log("Sending a report to " + formatter, Project.MSG_VERBOSE);
202 			formatter.start(getProject().getBaseDir().toString());
203 		}
204 
205 		//log("Setting Language Version " + languageVersion.getShortName(), Project.MSG_VERBOSE);
206 
207 		// TODO Do we really need all this in a loop over each FileSet?  Seems like a lot of redundancy
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 					// Nothing to do
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 					// Nothing to do
243 				}
244 
245 				public String defaultFileExtension() { return null;	}	// not relevant
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 			 * 'basedir' is added to the path to make sure that relative paths
312 			 * such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
313 			 * work when ant is invoked from a different directory using "-f"
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 }