View Javadoc

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