View Javadoc

1   /*
2    * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/BaseXPath.java,v 1.50 2007/04/16 15:44:37 elharo Exp $
3    * $Revision: 1.50 $
4    * $Date: 2007/04/16 15:44:37 $
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,v 1.50 2007/04/16 15:44:37 elharo Exp $
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      /*** Original expression text. */
88      private final String exprText;
89  
90      /*** the parsed form of the XPath expression */
91      private final XPathExpr xpath;
92      
93      /*** the support information and function, namespace and variable contexts */
94      private ContextSupport support;
95  
96      /*** the implementation-specific Navigator for retrieving XML nodes **/
97      private Navigator navigator;
98      
99      /*** Construct given an XPath expression string. 
100      *
101      *  @param xpathExpr the XPath expression
102      *
103      *  @throws JaxenException if there is a syntax error while
104      *          parsing the expression
105      */
106     protected BaseXPath(String xpathExpr) throws JaxenException
107     {
108         try
109         {
110             XPathReader reader = XPathReaderFactory.createReader();
111             JaxenHandler handler = new JaxenHandler();
112             reader.setXPathHandler( handler );
113             reader.parse( xpathExpr );
114             this.xpath = handler.getXPathExpr();
115         }
116         catch (org.jaxen.saxpath.XPathSyntaxException e)
117         {
118             throw new org.jaxen.XPathSyntaxException( e );
119         }
120         catch (SAXPathException e)
121         {
122             throw new JaxenException( e );
123         }
124 
125         this.exprText = xpathExpr;
126     }
127 
128     /*** Construct given an XPath expression string.
129      *
130      *  @param xpathExpr the XPath expression
131      *
132      *  @param navigator the XML navigator to use
133      *
134      *  @throws JaxenException if there is a syntax error while
135      *          parsing the expression
136      */
137     public BaseXPath(String xpathExpr, Navigator navigator) throws JaxenException
138     {
139         this( xpathExpr );
140         this.navigator = navigator;
141     }
142 
143     /*** Evaluate this XPath against a given context.
144      *  The context of evaluation may be any object type
145      *  the navigator recognizes as a node.
146      *  The return value is either a <code>String</code>,
147      *  <code>Double</code>, <code>Boolean</code>, or <code>List</code>
148      *  of nodes.
149      *
150      *  <p>
151      *  When using this method, one must be careful to
152      *  test the class of the returned object.  If the returned 
153      *  object is a list, then the items in this 
154      *  list will be the actual <code>Document</code>,
155      *  <code>Element</code>, <code>Attribute</code>, etc. objects
156      *  as defined by the concrete XML object-model implementation,
157      *  directly from the context document.  This method <strong>does
158      *  not return <em>copies</em> of anything</strong>, but merely 
159      *  returns references to objects within the source document.
160      *  </p>
161      *  
162      * @param context the node, node-set or Context object for evaluation. 
163      *      This value can be null.
164      *
165      * @return the result of evaluating the XPath expression
166      *          against the supplied context
167      * @throws JaxenException if an XPath error occurs during expression evaluation
168      * @throws ClassCastException if the context is not a node
169      */
170     public Object evaluate(Object context) throws JaxenException
171     {
172         List answer = selectNodes(context);
173 
174         if ( answer != null
175              &&
176              answer.size() == 1 )
177         {
178             Object first = answer.get(0);
179 
180             if ( first instanceof String
181                  ||
182                  first instanceof Number
183                  ||
184                  first instanceof Boolean ) 
185             {
186                 return first;
187             }
188         }
189         return answer;
190     }
191     
192     /*** Select all nodes that are selected by this XPath
193      *  expression. If multiple nodes match, multiple nodes
194      *  will be returned. Nodes will be returned
195      *  in document-order, as defined by the XPath
196      *  specification. If the expression selects a non-node-set
197      *  (i.e. a number, boolean, or string) then a List
198      *  containing just that one object is returned.
199      *  </p>
200      *
201      * @param node the node, node-set or Context object for evaluation. 
202      *     This value can be null.
203      *
204      * @return the node-set of all items selected
205      *          by this XPath expression
206      * @throws JaxenException if an XPath error occurs during expression evaluation
207      *
208      * @see #selectNodesForContext
209      */
210     public List selectNodes(Object node) throws JaxenException
211     {
212         Context context = getContext( node );
213         return selectNodesForContext( context );
214     }
215 
216     /*** Select only the first node selected by this XPath
217      *  expression.  If multiple nodes match, only one node will be
218      *  returned. The selected node will be the first
219      *  selected node in document-order, as defined by the XPath
220      *  specification.
221      *  </p>
222      *
223      * @param node the node, node-set or Context object for evaluation. 
224      *     This value can be null.
225      *
226      * @return the node-set of all items selected
227      *          by this XPath expression
228      * @throws JaxenException if an XPath error occurs during expression evaluation
229      *
230      * @see #selectNodes
231      */
232     public Object selectSingleNode(Object node) throws JaxenException
233     {
234         List results = selectNodes( node );
235 
236         if ( results.isEmpty() )
237         {
238             return null;
239         }
240 
241         return results.get( 0 );
242     }
243 
244     /***
245      * Returns the XPath string-value of the argument node.
246      * 
247      * @param node the node whose value to take
248      * @return the XPath string value of this node
249      * @throws JaxenException if an XPath error occurs during expression evaluation
250      * @deprecated replaced by {@link #stringValueOf}
251      */
252     public String valueOf(Object node) throws JaxenException
253     {
254         return stringValueOf( node );
255     }
256 
257     /*** Retrieves the string-value of the result of
258      *  evaluating this XPath expression when evaluated 
259      *  against the specified context.
260      *
261      *  <p>
262      *  The string-value of the expression is determined per
263      *  the <code>string(..)</code> core function defined
264      *  in the XPath specification.  This means that an expression
265      *  that selects zero nodes will return the empty string,
266      *  while an expression that selects one-or-more nodes will
267      *  return the string-value of the first node.
268      *  </p>
269      *
270      * @param node the node, node-set or Context object for evaluation. This value can be null.
271      *
272      * @return the string-value of the result of evaluating this expression with the specified context node
273      * @throws JaxenException if an XPath error occurs during expression evaluation
274      */
275     public String stringValueOf(Object node) throws JaxenException
276     {
277         Context context = getContext( node );
278         
279         Object result = selectSingleNodeForContext( context );
280 
281         if ( result == null )
282         {
283             return "";
284         }
285 
286         return StringFunction.evaluate( result,
287                                         context.getNavigator() );
288     }
289 
290     /*** Retrieve a boolean-value interpretation of this XPath
291      *  expression when evaluated against a given context.
292      *
293      *  <p>
294      *  The boolean-value of the expression is determined per
295      *  the <code>boolean(..)</code> function defined
296      *  in the XPath specification.  This means that an expression
297      *  that selects zero nodes will return <code>false</code>,
298      *  while an expression that selects one or more nodes will
299      *  return <code>true</code>.
300      *  </p>
301      *
302      * @param node the node, node-set or Context object for evaluation. This value can be null.
303      *
304      * @return the boolean-value of the result of evaluating this expression with the specified context node
305      * @throws JaxenException if an XPath error occurs during expression evaluation
306      */
307     public boolean booleanValueOf(Object node) throws JaxenException
308     {
309         Context context = getContext( node );
310         List result = selectNodesForContext( context );
311         if ( result == null ) return false;
312         return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
313     }
314 
315     /*** Retrieve a number-value interpretation of this XPath
316      *  expression when evaluated against a given context.
317      *
318      *  <p>
319      *  The number-value of the expression is determined per
320      *  the <code>number(..)</code> core function as defined
321      *  in the XPath specification. This means that if this
322      *  expression selects multiple nodes, the number-value
323      *  of the first node is returned.
324      *  </p>
325      *
326      * @param node the node, node-set or Context object for evaluation. This value can be null.
327      *
328      * @return a <code>Double</code> indicating the numeric value of
329      *      evaluating this expression against the specified context
330      * @throws JaxenException if an XPath error occurs during expression evaluation
331      */
332     public Number numberValueOf(Object node) throws JaxenException
333     {
334         Context context = getContext( node );
335         Object result = selectSingleNodeForContext( context );
336         return NumberFunction.evaluate( result,
337                                         context.getNavigator() );
338     }
339 
340     // Helpers
341 
342     /*** Add a namespace prefix-to-URI mapping for this XPath
343      *  expression.
344      *
345      *  <p>
346      *  Namespace prefix-to-URI mappings in an XPath are independent
347      *  of those used within any document.  Only the mapping explicitly
348      *  added to this XPath will be available for resolving the
349      *  XPath expression.
350      *  </p>
351      *
352      *  <p>
353      *  This is a convenience method for adding mappings to the
354      *  default {@link NamespaceContext} in place for this XPath.
355      *  If you have installed a custom <code>NamespaceContext</code>
356      *  that is not a <code>SimpleNamespaceContext</code>,
357      *  then this method will throw a <code>JaxenException</code>.
358      *  </p>
359      *
360      *  @param prefix the namespace prefix
361      *  @param uri the namespace URI
362      *
363      *  @throws JaxenException if the <code>NamespaceContext</code>
364      *          used by this XPath is not a <code>SimpleNamespaceContext</code>
365      */
366     public void addNamespace(String prefix,
367                              String uri) throws JaxenException
368     {
369         NamespaceContext nsContext = getNamespaceContext();
370         if ( nsContext instanceof SimpleNamespaceContext )
371         {
372             ((SimpleNamespaceContext)nsContext).addNamespace( prefix,
373                                                               uri );
374             return;
375         }
376 
377         throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
378     }
379 
380 
381     // ------------------------------------------------------------
382     // ------------------------------------------------------------
383     //     Properties
384     // ------------------------------------------------------------
385     // ------------------------------------------------------------
386 
387     
388     /*** Set a <code>NamespaceContext</code> for use with this
389      *  XPath expression.
390      *
391      *  <p>
392      *  A <code>NamespaceContext</code> is responsible for translating
393      *  namespace prefixes within the expression into namespace URIs.
394      *  </p>
395      *
396      *  @param namespaceContext the <code>NamespaceContext</code> to
397      *         install for this expression
398      *
399      *  @see NamespaceContext
400      *  @see NamespaceContext#translateNamespacePrefixToUri
401      */
402     public void setNamespaceContext(NamespaceContext namespaceContext)
403     {
404         getContextSupport().setNamespaceContext(namespaceContext);
405     }
406 
407     /*** Set a <code>FunctionContext</code> for use with this XPath
408      *  expression.
409      *
410      *  <p>
411      *  A <code>FunctionContext</code> is responsible for resolving
412      *  all function calls used within the expression.
413      *  </p>
414      *
415      *  @param functionContext the <code>FunctionContext</code> to
416      *         install for this expression
417      *
418      *  @see FunctionContext
419      *  @see FunctionContext#getFunction
420      */
421     public void setFunctionContext(FunctionContext functionContext)
422     {
423         getContextSupport().setFunctionContext(functionContext);
424     }
425 
426     /*** Set a <code>VariableContext</code> for use with this XPath
427      *  expression.
428      *
429      *  <p>
430      *  A <code>VariableContext</code> is responsible for resolving
431      *  all variables referenced within the expression.
432      *  </p>
433      *
434      *  @param variableContext The <code>VariableContext</code> to
435      *         install for this expression
436      *
437      *  @see VariableContext
438      *  @see VariableContext#getVariableValue
439      */
440     public void setVariableContext(VariableContext variableContext)
441     {
442         getContextSupport().setVariableContext(variableContext);
443     }
444 
445     /*** Retrieve the <code>NamespaceContext</code> used by this XPath
446      *  expression.
447      *
448      *  <p>
449      *  A <code>NamespaceContext</code> is responsible for mapping
450      *  prefixes used within the expression to namespace URIs.
451      *  </p>
452      *
453      *  <p>
454      *  If this XPath expression has not previously had a <code>NamespaceContext</code>
455      *  installed, a new default <code>NamespaceContext</code> will be created,
456      *  installed and returned.
457      *  </p>
458      *
459      *  @return the <code>NamespaceContext</code> used by this expression
460      *
461      *  @see NamespaceContext
462      */
463     public NamespaceContext getNamespaceContext()
464     {
465         return getContextSupport().getNamespaceContext();
466     }
467 
468     /*** Retrieve the <code>FunctionContext</code> used by this XPath
469      *  expression.
470      *
471      *  <p>
472      *  A <code>FunctionContext</code> is responsible for resolving
473      *  all function calls used within the expression.
474      *  </p>
475      *
476      *  <p>
477      *  If this XPath expression has not previously had a <code>FunctionContext</code>
478      *  installed, a new default <code>FunctionContext</code> will be created,
479      *  installed and returned.
480      *  </p>
481      *
482      *  @return the <code>FunctionContext</code> used by this expression
483      *
484      *  @see FunctionContext
485      */
486     public FunctionContext getFunctionContext()
487     {
488         return getContextSupport().getFunctionContext();
489     }
490 
491     /*** Retrieve the <code>VariableContext</code> used by this XPath
492      *  expression.
493      *
494      *  <p>
495      *  A <code>VariableContext</code> is responsible for resolving
496      *  all variables referenced within the expression.
497      *  </p>
498      *
499      *  <p>
500      *  If this XPath expression has not previously had a <code>VariableContext</code>
501      *  installed, a new default <code>VariableContext</code> will be created,
502      *  installed and returned.
503      *  </p>
504      *  
505      *  @return the <code>VariableContext</code> used by this expression
506      *
507      *  @see VariableContext
508      */
509     public VariableContext getVariableContext()
510     {
511         return getContextSupport().getVariableContext();
512     }
513     
514     
515     /*** Retrieve the root expression of the internal
516      *  compiled form of this XPath expression.
517      *
518      *  <p>
519      *  Internally, Jaxen maintains a form of Abstract Syntax
520      *  Tree (AST) to represent the structure of the XPath expression.
521      *  This is normally not required during normal consumer-grade
522      *  usage of Jaxen.  This method is provided for hard-core users
523      *  who wish to manipulate or inspect a tree-based version of
524      *  the expression.
525      *  </p>
526      *
527      *  @return the root of the AST of this expression
528      */
529     public Expr getRootExpr() 
530     {
531         return xpath.getRootExpr();
532     }
533     
534     /*** Return the original expression text.
535      *
536      *  @return the normalized XPath expression string
537      */
538     public String toString()
539     {
540         return this.exprText;
541     }
542 
543     /*** Returns a string representation of the parse tree.
544      *
545      *  @return a string representation of the parse tree.
546      */
547     public String debug()
548     {
549         return this.xpath.toString();
550     }
551     
552     // ------------------------------------------------------------
553     // ------------------------------------------------------------
554     //     Implementation methods
555     // ------------------------------------------------------------
556     // ------------------------------------------------------------
557 
558     
559     /*** Create a {@link Context} wrapper for the provided
560      *  implementation-specific object.
561      *
562      *  @param node the implementation-specific object 
563      *         to be used as the context
564      *
565      *  @return a <code>Context</code> wrapper around the object
566      */
567     protected Context getContext(Object node)
568     {
569         if ( node instanceof Context )
570         {
571             return (Context) node;
572         }
573 
574         Context fullContext = new Context( getContextSupport() );
575 
576         if ( node instanceof List )
577         {
578             fullContext.setNodeSet( (List) node );
579         }
580         else
581         {
582             List list = new SingletonList(node);
583             fullContext.setNodeSet( list );
584         }
585 
586         return fullContext;
587     }
588 
589     /*** Retrieve the {@link ContextSupport} aggregation of
590      *  <code>NamespaceContext</code>, <code>FunctionContext</code>,
591      *  <code>VariableContext</code>, and {@link Navigator}.
592      *
593      *  @return aggregate <code>ContextSupport</code> for this
594      *          XPath expression
595      */
596     protected ContextSupport getContextSupport()
597     {
598         if ( support == null )
599         {
600             support = new ContextSupport( 
601                 createNamespaceContext(),
602                 createFunctionContext(),
603                 createVariableContext(),
604                 getNavigator() 
605             );
606         }
607 
608         return support;
609     }
610 
611     /*** Retrieve the XML object-model-specific {@link Navigator} 
612      *  for us in evaluating this XPath expression.
613      *
614      *  @return the implementation-specific <code>Navigator</code>
615      */
616     public Navigator getNavigator()
617     {
618         return navigator;
619     }
620     
621     
622 
623     // ------------------------------------------------------------
624     // ------------------------------------------------------------
625     //     Factory methods for default contexts
626     // ------------------------------------------------------------
627     // ------------------------------------------------------------
628 
629     /*** Create a default <code>FunctionContext</code>.
630      *
631      *  @return a default <code>FunctionContext</code>
632      */
633     protected FunctionContext createFunctionContext()
634     {
635         return XPathFunctionContext.getInstance();
636     }
637     
638     /*** Create a default <code>NamespaceContext</code>.
639      *
640      *  @return a default <code>NamespaceContext</code> instance
641      */
642     protected NamespaceContext createNamespaceContext()
643     {
644         return new SimpleNamespaceContext();
645     }
646     
647     /*** Create a default <code>VariableContext</code>.
648      *
649      *  @return a default <code>VariableContext</code> instance
650      */
651     protected VariableContext createVariableContext()
652     {
653         return new SimpleVariableContext();
654     }
655     
656     /*** Select all nodes that match this XPath
657      *  expression on the given Context object. 
658      *  If multiple nodes match, multiple nodes
659      *  will be returned in document-order, as defined by the XPath
660      *  specification. If the expression selects a non-node-set
661      *  (i.e. a number, boolean, or string) then a List
662      *  containing just that one object is returned.
663      *  </p>
664      *
665      * @param context the Context which gets evaluated
666      *
667      * @return the node-set of all items selected
668      *          by this XPath expression
669      * @throws JaxenException if an XPath error occurs during expression evaluation
670      *
671      */
672     protected List selectNodesForContext(Context context) throws JaxenException
673     {
674         List list = this.xpath.asList( context );
675         return list;
676         
677     }
678  
679 
680     /*** Return only the first node that is selected by this XPath
681      *  expression.  If multiple nodes match, only one node will be
682      *  returned. The selected node will be the first
683      *  selected node in document-order, as defined by the XPath
684      *  specification. If the XPath expression selects a double,
685      *  String, or boolean, then that object is returned.
686      *  </p>
687      *
688      * @param context the Context against which this expression is evaluated
689      *
690      * @return the first node in document order of all nodes selected
691      *          by this XPath expression
692      * @throws JaxenException if an XPath error occurs during expression evaluation
693      *
694      *  @see #selectNodesForContext
695      */
696     protected Object selectSingleNodeForContext(Context context) throws JaxenException
697     {
698         List results = selectNodesForContext( context );
699 
700         if ( results.isEmpty() )
701         {
702             return null;
703         }
704 
705         return results.get( 0 );
706     }
707     
708 }