View Javadoc

1   /*
2    * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved.
3    * 
4    * This software is open source. 
5    * See the bottom of this file for the licence.
6    * 
7    * $Id: DefaultXPath.java,v 1.31 2004/06/25 08:03:42 maartenc Exp $
8    */
9   
10  package org.dom4j.xpath;
11  
12  import java.io.Serializable;
13  import java.util.Collections;
14  import java.util.Comparator;
15  import java.util.HashMap;
16  import java.util.HashSet;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Map;
20  
21  import org.dom4j.InvalidXPathException;
22  import org.dom4j.Node;
23  import org.dom4j.NodeFilter;
24  import org.dom4j.XPathException;
25  import org.jaxen.FunctionContext;
26  import org.jaxen.JaxenException;
27  import org.jaxen.NamespaceContext;
28  import org.jaxen.SimpleNamespaceContext;
29  import org.jaxen.VariableContext;
30  import org.jaxen.XPath;
31  import org.jaxen.dom4j.Dom4jXPath;
32  
33  
34  /*** <p>Default implementation of {@link org.dom4j.XPath} which uses the
35   * <a href="http://jaxen.org">Jaxen</a> project.</p>
36   *
37   *  @author bob mcwhirter (bob @ werken.com)
38    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
39   */
40  public class DefaultXPath implements org.dom4j.XPath, NodeFilter, Serializable {
41  
42      private String text;
43      private XPath xpath;
44      private NamespaceContext namespaceContext;
45      
46  
47      /*** Construct an XPath
48       */
49      public DefaultXPath(String text) throws InvalidXPathException {
50          this.text = text;
51          this.xpath = parse( text );
52      }
53  
54      public String toString() {
55          return "[XPath: " + xpath + "]";
56      }
57  
58      
59      // XPath interface 
60      
61      /*** Retrieve the textual XPath string used to initialize this Object
62       *
63       *  @return The XPath string
64       */
65      public String getText() {
66          return text;
67      }
68  
69      public FunctionContext getFunctionContext() {
70          return xpath.getFunctionContext();
71      }
72      
73      public void setFunctionContext(FunctionContext functionContext) {
74          xpath.setFunctionContext(functionContext);
75      }
76      
77      public NamespaceContext getNamespaceContext() {
78          return namespaceContext;
79      }
80      
81      public void setNamespaceURIs(Map map) {
82          setNamespaceContext( new SimpleNamespaceContext( map ) );
83      }
84      
85      public void setNamespaceContext(NamespaceContext namespaceContext) {
86          this.namespaceContext = namespaceContext;
87          xpath.setNamespaceContext(namespaceContext);
88      }
89      
90      public VariableContext getVariableContext() {
91          return xpath.getVariableContext();
92      }
93      
94      public void setVariableContext(VariableContext variableContext) {
95          xpath.setVariableContext(variableContext);
96      }
97      
98      public Object evaluate(Object context) {
99          try {
100             setNSContext(context);
101             List answer = xpath.selectNodes( context );
102             if ( answer != null && answer.size() == 1 ) {
103                 return answer.get(0);
104             }
105             return answer;
106         }
107         catch (JaxenException e) {
108             handleJaxenException(e);
109             return null;
110         }
111     }
112 
113     public Object selectObject(Object context) {
114         return evaluate(context);        
115     }
116     
117     public List selectNodes(Object context) {
118         try {
119             setNSContext(context);
120             return xpath.selectNodes( context );
121         }
122         catch (JaxenException e) {
123             handleJaxenException(e);
124             return Collections.EMPTY_LIST;
125         }
126     }
127     
128     
129     public List selectNodes(Object context, org.dom4j.XPath sortXPath) {
130         List answer = selectNodes( context );
131         sortXPath.sort( answer );
132         return answer;
133     }
134     
135     public List selectNodes(Object context, org.dom4j.XPath sortXPath, boolean distinct) {
136         List answer = selectNodes( context );
137         sortXPath.sort( answer, distinct );
138         return answer;
139     }
140     
141     public Node selectSingleNode(Object context) {
142         try {
143             setNSContext(context);
144             Object answer = xpath.selectSingleNode( context );
145             if ( answer instanceof Node ) {
146                 return (Node) answer;
147             }
148             if ( answer == null ) {
149                 return null;
150             }
151             throw new XPathException( 
152                 "The result of the XPath expression is not a Node. It was: " 
153                 + answer + " of type: " + answer.getClass().getName() 
154                 + ". You might want to use a different method such as selectObject() to evaluate this XPath expression" 
155             );
156         }
157         catch (JaxenException e) {
158             handleJaxenException(e);
159             return null;
160         }
161     }
162     
163     
164     public String valueOf(Object context) {
165         try {
166             setNSContext(context);
167             return xpath.valueOf( context );
168         }
169         catch (JaxenException e) {
170             handleJaxenException(e);
171             return "";
172         }
173     }
174 
175     public Number numberValueOf(Object context) {
176         try {
177             setNSContext(context);
178             return xpath.numberValueOf( context );
179         }
180         catch (JaxenException e) {
181             handleJaxenException(e);
182             return null;
183         }
184     }
185     
186     public boolean booleanValueOf(Object context) {
187         try {
188             setNSContext(context);
189             return xpath.booleanValueOf(context);
190         }
191         catch(JaxenException e) {
192             handleJaxenException(e);
193             return false;
194         }
195     }
196 
197     /*** <p><code>sort</code> sorts the given List of Nodes
198       * using this XPath expression as a {@link Comparator}.</p>
199       *
200       * @param list is the list of Nodes to sort
201       */
202     public void sort( List list ) {
203         sort( list, false );
204     }
205     
206     /*** <p><code>sort</code> sorts the given List of Nodes
207       * using this XPath expression as a {@link Comparator} 
208       * and optionally removing duplicates.</p>
209       *
210       * @param list is the list of Nodes to sort
211       * @param distinct if true then duplicate values (using the sortXPath for 
212       *     comparisions) will be removed from the List
213       */
214     public void sort( List list, boolean distinct ) {
215         if ( list != null && ! list.isEmpty() )  {
216             int size = list.size();
217             HashMap sortValues = new HashMap( size );
218             for ( int i = 0; i < size; i++ ) {
219                 Object object = list.get(i);
220                 if ( object instanceof Node ) {
221                     Node node = (Node) object;
222                     Object expression = getCompareValue(node);
223                     sortValues.put(node, expression);
224                 }
225             }
226             sort( list, sortValues );
227 
228             if (distinct) {
229                 removeDuplicates( list, sortValues );
230             }
231         }
232     }
233     
234     public boolean matches( Node node ) {
235         try {
236             setNSContext(node);
237             List answer = xpath.selectNodes( node );
238             if ( answer != null && answer.size() > 0 ) {
239                 Object item = answer.get(0);
240                 if ( item instanceof Boolean ) {
241                     return ((Boolean) item).booleanValue();
242                 }
243                 return answer.contains( node );
244             }
245             return false;
246         }
247         catch (JaxenException e) {
248             handleJaxenException(e);
249             return false;
250         }
251     }
252     
253     /*** Sorts the list based on the sortValues for each node
254       */
255     protected void sort( List list, final Map sortValues ) {
256         Collections.sort(
257             list,
258             new Comparator() {
259                 public int compare(Object o1, Object o2) {
260                     o1 = sortValues.get(o1);
261                     o2 = sortValues.get(o2);
262                     if ( o1 == o2 ) {
263                         return 0;
264                     }
265                     else if ( o1 instanceof Comparable )  {
266                         Comparable c1 = (Comparable) o1;
267                         return c1.compareTo(o2);
268                     }
269                     else if ( o1 == null )  {
270                         return 1;
271                     }
272                     else if ( o2 == null ) {
273                         return -1;
274                     }
275                     else {
276                         return o1.equals(o2) ? 0 : -1;
277                     }
278                 }
279             }
280         );
281     }
282 
283     // Implementation methods
284     
285     
286     
287     /*** Removes items from the list which have duplicate values
288       */
289     protected void removeDuplicates( List list, Map sortValues ) {
290         // remove distinct
291         HashSet distinctValues = new HashSet();
292         for ( Iterator iter = list.iterator(); iter.hasNext(); ) {
293             Object node = iter.next();
294             Object value = sortValues.get(node);
295             if ( distinctValues.contains( value ) ) {
296                 iter.remove();
297             }
298             else {
299                 distinctValues.add( value );
300             }
301         }
302     }
303     
304     /*** @return the node expression used for sorting comparisons
305       */
306     protected Object getCompareValue(Node node) {
307         return valueOf( node );
308     }
309     
310     protected static XPath parse(String text) {        
311         try {
312             return new Dom4jXPath( text );
313         }
314         catch (JaxenException e) {
315             throw new InvalidXPathException( text, e.getMessage() );
316         }
317         catch (RuntimeException e) {
318         }
319         throw new InvalidXPathException( text );
320     }
321     
322     protected void setNSContext(Object context) {
323         if ( namespaceContext == null ) {
324             xpath.setNamespaceContext( DefaultNamespaceContext.create(context) );
325         }
326     }
327         
328     protected void handleJaxenException(JaxenException e) throws XPathException {
329         throw new XPathException(text, e);
330     }
331 }
332 
333 
334 
335 
336 /*
337  * Redistribution and use of this software and associated documentation
338  * ("Software"), with or without modification, are permitted provided
339  * that the following conditions are met:
340  *
341  * 1. Redistributions of source code must retain copyright
342  *    statements and notices.  Redistributions must also contain a
343  *    copy of this document.
344  *
345  * 2. Redistributions in binary form must reproduce the
346  *    above copyright notice, this list of conditions and the
347  *    following disclaimer in the documentation and/or other
348  *    materials provided with the distribution.
349  *
350  * 3. The name "DOM4J" must not be used to endorse or promote
351  *    products derived from this Software without prior written
352  *    permission of MetaStuff, Ltd.  For written permission,
353  *    please contact dom4j-info@metastuff.com.
354  *
355  * 4. Products derived from this Software may not be called "DOM4J"
356  *    nor may "DOM4J" appear in their names without prior written
357  *    permission of MetaStuff, Ltd. DOM4J is a registered
358  *    trademark of MetaStuff, Ltd.
359  *
360  * 5. Due credit should be given to the DOM4J Project - 
361  *    http://www.dom4j.org
362  *
363  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
364  * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
365  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
366  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
367  * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
368  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
369  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
370  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
371  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
372  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
373  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
374  * OF THE POSSIBILITY OF SUCH DAMAGE.
375  *
376  * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved.
377  *
378  * $Id: DefaultXPath.java,v 1.31 2004/06/25 08:03:42 maartenc Exp $
379  */