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 = ((PropertyDescriptorWrapper) propertyDescriptor)
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((String) value);
085 } else if (propertyDescriptor instanceof BooleanProperty) {
086 valueRepresentation = BooleanValue.get(((Boolean) value).booleanValue());
087 } else if (propertyDescriptor instanceof IntegerProperty) {
088 valueRepresentation = Int64Value.makeIntegerValue((Integer) value);
089 } else if (propertyDescriptor instanceof EnumeratedProperty) {
090 if (value instanceof String) {
091 valueRepresentation = new StringValue((String) value);
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((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 }
|