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