View Javadoc

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