View Javadoc

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