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

net.sourceforge.pmd.RuleSetLoader 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.util.CollectionUtil;
import net.sourceforge.pmd.util.ResourceLoader;
import net.sourceforge.pmd.util.log.MessageReporter;

/**
 * Configurable object to load rulesets from XML resources.
 * This can be configured using a fluent API, see eg {@link #warnDeprecated(boolean)}.
 * To create a new ruleset, use {@link #loadFromResource(String)}
 * or some such overload.
 */
public final class RuleSetLoader {
    private static final Logger LOG = LoggerFactory.getLogger(RuleSetLoader.class);

    private LanguageRegistry languageRegistry = LanguageRegistry.PMD;
    private ResourceLoader resourceLoader = new ResourceLoader(RuleSetLoader.class.getClassLoader());
    private RulePriority minimumPriority = RulePriority.LOW;
    private boolean warnDeprecated = true;
    private @NonNull RuleSetFactoryCompatibility compatFilter = RuleSetFactoryCompatibility.DEFAULT;
    private boolean includeDeprecatedRuleReferences = false;
    private @NonNull MessageReporter reporter = MessageReporter.quiet();

    /**
     * Create a new RuleSetLoader with a default configuration.
     * The defaults are described on each configuration method of this class.
     */
    public RuleSetLoader() { // NOPMD UnnecessaryConstructor
        // default
    }

    RuleSetLoader withReporter(@NonNull MessageReporter reporter) {
        this.reporter = Objects.requireNonNull(reporter);
        return this;
    }

    /**
     * Specify that the given classloader should be used to resolve
     * paths to external ruleset references. The default uses PMD's
     * own classpath.
     */
    public RuleSetLoader loadResourcesWith(ClassLoader classLoader) {
        this.resourceLoader = new ResourceLoader(classLoader);
        return this;
    }

    // internal
    RuleSetLoader loadResourcesWith(ResourceLoader loader) {
        this.resourceLoader = loader;
        return this;
    }

    public RuleSetLoader withLanguages(LanguageRegistry languageRegistry) {
        this.languageRegistry = languageRegistry;
        return this;
    }

    /**
     * Filter loaded rules to only those that match or are above
     * the given priority. The default is {@link RulePriority#LOW},
     * ie, no filtering occurs.
     *
     * @return This instance, modified
     */
    public RuleSetLoader filterAbovePriority(RulePriority minimumPriority) {
        this.minimumPriority = minimumPriority;
        return this;
    }

    /**
     * Log a warning when referencing a deprecated rule.
     * This is enabled by default.
     *
     * @return This instance, modified
     */
    public RuleSetLoader warnDeprecated(boolean warn) {
        this.warnDeprecated = warn;
        return this;
    }

    /**
     * Enable translating old rule references to newer ones, if they have
     * been moved or renamed. This is enabled by default, if disabled,
     * unresolved references will not be translated and will produce an
     * error.
     *
     * @return This instance, modified
     */
    public RuleSetLoader enableCompatibility(boolean enable) {
        return setCompatibility(enable ? RuleSetFactoryCompatibility.DEFAULT
                                       : RuleSetFactoryCompatibility.EMPTY);
    }

    // test only
    RuleSetLoader setCompatibility(@NonNull RuleSetFactoryCompatibility filter) {
        this.compatFilter = filter;
        return this;
    }

    /**
     * Follow deprecated rule references. By default this is off,
     * and those references will be ignored (with a warning depending
     * on {@link #enableCompatibility(boolean)}).
     *
     * @return This instance, modified
     */
    public RuleSetLoader includeDeprecatedRuleReferences(boolean enable) {
        this.includeDeprecatedRuleReferences = enable;
        return this;
    }

    /**
     * Create a new rule set factory, if you have to (that class is deprecated).
     * That factory will use the configuration that was set using the setters of this.
     *
     * @deprecated {@link RuleSetFactory} is deprecated, replace its usages
     *     with usages of this class, or of static factory methods of {@link RuleSet}
     */
    @Deprecated
    public RuleSetFactory toFactory() {
        return new RuleSetFactory(
            this.resourceLoader,
            this.languageRegistry,
            this.minimumPriority,
            this.warnDeprecated,
            this.compatFilter,
            this.includeDeprecatedRuleReferences,
            this.reporter
        );
    }

    private @Nullable MessageReporter filteredReporter() {
        return warnDeprecated ? reporter : null;
    }

    /**
     * Parses and returns a ruleset from its location. The location may
     * be a file system path, or a resource path (see {@link #loadResourcesWith(ClassLoader)}).
     *
     * @param rulesetPath A reference to a single ruleset
     *
     * @throws RuleSetLoadException If any error occurs (eg, invalid syntax, or resource not found)
     */
    public RuleSet loadFromResource(String rulesetPath) {
        return loadFromResource(new RuleSetReferenceId(rulesetPath, null, filteredReporter()));
    }

