View Javadoc

1   /*
2    * $Header$
3    * $Revision$
4    * $Date$
5    *
6    * ====================================================================
7    *
8    * Copyright 2000-2002 bob mcwhirter & James Strachan.
9    * All rights reserved.
10   *
11   * Redistribution and use in source and binary forms, with or without
12   * modification, are permitted provided that the following conditions are
13   * met:
14   * 
15   *   * Redistributions of source code must retain the above copyright
16   *     notice, this list of conditions and the following disclaimer.
17   * 
18   *   * Redistributions in binary form must reproduce the above copyright
19   *     notice, this list of conditions and the following disclaimer in the
20   *     documentation and/or other materials provided with the distribution.
21   * 
22   *   * Neither the name of the Jaxen Project nor the names of its
23   *     contributors may be used to endorse or promote products derived 
24   *     from this software without specific prior written permission.
25   * 
26   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
27   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
29   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
30   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   *
38   * ====================================================================
39   * This software consists of voluntary contributions made by many 
40   * individuals on behalf of the Jaxen Project and was originally 
41   * created by bob mcwhirter <bob@werken.com> and 
42   * James Strachan <jstrachan@apache.org>.  For more information on the 
43   * Jaxen Project, please see <http://www.jaxen.org/>.
44   * 
45   * $Id$
46   */
47  
48  
49  package org.jaxen;
50  
51  import java.io.Serializable;
52  import java.util.List;
53  
54  import org.jaxen.expr.Expr;
55  import org.jaxen.expr.XPathExpr;
56  import org.jaxen.function.BooleanFunction;
57  import org.jaxen.function.NumberFunction;
58  import org.jaxen.function.StringFunction;
59  import org.jaxen.saxpath.SAXPathException;
60  import org.jaxen.saxpath.XPathReader;
61  import org.jaxen.saxpath.helpers.XPathReaderFactory;
62  import org.jaxen.util.SingletonList;
63  
64  /** Base functionality for all concrete, implementation-specific XPaths.
65   *
66   *  <p>
67   *  This class provides generic functionality for further-defined
68   *  implementation-specific XPaths.
69   *  </p>
70   *
71   *  <p>
72   *  If you want to adapt the Jaxen engine to traverse your own
73   *  object model, then this is a good base class to derive from.
74   *  Typically you only really need to provide your own 
75   *  {@link org.jaxen.Navigator} implementation.
76   *  </p>
77   *
78   *  @see org.jaxen.dom4j.Dom4jXPath XPath for dom4j
79   *  @see org.jaxen.jdom.JDOMXPath   XPath for JDOM
80   *  @see org.jaxen.dom.DOMXPath     XPath for W3C DOM
81   *
82   *  @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
83   *  @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
84   */
85  public class BaseXPath implements XPath, Serializable
86  {
87  
88      private static final long serialVersionUID = -1993731281300293168L;
89  
90      /** Original expression text. */
91      private final String exprText;
92  
93      /** the parsed form of the XPath expression */
94      private final XPathExpr xpath;
95      
96      /** the support information and function, namespace and variable contexts */
97      private ContextSupport support;
98  
99      /** the implementation-specific Navigator for retrieving XML nodes **/
100     private Navigator navigator;
101     
102     /** Construct given an XPath expression string. 
103      *
104      *  @param xpathExpr the XPath expression
105      *
106      *  @throws JaxenException if there is a syntax error while
107      *          parsing the expression
108      */
109     protected BaseXPath(String xpathExpr) throws JaxenException
110     {
111         try
112         {
113             XPathReader reader = XPathReaderFactory.createReader();
114             JaxenHandler handler = new JaxenHandler();
115             reader.setXPathHandler( handler );
116             reader.parse( xpathExpr );
117             this.xpath = handler.getXPathExpr();
118         }
119         catch (org.jaxen.saxpath.XPathSyntaxException e)
120         {
121             throw new org.jaxen.XPathSyntaxException( e );
122         }
123         catch (SAXPathException e)
124         {
125             throw new JaxenException( e );
126         }
127 
128         this.exprText = xpathExpr;
129     }
130 
131     /** Construct given an XPath expression string.
132      *
133      *  @param xpathExpr the XPath expression
134      *
135      *  @param navigator the XML navigator to use
136      *
137      *  @throws JaxenException if there is a syntax error while
138      *          parsing the expression
139      */
140     public BaseXPath(String xpathExpr, Navigator navigator) throws JaxenException
141     {
142         this( xpathExpr );
143         this.navigator = navigator;
144     }
145 
146     /** Evaluate this XPath against a given context.
147      *  The context of evaluation may be any object type
148      *  the navigator recognizes as a node.
149      *  The return value is either a <code>String</code>,
150      *  <code>Double</code>, <code>Boolean</code>, or <code>List</code>
151      *  of nodes.
152      *
153      *  <p>
154      *  When using this method, one must be careful to
155      *  test the class of the returned object.  If the returned 
156      *  object is a list, then the items in this 
157      *  list will be the actual <code>Document</code>,
158      *  <code>Element</code>, <code>Attribute</code>, etc. objects
159      *  as defined by the concrete XML object-model implementation,
160      *  directly from the context document.  This method <strong>does
161      *  not return <em>copies</em> of anything</strong>, but merely 
162      *  returns references to objects within the source document.
163      *  </p>
164      *  
165      * @param context the node, node-set or Context object for evaluation. 
166      *      This value can be null.
167      *
168      * @return the result of evaluating the XPath expression
169      *          against the supplied context
170      * @throws JaxenException if an XPath error occurs during expression evaluation
171      * @throws ClassCastException if the context is not a node
172      */
173     public Object evaluate(Object context) throws JaxenException
174     {
175         List answer = selectNodes(context);
176 
177         if ( answer != null
178              &&
179              answer.size() == 1 )
180         {
181             Object first = answer.get(0);
182 
183             if ( first instanceof String
184                  ||
185                  first instanceof Number
186                  ||
187                  first instanceof Boolean ) 
188             {
189                 return first;
190             }
191         }
192         return answer;
193     }
194     
195     /** 
196      *  List all the nodes selected by this XPath
197      *  expression. If multiple nodes match, multiple nodes
198      *  are returned. Nodes are returned
199      *  in document-order, as defined by the XPath
200      *  specification. If the expression selects a non-node-set
201      *  (i.e. a number, boolean, or string) then a List
202      *  containing just that one object is returned.
203      *
204      * @param node the node, node-set or Context object for evaluation. 
205      *     This value can be null.
206      *
207      * @return the node-set of all items selected by this XPath expression
208      * @throws JaxenException if an XPath error occurs during expression evaluation
209      *
210      * @see #selectNodesForContext
211      */
212     public List selectNodes(Object node) throws JaxenException
213     {
214         Context context = getContext( node );
215         return selectNodesForContext( context );
216     }
217 
218     /** 
219      * Return the first node selected by this XPath
220      * expression. If multiple nodes match, only one node is
221      * returned. The selected node will be the first
222      * selected node in document-order, as defined by the XPath
223      * specification.
224      *
225      * @param node the node, node-set or Context object for evaluation. 
226      *     This value can be null.
227      *
228      * @return the node-set of all items selected
229      *          by this XPath expression
230      * @throws JaxenException if an XPath error occurs during expression evaluation
231      *
232      * @see #selectNodes
233      */
234     public Object selectSingleNode(Object node) throws JaxenException
235     {
236         List results = selectNodes( node );
237 
238         if ( results.isEmpty() )
239         {
240             return null;
241         }
242 
243         return results.get( 0 );
244     }
245 
246     /**
247      * Returns the XPath string-value of the argument node.
248      * 
249      * @param node the node whose value to take
250      * @return the XPath string value of this node
251      * @throws JaxenException if an XPath error occurs during expression evaluation
252      * @deprecated replaced by {@link #stringValueOf}
253      */
254     public String valueOf(Object node) throws JaxenException
255     {
256         return stringValueOf( node );
257     }
258 
259     /** Retrieves the string-value of the result of
260      *  evaluating this XPath expression when evaluated 
261      *  against the specified context.
262      *
263      *  <p>
264      *  The string-value of the expression is determined per
265      *  the <code>string(..)</code> core function defined
266      *  in the XPath specification.  This means that an expression
267      *  that selects zero nodes will return the empty string,
268      *  while an expression that selects one-or-more nodes will
269      *  return the string-value of the first node.
270      *  </p>
271      *
272      * @param node the node, node-set or Context object for evaluation. This value can be null.
273      *
274      * @return the string-value of the result of evaluating this expression with the specified context node
275      * @throws JaxenException if an XPath error occurs during expression evaluation
276      */
277     public String stringValueOf(Object node) throws JaxenException
278     {
279         Context context = getContext( node );
280         
281         Object result = selectSingleNodeForContext( context );
282 
283         if ( result == null )
284         {
285             return "";
286         }
287 
288         return StringFunction.evaluate( result,
289                                         context.getNavigator() );
290     }
291 
292     /** Retrieve a boolean-value interpretation of this XPath
293      *  expression when evaluated against a given context.
294      *
295      *  <p>
296      *  The boolean-value of the expression is determined per
297      *  the <code>boolean(..)</code> function defined
298      *  in the XPath specification.  This means that an expression
299      *  that selects zero nodes will return <code>false</code>,
300      *  while an expression that selects one or more nodes will
301      *  return <code>true</code>.
302      *  </p>
303      *
304      * @param node the node, node-set or Context object for evaluation. This value can be null.
305      *
306      * @return the boolean-value of the result of evaluating this expression with the specified context node
307      * @throws JaxenException if an XPath error occurs during expression evaluation
308      */
309     public boolean booleanValueOf(Object node) throws JaxenException
310     {
311         Context context = getContext( node );
312         List result = selectNodesForContext( context );
313         if ( result == null ) return false;
314         return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
315     }
316 
317     /** Retrieve a number-value interpretation of this XPath
318      *  expression when evaluated against a given context.
319      *
320      *  <p>
321      *  The number-value of the expression is determined per
322      *  the <code>number(..)</code> core function as defined
323      *  in the XPath specification. This means that if this
324      *  expression selects multiple nodes, the number-value
325      *  of the first node is returned.
326      *  </p>
327      *
328      * @param node the node, node-set or Context object for evaluation. This value can be null.
329      *
330      * @return a <code>Double</code> indicating the numeric value of
331      *      evaluating this expression against the specified context
332      * @throws JaxenException if an XPath error occurs during expression evaluation
333      */
334     public Number numberValueOf(Object node) throws JaxenException
335     {
336         Context context = getContext( node );
337         Object result = selectSingleNodeForContext( context );
338         return NumberFunction.evaluate( result,
339                                         context.getNavigator() );
340     }
341 
342     // Helpers
343 
344     /** Add a namespace prefix-to-URI mapping for this XPath
345      *  expression.
346      *
347      *  <p>
348      *  Namespace prefix-to-URI mappings in an XPath are independent
349      *  of those used within any document.  Only the mapping explicitly
350      *  added to this XPath will be available for resolving the
351      *  XPath expression.
352      *  </p>
353      *
354      *  <p>
355      *  This is a convenience method for adding mappings to the
356      *  default {@link NamespaceContext} in place for this XPath.
357      *  If you have installed a custom <code>NamespaceContext</code>
358      *  that is not a <code>SimpleNamespaceContext</code>,
359      *  then this method will throw a <code>JaxenException</code>.
360      *  </p>
361      *
362      *  @param prefix the namespace prefix
363      *  @param uri the namespace URI
364      *
365      *  @throws JaxenException if the <code>NamespaceContext</code>
366      *          used by this XPath is not a <code>SimpleNamespaceContext</code>
367      */
368     public void addNamespace(String prefix,
369                              String uri) throws JaxenException
370     {
371         NamespaceContext nsContext = getNamespaceContext();
372         if ( nsContext instanceof SimpleNamespaceContext )
373         {
374             ((SimpleNamespaceContext)nsContext).addNamespace( prefix,
375                                                               uri );
376             return;
377         }
378 
379         throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
380     }
381 
382 
383     // ------------------------------------------------------------
384     // ------------------------------------------------------------
385     //     Properties
386     // ------------------------------------------------------------
387     // ------------------------------------------------------------
388 
389     
390     /** Set a <code>NamespaceContext</code> for use with this
391      *  XPath expression.
392      *
393      *  <p>
394      *  A <code>NamespaceContext</code> is responsible for translating
395      *  namespace prefixes within the expression into namespace URIs.
396      *  </p>
397      *
398      *  @param namespaceContext the <code>NamespaceContext</code> to
399      *         install for this expression
400      *
401      *  @see NamespaceContext
402      *  @see NamespaceContext#translateNamespacePrefixToUri
403      */
404     public void setNamespaceContext(NamespaceContext namespaceContext)
405     {
406         getContextSupport().setNamespaceContext(namespaceContext);
407     }
408 
409     /** Set a <code>FunctionContext</code> for use with this XPath
410      *  expression.
411      *
412      *  <p>
413      *  A <code>FunctionContext</code> is responsible for resolving
414      *  all function calls used within the expression.
415      *  </p>
416      *
417      *  @param functionContext the <code>FunctionContext</code> to
418      *         install for this expression
419      *
420      *  @see FunctionContext
421      *  @see FunctionContext#getFunction
422      */
423     public void setFunctionContext(FunctionContext functionContext)
424     {
425         getContextSupport().setFunctionContext(functionContext);
426     }
427 
428     /** Set a <code>VariableContext</code> for use with this XPath
429      *  expression.
430      *
431      *  <p>
432      *  A <code>VariableContext</code> is responsible for resolving
433      *  all variables referenced within the expression.
434      *  </p>
435      *
436      *  @param variableContext The <code>VariableContext</code> to
437      *         install for this expression
438      *
439      *  @see VariableContext
440      *  @see VariableContext#getVariableValue
441      */
442     public void setVariableContext(VariableContext variableContext)
443     {
444         getContextSupport().setVariableContext(variableContext);
445     }
446 
447     /** Retrieve the <code>NamespaceContext</code> used by this XPath
448      *  expression.
449      *
450      *  <p>
451      *  A <code>NamespaceContext</code> is responsible for mapping
452      *  prefixes used within the expression to namespace URIs.
453      *  </p>
454      *
455      *  <p>
456      *  If this XPath expression has not previously had a <code>NamespaceContext</code>
457      *  installed, a new default <code>NamespaceContext</code> will be created,
458      *  installed and returned.
459      *  </p>
460      *
461      *  @return the <code>NamespaceContext</code> used by this expression
462      *
463      *  @see NamespaceContext
464      */
465     public NamespaceContext getNamespaceContext()
466     {
467         return getContextSupport().getNamespaceContext();
468     }
469 
470     /** Retrieve the <code>FunctionContext</code> used by this XPath
471      *  expression.
472      *
473      *  <p>
474      *  A <code>FunctionContext</code> is responsible for resolving
475      *  all function calls used within the expression.
476      *  </p>
477      *
478      *  <p>
479      *  If this XPath expression has not previously had a <code>FunctionContext</code>
480      *  installed, a new default <code>FunctionContext</code> will be created,
481      *  installed and returned.
482      *  </p>
483      *
484      *  @return the <code>FunctionContext</code> used by this expression
485      *
486      *  @see FunctionContext
487      */
488     public FunctionContext getFunctionContext()
489     {
490         return getContextSupport().getFunctionContext();
491     }
492 
493     /** Retrieve the <code>VariableContext</code> used by this XPath
494      *  expression.
495      *
496      *  <p>
497      *  A <code>VariableContext</code> is responsible for resolving
498      *  all variables referenced within the expression.
499      *  </p>
500      *
501      *  <p>
502      *  If this XPath expression has not previously had a <code>VariableContext</code>
503      *  installed, a new default <code>VariableContext</code> will be created,
504      *  installed and returned.
505      *  </p>
506      *  
507      *  @return the <code>VariableContext</code> used by this expression
508      *
509      *  @see VariableContext
510      */
511     public VariableContext getVariableContext()
512     {
513         return getContextSupport().getVariableContext();
514     }
515     
516     
517     /** Retrieve the root expression of the internal
518      *  compiled form of this XPath expression.
519      *
520      *  <p>
521      *  Internally, Jaxen maintains a form of Abstract Syntax
522      *  Tree (AST) to represent the structure of the XPath expression.
523      *  This is normally not required during normal consumer-grade
524      *  usage of Jaxen.  This method is provided for hard-core users
525      *  who wish to manipulate or inspect a tree-based version of
526      *  the expression.
527      *  </p>
528      *
529      *  @return the root of the AST of this expression
530      */
531     public Expr getRootExpr() 
532     {
533         return xpath.getRootExpr();
534     }
535     
536     /** Return the original expression text.
537      *
538      *  @return the normalized XPath expression string
539      */
540     public String toString()
541     {
542         return this.exprText;
543     }
544 
545     /** Returns a string representation of the parse tree.
546      *
547      *  @return a string representation of the parse tree.
548      */
549     public String debug()
550     {
551         return this.xpath.toString();
552     }
553     
554     // ------------------------------------------------------------
555     // ------------------------------------------------------------
556     //     Implementation methods
557     // ------------------------------------------------------------
558     // ------------------------------------------------------------
559 
560     
561     /** Create a {@link Context} wrapper for the provided
562      *  implementation-specific object.
563      *
564      *  @param node the implementation-specific object 
565      *         to be used as the context
566      *
567      *  @return a <code>Context</code> wrapper around the object
568      */
569     protected Context getContext(Object node)
570     {
571         if ( node instanceof Context )
572         {
573             return (Context) node;
574         }
575 
576         Context fullContext = new Context( getContextSupport() );
577 
578         if ( node instanceof List )
579         {
580             fullContext.setNodeSet( (List) node );
581         }
582         else
583         {
584             List list = new SingletonList(node);
585             fullContext.setNodeSet( list );
586         }
587 
588         return fullContext;
589     }
590 
591     /** Retrieve the {@link ContextSupport} aggregation of
592      *  <code>NamespaceContext</code>, <code>FunctionContext</code>,
593      *  <code>VariableContext</code>, and {@link Navigator}.
594      *
595      *  @return aggregate <code>ContextSupport</code> for this
596      *          XPath expression
597      */
598     protected ContextSupport getContextSupport()
599     {
600         if ( support == null )
601         {
602             support = new ContextSupport( 
603                 createNamespaceContext(),
604                 createFunctionContext(),
605                 createVariableContext(),
606                 getNavigator() 
607             );
608         }
609 
610         return support;
611     }
612 
613     /** Retrieve the XML object-model-specific {@link Navigator} 
614      *  for us in evaluating this XPath expression.
615      *
616      *  @return the implementation-specific <code>Navigator</code>
617      */
618     public Navigator getNavigator()
619     {
620         return navigator;
621     }
622     
623     
624 
625     // ------------------------------------------------------------
626     // ------------------------------------------------------------
627     //     Factory methods for default contexts
628     // ------------------------------------------------------------
629     // ------------------------------------------------------------
630 
631     /** Create a default <code>FunctionContext</code>.
632      *
633      *  @return a default <code>FunctionContext</code>
634      */
635     protected FunctionContext createFunctionContext()
636     {
637         return XPathFunctionContext.getInstance();
638     }
639     
640     /** Create a default <code>NamespaceContext</code>.
641      *
642      *  @return a default <code>NamespaceContext</code> instance
643      */
644     protected NamespaceContext createNamespaceContext()
645     {
646         return new SimpleNamespaceContext();
647     }
648     
649     /** Create a default <code>VariableContext</code>.
650      *
651      *  @return a default <code>VariableContext</code> instance
652      */
653     protected VariableContext createVariableContext()
654     {
655         return new SimpleVariableContext();
656     }
657     
658     /** Select all nodes that match this XPath
659      *  expression on the given Context object. 
660      *  If multiple nodes match, multiple nodes
661      *  will be returned in document-order, as defined by the XPath
662      *  specification. If the expression selects a non-node-set
663      *  (i.e. a number, boolean, or string) then a List
664      *  containing just that one object is returned.
665      *
666      * @param context the Context which gets evaluated
667      *
668      * @return the node-set of all items selected
669      *          by this XPath expression
670      * @throws JaxenException if an XPath error occurs during expression evaluation
671      *
672      */
673     protected List selectNodesForContext(Context context) throws JaxenException
674     {
675         List list = this.xpath.asList( context );
676         return list;
677         
678     }
679  
680 
681     /** Return only the first node that is selected by this XPath
682      *  expression.  If multiple nodes match, only one node will be
683      *  returned. The selected node will be the first
684      *  selected node in document-order, as defined by the XPath
685      *  specification. If the XPath expression selects a double,
686      *  String, or boolean, then that object is returned.
687      *
688      * @param context the Context against which this expression is evaluated
689      *
690      * @return the first node in document order of all nodes selected
691      *          by this XPath expression
692      * @throws JaxenException if an XPath error occurs during expression evaluation
693      *
694      * @see #selectNodesForContext
695      */
696     protected Object selectSingleNodeForContext(Context context) throws JaxenException
697     {
698         List results = selectNodesForContext(context);
699 
700         if ( results.isEmpty() )
701         {
702             return null;
703         }
704 
705         return results.get( 0 );
706     }
707     
708 }