Coverage Report - net.sf.practicalxml.xpath.XPathWrapper
 
Classes in this File Line Coverage Branch Coverage Complexity
XPathWrapper
100%
71/71
95%
19/20
1.68
XPathWrapper$1
N/A
N/A
1.68
XPathWrapper$MyVariableResolver
100%
2/2
N/A
1.68
 
 1  
 // Copyright 2008-2014 severally by the contributors
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package net.sf.practicalxml.xpath;
 16  
 
 17  
 import java.util.ArrayList;
 18  
 import java.util.HashMap;
 19  
 import java.util.List;
 20  
 import java.util.Map;
 21  
 import javax.xml.namespace.QName;
 22  
 import javax.xml.xpath.XPath;
 23  
 import javax.xml.xpath.XPathConstants;
 24  
 import javax.xml.xpath.XPathExpression;
 25  
 import javax.xml.xpath.XPathExpressionException;
 26  
 import javax.xml.xpath.XPathFactory;
 27  
 import javax.xml.xpath.XPathFunction;
 28  
 import javax.xml.xpath.XPathVariableResolver;
 29  
 
 30  
 import org.w3c.dom.Element;
 31  
 import org.w3c.dom.Node;
 32  
 import org.w3c.dom.NodeList;
 33  
 
 34  
 import net.sf.practicalxml.DomUtil;
 35  
 import net.sf.practicalxml.XmlException;
 36  
 
 37  
 
 38  
 
 39  
 /**
 40  
  *  This class simplifies the use of XPath expressions, hiding the factory and
 41  
  *  return types, and providing a simple builder-style interface for adding
 42  
  *  resolvers. It also maintains the expression in a compiled form, improving
 43  
  *  reuse performance.
 44  
  *  <p>
 45  
  *  <strong>Warning:</strong>
 46  
  *  <code>XPathWrapper</code>, like the underlying JDK <code>XPath</code> object,
 47  
  *  is not thread-safe. However, separate instances may be created and evaluated
 48  
  *  on separate threads without explicit synchronization. To make this work, we
 49  
  *  create a new <code>XPathFactory</code> instance for each wrapper (with the
 50  
  *  Sun JDK, this adds little overhead), synchronizing <code>newInstance()</code>
 51  
  *  on <code>XPathWrapper.class</code>.
 52  
  */
 53  37
 public class XPathWrapper
 54  
 implements Cloneable
 55  
 {
 56  
     private final String _expr;
 57  201
     private final NamespaceResolver _nsResolver = new NamespaceResolver();
 58  201
     private Map<QName,Object> _variables = new HashMap<QName,Object>();
 59  201
     private FunctionResolver _functions = new FunctionResolver();
 60  
 
 61  
     private XPathExpression _compiled;
 62  
 
 63  
 
 64  
     /**
 65  
      *  Creates a new instance, which may then be customized with various
 66  
      *  resolvers, and used to evaluate expressions.
 67  
      */
 68  
     public XPathWrapper(String expr)
 69  201
     {
 70  201
         _expr = expr;
 71  201
     }
 72  
 
 73  
 
 74  
 //----------------------------------------------------------------------------
 75  
 //  Public methods
 76  
 //----------------------------------------------------------------------------
 77  
 
 78  
     /**
 79  
      *  Adds a namespace binding to this expression. All bindings must be
 80  
      *  added prior to the first call to <code>evaluate()</code>. If you add
 81  
      *  multiple bindings with the same namespace, only the last is retained.
 82  
      *
 83  
      *  @param  prefix  The prefix used to reference this namespace in the
 84  
      *                  XPath expression. Note that this does <em>not</em>
 85  
      *                  need to be the same prefix used by the document.
 86  
      *  @param  nsURI   The namespace URI to associate with this prefix.
 87  
      *
 88  
      *  @return The wrapper, so that calls may be chained.
 89  
      */
 90  
     public XPathWrapper bindNamespace(String prefix, String nsURI)
 91  
     {
 92  54
         _nsResolver.addNamespace(prefix, nsURI);
 93  54
         return this;
 94  
     }
 95  
 
 96  
 
 97  
     /**
 98  
      *  Sets the default namespace binding: this will be applied to all
 99  
      *  expressions that do not explicitly specify a prefix (although
 100  
      *  they must use the colon separating the non-existent prefix from
 101  
      *  the element name).
 102  
      *
 103  
      *  @param  nsURI   The default namespace for this document.
 104  
      *
 105  
      *  @return The wrapper, so that calls may be chained.
 106  
      *
 107  
      *  @deprecated In practice, this method isn't particularly useful:
 108  
      *              if you're going to specify ":" on a term, you might
 109  
      *              as well specify a single-character prefix. It won't
 110  
      *              be removed, but a future implementation might parse
 111  
      *              an expression sans colons, and insert references.
 112  
 
 113  
      */
 114  
     @Deprecated
 115  
     public XPathWrapper bindDefaultNamespace(String nsURI)
 116  
     {
 117  3
         _nsResolver.setDefaultNamespace(nsURI);
 118  3
         return this;
 119  
     }
 120  
 
 121  
 
 122  
     /**
 123  
      *  Binds a value to a variable, replacing any previous value for that
 124  
      *  variable. Unlike other configuration methods, this may be called
 125  
      *  after calling <code>evaluate()</code>; the new values will be used
 126  
      *  for subsequent evaluations.
 127  
      *
 128  
      *  @param  name    The name of the variable; this is turned into a
 129  
      *                  <code>QName</code> without namespace.
 130  
      *  @param  value   The value of the variable; the XPath evaluator must
 131  
      *                  be able to convert this value into a type usable in
 132  
      *                  an XPath expression.
 133  
      *
 134  
      *  @return The wrapper, so that calls may be chained.
 135  
      */
 136  
     public XPathWrapper bindVariable(String name, Object value)
 137  
     {
 138  3
         return bindVariable(new QName(name), value);
 139  
     }
 140  
 
 141  
 
 142  
     /**
 143  
      *  Binds a value to a variable, replacing any previous value for that
 144  
      *  variable. Unlike other configuration methods, this may be called
 145  
      *  after calling <code>evaluate()</code>; the new values will be used
 146  
      *  for subsequent evaluations.
 147  
      *
 148  
      *  @param  name    The fully-qualified name of the variable.
 149  
      *  @param  value   The value of the variable; the XPath evaluator must
 150  
      *                  be able to convert this value into a type usable in
 151  
      *                  an XPath expression.
 152  
      *
 153  
      *  @return The wrapper, so that calls may be chained.
 154  
      */
 155  
     public XPathWrapper bindVariable(QName name, Object value)
 156  
     {
 157  18
         _variables.put(name, value);
 158  18
         return this;
 159  
     }
 160  
 
 161  
 
 162  
     /**
 163  
      *  Binds a self-describing function to this expression. Subsequent calls
 164  
      *  with the same name/arity will silently replace the binding.
 165  
      *  <p>
 166  
      *  Per the JDK documentation, user-defined functions must occupy their
 167  
      *  own namespace. You must bind a namespace for this function, and use
 168  
      *  the bound prefix to reference it in your expression. Alternatively,
 169  
      *  you can call the variant of <code>bindFunction()</code> that binds
 170  
      *  a prefix to the function's namespace.
 171  
      *
 172  
      *  @param  func    The function.
 173  
      *
 174  
      *  @return The wrapper, so that calls may be chained.
 175  
      */
 176  
     public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func)
 177  
     {
 178  1
         _functions.addFunction(func);
 179  1
         return this;
 180  
     }
 181  
 
 182  
 
 183  
     /**
 184  
      *  Binds a self-describing function to this expression, along with the
 185  
      *  prefix used to access that function. This also establishes a namespace
 186  
      *  binding for that prefix.
 187  
      *  <p>
 188  
      *  Per the JDK documentation, user-defined functions must occupy their
 189  
      *  own namespace. This method will retrieve the namespace from the
 190  
      *  function, and bind it to the passed prefix.
 191  
      *
 192  
      *  @param  func    The function.
 193  
      *  @param  prefix  The prefix to bind to this function's namespace.
 194  
      *
 195  
      *  @return The wrapper, so that calls may be chained.
 196  
      */
 197  
     public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func, String prefix)
 198  
     {
 199  19
         _functions.addFunction(func);
 200  19
         return bindNamespace(prefix, func.getNamespaceUri());
 201  
     }
 202  
 
 203  
 
 204  
     /**
 205  
      *  Binds a standard <code>XPathFunction</code> to this expression,
 206  
      *  handling any number of arguments. Subsequent calls to this method
 207  
      *  with the same name will silently replace the binding.
 208  
      *  <p>
 209  
      *  Per the JDK documentation, user-defined functions must occupy their
 210  
      *  own namespace. If the qualified name that you pass to this method
 211  
      *  includes a prefix, the associated namespace will be bound to that
 212  
      *  prefix. If not, you must bind the namespace explicitly. In either
 213  
      *  case, you must refer to the function in your expression using a
 214  
      *  bound prefix.
 215  
      *
 216  
      *  @param  name    The qualified name for this function. Must contain
 217  
      *                  a name and namespace, may contain a prefix.
 218  
      *  @param  func    The function to bind to this name.
 219  
      *
 220  
      *  @return The wrapper, so that calls may be chained.
 221  
      */
 222  
     public XPathWrapper bindFunction(QName name, XPathFunction func)
 223  
     {
 224  3
         return bindFunction(name, func, 0, Integer.MAX_VALUE);
 225  
     }
 226  
 
 227  
 
 228  
     /**
 229  
      *  Binds a standard <code>XPathFunction</code> to this expression,
 230  
      *  handling a specific number of arguments. Subsequent calls to this
 231  
      *  method with the same name and arity will silently replace the binding.
 232  
      *  <p>
 233  
      *  Per the JDK documentation, user-defined functions must occupy their
 234  
      *  own namespace. If the qualified name that you pass to this method
 235  
      *  includes a prefix, the associated namespace will be bound to that
 236  
      *  prefix. If not, you must bind the namespace explicitly. In either
 237  
      *  case, you must refer to the function in your expression using a
 238  
      *  bound prefix.
 239  
      *
 240  
      *  @param  name    The qualified name for this function. Must contain
 241  
      *                  a name and namespace, may contain a prefix.
 242  
      *  @param  func    The function to bind to this name.
 243  
      *  @param  arity   The number of arguments accepted by this function.
 244  
      *
 245  
      *  @return The wrapper, so that calls may be chained.
 246  
      */
 247  
     public XPathWrapper bindFunction(QName name, XPathFunction func, int arity)
 248  
     {
 249  2
         return bindFunction(name, func, arity, arity);
 250  
     }
 251  
 
 252  
 
 253  
     /**
 254  
      *  Binds a standard <code>XPathFunction</code> to this expression,
 255  
      *  handling a specific range of arguments. Subsequent calls to this
 256  
      *  method with the same name and range of arguments will silently
 257  
      *  replace the binding.
 258  
      *  <p>
 259  
      *  Per the JDK documentation, user-defined functions must occupy their
 260  
      *  own namespace. If the qualified name that you pass to this method
 261  
      *  includes a prefix, the associated namespace will be bound to that
 262  
      *  prefix. If not, you must bind the namespace explicitly. In either
 263  
      *  case, you must refer to the function in your expression using a
 264  
      *  bound prefix.
 265  
      *
 266  
      *  @param  name      The qualified name for this function. Must contain
 267  
      *                    a name and namespace, may contain a prefix.
 268  
      *  @param  func      The function to bind to this name.
 269  
      *  @param  minArity  The minimum number of arguments accepted by this
 270  
      *                    function.
 271  
      *  @param  maxArity  The maximum number of arguments accepted by this
 272  
      *                    function.
 273  
      *
 274  
      *  @return The wrapper, so that calls may be chained.
 275  
      */
 276  
     public XPathWrapper bindFunction(QName name, XPathFunction func,
 277  
                                      int minArity, int maxArity)
 278  
     {
 279  5
         _functions.addFunction(func, name, minArity, maxArity);
 280  5
         if (!"".equals(name.getPrefix()))
 281  3
             bindNamespace(name.getPrefix(), name.getNamespaceURI());
 282  5
         return this;
 283  
     }
 284  
 
 285  
 
 286  
     /**
 287  
      *  Attaches a pre-build {@link FunctionResolver} to this wrapper,
 288  
      *  replacing the existing resolver (and removing all bound functions).
 289  
      *  This method is intended for use by {@link XPathWrapperFactory}, but
 290  
      *  is exposed for applications that wish to manage their own XPaths.
 291  
      *
 292  
      *  @since 1.1.12
 293  
      */
 294  
     public XPathWrapper setFunctionResolver(FunctionResolver resolver)
 295  
     {
 296  20
         _functions = resolver;
 297  20
         return this;
 298  
     }
 299  
 
 300  
 
 301  
     /**
 302  
      *  Applies this expression to the the specified node, converting the
 303  
      *  resulting <code>NodeList</code> into a <code>java.util.List</code>.
 304  
      */
 305  
     public List<Node> evaluate(Node context)
 306  
     {
 307  79
         return DomUtil.asList(
 308  
                 evaluate(context, XPathConstants.NODESET, NodeList.class),
 309  
                 Node.class);
 310  
     }
 311  
 
 312  
 
 313  
     /**
 314  
      *  Applies this expression to the the specified node, converting the
 315  
      *  resulting <code>NodeList</code> into a <code>java.util.List</code>
 316  
      *  containing only nodes of the specified type.
 317  
      */
 318  
     public <T> List<T> evaluate(Node context, Class<T> klass)
 319  
     {
 320  3
         return DomUtil.filter(
 321  
                 evaluate(context, XPathConstants.NODESET, NodeList.class),
 322  
                 klass);
 323  
     }
 324  
 
 325  
 
 326  
     /**
 327  
      *  Applies this expression to the the specified node, and returning the
 328  
      *  first <code>Element</code> from the resulting nodelist. Returns
 329  
      *  <code>null</code> if there is no element in the list.
 330  
      *
 331  
      *  @since 1.1.11
 332  
      */
 333  
     public Element evaluateAsElement(Node context)
 334  
     {
 335  3
         NodeList nodelist = evaluate(context, XPathConstants.NODESET, NodeList.class);
 336  3
         int size = nodelist.getLength();
 337  5
         for (int ii = 0 ; ii < size ; ii++)
 338  
         {
 339  4
             Node node = nodelist.item(ii);
 340  4
             if (node instanceof Element)
 341  2
                 return (Element)node;
 342  
         }
 343  1
         return null;
 344  
     }
 345  
 
 346  
 
 347  
     /**
 348  
      *  Applies this expression to the specified node, requesting the
 349  
      *  <code>STRING</code> return type.
 350  
      */
 351  
     public String evaluateAsString(Node context)
 352  
     {
 353  113
         return evaluate(context, XPathConstants.STRING, String.class);
 354  
     }
 355  
 
 356  
 
 357  
     /**
 358  
      *  Applies this expression to the specified node, requesting the
 359  
      *  <code>NODESET</code> return type, and then retrieving the text
 360  
      *  content for each returned node.
 361  
      *  <p>
 362  
      *  Note: this method calls <code>Node.getTextContent()</code> in
 363  
      *        keeping with normal XPath behavior. If you want to get
 364  
      *        only the immediate child text nodes, evaluate as a list
 365  
      *        of elements and use <code>DomUtil.getText()</code>.
 366  
      *
 367  
      *  @since 1.1.3
 368  
      */
 369  
     public List<String> evaluateAsStringList(Node context)
 370  
     {
 371  1
         List<Node> nodes = evaluate(context);
 372  1
         List<String> result = new ArrayList<String>(nodes.size());
 373  1
         for (Node node : nodes)
 374  2
             result.add(node.getTextContent());
 375  1
         return result;
 376  
     }
 377  
 
 378  
 
 379  
     /**
 380  
      *  Applies this expression to the specified node, requesting the
 381  
      *  <code>NUMBER</code> return type.
 382  
      */
 383  
     public Number evaluateAsNumber(Node context)
 384  
     {
 385  1
         return evaluate(context, XPathConstants.NUMBER, Number.class);
 386  
     }
 387  
 
 388  
 
 389  
     /**
 390  
      *  Applies this expression to the the specified node, requesting the
 391  
      *  <code>BOOLEAN</code> return type.
 392  
      */
 393  
     public Boolean evaluateAsBoolean(Node context)
 394  
     {
 395  3
         return evaluate(context, XPathConstants.BOOLEAN, Boolean.class);
 396  
     }
 397  
 
 398  
 
 399  
 //----------------------------------------------------------------------------
 400  
 //  Overrides of Object
 401  
 //----------------------------------------------------------------------------
 402  
 
 403  
     /**
 404  
      *  Two instances are considered equal if they have the same expression,
 405  
      *  namespace mappings, variable mappings, and function mappings. Note
 406  
      *  that instances with function mappings are all but guaranteed to be
 407  
      *  not-equal, unless you override the <code>equals()</code> method on
 408  
      *  the function implementation class.
 409  
      */
 410  
     @Override
 411  
     public final boolean equals(Object obj)
 412  
     {
 413  17
         if (obj instanceof XPathWrapper)
 414  
         {
 415  15
             XPathWrapper that = (XPathWrapper)obj;
 416  15
             return this._expr.equals(that._expr)
 417  
                 && this._nsResolver.equals(that._nsResolver)
 418  
                 && this._variables.equals(that._variables)
 419  
                 && this._functions.equals(that._functions);
 420  
         }
 421  2
         return false;
 422  
     }
 423  
 
 424  
 
 425  
     /**
 426  
      *  Hash code is driven by the expression, ignoring differences between
 427  
      *  namespaces, variables, and functions.
 428  
      */
 429  
     @Override
 430  
     public int hashCode()
 431  
     {
 432  18
         return _expr.hashCode();
 433  
     }
 434  
 
 435  
 
 436  
     /**
 437  
      *  The string value is the expression.
 438  
      */
 439  
     @Override
 440  
     public String toString()
 441  
     {
 442  52
         return _expr;
 443  
     }
 444  
 
 445  
 
 446  
     /**
 447  
      *  Creates a <em>shallow</em> clone of this object, omitting the compiled
 448  
      *  XPath expression. This method exists to support thread-local instances,
 449  
      *  which will recompile the expression.
 450  
      */
 451  
     @Override
 452  
     protected XPathWrapper clone() throws CloneNotSupportedException
 453  
     {
 454  1
         XPathWrapper newInstance = (XPathWrapper)super.clone();
 455  1
         newInstance._compiled = null;
 456  1
         return newInstance;
 457  
     }
 458  
 
 459  
 
 460  
 //----------------------------------------------------------------------------
 461  
 //  Internals
 462  
 //----------------------------------------------------------------------------
 463  
 
 464  
     /**
 465  
      *  Compiles the expression, if it has not already been compiled. This is
 466  
      *  called from the various <code>evaluate</code> methods, and ensures
 467  
      *  that the caller has completely configured the wrapper prior to use.
 468  
      */
 469  
     private void compileIfNeeded()
 470  
     {
 471  202
         if (_compiled != null)
 472  17
             return;
 473  
 
 474  
         try
 475  
         {
 476  185
             XPathFactory fact = null;
 477  185
             synchronized (XPathFactory.class)
 478  
             {
 479  185
                 fact = XPathFactory.newInstance();
 480  185
             }
 481  185
             XPath xpath = fact.newXPath();
 482  185
             xpath.setNamespaceContext(_nsResolver);
 483  185
             xpath.setXPathVariableResolver(new MyVariableResolver());
 484  185
             xpath.setXPathFunctionResolver(_functions);
 485  185
             _compiled = xpath.compile(_expr);
 486  
         }
 487  1
         catch (XPathExpressionException ee)
 488  
         {
 489  1
             throw new XmlException("unable to compile: " + _expr, ee);
 490  184
         }
 491  184
     }
 492  
 
 493  
 
 494  
     /**
 495  
      *  The base evaluation method: compiles the XPath, applies it as instructed
 496  
      *  by the caller, and casts the result (also as instructed).
 497  
      */
 498  
     private <T> T evaluate(Node context, QName returnType, Class<T> castTo)
 499  
     {
 500  202
         compileIfNeeded();
 501  
         try
 502  
         {
 503  201
             return castTo.cast(_compiled.evaluate(context, returnType));
 504  
         }
 505  2
         catch (Exception ee)
 506  
         {
 507  2
             throw new XmlException("unable to evaluate: " + _expr, ee);
 508  
         }
 509  
     }
 510  
 
 511  
 
 512  
     /**
 513  
      *  Resolver for variable references.
 514  
      */
 515  370
     private class MyVariableResolver
 516  
     implements XPathVariableResolver
 517  
     {
 518  
         public Object resolveVariable(QName name)
 519  
         {
 520  37
             return _variables.get(name);
 521  
         }
 522  
     }
 523  
 }