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