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.PMD;
20  import net.sourceforge.pmd.PMDConfiguration;
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.StringUtil;
33  import net.sourceforge.pmd.util.datasource.DataSource;
34  import net.sourceforge.pmd.util.datasource.FileDataSource;
35  import net.sourceforge.pmd.util.log.AntLogHandler;
36  import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
37  
38  import org.apache.commons.io.IOUtils;
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 		// Setup RuleSetFactory and validate RuleSets
162 		RuleSetFactory ruleSetFactory = new RuleSetFactory();
163 		ruleSetFactory.setClassLoader(configuration.getClassLoader());
164 		try {
165 			// This is just used to validate and display rules. Each thread will create its own ruleset
166 			ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
167 			ruleSetFactory.setWarnDeprecated(true);
168 			String ruleSets = configuration.getRuleSets();
169 			if (StringUtil.isNotEmpty(ruleSets)) {
170 				// Substitute env variables/properties
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 		// Start the Formatters
185 		for (Formatter formatter : formatters) {
186 			log("Sending a report to " + formatter, Project.MSG_VERBOSE);
187 			formatter.start(getProject().getBaseDir().toString());
188 		}
189 
190 		//log("Setting Language Version " + languageVersion.getShortName(), Project.MSG_VERBOSE);
191 
192 		// TODO Do we really need all this in a loop over each FileSet?  Seems like a lot of redundancy
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 					// Nothing to do
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 					// Nothing to do
228 				}
229 
230 				public String defaultFileExtension() { return null;	}	// not relevant
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 			IOUtils.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 			// must be true, otherwise you'll get ClassCastExceptions as classes are loaded twice
293 			// and exist in multiple class loaders
294 			boolean parentFirst = true;
295 			configuration.setClassLoader(
296 			        new AntClassLoader(Thread.currentThread().getContextClassLoader(), getProject(),
297 			                classpath, parentFirst));
298 		}
299 		try {
300 			/*
301 			 * 'basedir' is added to the path to make sure that relative paths
302 			 * such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
303 			 * work when ant is invoked from a different directory using "-f"
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 }