1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
55
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
98
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
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();
315
316
317
318
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
330
331 if (parentContext.nextNode()) {
332 return true;
333 }
334
335
336
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 }