package pl.touk.sputnik.processor.pmd; import com.google.common.base.Joiner; import lombok.extern.slf4j.Slf4j; import net.sourceforge.pmd.*; import net.sourceforge.pmd.benchmark.Benchmark; import net.sourceforge.pmd.benchmark.Benchmarker; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.util.datasource.DataSource; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import pl.touk.sputnik.configuration.Configuration; import pl.touk.sputnik.configuration.GeneralOption; import pl.touk.sputnik.review.Review; import pl.touk.sputnik.review.ReviewException; import pl.touk.sputnik.review.ReviewProcessor; import pl.touk.sputnik.review.ReviewResult; import pl.touk.sputnik.review.filter.PmdFilter; import pl.touk.sputnik.review.transformer.FileNameTransformer; import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @Slf4j public class PmdProcessor implements ReviewProcessor { private static final String SOURCE_NAME = "PMD"; private static final char PMD_INPUT_PATH_SEPARATOR = ','; private Renderer renderer; @NotNull private final Configuration config; public PmdProcessor(Configuration configuration) { config = configuration; } @Nullable @Override public ReviewResult process(@NotNull Review review) { List<String> filesToReview = review.getFiles(new PmdFilter(), new FileNameTransformer()); if (filesToReview.isEmpty()) { return null; } try { PMDConfiguration configuration = new PMDConfiguration(); configuration.setReportFormat(CollectorRenderer.class.getCanonicalName()); configuration.setRuleSets(getRulesets()); configuration.setInputPaths(Joiner.on(PMD_INPUT_PATH_SEPARATOR).join(filesToReview)); doPMD(configuration); } catch (RuntimeException e) { log.error("PMD processing error. Something wrong with configuration or analyzed files are not in workspace.", e); throw new ReviewException("PMD processing error", e); } return renderer != null ? ((CollectorRenderer)renderer).getReviewResult() : null; } @NotNull @Override public String getName() { return SOURCE_NAME; } @Nullable private String getRulesets() { String ruleSets = config.getProperty(GeneralOption.PMD_RULESETS); log.info("Using PMD rulesets {}", ruleSets); return ruleSets; } /** * PMD has terrible design of process configuration. You must use report file with it. I paste this method here and * improve it. * * @throws IllegalArgumentException * if the configuration is not correct */ private void doPMD(@NotNull PMDConfiguration configuration) throws IllegalArgumentException { // Load the RuleSets RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration); RuleSets ruleSets = RulesetsFactoryUtils.getRuleSets(configuration.getRuleSets(), ruleSetFactory); // this is just double check - we don't get null here // instead IllegalArgumentException/RuntimeException is thrown if configuration is wrong if (ruleSets == null) { return; } Set<Language> languages = getApplicableLanguages(configuration, ruleSets); // this throws RuntimeException when modified file does not exist in workspace List<DataSource> files = PMD.getApplicableFiles(configuration, languages); long reportStart = System.nanoTime(); try { renderer = configuration.createRenderer(); List<Renderer> renderers = new LinkedList<>(); renderers.add(renderer); renderer.start(); Benchmarker.mark(Benchmark.Reporting, System.nanoTime() - reportStart, 0); RuleContext ctx = new RuleContext(); PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers); reportStart = System.nanoTime(); renderer.end(); } catch (IOException e) { log.error("PMD analysis error", e); } finally { Benchmarker.mark(Benchmark.Reporting, System.nanoTime() - reportStart, 0); } } /** * Paste from PMD */ private static Set<Language> getApplicableLanguages(PMDConfiguration configuration, RuleSets ruleSets) { Set<Language> languages = new HashSet<>(); LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer(); for (Rule rule : ruleSets.getAllRules()) { Language language = rule.getLanguage(); if (languages.contains(language)) continue; LanguageVersion version = discoverer.getDefaultLanguageVersion(language); if (RuleSet.applies(rule, version)) { languages.add(language); log.debug("Using {} version: {}", language.getShortName(), version.getShortName()); } } return languages; } }