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-20 22:11:46 -0500 (Fri, 20 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 }