/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.rule; import static net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery.XPATH_1_0; import static net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery.XPATH_1_0_COMPATIBILITY; import static net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery.XPATH_2_0; import java.util.List; import net.sourceforge.pmd.PropertySource; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty; import net.sourceforge.pmd.lang.rule.properties.StringProperty; import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; import net.sourceforge.pmd.util.StringUtil; /** * Rule that tries to match an XPath expression against a DOM view of an AST. * * This rule needs a "xpath" property value in order to function. */ public class XPathRule extends AbstractRule { public static final StringProperty XPATH_DESCRIPTOR = new StringProperty("xpath", "XPath expression", "", 1.0f); public static final EnumeratedProperty<String> VERSION_DESCRIPTOR = new EnumeratedProperty<>("version", "XPath specification version", new String[] { XPATH_1_0, XPATH_1_0_COMPATIBILITY, XPATH_2_0 }, new String[] { XPATH_1_0, XPATH_1_0_COMPATIBILITY, XPATH_2_0 }, 0, 2.0f); private XPathRuleQuery xpathRuleQuery; public XPathRule() { definePropertyDescriptor(XPATH_DESCRIPTOR); definePropertyDescriptor(VERSION_DESCRIPTOR); } public XPathRule(String xPath) { this(); setXPath(xPath); } public void setXPath(String xPath) { setProperty(XPathRule.XPATH_DESCRIPTOR, xPath); } public void setVersion(String version) { setProperty(XPathRule.VERSION_DESCRIPTOR, version); } /** * Apply the rule to all nodes. */ @Override public void apply(List<? extends Node> nodes, RuleContext ctx) { for (Node node : nodes) { evaluate(node, ctx); } } /** * Evaluate the XPath query with the AST node. All matches are reported as * violations. * * @param node * The Node that to be checked. * @param data * The RuleContext. */ public void evaluate(Node node, RuleContext data) { init(); List<Node> nodes = xpathRuleQuery.evaluate(node, data); if (nodes != null) { for (Node n : nodes) { addViolation(data, n, n.getImage()); } } } @Override public List<String> getRuleChainVisits() { if (init()) { for (String nodeName : xpathRuleQuery.getRuleChainVisits()) { super.addRuleChainVisit(nodeName); } } return super.getRuleChainVisits(); } private boolean init() { if (xpathRuleQuery == null) { String xpath = getProperty(XPATH_DESCRIPTOR); String version = (String) getProperty(VERSION_DESCRIPTOR); if (XPATH_1_0.equals(version)) { xpathRuleQuery = new JaxenXPathRuleQuery(); } else { xpathRuleQuery = new SaxonXPathRuleQuery(); } xpathRuleQuery.setXPath(xpath); xpathRuleQuery.setVersion(version); xpathRuleQuery.setProperties(this.getPropertiesByPropertyDescriptor()); return true; } return false; } public boolean hasXPathExpression() { String xPath = getProperty(XPATH_DESCRIPTOR); return StringUtil.isNotEmpty(xPath); } /** * @see PropertySource#dysfunctionReason() */ @Override public String dysfunctionReason() { return hasXPathExpression() ? null : "Missing xPath expression"; } }