View Javadoc

1   package net.sourceforge.pmd.lang.rule.xpath;
2   
3   import java.util.ArrayList;
4   import java.util.HashMap;
5   import java.util.List;
6   import java.util.Map;
7   
8   import net.sf.saxon.om.ValueRepresentation;
9   import net.sf.saxon.sxpath.AbstractStaticContext;
10  import net.sf.saxon.sxpath.IndependentContext;
11  import net.sf.saxon.sxpath.XPathDynamicContext;
12  import net.sf.saxon.sxpath.XPathEvaluator;
13  import net.sf.saxon.sxpath.XPathExpression;
14  import net.sf.saxon.sxpath.XPathStaticContext;
15  import net.sf.saxon.sxpath.XPathVariable;
16  import net.sf.saxon.trans.XPathException;
17  import net.sf.saxon.value.BooleanValue;
18  import net.sf.saxon.value.Int64Value;
19  import net.sf.saxon.value.StringValue;
20  import net.sourceforge.pmd.PropertyDescriptor;
21  import net.sourceforge.pmd.RuleContext;
22  import net.sourceforge.pmd.lang.ast.Node;
23  import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode;
24  import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode;
25  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
26  import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty;
27  import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
28  import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper;
29  import net.sourceforge.pmd.lang.rule.properties.StringProperty;
30  import net.sourceforge.pmd.lang.xpath.Initializer;
31  
32  /**
33   * This is a Saxon based XPathRule query.
34   */
35  public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery {
36  
37      // Mapping from Node name to applicable XPath queries
38      private XPathExpression xpathExpression;
39      private List<XPathVariable> xpathVariables;
40  
41      /**
42       * {@inheritDoc}
43       */
44      @Override
45      public boolean isSupportedVersion(String version) {
46  	return XPATH_1_0_COMPATIBILITY.equals(version) || XPATH_2_0.equals(version);
47      }
48  
49      /**
50       * {@inheritDoc}
51       */
52      @Override
53      @SuppressWarnings("unchecked")
54      public List<Node> evaluate(Node node, RuleContext data) {
55  	initializeXPathExpression();
56  
57  	List<Node> results = new ArrayList<Node>();
58  	try {
59  	    // Get the DocumentNode for the AST
60  	    DocumentNode documentNode = getDocumentNode(node);
61  
62  	    // Get the corresponding ElementNode for this node.
63  	    ElementNode rootElementNode = documentNode.nodeToElementNode.get(node);
64  
65  	    // Create a dynamic context for this node
66  	    XPathDynamicContext xpathDynamicContext = xpathExpression.createDynamicContext(rootElementNode);
67  
68  	    // Set variable values on the dynamic context
69  	    for (XPathVariable xpathVariable : xpathVariables) {
70  		String name = xpathVariable.getVariableQName().getLocalName();
71  		for (Map.Entry<PropertyDescriptor<?>, Object> entry : super.properties.entrySet()) {
72  		    if (name.equals(entry.getKey().name())) {
73  			PropertyDescriptor<?> propertyDescriptor = entry.getKey();
74  			if (propertyDescriptor instanceof PropertyDescriptorWrapper) {
75  			    propertyDescriptor = ((PropertyDescriptorWrapper) propertyDescriptor)
76  				    .getPropertyDescriptor();
77  			}
78  			Object value = entry.getValue();
79  			ValueRepresentation valueRepresentation;
80  
81  			// TODO Need to handle null values?
82  			// TODO Need to handle more PropertyDescriptors, is there an easy factory in Saxon we can use for this?
83  			if (propertyDescriptor instanceof StringProperty) {
84  			    valueRepresentation = new StringValue((String) value);
85  			} else if (propertyDescriptor instanceof BooleanProperty) {
86  			    valueRepresentation = BooleanValue.get(((Boolean) value).booleanValue());
87  			} else if (propertyDescriptor instanceof IntegerProperty) {
88  			    valueRepresentation = Int64Value.makeIntegerValue((Integer) value);
89  			} else if (propertyDescriptor instanceof EnumeratedProperty) {
90  			    if (value instanceof String) {
91  				valueRepresentation = new StringValue((String) value);
92  			    } else {
93  				throw new RuntimeException(
94  					"Unable to create ValueRepresentaton for non-String EnumeratedProperty value: "
95  						+ value);
96  			    }
97  			} else {
98  			    throw new RuntimeException("Unable to create ValueRepresentaton for PropertyDescriptor: "
99  				    + 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((Node) elementNode.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 		((AbstractStaticContext) xpathStaticContext).setBackwardsCompatibilityMode(true);
152 	    }
153 
154 	    // Register PMD functions
155 	    Initializer.initialize((IndependentContext) xpathStaticContext);
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 }