SaxonXPathRuleQuery.java
001 package net.sourceforge.pmd.lang.rule.xpath;
002 
003 import java.util.ArrayList;
004 import java.util.HashMap;
005 import java.util.List;
006 import java.util.Map;
007 
008 import net.sf.saxon.om.ValueRepresentation;
009 import net.sf.saxon.sxpath.AbstractStaticContext;
010 import net.sf.saxon.sxpath.IndependentContext;
011 import net.sf.saxon.sxpath.XPathDynamicContext;
012 import net.sf.saxon.sxpath.XPathEvaluator;
013 import net.sf.saxon.sxpath.XPathExpression;
014 import net.sf.saxon.sxpath.XPathStaticContext;
015 import net.sf.saxon.sxpath.XPathVariable;
016 import net.sf.saxon.trans.XPathException;
017 import net.sf.saxon.value.BooleanValue;
018 import net.sf.saxon.value.Int64Value;
019 import net.sf.saxon.value.StringValue;
020 import net.sourceforge.pmd.PropertyDescriptor;
021 import net.sourceforge.pmd.RuleContext;
022 import net.sourceforge.pmd.lang.ast.Node;
023 import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode;
024 import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode;
025 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
026 import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty;
027 import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
028 import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper;
029 import net.sourceforge.pmd.lang.rule.properties.StringProperty;
030 import net.sourceforge.pmd.lang.xpath.Initializer;
031 
032 /**
033  * This is a Saxon based XPathRule query.
034  */
035 public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery {
036 
037     // Mapping from Node name to applicable XPath queries
038     private XPathExpression xpathExpression;
039     private List<XPathVariable> xpathVariables;
040 
041     /**
042      * {@inheritDoc}
043      */
044     @Override
045     public boolean isSupportedVersion(String version) {
046   return XPATH_1_0_COMPATIBILITY.equals(version|| XPATH_2_0.equals(version);
047     }
048 
049     /**
050      * {@inheritDoc}
051      */
052     @Override
053     @SuppressWarnings("unchecked")
054     public List<Node> evaluate(Node node, RuleContext data) {
055   initializeXPathExpression();
056 
057   List<Node> results = new ArrayList<Node>();
058   try {
059       // Get the DocumentNode for the AST
060       DocumentNode documentNode = getDocumentNode(node);
061 
062       // Get the corresponding ElementNode for this node.
063       ElementNode rootElementNode = documentNode.nodeToElementNode.get(node);
064 
065       // Create a dynamic context for this node
066       XPathDynamicContext xpathDynamicContext = xpathExpression.createDynamicContext(rootElementNode);
067 
068       // Set variable values on the dynamic context
069       for (XPathVariable xpathVariable : xpathVariables) {
070     String name = xpathVariable.getVariableQName().getLocalName();
071     for (Map.Entry<PropertyDescriptor<?>, Object> entry : super.properties.entrySet()) {
072         if (name.equals(entry.getKey().name())) {
073       PropertyDescriptor<?> propertyDescriptor = entry.getKey();
074       if (propertyDescriptor instanceof PropertyDescriptorWrapper) {
075           propertyDescriptor = ((PropertyDescriptorWrapperpropertyDescriptor)
076             .getPropertyDescriptor();
077       }
078       Object value = entry.getValue();
079       ValueRepresentation valueRepresentation;
080 
081       // TODO Need to handle null values?
082       // TODO Need to handle more PropertyDescriptors, is there an easy factory in Saxon we can use for this?
083       if (propertyDescriptor instanceof StringProperty) {
084           valueRepresentation = new StringValue((Stringvalue);
085       else if (propertyDescriptor instanceof BooleanProperty) {
086           valueRepresentation = BooleanValue.get(((Booleanvalue).booleanValue());
087       else if (propertyDescriptor instanceof IntegerProperty) {
088           valueRepresentation = Int64Value.makeIntegerValue((Integervalue);
089       else if (propertyDescriptor instanceof EnumeratedProperty) {
090           if (value instanceof String) {
091         valueRepresentation = new StringValue((Stringvalue);
092           else {
093         throw new RuntimeException(
094           "Unable to create ValueRepresentaton for non-String EnumeratedProperty value: "
095             + value);
096           }
097       else {
098           throw new RuntimeException("Unable to create ValueRepresentaton for PropertyDescriptor: "
099             + propertyDescriptor);
100       }
101       xpathDynamicContext.setVariable(xpathVariable, valueRepresentation);
102         }
103     }
104       }
105 
106       List<ElementNode> nodes = xpathExpression.evaluate(xpathDynamicContext);
107       for (ElementNode elementNode : nodes) {
108     results.add((NodeelementNode.getUnderlyingNode());
109       }
110   catch (XPathException e) {
111       throw new RuntimeException(super.xpath + " had problem: " + e.getMessage(), e);
112   }
113   return results;
114     }
115 
116     private static final Map<Node, DocumentNode> CACHE = new HashMap<Node, DocumentNode>();
117 
118     private DocumentNode getDocumentNode(Node node) {
119   // Get the root AST node
120   Node root = node;
121   while (root.jjtGetParent() != null) {
122       root = root.jjtGetParent();
123   }
124 
125   // Cache DocumentNode trees, so that different XPath queries can re-use them.
126   // Ideally this would be an LRU cache.
127   DocumentNode documentNode;
128   synchronized (CACHE) {
129       documentNode = CACHE.get(root);
130       if (documentNode == null) {
131     documentNode = new DocumentNode(root);
132     if (CACHE.size() 20) {
133         CACHE.clear();
134     }
135     CACHE.put(root, documentNode);
136       }
137   }
138   return documentNode;
139     }
140 
141     private void initializeXPathExpression() {
142   if (xpathExpression != null) {
143       return;
144   }
145   try {
146       XPathEvaluator xpathEvaluator = new XPathEvaluator();
147       XPathStaticContext xpathStaticContext = xpathEvaluator.getStaticContext();
148 
149       // Enable XPath 1.0 compatibility
150       if (XPATH_1_0_COMPATIBILITY.equals(version)) {
151     ((AbstractStaticContextxpathStaticContext).setBackwardsCompatibilityMode(true);
152       }
153 
154       // Register PMD functions
155       Initializer.initialize((IndependentContextxpathStaticContext);
156 
157       // Create XPathVariables for later use.  It is a Saxon quirk that
158       // XPathVariables must be defined on the static context, and
159       // reused later to associate an actual value on the dynamic context.
160       xpathVariables = new ArrayList<XPathVariable>();
161       for (PropertyDescriptor<?> propertyDescriptor : super.properties.keySet()) {
162     String name = propertyDescriptor.name();
163     if (!"xpath".equals(name)) {
164         XPathVariable xpathVariable = xpathStaticContext.declareVariable(null, name);
165         xpathVariables.add(xpathVariable);
166     }
167       }
168 
169       // TODO Come up with a way to make use of RuleChain.  I had hacked up
170       // an approach which used Jaxen's stuff, but that only works for
171       // 1.0 compatibility mode.  Rather do it right instead of kludging.
172       xpathExpression = xpathEvaluator.createExpression(super.xpath);
173   catch (XPathException e) {
174       throw new RuntimeException(e);
175   }
176     }
177 }