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