View Javadoc

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