All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.pmd.PMDConfiguration Maven / Gradle / Ivy

There is a newer version: 7.7.0
Show newest version
/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.LoggerFactory;

import net.sourceforge.pmd.annotation.DeprecatedUntil700;
import net.sourceforge.pmd.cache.AnalysisCache;
import net.sourceforge.pmd.cache.FileAnalysisCache;
import net.sourceforge.pmd.cache.NoopAnalysisCache;
import net.sourceforge.pmd.internal.util.ClasspathClassLoader;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.renderers.RendererFactory;
import net.sourceforge.pmd.util.AssertionUtil;
import net.sourceforge.pmd.util.log.MessageReporter;
import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter;

/**
 * This class contains the details for the runtime configuration of a
 * PMD run. Once configured, use {@link PmdAnalysis#create(PMDConfiguration)}
 * in a try-with-resources to execute the analysis (see {@link PmdAnalysis}).
 *
 * 

Rulesets

* *
    *
  • You can configure paths to the rulesets to use with {@link #addRuleSet(String)}. * These can be file paths or classpath resources.
  • *
  • Use {@link #setMinimumPriority(RulePriority)} to control the minimum priority a * rule must have to be included. Defaults to the lowest priority, ie all rules are loaded.
  • *
  • Use {@link #setRuleSetFactoryCompatibilityEnabled(boolean)} to disable the * compatibility measures for removed and renamed rules in the rulesets that will * be loaded. *
* *

Source files

* *
    *
  • The default encoding of source files is the system default as * returned by System.getProperty("file.encoding"). * You can set it with {@link #setSourceEncoding(String)}.
  • *
  • The source files to analyze can be given in many ways. See * {@link #addInputPath(Path)} {@link #setInputFilePath(Path)}, {@link #setInputUri(URI)}. *
  • Files are assigned a language based on their name. The language * version of languages can be given with * {@link #setDefaultLanguageVersion(LanguageVersion)}. * The default language assignment can be overridden with * {@link #setForceLanguageVersion(LanguageVersion)}.
  • *
* *

Rendering

* *
    *
  • The renderer format to use for Reports. {@link #getReportFormat()}
  • *
  • The file to which the Report should render. {@link #getReportFile()}
  • *
  • Configure the root paths that are used to relativize file names in reports via {@link #addRelativizeRoot(Path)}. * This enables to get short names in reports.
  • *
  • The initialization properties to use when creating a Renderer instance. * {@link #getReportProperties()}
  • *
  • An indicator of whether to show suppressed Rule violations in Reports. * {@link #isShowSuppressedViolations()}
  • *
* *

Language configuration

*
    *
  • Use {@link #setSuppressMarker(String)} to change the comment marker for suppression comments. Defaults to {@value #DEFAULT_SUPPRESS_MARKER}.
  • *
  • See {@link #setClassLoader(ClassLoader)} and {@link #prependAuxClasspath(String)} for * information for how to configure classpath for Java analysis.
  • *
  • You can set additional language properties with {@link #getLanguageProperties(Language)}
  • *
* *

Miscellaneous

*
    *
  • Use {@link #setThreads(int)} to control the parallelism of the analysis. Defaults * one thread per available processor. {@link #getThreads()}
  • *
*/ public class PMDConfiguration extends AbstractConfiguration { private static final LanguageRegistry DEFAULT_REGISTRY = LanguageRegistry.PMD; /** The default suppress marker string. */ public static final String DEFAULT_SUPPRESS_MARKER = "NOPMD"; // General behavior options private String suppressMarker = DEFAULT_SUPPRESS_MARKER; private int threads = Runtime.getRuntime().availableProcessors(); private ClassLoader classLoader = getClass().getClassLoader(); private final LanguageVersionDiscoverer languageVersionDiscoverer; private LanguageVersion forceLanguageVersion; private MessageReporter reporter = new SimpleMessageReporter(LoggerFactory.getLogger(PmdAnalysis.class)); // Rule and source file options private List ruleSets = new ArrayList<>(); private RulePriority minimumPriority = RulePriority.LOW; private @NonNull List inputPaths = new ArrayList<>(); private URI inputUri; private Path inputFilePath; private Path ignoreFilePath; private boolean ruleSetFactoryCompatibilityEnabled = true; // Reporting options private String reportFormat; private Path reportFile; private Properties reportProperties = new Properties(); private boolean showSuppressedViolations = false; private boolean failOnViolation = true; private AnalysisCache analysisCache = new NoopAnalysisCache(); private boolean ignoreIncrementalAnalysis; private final LanguageRegistry langRegistry; private final List relativizeRoots = new ArrayList<>(); private final Map langProperties = new HashMap<>(); public PMDConfiguration() { this(DEFAULT_REGISTRY); } public PMDConfiguration(@NonNull LanguageRegistry languageRegistry) { this.langRegistry = Objects.requireNonNull(languageRegistry); this.languageVersionDiscoverer = new LanguageVersionDiscoverer(languageRegistry); } /** * Get the suppress marker. This is the source level marker used to indicate * a RuleViolation should be suppressed. * * @return The suppress marker. */ public String getSuppressMarker() { return suppressMarker; } /** * Set the suppress marker. * * @param suppressMarker * The suppress marker to use. */ public void setSuppressMarker(String suppressMarker) { Objects.requireNonNull(suppressMarker, "Suppress marker was null"); this.suppressMarker = suppressMarker; } /** * Get the number of threads to use when processing Rules. * * @return The number of threads. */ public int getThreads() { return threads; } /** * Set the number of threads to use when processing Rules. * * @param threads * The number of threads. */ public void setThreads(int threads) { this.threads = threads; } /** * Get the ClassLoader being used by PMD when processing Rules. * * @return The ClassLoader being used */ public ClassLoader getClassLoader() { return classLoader; } /** * Set the ClassLoader being used by PMD when processing Rules. Setting a * value of null will cause the default ClassLoader to be used. * * @param classLoader * The ClassLoader to use */ public void setClassLoader(ClassLoader classLoader) { if (classLoader == null) { this.classLoader = getClass().getClassLoader(); } else { this.classLoader = classLoader; } } /** * Prepend the specified classpath like string to the current ClassLoader of * the configuration. If no ClassLoader is currently configured, the * ClassLoader used to load the {@link PMDConfiguration} class will be used * as the parent ClassLoader of the created ClassLoader. * *

If the classpath String looks like a URL to a file (i.e. starts with * file://) the file will be read with each line representing * an entry on the classpath.

* * @param classpath * The prepended classpath. * @throws IOException * if the given classpath is invalid (e.g. does not exist) * @see PMDConfiguration#setClassLoader(ClassLoader) * @see ClasspathClassLoader * * @deprecated Use {@link #prependAuxClasspath(String)}, which doesn't * throw a checked {@link IOException} */ @Deprecated public void prependClasspath(String classpath) throws IOException { try { prependAuxClasspath(classpath); } catch (IllegalArgumentException e) { throw new IOException(e); } } /** * Prepend the specified classpath like string to the current ClassLoader of * the configuration. If no ClassLoader is currently configured, the * ClassLoader used to load the {@link PMDConfiguration} class will be used * as the parent ClassLoader of the created ClassLoader. * *

If the classpath String looks like a URL to a file (i.e. starts with * file://) the file will be read with each line representing * an entry on the classpath.

* *

You can specify multiple class paths separated by `:` on Unix-systems or `;` under Windows. * See {@link File#pathSeparator}. * * @param classpath The prepended classpath. * * @throws IllegalArgumentException if the given classpath is invalid (e.g. does not exist) * @see PMDConfiguration#setClassLoader(ClassLoader) */ public void prependAuxClasspath(String classpath) { try { if (classLoader == null) { classLoader = PMDConfiguration.class.getClassLoader(); } if (classpath != null) { classLoader = new ClasspathClassLoader(classpath, classLoader); } } catch (IOException e) { // Note: IOExceptions shouldn't appear anymore, they should already be converted // to IllegalArgumentException in ClasspathClassLoader. throw new IllegalArgumentException(e); } } /** * Returns the message reporter that is to be used while running * the analysis. */ public @NonNull MessageReporter getReporter() { return reporter; } /** * Sets the message reporter that is to be used while running * the analysis. * * @param reporter A non-null message reporter */ public void setReporter(@NonNull MessageReporter reporter) { AssertionUtil.requireParamNotNull("reporter", reporter); this.reporter = reporter; } /** * Get the LanguageVersionDiscoverer, used to determine the LanguageVersion * of a source file. * * @return The LanguageVersionDiscoverer. */ public LanguageVersionDiscoverer getLanguageVersionDiscoverer() { return languageVersionDiscoverer; } /** * Get the LanguageVersion specified by the force-language parameter. This overrides detection based on file * extensions * * @return The LanguageVersion. */ public LanguageVersion getForceLanguageVersion() { return forceLanguageVersion; } /** * Is the force-language parameter set to anything? * * @return true if ${@link #getForceLanguageVersion()} is not null */ public boolean isForceLanguageVersion() { return forceLanguageVersion != null; } /** * Set the LanguageVersion specified by the force-language parameter. This overrides detection based on file * extensions * * @param forceLanguageVersion the language version */ public void setForceLanguageVersion(@Nullable LanguageVersion forceLanguageVersion) { if (forceLanguageVersion != null) { checkLanguageIsRegistered(forceLanguageVersion.getLanguage()); } this.forceLanguageVersion = forceLanguageVersion; languageVersionDiscoverer.setForcedVersion(forceLanguageVersion); } /** * Set the given LanguageVersion as the current default for it's Language. * * @param languageVersion * the LanguageVersion */ public void setDefaultLanguageVersion(LanguageVersion languageVersion) { Objects.requireNonNull(languageVersion); languageVersionDiscoverer.setDefaultLanguageVersion(languageVersion); getLanguageProperties(languageVersion.getLanguage()).setLanguageVersion(languageVersion.getVersion()); } /** * Set the given LanguageVersions as the current default for their * Languages. * * @param languageVersions * The LanguageVersions. */ public void setDefaultLanguageVersions(List languageVersions) { for (LanguageVersion languageVersion : languageVersions) { setDefaultLanguageVersion(languageVersion); } } /** * Get the LanguageVersion of the source file with given name. This depends * on the fileName extension, and the java version. *

* For compatibility with older code that does not always pass in a correct * filename, unrecognized files are assumed to be java files. *

* * @param fileName * Name of the file, can be absolute, or simple. * @return the LanguageVersion */ // FUTURE Delete this? I can't think of a good reason to keep it around. // Failure to determine the LanguageVersion for a file should be a hard // error, or simply cause the file to be skipped? public @Nullable LanguageVersion getLanguageVersionOfFile(String fileName) { LanguageVersion forcedVersion = getForceLanguageVersion(); if (forcedVersion != null) { // use force language if given return forcedVersion; } // otherwise determine by file extension return languageVersionDiscoverer.getDefaultLanguageVersionForFile(fileName); } LanguageRegistry getLanguageRegistry() { return langRegistry; } /** * Get the comma separated list of RuleSet URIs. * * @return The RuleSet URIs. * * @deprecated Use {@link #getRuleSetPaths()} */ @Deprecated @DeprecatedUntil700 public @Nullable String getRuleSets() { if (ruleSets.isEmpty()) { return null; } return String.join(",", ruleSets); } /** * Returns the list of ruleset URIs. * * @see RuleSetLoader#loadFromResource(String) */ public @NonNull List<@NonNull String> getRuleSetPaths() { return ruleSets; } /** * Sets the list of ruleset paths to load when starting the analysis. * * @param ruleSetPaths A list of ruleset paths, understandable by {@link RuleSetLoader#loadFromResource(String)}. * * @throws NullPointerException If the parameter is null */ public void setRuleSets(@NonNull List<@NonNull String> ruleSetPaths) { AssertionUtil.requireParamNotNull("ruleSetPaths", ruleSetPaths); AssertionUtil.requireContainsNoNullValue("ruleSetPaths", ruleSetPaths); this.ruleSets = new ArrayList<>(ruleSetPaths); } /** * Add a new ruleset paths to load when starting the analysis. * This list is initially empty. * * @param rulesetPath A ruleset path, understandable by {@link RuleSetLoader#loadFromResource(String)}. * * @throws NullPointerException If the parameter is null */ public void addRuleSet(@NonNull String rulesetPath) { AssertionUtil.requireParamNotNull("rulesetPath", rulesetPath); this.ruleSets.add(rulesetPath); } /** * Set the comma separated list of RuleSet URIs. * * @param ruleSets the rulesets to set * * @deprecated Use {@link #setRuleSets(List)} or {@link #addRuleSet(String)}. */ @Deprecated @DeprecatedUntil700 public void setRuleSets(@Nullable String ruleSets) { if (ruleSets == null) { this.ruleSets = new ArrayList<>(); } else { this.ruleSets = new ArrayList<>(Arrays.asList(ruleSets.split(","))); } } /** * Get the minimum priority threshold when loading Rules from RuleSets. * * @return The minimum priority threshold. */ public RulePriority getMinimumPriority() { return minimumPriority; } /** * Set the minimum priority threshold when loading Rules from RuleSets. * * @param minimumPriority * The minimum priority. */ public void setMinimumPriority(RulePriority minimumPriority) { this.minimumPriority = minimumPriority; } /** * Returns the list of input paths to explore. This is an * unmodifiable list. */ public @NonNull List getInputPathList() { return Collections.unmodifiableList(inputPaths); } /** * Set the comma separated list of input paths to process for source files. * * @param inputPaths The comma separated list. * * @throws NullPointerException If the parameter is null * @deprecated Use {@link #setInputPathList(List)} or {@link #addInputPath(Path)} */ @Deprecated public void setInputPaths(String inputPaths) { if (inputPaths.isEmpty()) { return; } List paths = new ArrayList<>(); for (String s : inputPaths.split(",")) { paths.add(Paths.get(s)); } this.inputPaths = paths; } /** * Set the input paths to the given list of paths. * * @throws NullPointerException If the parameter is null or contains a null value */ public void setInputPathList(final List inputPaths) { AssertionUtil.requireContainsNoNullValue("input paths", inputPaths); this.inputPaths = new ArrayList<>(inputPaths); } /** * Add an input path. It is not split on commas. * * @throws NullPointerException If the parameter is null */ public void addInputPath(@NonNull Path inputPath) { Objects.requireNonNull(inputPath); this.inputPaths.add(inputPath); } /** Returns the path to the file list text file. */ public @Nullable Path getInputFile() { return inputFilePath; } public @Nullable Path getIgnoreFile() { return ignoreFilePath; } /** * The input file path points to a single file, which contains a * comma-separated list of source file names to process. * * @param inputFilePath path to the file * @deprecated Use {@link #setInputFilePath(Path)} */ @Deprecated public void setInputFilePath(String inputFilePath) { this.inputFilePath = inputFilePath == null ? null : Paths.get(inputFilePath); } /** * The input file path points to a single file, which contains a * comma-separated list of source file names to process. * * @param inputFilePath path to the file */ public void setInputFilePath(Path inputFilePath) { this.inputFilePath = inputFilePath; } /** * The input file path points to a single file, which contains a * comma-separated list of source file names to ignore. * * @param ignoreFilePath path to the file * @deprecated Use {@link #setIgnoreFilePath(Path)} */ @Deprecated public void setIgnoreFilePath(String ignoreFilePath) { this.ignoreFilePath = ignoreFilePath == null ? null : Paths.get(ignoreFilePath); } /** * The input file path points to a single file, which contains a * comma-separated list of source file names to ignore. * * @param ignoreFilePath path to the file */ public void setIgnoreFilePath(Path ignoreFilePath) { this.ignoreFilePath = ignoreFilePath; } /** * Get the input URI to process for source code objects. * * @return URI */ public URI getUri() { return inputUri; } /** * Set the input URI to process for source code objects. * * @param inputUri a single URI * @deprecated Use {@link PMDConfiguration#setInputUri(URI)} */ @Deprecated public void setInputUri(String inputUri) { this.inputUri = inputUri == null ? null : URI.create(inputUri); } /** * Set the input URI to process for source code objects. * * @param inputUri a single URI */ public void setInputUri(URI inputUri) { this.inputUri = inputUri; } /** * Create a Renderer instance based upon the configured reporting options. * No writer is created. * * @return renderer */ public Renderer createRenderer() { return createRenderer(false); } /** * Create a Renderer instance based upon the configured reporting options. * If withReportWriter then we'll configure it with a writer for the * reportFile specified. * * @param withReportWriter * whether to configure a writer or not * @return A Renderer instance. */ public Renderer createRenderer(boolean withReportWriter) { Renderer renderer = RendererFactory.createRenderer(reportFormat, reportProperties); renderer.setShowSuppressedViolations(showSuppressedViolations); if (withReportWriter) { renderer.setReportFile(reportFile == null ? null : reportFile.toString()); } return renderer; } /** * Get the report format. * * @return The report format. */ public String getReportFormat() { return reportFormat; } /** * Set the report format. This should be a name of a Renderer. * * @param reportFormat * The report format. * * @see Renderer */ public void setReportFormat(String reportFormat) { this.reportFormat = reportFormat; } /** * Get the file to which the report should render. * * @return The file to which to render. * @deprecated Use {@link #getReportFilePath()} */ @Deprecated public String getReportFile() { return reportFile == null ? null : reportFile.toString(); } /** * Get the file to which the report should render. * * @return The file to which to render. */ public Path getReportFilePath() { return reportFile; } /** * Set the file to which the report should render. * * @param reportFile the file to set * @deprecated Use {@link #setReportFile(Path)} */ @Deprecated public void setReportFile(String reportFile) { this.reportFile = reportFile == null ? null : Paths.get(reportFile); } /** * Set the file to which the report should render. * * @param reportFile the file to set */ public void setReportFile(Path reportFile) { this.reportFile = reportFile; } /** * Get whether the report should show suppressed violations. * * @return true if showing suppressed violations, * false otherwise. */ public boolean isShowSuppressedViolations() { return showSuppressedViolations; } /** * Set whether the report should show suppressed violations. * * @param showSuppressedViolations * true if showing suppressed violations, * false otherwise. */ public void setShowSuppressedViolations(boolean showSuppressedViolations) { this.showSuppressedViolations = showSuppressedViolations; } /** * Get the Report properties. These are used to create the Renderer. * * @return The report properties. */ public Properties getReportProperties() { return reportProperties; } /** * Set the Report properties. These are used to create the Renderer. * * @param reportProperties * The Report properties to set. */ public void setReportProperties(Properties reportProperties) { this.reportProperties = reportProperties; } /** * Whether PMD should exit with status 4 (the default behavior, true) if * violations are found or just with 0 (to not break the build, e.g.). * * @return failOnViolation */ public boolean isFailOnViolation() { return failOnViolation; } /** * Sets whether PMD should exit with status 4 (the default behavior, true) * if violations are found or just with 0 (to not break the build, e.g.). * * @param failOnViolation * failOnViolation */ public void setFailOnViolation(boolean failOnViolation) { this.failOnViolation = failOnViolation; } /** * Checks if the rule set factory compatibility feature is enabled. * * @return true, if the rule set factory compatibility feature is enabled * * @see RuleSetLoader#enableCompatibility(boolean) */ public boolean isRuleSetFactoryCompatibilityEnabled() { return ruleSetFactoryCompatibilityEnabled; } /** * Sets the rule set factory compatibility feature enabled/disabled. * * @param ruleSetFactoryCompatibilityEnabled {@code true} if the feature should be enabled * * @see RuleSetLoader#enableCompatibility(boolean) */ public void setRuleSetFactoryCompatibilityEnabled(boolean ruleSetFactoryCompatibilityEnabled) { this.ruleSetFactoryCompatibilityEnabled = ruleSetFactoryCompatibilityEnabled; } /** * Retrieves the currently used analysis cache. Will never be null. * * @return The currently used analysis cache. Never null. */ public AnalysisCache getAnalysisCache() { // Make sure we are not null if (analysisCache == null || isIgnoreIncrementalAnalysis() && !(analysisCache instanceof NoopAnalysisCache)) { // sets a noop cache setAnalysisCache(new NoopAnalysisCache()); } return analysisCache; } /** * Sets the analysis cache to be used. Setting a * value of {@code null} will cause a Noop AnalysisCache to be used. * If incremental analysis was explicitly disabled ({@link #isIgnoreIncrementalAnalysis()}), * then this method is a noop. * * @param cache The analysis cache to be used. */ public void setAnalysisCache(final AnalysisCache cache) { // the doc says it's a noop if incremental analysis was disabled, // but it's actually the getter that enforces that this.analysisCache = cache == null ? new NoopAnalysisCache() : cache; } /** * Sets the location of the analysis cache to be used. This will automatically configure * and appropriate AnalysisCache implementation. * * @param cacheLocation The location of the analysis cache to be used. */ public void setAnalysisCacheLocation(final String cacheLocation) { setAnalysisCache(cacheLocation == null ? new NoopAnalysisCache() : new FileAnalysisCache(new File(cacheLocation))); } /** * Sets whether the user has explicitly disabled incremental analysis or not. * If so, incremental analysis is not used, and all suggestions to use it are * disabled. The analysis cached location is ignored, even if it's specified. * * @param noCache Whether to ignore incremental analysis or not */ public void setIgnoreIncrementalAnalysis(boolean noCache) { // see #getAnalysisCache for the implementation. this.ignoreIncrementalAnalysis = noCache; } /** * Returns whether incremental analysis was explicitly disabled by the user * or not. * * @return {@code true} if incremental analysis is explicitly disabled */ public boolean isIgnoreIncrementalAnalysis() { return ignoreIncrementalAnalysis; } /** * Set the path used to shorten paths output in the report. * The path does not need to exist. If it exists, it must point * to a directory and not a file. See {@link #getRelativizeRoots()} * for the interpretation. * *

If several paths are added, the shortest paths possible are * built. * * @param path A path * * @throws IllegalArgumentException If the path points to a file, and not a directory * @throws NullPointerException If the path is null */ public void addRelativizeRoot(Path path) { // Note: the given path is not further modified or resolved. E.g. there is no special handling for symlinks. // The goal is, that if the user inputs a path, PMD should output in terms of that path, not it's resolution. this.relativizeRoots.add(Objects.requireNonNull(path)); if (Files.isRegularFile(path)) { throw new IllegalArgumentException("Relativize root should be a directory: " + path); } } /** * Add several paths to shorten paths that are output in the report. * See {@link #addRelativizeRoot(Path)}. * * @param paths A list of non-null paths * * @throws IllegalArgumentException If any path points to a file, and not a directory * @throws NullPointerException If the list, or any path in the list is null */ public void addRelativizeRoots(List paths) { for (Path path : paths) { addRelativizeRoot(path); } } /** * Returns the paths used to shorten paths output in the report. *

    *
  • If the list is empty, then paths are not touched *
  • If the list is non-empty, then source file paths are relativized with all the items in the list. * The shortest of these relative paths is taken as the display name of the file. *
*/ public List getRelativizeRoots() { return Collections.unmodifiableList(relativizeRoots); } /** * Returns a mutable bundle of language properties that are associated * to the given language (always the same for a given language). * * @param language A language, which must be registered */ public @NonNull LanguagePropertyBundle getLanguageProperties(Language language) { checkLanguageIsRegistered(language); return langProperties.computeIfAbsent(language, Language::newPropertyBundle); } void checkLanguageIsRegistered(Language language) { if (!langRegistry.getLanguages().contains(language)) { throw new IllegalArgumentException( "Language '" + language.getId() + "' is not registered in " + getLanguageRegistry()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy