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.LanguageVersion;
    30  import net.sourceforge.pmd.renderers.AbstractRenderer;
    31  import net.sourceforge.pmd.renderers.Renderer;
    32  import net.sourceforge.pmd.util.IOUtil;
    33  import net.sourceforge.pmd.util.StringUtil;
    34  import net.sourceforge.pmd.util.datasource.DataSource;
    35  import net.sourceforge.pmd.util.datasource.FileDataSource;
    36  import net.sourceforge.pmd.util.log.AntLogHandler;
    37  import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
    38
    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 			IOUtil.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 }