001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jxpath.ri;
017:
018: import java.util.ArrayList;
019: import java.util.Collections;
020: import java.util.Comparator;
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:
035: /**
036: * An XPath evaluation context.
037: *
038: * When evaluating a path, a chain of EvalContexts is created, each context in
039: * the chain representing a step of the path. Subclasses of EvalContext
040: * implement behavior of various XPath axes: "child::", "parent::" etc.
041: *
042: * @author Dmitri Plotnikov
043: * @version $Revision: 1.30 $ $Date: 2004/03/25 05:42:01 $
044: */
045: public abstract class EvalContext implements ExpressionContext,
046: Iterator {
047: protected EvalContext parentContext;
048: protected RootContext rootContext;
049: protected int position = 0;
050: private boolean startedSetIteration = false;
051: private boolean done = false;
052: private boolean hasPerformedIteratorStep = false;
053: private Iterator pointerIterator;
054:
055: // Sorts in the reverse order to the one defined by the Comparable
056: // interface.
057: private static final Comparator REVERSE_COMPARATOR = new Comparator() {
058: public int compare(Object o1, Object o2) {
059: return ((Comparable) o2).compareTo(o1);
060: }
061: };
062:
063: public EvalContext(EvalContext parentContext) {
064: this .parentContext = parentContext;
065: }
066:
067: public Pointer getContextNodePointer() {
068: return getCurrentNodePointer();
069: }
070:
071: public JXPathContext getJXPathContext() {
072: return getRootContext().getJXPathContext();
073: }
074:
075: public int getPosition() {
076: return position;
077: }
078:
079: /**
080: * Determines the document order for this context.
081: *
082: * @return 1 ascending order, -1 descending order,
083: * 0 - does not require ordering
084: */
085: public int getDocumentOrder() {
086: if (parentContext != null
087: && parentContext.isChildOrderingRequired()) {
088: return 1;
089: }
090: return 0;
091: }
092:
093: /**
094: * Even if this context has the natural ordering and therefore does
095: * not require collecting and sorting all nodes prior to returning them,
096: * such operation may be required for any child context.
097: */
098: public boolean isChildOrderingRequired() {
099: // Default behavior: if this context needs to be ordered,
100: // the children need to be ordered too
101: if (getDocumentOrder() != 0) {
102: return true;
103: }
104: return false;
105: }
106:
107: /**
108: * Returns true if there are mode nodes matching the context's constraints.
109: */
110: public boolean hasNext() {
111: if (pointerIterator != null) {
112: return pointerIterator.hasNext();
113: }
114:
115: if (getDocumentOrder() != 0) {
116: return constructIterator();
117: } else {
118: if (!done && !hasPerformedIteratorStep) {
119: performIteratorStep();
120: }
121: return !done;
122: }
123: }
124:
125: /**
126: * Returns the next node pointer in the context
127: */
128: public Object next() {
129: if (pointerIterator != null) {
130: return pointerIterator.next();
131: }
132:
133: if (getDocumentOrder() != 0) {
134: if (!constructIterator()) {
135: throw new NoSuchElementException();
136: }
137: return pointerIterator.next();
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: } else {
158: while (nextSet()) {
159: if (nextNode()) {
160: done = false;
161: break;
162: }
163: }
164: }
165: hasPerformedIteratorStep = true;
166: }
167:
168: /**
169: * Operation is not supported
170: */
171: public void remove() {
172: throw new UnsupportedOperationException(
173: "JXPath iterators cannot remove nodes");
174: }
175:
176: private boolean constructIterator() {
177: HashSet set = new HashSet();
178: ArrayList list = new ArrayList();
179: while (nextSet()) {
180: while (nextNode()) {
181: NodePointer pointer = getCurrentNodePointer();
182: if (!set.contains(pointer)) {
183: // Pointer cln = (Pointer) pointer.clone();
184: set.add(pointer);
185: list.add(pointer);
186: }
187: }
188: }
189: if (list.isEmpty()) {
190: return false;
191: }
192:
193: if (getDocumentOrder() == 1) {
194: Collections.sort(list);
195: } else {
196: Collections.sort(list, REVERSE_COMPARATOR);
197: }
198: pointerIterator = list.iterator();
199: return true;
200: }
201:
202: /**
203: * Returns the list of all Pointers in this context for the current
204: * position of the parent context.
205: */
206: public List getContextNodeList() {
207: int pos = position;
208: if (pos != 0) {
209: reset();
210: }
211: List list = new ArrayList();
212: while (nextNode()) {
213: list.add(getCurrentNodePointer());
214: }
215: if (pos != 0) {
216: setPosition(pos);
217: } else {
218: reset();
219: }
220: return list;
221: }
222:
223: /**
224: * Returns the list of all Pointers in this context for all positions
225: * of the parent contexts. If there was an ongoing iteration over
226: * this context, the method should not be called.
227: */
228: public NodeSet getNodeSet() {
229: if (position != 0) {
230: throw new JXPathException("Simultaneous operations: "
231: + "should not request pointer list while "
232: + "iterating over an EvalContext");
233: }
234: BasicNodeSet set = new BasicNodeSet();
235: while (nextSet()) {
236: while (nextNode()) {
237: set.add((Pointer) getCurrentNodePointer().clone());
238: }
239: }
240:
241: return set;
242: }
243:
244: /**
245: * Typically returns the NodeSet by calling getNodeSet(),
246: * but will be overridden for contexts that more naturally produce
247: * individual values, e.g. VariableContext
248: */
249: public Object getValue() {
250: return getNodeSet();
251: }
252:
253: public String toString() {
254: Pointer ptr = getContextNodePointer();
255: if (ptr == null) {
256: return "Empty expression context";
257: } else {
258: return "Expression context [" + getPosition() + "] "
259: + ptr.asPath();
260: }
261: }
262:
263: /**
264: * Returns the root context of the path, which provides easy
265: * access to variables and functions.
266: */
267: public RootContext getRootContext() {
268: if (rootContext == null) {
269: rootContext = parentContext.getRootContext();
270: }
271: return rootContext;
272: }
273:
274: /**
275: * Sets current position = 0, which is the pre-iteration state.
276: */
277: public void reset() {
278: position = 0;
279: }
280:
281: public int getCurrentPosition() {
282: return position;
283: }
284:
285: /**
286: * Returns the first encountered Pointer that matches the current
287: * context's criteria.
288: */
289: public Pointer getSingleNodePointer() {
290: reset();
291: while (nextSet()) {
292: if (nextNode()) {
293: return getCurrentNodePointer();
294: }
295: }
296: return null;
297: }
298:
299: /**
300: * Returns the current context node. Undefined before the beginning
301: * of the iteration.
302: */
303: public abstract NodePointer getCurrentNodePointer();
304:
305: /**
306: * Returns true if there is another sets of objects to interate over.
307: * Resets the current position and node.
308: */
309: public boolean nextSet() {
310: reset(); // Restart iteration within the set
311:
312: // Most of the time you have one set per parent node
313: // First time this method is called, we should look for
314: // the first parent set that contains at least one node.
315: if (!startedSetIteration) {
316: startedSetIteration = true;
317: while (parentContext.nextSet()) {
318: if (parentContext.nextNode()) {
319: return true;
320: }
321: }
322: return false;
323: }
324:
325: // In subsequent calls, we see if the parent context
326: // has any nodes left in the current set
327: if (parentContext.nextNode()) {
328: return true;
329: }
330:
331: // If not, we look for the next set that contains
332: // at least one node
333: while (parentContext.nextSet()) {
334: if (parentContext.nextNode()) {
335: return true;
336: }
337: }
338: return false;
339: }
340:
341: /**
342: * Returns true if there is another object in the current set.
343: * Switches the current position and node to the next object.
344: */
345: public abstract boolean nextNode();
346:
347: /**
348: * Moves the current position to the specified index. Used with integer
349: * predicates to quickly get to the n'th element of the node set.
350: * Returns false if the position is out of the node set range.
351: * You can call it with 0 as the position argument to restart the iteration.
352: */
353: public boolean setPosition(int position) {
354: this .position = position;
355: return true;
356: }
357: }
|