    /**
     * Parses and returns a ruleset from string content.
     *
     * @param filename          The symbolic "file name", for error messages.
     * @param rulesetXmlContent Xml file contents
     *
     * @throws RuleSetLoadException If any error occurs (eg, invalid syntax)
     */
    public RuleSet loadFromString(String filename, final String rulesetXmlContent) {
        return loadFromResource(new RuleSetReferenceId(filename, null, filteredReporter()) {
            @Override
            public InputStream getInputStream(ResourceLoader rl) {
                return new ByteArrayInputStream(rulesetXmlContent.getBytes(StandardCharsets.UTF_8));
            }
        });
    }

    /**
     * Parses several resources into a list of rulesets.
     *
     * @param paths Paths
     *
     * @throws RuleSetLoadException If any error occurs (eg, invalid syntax, or resource not found),
     *                              for any of the parameters
     * @throws NullPointerException If the parameter, or any component is null
     */
    public List loadFromResources(Collection paths) {
        List ruleSets = new ArrayList<>(paths.size());
        for (String path : paths) {
            ruleSets.add(loadFromResource(path));
        }
        return ruleSets;
    }

    /**
     * Loads a list of rulesets, if any has an error, report it on the contextual
     * error reporter instead of aborting, and continue loading the rest.
     *
     * 

Internal API: might be published later, or maybe in PMD 7 this * will be the default behaviour of every method of this class. */ @InternalApi public List loadRuleSetsWithoutException(List rulesetPaths) { List ruleSets = new ArrayList<>(rulesetPaths.size()); boolean anyRules = false; boolean error = false; for (String path : rulesetPaths) { try { RuleSet ruleset = this.loadFromResource(path); anyRules |= !ruleset.getRules().isEmpty(); printRulesInDebug(path, ruleset); ruleSets.add(ruleset); } catch (RuleSetLoadException e) { error = true; reporter.error(e); } } if (!anyRules && !error) { reporter.warn("No rules found. Maybe you misspelled a rule name? ({0})", StringUtils.join(rulesetPaths, ',')); } return ruleSets; } void printRulesInDebug(String path, RuleSet ruleset) { if (LOG.isDebugEnabled()) { LOG.debug("Rules loaded from {}:", path); for (Rule rule : ruleset.getRules()) { LOG.debug("- {} ({})", rule.getName(), rule.getLanguage().getName()); } } if (ruleset.getRules().isEmpty()) { reporter.warn("No rules found in ruleset {0}", path); } } /** * Parses several resources into a list of rulesets. * * @param first First path * @param rest Paths * * @throws RuleSetLoadException If any error occurs (eg, invalid syntax, or resource not found), * for any of the parameters * @throws NullPointerException If the parameter, or any component is null */ public List loadFromResources(String first, String... rest) { return loadFromResources(CollectionUtil.listOf(first, rest)); } // package private RuleSet loadFromResource(RuleSetReferenceId ruleSetReferenceId) { try { return toFactory().createRuleSet(ruleSetReferenceId); } catch (RuleSetLoadException e) { throw e; } catch (Exception e) { throw new RuleSetLoadException(ruleSetReferenceId, e); } } /** * Configure a new ruleset factory builder according to the parameters * of the given PMD configuration. */ public static RuleSetLoader fromPmdConfig(PMDConfiguration configuration) { return new RuleSetLoader().filterAbovePriority(configuration.getMinimumPriority()) .enableCompatibility(configuration.isRuleSetFactoryCompatibilityEnabled()) .withLanguages(configuration.getLanguageRegistry()) .withReporter(configuration.getReporter()); } /** * Returns an Iterator of RuleSet objects loaded from descriptions from the * "categories.properties" resource for each language. This * uses the classpath of the resource loader ({@link #loadResourcesWith(ClassLoader)}). * * @return A list of all category rulesets * * @throws RuleSetLoadException If a standard ruleset cannot be loaded. * This is a corner case, that probably should not be caught by clients. * The standard rulesets are well-formed, at least in stock PMD distributions. * */ public List getStandardRuleSets() { String rulesetsProperties; List ruleSetReferenceIds = new ArrayList<>(); for (Language language : languageRegistry.getLanguages()) { Properties props = new Properties(); rulesetsProperties = "category/" + language.getTerseName() + "/categories.properties"; try (InputStream inputStream = resourceLoader.loadClassPathResourceAsStreamOrThrow(rulesetsProperties)) { props.load(inputStream); String rulesetFilenames = props.getProperty("rulesets.filenames"); // some languages might not have any rules and this property either doesn't exist or is empty if (StringUtils.isNotBlank(rulesetFilenames)) { ruleSetReferenceIds.addAll(Arrays.asList(rulesetFilenames.split(","))); } } catch (IOException e) { throw new RuntimeException("Couldn't find " + rulesetsProperties + "; please ensure that the directory is on the classpath. The current classpath is: " + System.getProperty("java.class.path")); } } List ruleSets = new ArrayList<>(); for (String id : ruleSetReferenceIds) { ruleSets.add(loadFromResource(id)); // may throw } return ruleSets; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy