View Javadoc

1   /*
2    * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/BaseXPath.java,v 1.48 2005/09/24 23:05:15 elharo Exp $
3    * $Revision: 1.48 $
4    * $Date: 2005/09/24 23:05:15 $
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.48 2005/09/24 23:05:15 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 replaced by {@link #stringValueOf}
265      */
266     public String valueOf(Object node) throws JaxenException
267     {
268         return stringValueOf( node );
269     }
270 
271     /*** Retrieves the string-value of the result of
272      *  evaluating this XPath expression when evaluated 
273      *  against the specified context.
274      *
275      *  <p>
276      *  The string-value of the expression is determined per
277      *  the <code>string(..)</code> core function defined
278      *  in the XPath specification.  This means that an expression
279      *  that selects zero nodes will return the empty string,
280      *  while an expression that selects one-or-more nodes will
281      *  return the string-value of the first node.
282      *  </p>
283      *
284      * @param node the node, node-set or Context object for evaluation. This value can be null.
285      *
286      * @return the string-value of the result of evaluating this expression with the specified context node
287      * @throws JaxenException if an XPath error occurs during expression evaluation
288      */
289     public String stringValueOf(Object node) throws JaxenException
290     {
291         Context context = getContext( node );
292         
293         Object result = selectSingleNodeForContext( context );
294 
295         if ( result == null )
296         {
297             return "";
298         }
299 
300         return StringFunction.evaluate( result,
301                                         context.getNavigator() );
302     }
303 
304     /*** Retrieve a boolean-value interpretation of this XPath
305      *  expression when evaluated against a given context.
306      *
307      *  <p>
308      *  The boolean-value of the expression is determined per
309      *  the <code>boolean(..)</code> function defined
310      *  in the XPath specification.  This means that an expression
311      *  that selects zero nodes will return <code>false</code>,
312      *  while an expression that selects one or more nodes will
313      *  return <code>true</code>.
314      *  </p>
315      *
316      * @param node the node, node-set or Context object for evaluation. This value can be null.
317      *
318      * @return the boolean-value of the result of evaluating this expression with the specified context node
319      * @throws JaxenException if an XPath error occurs during expression evaluation
320      */
321     public boolean booleanValueOf(Object node) throws JaxenException
322     {
323         Context context = getContext( node );
324         List result = selectNodesForContext( context );
325         if ( result == null ) return false;
326         return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
327     }
328 
329     /*** Retrieve a number-value interpretation of this XPath
330      *  expression when evaluated against a given context.
331      *
332      *  <p>
333      *  The number-value of the expression is determined per
334      *  the <code>number(..)</code> core function as defined
335      *  in the XPath specification. This means that if this
336      *  expression selects multiple nodes, the number-value
337      *  of the first node is returned.
338      *  </p>
339      *
340      * @param node the node, node-set or Context object for evaluation. This value can be null.
341      *
342      * @return a <code>Double</code> indicating the numeric value of
343      *      evaluating this expression against the specified context
344      * @throws JaxenException if an XPath error occurs during expression evaluation
345      */
346     public Number numberValueOf(Object node) throws JaxenException
347     {
348         Context context = getContext( node );
349         Object result = selectSingleNodeForContext( context );
350         return NumberFunction.evaluate( result,
351                                         context.getNavigator() );
352     }
353 
354     // Helpers
355 
356     /*** Add a namespace prefix-to-URI mapping for this XPath
357      *  expression.
358      *
359      *  <p>
360      *  Namespace prefix-to-URI mappings in an XPath are independent
361      *  of those used within any document.  Only the mapping explicitly
362      *  added to this XPath will be available for resolving the
363      *  XPath expression.
364      *  </p>
365      *
366      *  <p>
367      *  This is a convenience method for adding mappings to the
368      *  default {@link NamespaceContext} in place for this XPath.
369      *  If you have installed a custom <code>NamespaceContext</code>
370      *  that is not a <code>SimpleNamespaceContext</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 the <code>NamespaceContext</code>
378      *          used by this XPath is not a <code>SimpleNamespaceContext</code>
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 non-simple 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         return getContextSupport().getNamespaceContext();
480     }
481 
482     /*** Retrieve the <code>FunctionContext</code> used by this XPath
483      *  expression.
484      *
485      *  <p>
486      *  A <code>FunctionContext</code> is responsible for resolving
487      *  all function calls used within the expression.
488      *  </p>
489      *
490      *  <p>
491      *  If this XPath expression has not previously had a <code>FunctionContext</code>
492      *  installed, a new default <code>FunctionContext</code> will be created,
493      *  installed and returned.
494      *  </p>
495      *
496      *  @return the <code>FunctionContext</code> used by this expression
497      *
498      *  @see FunctionContext
499      */
500     public FunctionContext getFunctionContext()
501     {
502         return getContextSupport().getFunctionContext();
503     }
504 
505     /*** Retrieve the <code>VariableContext</code> used by this XPath
506      *  expression.
507      *
508      *  <p>
509      *  A <code>VariableContext</code> is responsible for resolving
510      *  all variables referenced within the expression.
511      *  </p>
512      *
513      *  <p>
514      *  If this XPath expression has not previously had a <code>VariableContext</code>
515      *  installed, a new default <code>VariableContext</code> will be created,
516      *  installed and returned.
517      *  </p>
518      *  
519      *  @return the <code>VariableContext</code> used by this expression
520      *
521      *  @see VariableContext
522      */
523     public VariableContext getVariableContext()
524     {
525         return getContextSupport().getVariableContext();
526     }
527     
528     
529     /*** Retrieve the root expression of the internal
530      *  compiled form of this XPath expression.
531      *
532      *  <p>
533      *  Internally, Jaxen maintains a form of Abstract Syntax
534      *  Tree (AST) to represent the structure of the XPath expression.
535      *  This is normally not required during normal consumer-grade
536      *  usage of Jaxen.  This method is provided for hard-core users
537      *  who wish to manipulate or inspect a tree-based version of
538      *  the expression.
539      *  </p>
540      *
541      *  @return the root of the AST of this expression
542      */
543     public Expr getRootExpr() 
544     {
545         return xpath.getRootExpr();
546     }
547     
548     /*** Return the original expression text.
549      *
550      *  @return the normalized XPath expression string
551      */
552     public String toString()
553     {
554         return this.exprText;
555     }
556 
557     /*** Returns a string representation of the parse tree.
558      *
559      *  @return a string representation of the parse tree.
560      */
561     public String debug()
562     {
563         return this.xpath.toString();
564     }
565     
566     // ------------------------------------------------------------
567     // ------------------------------------------------------------
568     //     Implementation methods
569     // ------------------------------------------------------------
570     // ------------------------------------------------------------
571 
572     
573     /*** Create a {@link Context} wrapper for the provided
574      *  implementation-specific object.
575      *
576      *  @param node the implementation-specific object 
577      *         to be used as the context
578      *
579      *  @return a <code>Context</code> wrapper around the object
580      */
581     protected Context getContext(Object node)
582     {
583         if ( node instanceof Context )
584         {
585             return (Context) node;
586         }
587 
588         Context fullContext = new Context( getContextSupport() );
589 
590         if ( node instanceof List )
591         {
592             fullContext.setNodeSet( (List) node );
593         }
594         else
595         {
596             List list = new SingletonList(node);
597             fullContext.setNodeSet( list );
598         }
599 
600         return fullContext;
601     }
602 
603     /*** Retrieve the {@link ContextSupport} aggregation of
604      *  <code>NamespaceContext</code>, <code>FunctionContext</code>,
605      *  <code>VariableContext</code>, and {@link Navigator}.
606      *
607      *  @return aggregate <code>ContextSupport</code> for this
608      *          XPath expression
609      */
610     protected ContextSupport getContextSupport()
611     {
612         if ( support == null )
613         {
614             support = new ContextSupport( 
615                 createNamespaceContext(),
616                 createFunctionContext(),
617                 createVariableContext(),
618                 getNavigator() 
619             );
620         }
621 
622         return support;
623     }
624 
625     /*** Retrieve the XML object-model-specific {@link Navigator} 
626      *  for us in evaluating this XPath expression.
627      *
628      *  @return the implementation-specific <code>Navigator</code>
629      */
630     public Navigator getNavigator()
631     {
632         return navigator;
633     }
634     
635     
636 
637     // ------------------------------------------------------------
638     // ------------------------------------------------------------
639     //     Factory methods for default contexts
640     // ------------------------------------------------------------
641     // ------------------------------------------------------------
642 
643     /*** Create a default <code>FunctionContext</code>.
644      *
645      *  @return a default <code>FunctionContext</code>
646      */
647     protected FunctionContext createFunctionContext()
648     {
649         return XPathFunctionContext.getInstance();
650     }
651     
652     /*** Create a default <code>NamespaceContext</code>.
653      *
654      *  @return a default <code>NamespaceContext</code> instance
655      */
656     protected NamespaceContext createNamespaceContext()
657     {
658         return new SimpleNamespaceContext();
659     }
660     
661     /*** Create a default <code>VariableContext</code>.
662      *
663      *  @return a default <code>VariableContext</code> instance
664      */
665     protected VariableContext createVariableContext()
666     {
667         return new SimpleVariableContext();
668     }
669     
670     /*** Select all nodes that match this XPath
671      *  expression on the given Context object. 
672      *  If multiple nodes match, multiple nodes
673      *  will be returned in document-order, as defined by the XPath
674      *  specification. If the expression selects a non-node-set
675      *  (i.e. a number, boolean, or string) then a List
676      *  containing just that one object is returned.
677      *  </p>
678      *
679      * @param context the Context which gets evaluated
680      *
681      * @return the node-set of all items selected
682      *          by this XPath expression
683      * @throws JaxenException if an XPath error occurs during expression evaluation
684      *
685      */
686     protected List selectNodesForContext(Context context) throws JaxenException
687     {
688         List list = this.xpath.asList( context );
689         return list;
690         
691     }
692  
693 
694     /*** Return only the first node that is selected by this XPath
695      *  expression.  If multiple nodes match, only one node will be
696      *  returned. The selected node will be the first
697      *  selected node in document-order, as defined by the XPath
698      *  specification. If the XPath expression selects a double,
699      *  String, or boolean, then that object is returned.
700      *  </p>
701      *
702      * @param context the Context against which this expression is evaluated
703      *
704      * @return the first node in document order of all nodes selected
705      *          by this XPath expression
706      * @throws JaxenException if an XPath error occurs during expression evaluation
707      *
708      *  @see #selectNodesForContext
709      */
710     protected Object selectSingleNodeForContext(Context context) throws JaxenException
711     {
712         List results = selectNodesForContext( context );
713 
714         if ( results.isEmpty() )
715         {
716             return null;
717         }
718 
719         return results.get( 0 );
720     }
721     
722 }