/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.Adler32; import java.util.zip.CheckedInputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import net.sourceforge.pmd.RuleSet.RuleSetBuilder; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.rule.MockRule; import net.sourceforge.pmd.lang.rule.RuleReference; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper; import net.sourceforge.pmd.lang.rule.properties.factories.PropertyDescriptorUtil; import net.sourceforge.pmd.util.ResourceLoader; import net.sourceforge.pmd.util.StringUtil; /** * RuleSetFactory is responsible for creating RuleSet instances from XML * content. By default Rules will be loaded using the ClassLoader for this * class, using the {@link RulePriority#LOW} priority, with Rule deprecation * warnings off. By default, the ruleset compatibility filter is active, too. * See {@link RuleSetFactoryCompatibility}. */ public class RuleSetFactory { private static final Logger LOG = Logger.getLogger(RuleSetFactory.class.getName()); private static final String DESCRIPTION = "description"; private static final String UNEXPECTED_ELEMENT = "Unexpected element <"; private static final String PRIORITY = "priority"; private static final String FOR_RULE = "' for Rule "; private static final String MESSAGE = "message"; private static final String EXTERNAL_INFO_URL = "externalInfoUrl"; private final ClassLoader classLoader; private final RulePriority minimumPriority; private final boolean warnDeprecated; private final RuleSetFactoryCompatibility compatibilityFilter; public RuleSetFactory() { this(RuleSetFactory.class.getClassLoader(), RulePriority.LOW, false, true); } public RuleSetFactory(final ClassLoader classLoader, final RulePriority minimumPriority, final boolean warnDeprecated, final boolean enableCompatibility) { this.classLoader = classLoader; this.minimumPriority = minimumPriority; this.warnDeprecated = warnDeprecated; if (enableCompatibility) { this.compatibilityFilter = new RuleSetFactoryCompatibility(); } else { this.compatibilityFilter = null; } } /** * Constructor copying all configuration from another factory. * * @param factory * The factory whose configuration to copy. * @param warnDeprecated * Whether deprecation warnings are to be produced by this * factory. */ public RuleSetFactory(final RuleSetFactory factory, final boolean warnDeprecated) { this(factory.classLoader, factory.minimumPriority, warnDeprecated, factory.compatibilityFilter != null); } /** * Gets the compatibility filter in order to adjust it, e.g. add additional * filters. * * @return the {@link RuleSetFactoryCompatibility} */ /* package */ RuleSetFactoryCompatibility getCompatibilityFilter() { return compatibilityFilter; } /** * Returns an Iterator of RuleSet objects loaded from descriptions from the * "rulesets.properties" resource for each Language with Rule support. * * @return An Iterator of RuleSet objects. * * @throws RuleSetNotFoundException if the ruleset file could not be found */ public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException { String rulesetsProperties = null; try { List<RuleSetReferenceId> ruleSetReferenceIds = new ArrayList<>(); for (Language language : LanguageRegistry.findWithRuleSupport()) { Properties props = new Properties(); rulesetsProperties = "rulesets/" + language.getTerseName() + "/rulesets.properties"; try (InputStream inputStream = ResourceLoader.loadResourceAsStream(rulesetsProperties);) { props.load(inputStream); } String rulesetFilenames = props.getProperty("rulesets.filenames"); ruleSetReferenceIds.addAll(RuleSetReferenceId.parse(rulesetFilenames)); } return createRuleSets(ruleSetReferenceIds).getRuleSetsIterator(); } catch (IOException ioe) { throw new RuntimeException("Couldn't find " + rulesetsProperties + "; please ensure that the rulesets directory is on the classpath. The current classpath is: " + System.getProperty("java.class.path")); } } /** * Create a RuleSets from a comma separated list of RuleSet reference IDs. * This is a convenience method which calls * {@link RuleSetReferenceId#parse(String)}, and then calls * {@link #createRuleSets(List)}. The currently configured ClassLoader is * used. * * @param referenceString * A comma separated list of RuleSet reference IDs. * @return The new RuleSets. * @throws RuleSetNotFoundException * if unable to find a resource. */ public RuleSets createRuleSets(String referenceString) throws RuleSetNotFoundException { return createRuleSets(RuleSetReferenceId.parse(referenceString)); } /** * Create a RuleSets from a list of RuleSetReferenceIds. The currently * configured ClassLoader is used. * * @param ruleSetReferenceIds * The List of RuleSetReferenceId of the RuleSets to create. * @return The new RuleSets. * @throws RuleSetNotFoundException * if unable to find a resource. */ public RuleSets createRuleSets(List<RuleSetReferenceId> ruleSetReferenceIds) throws RuleSetNotFoundException { RuleSets ruleSets = new RuleSets(); for (RuleSetReferenceId ruleSetReferenceId : ruleSetReferenceIds) { RuleSet ruleSet = createRuleSet(ruleSetReferenceId); ruleSets.addRuleSet(ruleSet); } return ruleSets; } /** * Create a RuleSet from a RuleSet reference ID string. This is a * convenience method which calls {@link RuleSetReferenceId#parse(String)}, * gets the first item in the List, and then calls * {@link #createRuleSet(RuleSetReferenceId)}. The currently configured * ClassLoader is used. * * @param referenceString * A comma separated list of RuleSet reference IDs. * @return A new RuleSet. * @throws RuleSetNotFoundException * if unable to find a resource. */ public RuleSet createRuleSet(String referenceString) throws RuleSetNotFoundException { List<RuleSetReferenceId> references = RuleSetReferenceId.parse(referenceString); if (references.isEmpty()) { throw new RuleSetNotFoundException( "No RuleSetReferenceId can be parsed from the string: <" + referenceString + ">"); } return createRuleSet(references.get(0)); } /** * Create a RuleSet from a RuleSetReferenceId. Priority filtering is ignored * when loading a single Rule. The currently configured ClassLoader is used. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet to create. * @return A new RuleSet. * @throws RuleSetNotFoundException * if unable to find a resource. */ public RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceId) throws RuleSetNotFoundException { return createRuleSet(ruleSetReferenceId, false); } private RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceId, boolean withDeprecatedRuleReferences) throws RuleSetNotFoundException { return parseRuleSetNode(ruleSetReferenceId, withDeprecatedRuleReferences); } /** * Creates a copy of the given ruleset. All properties like name, description, fileName * and exclude/include patterns are copied. * * <p><strong>Note:</strong> The rule instances are shared between the original * and the new ruleset (copy-by-reference). This might lead to concurrency issues, * if the original ruleset and the new ruleset are used in different threads. * </p> * * @param original the original rule set to copy from * @return the copy */ public RuleSet createRuleSetCopy(RuleSet original) { RuleSetBuilder builder = new RuleSetBuilder(original); return builder.build(); } /** * Creates a new ruleset with the given metadata such as name, description, * fileName, exclude/include patterns are used. The rules are taken from the given * collection. * * <p><strong>Note:</strong> The rule instances are shared between the collection * and the new ruleset (copy-by-reference). This might lead to concurrency issues, * if the rules of the collection are also referenced by other rulesets and used * in different threads. * </p> * * @param name the name of the ruleset * @param description the description * @param fileName the filename * @param excludePatterns list of exclude patterns * @param includePatterns list of include patterns * @param rules the collection with the rules to add to the new ruleset * @return the new ruleset */ public RuleSet createNewRuleSet(String name, String description, String fileName, Collection<String> excludePatterns, Collection<String> includePatterns, Collection<Rule> rules) { RuleSetBuilder builder = new RuleSetBuilder(0L); // TODO: checksum missing builder.withName(name) .withDescription(description) .withFileName(fileName) .setExcludePatterns(excludePatterns) .setIncludePatterns(includePatterns); for (Rule rule : rules) { builder.addRule(rule); } return builder.build(); } /** * Creates a new RuleSet for a single rule * * @param rule * The rule being created * @return The newly created RuleSet */ public RuleSet createSingleRuleRuleSet(final Rule rule) { final long checksum; if (rule instanceof XPathRule) { checksum = rule.getProperty(XPathRule.XPATH_DESCRIPTOR).hashCode(); } else { // TODO : Is this good enough? all properties' values + rule name checksum = rule.getPropertiesByPropertyDescriptor().values().hashCode() * 31 + rule.getName().hashCode(); } final RuleSetBuilder builder = new RuleSetBuilder(checksum); builder.addRule(rule); return builder.build(); } /** * Create a Rule from a RuleSet created from a file name resource. The * currently configured ClassLoader is used. * <p> * Any Rules in the RuleSet other than the one being created, are _not_ * created. Deprecated rules are _not_ ignored, so that they can be * referenced. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet with the Rule to create. * @param withDeprecatedRuleReferences * Whether RuleReferences that are deprecated should be ignored * or not * @return A new Rule. * @throws RuleSetNotFoundException * if unable to find a resource. */ private Rule createRule(RuleSetReferenceId ruleSetReferenceId, boolean withDeprecatedRuleReferences) throws RuleSetNotFoundException { if (ruleSetReferenceId.isAllRules()) { throw new IllegalArgumentException( "Cannot parse a single Rule from an all Rule RuleSet reference: <" + ruleSetReferenceId + ">."); } RuleSet ruleSet = createRuleSet(ruleSetReferenceId, withDeprecatedRuleReferences); return ruleSet.getRuleByName(ruleSetReferenceId.getRuleName()); } /** * Parse a ruleset node to construct a RuleSet. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet being parsed. * @param withDeprecatedRuleReferences * whether rule references that are deprecated should be ignored * or not * @return The new RuleSet. */ private RuleSet parseRuleSetNode(RuleSetReferenceId ruleSetReferenceId, boolean withDeprecatedRuleReferences) throws RuleSetNotFoundException { try (CheckedInputStream inputStream = new CheckedInputStream( ruleSetReferenceId.getInputStream(this.classLoader), new Adler32());) { if (!ruleSetReferenceId.isExternal()) { throw new IllegalArgumentException( "Cannot parse a RuleSet from a non-external reference: <" + ruleSetReferenceId + ">."); } DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); InputSource inputSource; if (compatibilityFilter != null) { inputSource = new InputSource(compatibilityFilter.filterRuleSetFile(inputStream)); } else { inputSource = new InputSource(inputStream); } Document document = builder.parse(inputSource); Element ruleSetElement = document.getDocumentElement(); RuleSetBuilder ruleSetBuilder = new RuleSetBuilder(inputStream.getChecksum().getValue()) .withFileName(ruleSetReferenceId.getRuleSetFileName()) .withName(ruleSetElement.getAttribute("name")); NodeList nodeList = ruleSetElement.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { String nodeName = node.getNodeName(); if (DESCRIPTION.equals(nodeName)) { ruleSetBuilder.withDescription(parseTextNode(node)); } else if ("include-pattern".equals(nodeName)) { ruleSetBuilder.addIncludePattern(parseTextNode(node)); } else if ("exclude-pattern".equals(nodeName)) { ruleSetBuilder.addExcludePattern(parseTextNode(node)); } else if ("rule".equals(nodeName)) { parseRuleNode(ruleSetReferenceId, ruleSetBuilder, node, withDeprecatedRuleReferences); } else { throw new IllegalArgumentException(UNEXPECTED_ELEMENT + node.getNodeName() + "> encountered as child of <ruleset> element."); } } } return ruleSetBuilder.build(); } catch (ClassNotFoundException cnfe) { return classNotFoundProblem(cnfe); } catch (InstantiationException ie) { return classNotFoundProblem(ie); } catch (IllegalAccessException iae) { return classNotFoundProblem(iae); } catch (ParserConfigurationException pce) { return classNotFoundProblem(pce); } catch (IOException ioe) { return classNotFoundProblem(ioe); } catch (SAXException se) { return classNotFoundProblem(se); } } private static RuleSet classNotFoundProblem(Exception ex) { ex.printStackTrace(); throw new RuntimeException("Couldn't find the class " + ex.getMessage()); } /** * Parse a rule node. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet being parsed. * @param ruleSetBuilder * The RuleSet being constructed. * @param ruleNode * Must be a rule element node. * @param withDeprecatedRuleReferences * whether rule references that are deprecated should be ignored * or not */ private void parseRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder ruleSetBuilder, Node ruleNode, boolean withDeprecatedRuleReferences) throws ClassNotFoundException, InstantiationException, IllegalAccessException, RuleSetNotFoundException { Element ruleElement = (Element) ruleNode; String ref = ruleElement.getAttribute("ref"); if (ref.endsWith("xml")) { parseRuleSetReferenceNode(ruleSetReferenceId, ruleSetBuilder, ruleElement, ref); } else if (StringUtil.isEmpty(ref)) { parseSingleRuleNode(ruleSetReferenceId, ruleSetBuilder, ruleNode); } else { parseRuleReferenceNode(ruleSetReferenceId, ruleSetBuilder, ruleNode, ref, withDeprecatedRuleReferences); } } /** * Parse a rule node as an RuleSetReference for all Rules. Every Rule from * the referred to RuleSet will be added as a RuleReference except for those * explicitly excluded, below the minimum priority threshold for this * RuleSetFactory, or which are deprecated. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet being parsed. * @param ruleSetBuilder * The RuleSet being constructed. * @param ruleElement * Must be a rule element node. * @param ref * The RuleSet reference. */ private void parseRuleSetReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder ruleSetBuilder, Element ruleElement, String ref) throws RuleSetNotFoundException { RuleSetReference ruleSetReference = new RuleSetReference(); ruleSetReference.setAllRules(true); ruleSetReference.setRuleSetFileName(ref); String priority = null; NodeList childNodes = ruleElement.getChildNodes(); Set<String> excludedRulesCheck = new HashSet<>(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (isElementNode(child, "exclude")) { Element excludeElement = (Element) child; String excludedRuleName = excludeElement.getAttribute("name"); ruleSetReference.addExclude(excludedRuleName); excludedRulesCheck.add(excludedRuleName); } else if (isElementNode(child, PRIORITY)) { priority = parseTextNode(child).trim(); } } RuleSetFactory ruleSetFactory = new RuleSetFactory(this, warnDeprecated); RuleSet otherRuleSet = ruleSetFactory.createRuleSet(RuleSetReferenceId.parse(ref).get(0)); for (Rule rule : otherRuleSet.getRules()) { excludedRulesCheck.remove(rule.getName()); if (!ruleSetReference.getExcludes().contains(rule.getName()) && !rule.isDeprecated()) { RuleReference ruleReference = new RuleReference(); ruleReference.setRuleSetReference(ruleSetReference); ruleReference.setRule(rule); ruleSetBuilder.addRuleIfNotExists(ruleReference); // override the priority if (priority != null) { ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(priority))); } } } if (!excludedRulesCheck.isEmpty()) { throw new IllegalArgumentException( "Unable to exclude rules " + excludedRulesCheck + "; perhaps the rule name is mispelled?"); } } /** * Parse a rule node as a single Rule. The Rule has been fully defined * within the context of the current RuleSet. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet being parsed. * @param ruleSet * The RuleSet being constructed. * @param ruleNode * Must be a rule element node. * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet being parsed. * @param ruleSetBuilder * The RuleSet being constructed. * @param ruleNode * Must be a rule element node. */ private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder ruleSetBuilder, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Element ruleElement = (Element) ruleNode; // Stop if we're looking for a particular Rule, and this element is not // it. if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName()) && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) { return; } String attribute = ruleElement.getAttribute("class"); if (attribute == null || "".equals(attribute)) { throw new IllegalArgumentException("The 'class' field of rule can't be null, nor empty."); } Rule rule = (Rule) classLoader.loadClass(attribute).newInstance(); rule.setName(ruleElement.getAttribute("name")); if (ruleElement.hasAttribute("language")) { String languageName = ruleElement.getAttribute("language"); Language language = LanguageRegistry.findLanguageByTerseName(languageName); if (language == null) { throw new IllegalArgumentException("Unknown Language '" + languageName + FOR_RULE + rule.getName() + ", supported Languages are " + LanguageRegistry.commaSeparatedTerseNamesForLanguage(LanguageRegistry.findWithRuleSupport())); } rule.setLanguage(language); } Language language = rule.getLanguage(); if (language == null) { throw new IllegalArgumentException( "Rule " + rule.getName() + " does not have a Language; missing 'language' attribute?"); } if (ruleElement.hasAttribute("minimumLanguageVersion")) { String minimumLanguageVersionName = ruleElement.getAttribute("minimumLanguageVersion"); LanguageVersion minimumLanguageVersion = language.getVersion(minimumLanguageVersionName); if (minimumLanguageVersion == null) { throw new IllegalArgumentException("Unknown minimum Language Version '" + minimumLanguageVersionName + "' for Language '" + language.getTerseName() + FOR_RULE + rule.getName() + "; supported Language Versions are: " + LanguageRegistry.commaSeparatedTerseNamesForLanguageVersion(language.getVersions())); } rule.setMinimumLanguageVersion(minimumLanguageVersion); } if (ruleElement.hasAttribute("maximumLanguageVersion")) { String maximumLanguageVersionName = ruleElement.getAttribute("maximumLanguageVersion"); LanguageVersion maximumLanguageVersion = language.getVersion(maximumLanguageVersionName); if (maximumLanguageVersion == null) { throw new IllegalArgumentException("Unknown maximum Language Version '" + maximumLanguageVersionName + "' for Language '" + language.getTerseName() + FOR_RULE + rule.getName() + "; supported Language Versions are: " + LanguageRegistry.commaSeparatedTerseNamesForLanguageVersion(language.getVersions())); } rule.setMaximumLanguageVersion(maximumLanguageVersion); } if (rule.getMinimumLanguageVersion() != null && rule.getMaximumLanguageVersion() != null) { throw new IllegalArgumentException( "The minimum Language Version '" + rule.getMinimumLanguageVersion().getTerseName() + "' must be prior to the maximum Language Version '" + rule.getMaximumLanguageVersion().getTerseName() + FOR_RULE + rule.getName() + "; perhaps swap them around?"); } String since = ruleElement.getAttribute("since"); if (StringUtil.isNotEmpty(since)) { rule.setSince(since); } rule.setMessage(ruleElement.getAttribute(MESSAGE)); rule.setRuleSetName(ruleSetBuilder.getName()); rule.setExternalInfoUrl(ruleElement.getAttribute(EXTERNAL_INFO_URL)); if (hasAttributeSetTrue(ruleElement, "dfa")) { rule.setUsesDFA(); } if (hasAttributeSetTrue(ruleElement, "typeResolution")) { rule.setUsesTypeResolution(); } final NodeList nodeList = ruleElement.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } String nodeName = node.getNodeName(); if (DESCRIPTION.equals(nodeName)) { rule.setDescription(parseTextNode(node)); } else if ("example".equals(nodeName)) { rule.addExample(parseTextNode(node)); } else if (PRIORITY.equals(nodeName)) { rule.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node).trim()))); } else if ("properties".equals(nodeName)) { parsePropertiesNode(rule, node); } else { throw new IllegalArgumentException(UNEXPECTED_ELEMENT + nodeName + "> encountered as child of <rule> element for Rule " + rule.getName()); } } if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName()) || rule.getPriority().compareTo(minimumPriority) <= 0) { ruleSetBuilder.addRule(rule); } } private static boolean hasAttributeSetTrue(Element element, String attributeId) { return element.hasAttribute(attributeId) && "true".equalsIgnoreCase(element.getAttribute(attributeId)); } /** * Parse a rule node as a RuleReference. A RuleReference is a single Rule * which comes from another RuleSet with some of it's attributes potentially * overridden. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet being parsed. * @param ruleSetBuilder * The RuleSet being constructed. * @param ruleNode * Must be a rule element node. * @param ref * A reference to a Rule. * @param withDeprecatedRuleReferences * whether rule references that are deprecated should be ignored * or not */ private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder ruleSetBuilder, Node ruleNode, String ref, boolean withDeprecatedRuleReferences) throws RuleSetNotFoundException { Element ruleElement = (Element) ruleNode; // Stop if we're looking for a particular Rule, and this element is not // it. if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName()) && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) { return; } RuleSetFactory ruleSetFactory = new RuleSetFactory(this, warnDeprecated); boolean isSameRuleSet = false; RuleSetReferenceId otherRuleSetReferenceId = RuleSetReferenceId.parse(ref).get(0); if (!otherRuleSetReferenceId.isExternal() && containsRule(ruleSetReferenceId, otherRuleSetReferenceId.getRuleName())) { otherRuleSetReferenceId = new RuleSetReferenceId(ref, ruleSetReferenceId); isSameRuleSet = true; } // do not ignore deprecated rule references Rule referencedRule = ruleSetFactory.createRule(otherRuleSetReferenceId, true); if (referencedRule == null) { throw new IllegalArgumentException("Unable to find referenced rule " + otherRuleSetReferenceId.getRuleName() + "; perhaps the rule name is mispelled?"); } if (warnDeprecated && referencedRule.isDeprecated()) { if (referencedRule instanceof RuleReference) { RuleReference ruleReference = (RuleReference) referencedRule; if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + ruleReference.getOriginalName() + " instead of the deprecated Rule name " + otherRuleSetReferenceId + ". Future versions of PMD will remove support for this deprecated Rule name usage."); } } else if (referencedRule instanceof MockRule) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId + " as it has been removed from PMD and no longer functions." + " Future versions of PMD will remove support for this Rule."); } } else { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId + " as it is scheduled for removal from PMD." + " Future versions of PMD will remove support for this Rule."); } } } RuleSetReference ruleSetReference = new RuleSetReference(); ruleSetReference.setAllRules(false); ruleSetReference.setRuleSetFileName(otherRuleSetReferenceId.getRuleSetFileName()); RuleReference ruleReference = new RuleReference(); ruleReference.setRuleSetReference(ruleSetReference); ruleReference.setRule(referencedRule); if (ruleElement.hasAttribute("deprecated")) { ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute("deprecated"))); } if (ruleElement.hasAttribute("name")) { ruleReference.setName(ruleElement.getAttribute("name")); } if (ruleElement.hasAttribute(MESSAGE)) { ruleReference.setMessage(ruleElement.getAttribute(MESSAGE)); } if (ruleElement.hasAttribute(EXTERNAL_INFO_URL)) { ruleReference.setExternalInfoUrl(ruleElement.getAttribute(EXTERNAL_INFO_URL)); } for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) { Node node = ruleElement.getChildNodes().item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { if (node.getNodeName().equals(DESCRIPTION)) { ruleReference.setDescription(parseTextNode(node)); } else if (node.getNodeName().equals("example")) { ruleReference.addExample(parseTextNode(node)); } else if (node.getNodeName().equals(PRIORITY)) { ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node)))); } else if (node.getNodeName().equals("properties")) { parsePropertiesNode(ruleReference, node); } else { throw new IllegalArgumentException(UNEXPECTED_ELEMENT + node.getNodeName() + "> encountered as child of <rule> element for Rule " + ruleReference.getName()); } } } if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName()) || referencedRule.getPriority().compareTo(minimumPriority) <= 0) { if (withDeprecatedRuleReferences || !isSameRuleSet || !ruleReference.isDeprecated()) { ruleSetBuilder.addRuleReplaceIfExists(ruleReference); } } } /** * Check whether the given ruleName is contained in the given ruleset. * * @param ruleSetReferenceId * the ruleset to check * @param ruleName * the rule name to search for * @return <code>true</code> if the ruleName exists */ private boolean containsRule(RuleSetReferenceId ruleSetReferenceId, String ruleName) { boolean found = false; try (InputStream ruleSet = ruleSetReferenceId.getInputStream(classLoader)) { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document document = builder.parse(ruleSet); Element ruleSetElement = document.getDocumentElement(); NodeList rules = ruleSetElement.getElementsByTagName("rule"); for (int i = 0; i < rules.getLength(); i++) { Element rule = (Element) rules.item(i); if (rule.hasAttribute("name") && rule.getAttribute("name").equals(ruleName)) { found = true; break; } } } catch (Exception e) { throw new RuntimeException(e); } return found; } private static boolean isElementNode(Node node, String name) { return node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name); } /** * Parse a properties node. * * @param rule * The Rule to which the properties should be added. * @param propertiesNode * Must be a properties element node. */ private static void parsePropertiesNode(Rule rule, Node propertiesNode) { for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) { Node node = propertiesNode.getChildNodes().item(i); if (isElementNode(node, "property")) { parsePropertyNodeBR(rule, node); } } } private static String valueFrom(Node parentNode) { final NodeList nodeList = parentNode.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (isElementNode(node, "value")) { return parseTextNode(node); } } return null; } /** * Parse a property node. * * @param rule * The Rule to which the property should be added. //@param * propertyNode Must be a property element node. */ // private static void parsePropertyNode(Rule rule, Node propertyNode) { // Element propertyElement = (Element) propertyNode; // String name = propertyElement.getAttribute("name"); // String description = propertyElement.getAttribute("description"); // String type = propertyElement.getAttribute("type"); // String delimiter = propertyElement.getAttribute("delimiter"); // String min = propertyElement.getAttribute("min"); // String max = propertyElement.getAttribute("max"); // String value = propertyElement.getAttribute("value"); // // // If value not provided, get from child <value> element. // if (StringUtil.isEmpty(value)) { // for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) { // Node node = propertyNode.getChildNodes().item(i); // if ((node.getNodeType() == Node.ELEMENT_NODE) && // node.getNodeName().equals("value")) { // value = parseTextNode(node); // } // } // } // // // Setting of existing property, or defining a new property? // if (StringUtil.isEmpty(type)) { // PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(name); // if (propertyDescriptor == null) { // throw new IllegalArgumentException("Cannot set non-existant property '" + // name + "' on Rule " + rule.getName()); // } else { // Object realValue = propertyDescriptor.valueFrom(value); // rule.setProperty(propertyDescriptor, realValue); // } // } else { // PropertyDescriptor propertyDescriptor = // PropertyDescriptorFactory.createPropertyDescriptor(name, description, // type, delimiter, min, max, value); // rule.definePropertyDescriptor(propertyDescriptor); // } // } private static <T> void setValue(Rule rule, PropertyDescriptor<T> desc, String strValue) { T realValue = desc.valueFrom(strValue); rule.setProperty(desc, realValue); } private static void parsePropertyNodeBR(Rule rule, Node propertyNode) { Element propertyElement = (Element) propertyNode; String typeId = propertyElement.getAttribute(PropertyDescriptorFields.TYPE); String strValue = propertyElement.getAttribute(PropertyDescriptorFields.VALUE); if (StringUtil.isEmpty(strValue)) { strValue = valueFrom(propertyElement); } // Setting of existing property, or defining a new property? if (StringUtil.isEmpty(typeId)) { String name = propertyElement.getAttribute(PropertyDescriptorFields.NAME); PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name); if (propertyDescriptor == null) { throw new IllegalArgumentException( "Cannot set non-existant property '" + name + "' on Rule " + rule.getName()); } else { setValue(rule, propertyDescriptor, strValue); } return; } net.sourceforge.pmd.PropertyDescriptorFactory pdFactory = PropertyDescriptorUtil.factoryFor(typeId); if (pdFactory == null) { throw new RuntimeException("No property descriptor factory for type: " + typeId); } Map<String, Boolean> valueKeys = pdFactory.expectedFields(); Map<String, String> values = new HashMap<>(valueKeys.size()); // populate a map of values for an individual descriptor for (Map.Entry<String, Boolean> entry : valueKeys.entrySet()) { String valueStr = propertyElement.getAttribute(entry.getKey()); if (entry.getValue() && StringUtil.isEmpty(valueStr)) { // TODO debug pt System.out.println("Missing required value for: " + entry.getKey()); } values.put(entry.getKey(), valueStr); } PropertyDescriptor<?> desc = pdFactory.createWith(values); PropertyDescriptorWrapper<?> wrapper = new PropertyDescriptorWrapper<>(desc); rule.definePropertyDescriptor(wrapper); setValue(rule, desc, strValue); } /** * Parse a String from a textually type node. * * @param node * The node. * @return The String. */ private static String parseTextNode(Node node) { final int nodeCount = node.getChildNodes().getLength(); if (nodeCount == 0) { return ""; } StringBuilder buffer = new StringBuilder(); for (int i = 0; i < nodeCount; i++) { Node childNode = node.getChildNodes().item(i); if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) { buffer.append(childNode.getNodeValue()); } } return buffer.toString(); } /** * Determine if the specified rule element will represent a Rule with the * given name. * * @param ruleElement * The rule element. * @param ruleName * The Rule name. * @return <code>true</code> if the Rule would have the given name, * <code>false</code> otherwise. */ private boolean isRuleName(Element ruleElement, String ruleName) { if (ruleElement.hasAttribute("name")) { return ruleElement.getAttribute("name").equals(ruleName); } else if (ruleElement.hasAttribute("ref")) { RuleSetReferenceId ruleSetReferenceId = RuleSetReferenceId.parse(ruleElement.getAttribute("ref")).get(0); return ruleSetReferenceId.getRuleName() != null && ruleSetReferenceId.getRuleName().equals(ruleName); } else { return false; } } }