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 }