View Javadoc

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