1   
2   
3   
4   
5   
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      
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     
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         
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 
380 
381 
382 
383 
384 
385 
386 
387 
388 
389 
390 
391 
392 
393 
394 
395 
396 
397 
398 
399 
400 
401 
402 
403 
404 
405 
406 
407 
408 
409 
410 
411 
412 
413