001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.jxpath.ri;
018
019 import java.util.ArrayList;
020 import java.util.Collections;
021 import java.util.HashSet;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.NoSuchElementException;
025
026 import org.apache.commons.jxpath.BasicNodeSet;
027 import org.apache.commons.jxpath.ExpressionContext;
028 import org.apache.commons.jxpath.JXPathContext;
029 import org.apache.commons.jxpath.JXPathException;
030 import org.apache.commons.jxpath.NodeSet;
031 import org.apache.commons.jxpath.Pointer;
032 import org.apache.commons.jxpath.ri.axes.RootContext;
033 import org.apache.commons.jxpath.ri.model.NodePointer;
034 import org.apache.commons.jxpath.util.ReverseComparator;
035
036 /**
037 * An XPath evaluation context.
038 *
039 * When evaluating a path, a chain of EvalContexts is created, each context in
040 * the chain representing a step of the path. Subclasses of EvalContext
041 * implement behavior of various XPath axes: "child::", "parent::" etc.
042 *
043 * @author Dmitri Plotnikov
044 * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
045 */
046 public abstract class EvalContext implements ExpressionContext, Iterator {
047 /** parent context */
048 protected EvalContext parentContext;
049
050 /** root context */
051 protected RootContext rootContext;
052
053 /** position */
054 protected int position = 0;
055
056 private boolean startedSetIteration = false;
057 private boolean done = false;
058 private boolean hasPerformedIteratorStep = false;
059 private Iterator pointerIterator;
060
061 /**
062 * Create a new EvalContext.
063 * @param parentContext parent context
064 */
065 public EvalContext(EvalContext parentContext) {
066 this.parentContext = parentContext;
067 }
068
069 public Pointer getContextNodePointer() {
070 return getCurrentNodePointer();
071 }
072
073 public JXPathContext getJXPathContext() {
074 return getRootContext().getJXPathContext();
075 }
076
077 public int getPosition() {
078 return position;
079 }
080
081 /**
082 * Determines the document order for this context.
083 *
084 * @return 1 ascending order, -1 descending order,
085 * 0 - does not require ordering
086 */
087 public int getDocumentOrder() {
088 return parentContext != null && parentContext.isChildOrderingRequired() ? 1 : 0;
089 }
090
091 /**
092 * Even if this context has the natural ordering and therefore does
093 * not require collecting and sorting all nodes prior to returning them,
094 * such operation may be required for any child context.
095 * @return boolean
096 */
097 public boolean isChildOrderingRequired() {
098 // Default behavior: if this context needs to be ordered,
099 // 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 }
213 }
214
215 /**
216 * Returns the list of all Pointers in this context for the current
217 * position of the parent context.
218 * @return List
219 */
220 public List getContextNodeList() {
221 int pos = position;
222 if (pos != 0) {
223 reset();
224 }
225 List list = new ArrayList();
226 while (nextNode()) {
227 list.add(getCurrentNodePointer());
228 }
229 if (pos != 0) {
230 setPosition(pos);
231 }
232 else {
233 reset();
234 }
235 return list;
236 }
237
238 /**
239 * Returns the list of all Pointers in this context for all positions
240 * of the parent contexts. If there was an ongoing iteration over
241 * this context, the method should not be called.
242 * @return NodeSet
243 */
244 public NodeSet getNodeSet() {
245 if (position != 0) {
246 throw new JXPathException(
247 "Simultaneous operations: "
248 + "should not request pointer list while "
249 + "iterating over an EvalContext");
250 }
251 BasicNodeSet set = new BasicNodeSet();
252 while (nextSet()) {
253 while (nextNode()) {
254 set.add((Pointer) getCurrentNodePointer().clone());
255 }
256 }
257
258 return set;
259 }
260
261 /**
262 * Typically returns the NodeSet by calling getNodeSet(),
263 * but will be overridden for contexts that more naturally produce
264 * individual values, e.g. VariableContext
265 * @return Object
266 */
267 public Object getValue() {
268 return getNodeSet();
269 }
270
271 public String toString() {
272 Pointer ptr = getContextNodePointer();
273 return ptr == null ? "Empty expression context" : "Expression context [" + getPosition()
274 + "] " + ptr.asPath();
275 }
276
277 /**
278 * Returns the root context of the path, which provides easy
279 * access to variables and functions.
280 * @return RootContext
281 */
282 public RootContext getRootContext() {
283 if (rootContext == null) {
284 rootContext = parentContext.getRootContext();
285 }
286 return rootContext;
287 }
288
289 /**
290 * Sets current position = 0, which is the pre-iteration state.
291 */
292 public void reset() {
293 position = 0;
294 }
295
296 /**
297 * Get the current position.
298 * @return int position.
299 */
300 public int getCurrentPosition() {
301 return position;
302 }
303
304 /**
305 * Returns the first encountered Pointer that matches the current
306 * context's criteria.
307 * @return Pointer
308 */
309 public Pointer getSingleNodePointer() {
310 reset();
311 while (nextSet()) {
312 if (nextNode()) {
313 return getCurrentNodePointer();
314 }
315 }
316 return null;
317 }
318
319 /**
320 * Returns the current context node. Undefined before the beginning
321 * of the iteration.
322 * @return NodePoiner
323 */
324 public abstract NodePointer getCurrentNodePointer();
325
326 /**
327 * Returns true if there is another sets of objects to interate over.
328 * Resets the current position and node.
329 * @return boolean
330 */
331 public boolean nextSet() {
332 reset(); // Restart iteration within the set
333
334 // Most of the time you have one set per parent node
335 // First time this method is called, we should look for
336 // the first parent set that contains at least one node.
337 if (!startedSetIteration) {
338 startedSetIteration = true;
339 while (parentContext.nextSet()) {
340 if (parentContext.nextNode()) {
341 return true;
342 }
343 }
344 return false;
345 }
346
347 // In subsequent calls, we see if the parent context
348 // has any nodes left in the current set
349 if (parentContext.nextNode()) {
350 return true;
351 }
352
353 // If not, we look for the next set that contains
354 // at least one node
355 while (parentContext.nextSet()) {
356 if (parentContext.nextNode()) {
357 return true;
358 }
359 }
360 return false;
361 }
362
363 /**
364 * Returns true if there is another object in the current set.
365 * Switches the current position and node to the next object.
366 * @return boolean
367 */
368 public abstract boolean nextNode();
369
370 /**
371 * Moves the current position to the specified index. Used with integer
372 * predicates to quickly get to the n'th element of the node set.
373 * Returns false if the position is out of the node set range.
374 * You can call it with 0 as the position argument to restart the iteration.
375 * @param position to set
376 * @return boolean
377 */
378 public boolean setPosition(int position) {
379 this.position = position;
380 return true;
381 }
382 }