View Javadoc

1   /*
2    * $Header$
3    * $Revision: 1321 $
4    * $Date: 2008-04-26 17:30:06 -0700 (Sat, 26 Apr 2008) $
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: BaseXPath.java 1321 2008-04-27 00:30:06Z elharo $
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 so that it can 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     /** Select all nodes that are selected by this XPath
196      *  expression. If multiple nodes match, multiple nodes
197      *  will be returned. Nodes will be returned
198      *  in document-order, as defined by the XPath
199      *  specification. If the expression selects a non-node-set
200      *  (i.e. a number, boolean, or string) then a List
201      *  containing just that one object is returned.
202      *  </p>
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
208      *          by this XPath expression
209      * @throws JaxenException if an XPath error occurs during expression evaluation
210      *
211      * @see #selectNodesForContext
212      */
213     public List selectNodes(Object node) throws JaxenException
214     {
215         Context context = getContext( node );
216         return selectNodesForContext( context );
217     }
218 
219     /** Select only the first node selected by this XPath
220      *  expression.  If multiple nodes match, only one node will be
221      *  returned. The selected node will be the first
222      *  selected node in document-order, as defined by the XPath
223      *  specification.
224      *  </p>
225      *
226      * @param node the node, node-set or Context object for evaluation. 
227      *     This value can be null.
228      *
229      * @return the node-set of all items selected
230      *          by this XPath expression
231      * @throws JaxenException if an XPath error occurs during expression evaluation
232      *
233      * @see #selectNodes
234      */
235     public Object selectSingleNode(Object node) throws JaxenException
236     {
237         List results = selectNodes( node );
238 
239         if ( results.isEmpty() )
240         {
241             return null;
242         }
243 
244         return results.get( 0 );
245     }
246 
247     /**
248      * Returns the XPath string-value of the argument node.
249      * 
250      * @param node the node whose value to take
251      * @return the XPath string value of this node
252      * @throws JaxenException if an XPath error occurs during expression evaluation
253      * @deprecated replaced by {@link #stringValueOf}
254      */
255     public String valueOf(Object node) throws JaxenException
256     {
257         return stringValueOf( node );
258     }
259 
260     /** Retrieves the string-value of the result of
261      *  evaluating this XPath expression when evaluated 
262      *  against the specified context.
263      *
264      *  <p>
265      *  The string-value of the expression is determined per
266      *  the <code>string(..)</code> core function defined
267      *  in the XPath specification.  This means that an expression
268      *  that selects zero nodes will return the empty string,
269      *  while an expression that selects one-or-more nodes will
270      *  return the string-value of the first node.
271      *  </p>
272      *
273      * @param node the node, node-set or Context object for evaluation. This value can be null.
274      *
275      * @return the string-value of the result of evaluating this expression with the specified context node
276      * @throws JaxenException if an XPath error occurs during expression evaluation
277      */
278     public String stringValueOf(Object node) throws JaxenException
279     {
280         Context context = getContext( node );
281         
282         Object result = selectSingleNodeForContext( context );
283 
284         if ( result == null )
285         {
286             return "";
287         }
288 
289         return StringFunction.evaluate( result,
290                                         context.getNavigator() );
291     }
292 
293     /** Retrieve a boolean-value interpretation of this XPath
294      *  expression when evaluated against a given context.
295      *
296      *  <p>
297      *  The boolean-value of the expression is determined per
298      *  the <code>boolean(..)</code> function defined
299      *  in the XPath specification.  This means that an expression
300      *  that selects zero nodes will return <code>false</code>,
301      *  while an expression that selects one or more nodes will
302      *  return <code>true</code>.
303      *  </p>
304      *
305      * @param node the node, node-set or Context object for evaluation. This value can be null.
306      *
307      * @return the boolean-value of the result of evaluating this expression with the specified context node
308      * @throws JaxenException if an XPath error occurs during expression evaluation
309      */
310     public boolean booleanValueOf(Object node) throws JaxenException
311     {
312         Context context = getContext( node );
313         List result = selectNodesForContext( context );
314         if ( result == null ) return false;
315         return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
316     }
317 
318     /** Retrieve a number-value interpretation of this XPath
319      *  expression when evaluated against a given context.
320      *
321      *  <p>
322      *  The number-value of the expression is determined per
323      *  the <code>number(..)</code> core function as defined
324      *  in the XPath specification. This means that if this
325      *  expression selects multiple nodes, the number-value
326      *  of the first node is returned.
327      *  </p>
328      *
329      * @param node the node, node-set or Context object for evaluation. This value can be null.
330      *
331      * @return a <code>Double</code> indicating the numeric value of
332      *      evaluating this expression against the specified context
333      * @throws JaxenException if an XPath error occurs during expression evaluation
334      */
335     public Number numberValueOf(Object node) throws JaxenException
336     {
337         Context context = getContext( node );
338         Object result = selectSingleNodeForContext( context );
339         return NumberFunction.evaluate( result,
340                                         context.getNavigator() );
341     }
342 
343     // Helpers
344 
345     /** Add a namespace prefix-to-URI mapping for this XPath
346      *  expression.
347      *
348      *  <p>
349      *  Namespace prefix-to-URI mappings in an XPath are independent
350      *  of those used within any document.  Only the mapping explicitly
351      *  added to this XPath will be available for resolving the
352      *  XPath expression.
353      *  </p>
354      *
355      *  <p>
356      *  This is a convenience method for adding mappings to the
357      *  default {@link NamespaceContext} in place for this XPath.
358      *  If you have installed a custom <code>NamespaceContext</code>
359      *  that is not a <code>SimpleNamespaceContext</code>,
360      *  then this method will throw a <code>JaxenException</code>.
361      *  </p>
362      *
363      *  @param prefix the namespace prefix
364      *  @param uri the namespace URI
365      *
366      *  @throws JaxenException if the <code>NamespaceContext</code>
367      *          used by this XPath is not a <code>SimpleNamespaceContext</code>
368      */
369     public void addNamespace(String prefix,
370                              String uri) throws JaxenException
371     {
372         NamespaceContext nsContext = getNamespaceContext();
373         if ( nsContext instanceof SimpleNamespaceContext )
374         {
375             ((SimpleNamespaceContext)nsContext).addNamespace( prefix,
376                                                               uri );
377             return;
378         }
379 
380         throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
381     }
382 
383 
384     // ------------------------------------------------------------
385     // ------------------------------------------------------------
386     //     Properties
387     // ------------------------------------------------------------
388     // ------------------------------------------------------------
389 
390     
391     /** Set a <code>NamespaceContext</code> for use with this
392      *  XPath expression.
393      *
394      *  <p>
395      *  A <code>NamespaceContext</code> is responsible for translating
396      *  namespace prefixes within the expression into namespace URIs.
397      *  </p>
398      *
399      *  @param namespaceContext the <code>NamespaceContext</code> to
400      *         install for this expression
401      *
402      *  @see NamespaceContext
403      *  @see NamespaceContext#translateNamespacePrefixToUri
404      */
405     public void setNamespaceContext(NamespaceContext namespaceContext)
406     {
407         getContextSupport().setNamespaceContext(namespaceContext);
408     }
409 
410     /** Set a <code>FunctionContext</code> for use with this XPath
411      *  expression.
412      *
413      *  <p>
414      *  A <code>FunctionContext</code> is responsible for resolving
415      *  all function calls used within the expression.
416      *  </p>
417      *
418      *  @param functionContext the <code>FunctionContext</code> to
419      *         install for this expression
420      *
421      *  @see FunctionContext
422      *  @see FunctionContext#getFunction
423      */
424     public void setFunctionContext(FunctionContext functionContext)
425     {
426         getContextSupport().setFunctionContext(functionContext);
427     }
428 
429     /** Set a <code>VariableContext</code> for use with this XPath
430      *  expression.
431      *
432      *  <p>
433      *  A <code>VariableContext</code> is responsible for resolving
434      *  all variables referenced within the expression.
435      *  </p>
436      *
437      *  @param variableContext The <code>VariableContext</code> to
438      *         install for this expression
439      *
440      *  @see VariableContext
441      *  @see VariableContext#getVariableValue
442      */
443     public void setVariableContext(VariableContext variableContext)
444     {
445         getContextSupport().setVariableContext(variableContext);
446     }
447 
448     /** Retrieve the <code>NamespaceContext</code> used by this XPath
449      *  expression.
450      *
451      *  <p>
452      *  A <code>NamespaceContext</code> is responsible for mapping
453      *  prefixes used within the expression to namespace URIs.
454      *  </p>
455      *
456      *  <p>
457      *  If this XPath expression has not previously had a <code>NamespaceContext</code>
458      *  installed, a new default <code>NamespaceContext</code> will be created,
459      *  installed and returned.
460      *  </p>
461      *
462      *  @return the <code>NamespaceContext</code> used by this expression
463      *
464      *  @see NamespaceContext
465      */
466     public NamespaceContext getNamespaceContext()
467     {
468         return getContextSupport().getNamespaceContext();
469     }
470 
471     /** Retrieve the <code>FunctionContext</code> used by this XPath
472      *  expression.
473      *
474      *  <p>
475      *  A <code>FunctionContext</code> is responsible for resolving
476      *  all function calls used within the expression.
477      *  </p>
478      *
479      *  <p>
480      *  If this XPath expression has not previously had a <code>FunctionContext</code>
481      *  installed, a new default <code>FunctionContext</code> will be created,
482      *  installed and returned.
483      *  </p>
484      *
485      *  @return the <code>FunctionContext</code> used by this expression
486      *
487      *  @see FunctionContext
488      */
489     public FunctionContext getFunctionContext()
490     {
491         return getContextSupport().getFunctionContext();
492     }
493 
494     /** Retrieve the <code>VariableContext</code> used by this XPath
495      *  expression.
496      *
497      *  <p>
498      *  A <code>VariableContext</code> is responsible for resolving
499      *  all variables referenced within the expression.
500      *  </p>
501      *
502      *  <p>
503      *  If this XPath expression has not previously had a <code>VariableContext</code>
504      *  installed, a new default <code>VariableContext</code> will be created,
505      *  installed and returned.
506      *  </p>
507      *  
508      *  @return the <code>VariableContext</code> used by this expression
509      *
510      *  @see VariableContext
511      */
512     public VariableContext getVariableContext()
513     {
514         return getContextSupport().getVariableContext();
515     }
516     
517     
518     /** Retrieve the root expression of the internal
519      *  compiled form of this XPath expression.
520      *
521      *  <p>
522      *  Internally, Jaxen maintains a form of Abstract Syntax
523      *  Tree (AST) to represent the structure of the XPath expression.
524      *  This is normally not required during normal consumer-grade
525      *  usage of Jaxen.  This method is provided for hard-core users
526      *  who wish to manipulate or inspect a tree-based version of
527      *  the expression.
528      *  </p>
529      *
530      *  @return the root of the AST of this expression
531      */
532     public Expr getRootExpr() 
533     {
534         return xpath.getRootExpr();
535     }
536     
537     /** Return the original expression text.
538      *
539      *  @return the normalized XPath expression string
540      */
541     public String toString()
542     {
543         return this.exprText;
544     }
545 
546     /** Returns a string representation of the parse tree.
547      *
548      *  @return a string representation of the parse tree.
549      */
550     public String debug()
551     {
552         return this.xpath.toString();
553     }
554     
555     // ------------------------------------------------------------
556     // ------------------------------------------------------------
557     //     Implementation methods
558     // ------------------------------------------------------------
559     // ------------------------------------------------------------
560 
561     
562     /** Create a {@link Context} wrapper for the provided
563      *  implementation-specific object.
564      *
565      *  @param node the implementation-specific object 
566      *         to be used as the context
567      *
568      *  @return a <code>Context</code> wrapper around the object
569      */
570     protected Context getContext(Object node)
571     {
572         if ( node instanceof Context )
573         {
574             return (Context) node;
575         }
576 
577         Context fullContext = new Context( getContextSupport() );
578 
579         if ( node instanceof List )
580         {
581             fullContext.setNodeSet( (List) node );
582         }
583         else
584         {
585             List list = new SingletonList(node);
586             fullContext.setNodeSet( list );
587         }
588 
589         return fullContext;
590     }
591 
592     /** Retrieve the {@link ContextSupport} aggregation of
593      *  <code>NamespaceContext</code>, <code>FunctionContext</code>,
594      *  <code>VariableContext</code>, and {@link Navigator}.
595      *
596      *  @return aggregate <code>ContextSupport</code> for this
597      *          XPath expression
598      */
599     protected ContextSupport getContextSupport()
600     {
601         if ( support == null )
602         {
603             support = new ContextSupport( 
604                 createNamespaceContext(),
605                 createFunctionContext(),
606                 createVariableContext(),
607                 getNavigator() 
608             );
609         }
610 
611         return support;
612     }
613 
614     /** Retrieve the XML object-model-specific {@link Navigator} 
615      *  for us in evaluating this XPath expression.
616      *
617      *  @return the implementation-specific <code>Navigator</code>
618      */
619     public Navigator getNavigator()
620     {
621         return navigator;
622     }
623     
624     
625 
626     // ------------------------------------------------------------
627     // ------------------------------------------------------------
628     //     Factory methods for default contexts
629     // ------------------------------------------------------------
630     // ------------------------------------------------------------
631 
632     /** Create a default <code>FunctionContext</code>.
633      *
634      *  @return a default <code>FunctionContext</code>
635      */
636     protected FunctionContext createFunctionContext()
637     {
638         return XPathFunctionContext.getInstance();
639     }
640     
641     /** Create a default <code>NamespaceContext</code>.
642      *
643      *  @return a default <code>NamespaceContext</code> instance
644      */
645     protected NamespaceContext createNamespaceContext()
646     {
647         return new SimpleNamespaceContext();
648     }
649     
650     /** Create a default <code>VariableContext</code>.
651      *
652      *  @return a default <code>VariableContext</code> instance
653      */
654     protected VariableContext createVariableContext()
655     {
656         return new SimpleVariableContext();
657     }
658     
659     /** Select all nodes that match this XPath
660      *  expression on the given Context object. 
661      *  If multiple nodes match, multiple nodes
662      *  will be returned in document-order, as defined by the XPath
663      *  specification. If the expression selects a non-node-set
664      *  (i.e. a number, boolean, or string) then a List
665      *  containing just that one object is returned.
666      *  </p>
667      *
668      * @param context the Context which gets evaluated
669      *
670      * @return the node-set of all items selected
671      *          by this XPath expression
672      * @throws JaxenException if an XPath error occurs during expression evaluation
673      *
674      */
675     protected List selectNodesForContext(Context context) throws JaxenException
676     {
677         List list = this.xpath.asList( context );
678         return list;
679         
680     }
681  
682 
683     /** Return only the first node that is selected by this XPath
684      *  expression.  If multiple nodes match, only one node will be
685      *  returned. The selected node will be the first
686      *  selected node in document-order, as defined by the XPath
687      *  specification. If the XPath expression selects a double,
688      *  String, or boolean, then that object is returned.
689      *  </p>
690      *
691      * @param context the Context against which this expression is evaluated
692      *
693      * @return the first node in document order of all nodes selected
694      *          by this XPath expression
695      * @throws JaxenException if an XPath error occurs during expression evaluation
696      *
697      *  @see #selectNodesForContext
698      */
699     protected Object selectSingleNodeForContext(Context context) throws JaxenException
700     {
701         List results = selectNodesForContext( context );
702 
703         if ( results.isEmpty() )
704         {
705             return null;
706         }
707 
708         return results.get( 0 );
709     }
710     
711 }