View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jxpath.ri;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.Comparator;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.NoSuchElementException;
25  
26  import org.apache.commons.jxpath.BasicNodeSet;
27  import org.apache.commons.jxpath.ExpressionContext;
28  import org.apache.commons.jxpath.JXPathContext;
29  import org.apache.commons.jxpath.JXPathException;
30  import org.apache.commons.jxpath.NodeSet;
31  import org.apache.commons.jxpath.Pointer;
32  import org.apache.commons.jxpath.ri.axes.RootContext;
33  import org.apache.commons.jxpath.ri.model.NodePointer;
34  
35  /***
36   * An XPath evaluation context.
37   *
38   * When  evaluating a path, a chain of EvalContexts is created, each context in
39   * the chain representing a step of the path. Subclasses of EvalContext
40   * implement behavior of various XPath axes: "child::", "parent::" etc.
41   *
42   * @author Dmitri Plotnikov
43   * @version $Revision: 1.30 $ $Date: 2004/03/25 05:42:01 $
44   */
45  public abstract class EvalContext implements ExpressionContext, Iterator {
46      protected EvalContext parentContext;
47      protected RootContext rootContext;
48      protected int position = 0;
49      private boolean startedSetIteration = false;
50      private boolean done = false;
51      private boolean hasPerformedIteratorStep = false;
52      private Iterator pointerIterator;
53  
54      // Sorts in the reverse order to the one defined by the Comparable
55      // interface.
56      private static final Comparator REVERSE_COMPARATOR = new Comparator() {
57          public int compare(Object o1, Object o2) {
58              return ((Comparable) o2).compareTo(o1);
59          }
60      };
61  
62      public EvalContext(EvalContext parentContext) {
63          this.parentContext = parentContext;
64      }
65  
66      public Pointer getContextNodePointer() {
67          return getCurrentNodePointer();
68      }
69  
70      public JXPathContext getJXPathContext() {
71          return getRootContext().getJXPathContext();
72      }
73  
74      public int getPosition() {
75          return position;
76      }
77  
78      /***
79       * Determines the document order for this context.
80       *
81       * @return 1 ascending order, -1 descending order,
82       *  0 - does not require ordering
83       */
84      public int getDocumentOrder() {
85          if (parentContext != null && parentContext.isChildOrderingRequired()) {
86              return 1;
87          }
88          return 0;
89      }
90      
91      /***
92       * Even if this context has the natural ordering and therefore does
93       * not require collecting and sorting all nodes prior to returning them,
94       * such operation may be required for any child context.
95       */
96      public boolean isChildOrderingRequired() {
97          // Default behavior: if this context needs to be ordered,
98          // the children need to be ordered too
99          if (getDocumentOrder() != 0) {
100             return true;
101         }
102         return false;
103     }
104 
105     /***
106      * Returns true if there are mode nodes matching the context's constraints.
107      */
108     public boolean hasNext() {
109         if (pointerIterator != null) {
110             return pointerIterator.hasNext();
111         }
112 
113         if (getDocumentOrder() != 0) {
114             return constructIterator();
115         }
116         else {
117             if (!done && !hasPerformedIteratorStep) {
118                 performIteratorStep();
119             }
120             return !done;
121         }
122     }
123 
124     /***
125      * Returns the next node pointer in the context
126      */
127     public Object next() {
128         if (pointerIterator != null) {
129             return pointerIterator.next();
130         }
131 
132         if (getDocumentOrder() != 0) {
133             if (!constructIterator()) {
134                 throw new NoSuchElementException();
135             }
136             return pointerIterator.next();
137         }
138         else {
139             if (!done && !hasPerformedIteratorStep) {
140                 performIteratorStep();
141             }
142             if (done) {
143                 throw new NoSuchElementException();
144             }
145             hasPerformedIteratorStep = false;
146             return getCurrentNodePointer();
147         }
148     }
149 
150     /***
151      * Moves the iterator forward by one position
152      */
153     private void performIteratorStep() {
154         done = true;
155         if (position != 0 && nextNode()) {
156             done = false;
157         }
158         else {
159             while (nextSet()) {
160                 if (nextNode()) {
161                     done = false;
162                     break;
163                 }
164             }
165         }
166         hasPerformedIteratorStep = true;
167     }
168 
169     /***
170      * Operation is not supported
171      */
172     public void remove() {
173         throw new UnsupportedOperationException(
174             "JXPath iterators cannot remove nodes");
175     }
176 
177     private boolean constructIterator() {
178         HashSet set = new HashSet();
179         ArrayList list = new ArrayList();
180         while (nextSet()) {
181             while (nextNode()) {
182                 NodePointer pointer = getCurrentNodePointer();
183                 if (!set.contains(pointer)) {
184 //                    Pointer cln = (Pointer) pointer.clone();
185                     set.add(pointer);
186                     list.add(pointer);
187                 }
188             }
189         }
190         if (list.isEmpty()) {
191             return false;
192         }
193 
194         if (getDocumentOrder() == 1) {
195             Collections.sort(list);
196         }
197         else {
198             Collections.sort(list, REVERSE_COMPARATOR);
199         }
200         pointerIterator = list.iterator();
201         return true;
202     }
203 
204     /***
205      * Returns the list of all Pointers in this context for the current
206      * position of the parent context.
207      */
208     public List getContextNodeList() {
209         int pos = position;
210         if (pos != 0) {
211             reset();
212         }
213         List list = new ArrayList();
214         while (nextNode()) {
215             list.add(getCurrentNodePointer());
216         }
217         if (pos != 0) {
218             setPosition(pos);
219         }
220         else {
221             reset();
222         }
223         return list;
224     }
225 
226     /***
227      * Returns the list of all Pointers in this context for all positions
228      * of the parent contexts.  If there was an ongoing iteration over
229      * this context, the method should not be called.
230      */
231     public NodeSet getNodeSet() {
232         if (position != 0) {
233             throw new JXPathException(
234                 "Simultaneous operations: "
235                     + "should not request pointer list while "
236                     + "iterating over an EvalContext");
237         }
238         BasicNodeSet set = new BasicNodeSet();
239         while (nextSet()) {
240             while (nextNode()) {
241                 set.add((Pointer)getCurrentNodePointer().clone());
242             }
243         }
244 
245         return set;
246     }
247     
248     /***
249      * Typically returns the NodeSet by calling getNodeSet(), 
250      * but will be overridden for contexts that more naturally produce
251      * individual values, e.g. VariableContext
252      */
253     public Object getValue() {
254         return getNodeSet();
255     }
256 
257     public String toString() {
258         Pointer ptr = getContextNodePointer();
259         if (ptr == null) {
260             return "Empty expression context";
261         }
262         else {
263             return "Expression context [" + getPosition() + "] " + ptr.asPath();
264         }
265     }
266 
267     /***
268      * Returns the root context of the path, which provides easy
269      * access to variables and functions.
270      */
271     public RootContext getRootContext() {
272         if (rootContext == null) {
273             rootContext = parentContext.getRootContext();
274         }
275         return rootContext;
276     }
277 
278     /***
279      * Sets current position = 0, which is the pre-iteration state.
280      */
281     public void reset() {
282         position = 0;
283     }
284 
285     public int getCurrentPosition() {
286         return position;
287     }
288 
289     /***
290      * Returns the first encountered Pointer that matches the current
291      * context's criteria.
292      */
293     public Pointer getSingleNodePointer() {
294         reset();
295         while (nextSet()) {
296             if (nextNode()) {
297                 return getCurrentNodePointer();
298             }
299         }
300         return null;
301     }
302 
303     /***
304      * Returns the current context node. Undefined before the beginning
305      * of the iteration.
306      */
307     public abstract NodePointer getCurrentNodePointer();
308 
309     /***
310      * Returns true if there is another sets of objects to interate over.
311      * Resets the current position and node.
312      */
313     public boolean nextSet() {
314         reset(); // Restart iteration within the set
315 
316         // Most of the time you have one set per parent node
317         // First time this method is called, we should look for
318         // the first parent set that contains at least one node.
319         if (!startedSetIteration) {
320             startedSetIteration = true;
321             while (parentContext.nextSet()) {
322                 if (parentContext.nextNode()) {
323                     return true;
324                 }
325             }
326             return false;
327         }
328 
329         // In subsequent calls, we see if the parent context
330         // has any nodes left in the current set
331         if (parentContext.nextNode()) {
332             return true;
333         }
334 
335         // If not, we look for the next set that contains
336         // at least one node
337         while (parentContext.nextSet()) {
338             if (parentContext.nextNode()) {
339                 return true;
340             }
341         }
342         return false;
343     }
344 
345     /***
346      * Returns true if there is another object in the current set.
347      * Switches the current position and node to the next object.
348      */
349     public abstract boolean nextNode();
350 
351     /***
352      * Moves the current position to the specified index. Used with integer
353      * predicates to quickly get to the n'th element of the node set.
354      * Returns false if the position is out of the node set range.
355      * You can call it with 0 as the position argument to restart the iteration.
356      */
357     public boolean setPosition(int position) {
358         this.position = position;
359         return true;
360     }
361 }