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 }