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 */
017package org.apache.commons.jxpath.ri;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.List;
024import java.util.NoSuchElementException;
025
026import org.apache.commons.jxpath.BasicNodeSet;
027import org.apache.commons.jxpath.ExpressionContext;
028import org.apache.commons.jxpath.JXPathContext;
029import org.apache.commons.jxpath.JXPathException;
030import org.apache.commons.jxpath.NodeSet;
031import org.apache.commons.jxpath.Pointer;
032import org.apache.commons.jxpath.ri.axes.RootContext;
033import org.apache.commons.jxpath.ri.model.NodePointer;
034import 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: 1234255 $ $Date: 2012-01-21 04:11:46 +0100 (Sa, 21 Jan 2012) $
045 */
046public 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            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}