View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.rule.xpath;
5   
6   import java.util.ArrayList;
7   import java.util.HashMap;
8   import java.util.List;
9   import java.util.Map;
10  
11  import net.sf.saxon.om.ValueRepresentation;
12  import net.sf.saxon.sxpath.AbstractStaticContext;
13  import net.sf.saxon.sxpath.IndependentContext;
14  import net.sf.saxon.sxpath.XPathDynamicContext;
15  import net.sf.saxon.sxpath.XPathEvaluator;
16  import net.sf.saxon.sxpath.XPathExpression;
17  import net.sf.saxon.sxpath.XPathStaticContext;
18  import net.sf.saxon.sxpath.XPathVariable;
19  import net.sf.saxon.trans.XPathException;
20  import net.sf.saxon.value.BooleanValue;
21  import net.sf.saxon.value.Int64Value;
22  import net.sf.saxon.value.StringValue;
23  import net.sourceforge.pmd.PropertyDescriptor;
24  import net.sourceforge.pmd.RuleContext;
25  import net.sourceforge.pmd.lang.ast.Node;
26  import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode;
27  import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode;
28  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
29  import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty;
30  import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
31  import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper;
32  import net.sourceforge.pmd.lang.rule.properties.StringProperty;
33  import net.sourceforge.pmd.lang.xpath.Initializer;
34  
35  /**
36   * This is a Saxon based XPathRule query.
37   */
38  public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery {
39  
40      // Mapping from Node name to applicable XPath queries
41      private XPathExpression xpathExpression;
42      private List<XPathVariable> xpathVariables;
43  
44      /**
45       * {@inheritDoc}
46       */
47      @Override
48      public boolean isSupportedVersion(String version) {
49          return XPATH_1_0_COMPATIBILITY.equals(version) || XPATH_2_0.equals(version);
50      }
51  
52      /**
53       * {@inheritDoc}
54       */
55      @Override
56      @SuppressWarnings("unchecked")
57      public List<Node> evaluate(Node node, RuleContext data) {
58          initializeXPathExpression();
59  
60          List<Node> results = new ArrayList<Node>();
61          try {
62              // Get the DocumentNode for the AST
63              DocumentNode documentNode = getDocumentNode(node);
64  
65              // Get the corresponding ElementNode for this node.
66              ElementNode rootElementNode = documentNode.nodeToElementNode.get(node);
67  
68              // Create a dynamic context for this node
69              XPathDynamicContext xpathDynamicContext = xpathExpression.createDynamicContext(rootElementNode);
70  
71              // Set variable values on the dynamic context
72              for (XPathVariable xpathVariable : xpathVariables) {
73                  String name = xpathVariable.getVariableQName().getLocalName();
74                  for (Map.Entry<PropertyDescriptor<?>, Object> entry : super.properties.entrySet()) {
75                      if (name.equals(entry.getKey().name())) {
76                          PropertyDescriptor<?> propertyDescriptor = entry.getKey();
77                          if (propertyDescriptor instanceof PropertyDescriptorWrapper) {
78                              propertyDescriptor = ((PropertyDescriptorWrapper) propertyDescriptor)
79                                      .getPropertyDescriptor();
80                          }
81                          Object value = entry.getValue();
82                          ValueRepresentation valueRepresentation;
83  
84                          // TODO Need to handle null values?
85                          // TODO Need to handle more PropertyDescriptors, is
86                          // there an easy factory in Saxon we can use for this?
87                          if (propertyDescriptor instanceof StringProperty) {
88                              valueRepresentation = new StringValue((String) value);
89                          } else if (propertyDescriptor instanceof BooleanProperty) {
90                              valueRepresentation = BooleanValue.get(((Boolean) value).booleanValue());
91                          } else if (propertyDescriptor instanceof IntegerProperty) {
92                              valueRepresentation = Int64Value.makeIntegerValue((Integer) value);
93                          } else if (propertyDescriptor instanceof EnumeratedProperty) {
94                              if (value instanceof String) {
95                                  valueRepresentation = new StringValue((String) value);
96                              } else {
97                                  throw new RuntimeException(
98                                          "Unable to create ValueRepresentaton for non-String EnumeratedProperty value: "
99                                                  + value);
100                             }
101                         } else {
102                             throw new RuntimeException("Unable to create ValueRepresentaton for PropertyDescriptor: "
103                                     + propertyDescriptor);
104                         }
105                         xpathDynamicContext.setVariable(xpathVariable, valueRepresentation);
106                     }
107                 }
108             }
109 
110             List<ElementNode> nodes = xpathExpression.evaluate(xpathDynamicContext);
111             for (ElementNode elementNode : nodes) {
112                 results.add((Node) elementNode.getUnderlyingNode());
113             }
114         } catch (XPathException e) {
115             throw new RuntimeException(super.xpath + " had problem: " + e.getMessage(), e);
116         }
117         return results;
118     }
119 
120     private static final Map<Node, DocumentNode> CACHE = new HashMap<Node, DocumentNode>();
121 
122     private DocumentNode getDocumentNode(Node node) {
123         // Get the root AST node
124         Node root = node;
125         while (root.jjtGetParent() != null) {
126             root = root.jjtGetParent();
127         }
128 
129         // Cache DocumentNode trees, so that different XPath queries can re-use
130         // them.
131         // Ideally this would be an LRU cache.
132         DocumentNode documentNode;
133         synchronized (CACHE) {
134             documentNode = CACHE.get(root);
135             if (documentNode == null) {
136                 documentNode = new DocumentNode(root);
137                 if (CACHE.size() > 20) {
138                     CACHE.clear();
139                 }
140                 CACHE.put(root, documentNode);
141             }
142         }
143         return documentNode;
144     }
145 
146     private void initializeXPathExpression() {
147         if (xpathExpression != null) {
148             return;
149         }
150         try {
151             XPathEvaluator xpathEvaluator = new XPathEvaluator();
152             XPathStaticContext xpathStaticContext = xpathEvaluator.getStaticContext();
153 
154             // Enable XPath 1.0 compatibility
155             if (XPATH_1_0_COMPATIBILITY.equals(version)) {
156                 ((AbstractStaticContext) xpathStaticContext).setBackwardsCompatibilityMode(true);
157             }
158 
159             // Register PMD functions
160             Initializer.initialize((IndependentContext) xpathStaticContext);
161 
162             // Create XPathVariables for later use. It is a Saxon quirk that
163             // XPathVariables must be defined on the static context, and
164             // reused later to associate an actual value on the dynamic context.
165             xpathVariables = new ArrayList<XPathVariable>();
166             for (PropertyDescriptor<?> propertyDescriptor : super.properties.keySet()) {
167                 String name = propertyDescriptor.name();
168                 if (!"xpath".equals(name)) {
169                     XPathVariable xpathVariable = xpathStaticContext.declareVariable(null, name);
170                     xpathVariables.add(xpathVariable);
171                 }
172             }
173 
174             // TODO Come up with a way to make use of RuleChain. I had hacked up
175             // an approach which used Jaxen's stuff, but that only works for
176             // 1.0 compatibility mode. Rather do it right instead of kludging.
177             xpathExpression = xpathEvaluator.createExpression(super.xpath);
178         } catch (XPathException e) {
179             throw new RuntimeException(e);
180         }
181     }
182 }