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
018package org.apache.commons.jxpath.ri;
019
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.List;
025import java.util.NoSuchElementException;
026
027import org.apache.commons.jxpath.BasicNodeSet;
028import org.apache.commons.jxpath.ExpressionContext;
029import org.apache.commons.jxpath.JXPathContext;
030import org.apache.commons.jxpath.JXPathException;
031import org.apache.commons.jxpath.NodeSet;
032import org.apache.commons.jxpath.Pointer;
033import org.apache.commons.jxpath.ri.axes.RootContext;
034import org.apache.commons.jxpath.ri.model.NodePointer;
035import org.apache.commons.jxpath.util.ReverseComparator;
036
037/**
038 * An XPath evaluation context.
039 *
040 * When evaluating a path, a chain of EvalContexts is created, each context in the chain representing a step of the path. Subclasses of EvalContext implement
041 * behavior of various XPath axes: "child::", "parent::" etc.
042 */
043public abstract class EvalContext implements ExpressionContext, Iterator {
044
045    /** Parent context */
046    protected EvalContext parentContext;
047    /** Root context */
048    protected RootContext rootContext;
049    /** Position */
050    protected int position;
051    private boolean startedSetIteration;
052    private boolean done;
053    private boolean hasPerformedIteratorStep;
054    private Iterator<Pointer> pointerIterator;
055
056    /**
057     * Constructs a new EvalContext.
058     *
059     * @param parentContext parent context
060     */
061    public EvalContext(final EvalContext parentContext) {
062        this.parentContext = parentContext;
063    }
064
065    /**
066     * Constructs an iterator.
067     *
068     * @return whether the Iterator was constructed
069     */
070    private boolean constructIterator() {
071        final HashSet<Pointer> set = new HashSet<>();
072        final ArrayList<Pointer> list = new ArrayList<>();
073        while (nextSet()) {
074            while (nextNode()) {
075                final NodePointer pointer = getCurrentNodePointer();
076                if (!set.contains(pointer)) {
077                    set.add(pointer);
078                    list.add(pointer);
079                }
080            }
081        }
082        if (list.isEmpty()) {
083            return false;
084        }
085        sortPointers(list);
086        pointerIterator = list.iterator();
087        return true;
088    }
089
090    /**
091     * Returns the list of all Pointers in this context for the current position of the parent context.
092     *
093     * @return List
094     */
095    @Override
096    public List<Pointer> getContextNodeList() {
097        final int pos = position;
098        if (pos != 0) {
099            reset();
100        }
101        final List<Pointer> list = new ArrayList<>();
102        while (nextNode()) {
103            list.add(getCurrentNodePointer());
104        }
105        if (pos != 0) {
106            setPosition(pos);
107        } else {
108            reset();
109        }
110        return list;
111    }
112
113    @Override
114    public Pointer getContextNodePointer() {
115        return getCurrentNodePointer();
116    }
117
118    /**
119     * Returns the current context node. Undefined before the beginning of the iteration.
120     *
121     * @return NodePoiner
122     */
123    public abstract NodePointer getCurrentNodePointer();
124
125    /**
126     * Gets the current position.
127     *
128     * @return int position.
129     */
130    public int getCurrentPosition() {
131        return position;
132    }
133
134    /**
135     * Determines the document order for this context.
136     *
137     * @return 1 ascending order, -1 descending order, 0 - does not require ordering
138     */
139    public int getDocumentOrder() {
140        return parentContext != null && parentContext.isChildOrderingRequired() ? 1 : 0;
141    }
142
143    @Override
144    public JXPathContext getJXPathContext() {
145        return getRootContext().getJXPathContext();
146    }
147
148    /**
149     * Returns the list of all Pointers in this context for all positions of the parent contexts. If there was an ongoing iteration over this context, the
150     * method should not be called.
151     *
152     * @return NodeSet
153     */
154    public NodeSet getNodeSet() {
155        if (position != 0) {
156            throw new JXPathException("Simultaneous operations: " + "should not request pointer list while " + "iterating over an EvalContext");
157        }
158        final BasicNodeSet set = new BasicNodeSet();
159        while (nextSet()) {
160            while (nextNode()) {
161                set.add((Pointer) getCurrentNodePointer().clone());
162            }
163        }
164        return set;
165    }
166
167    @Override
168    public int getPosition() {
169        return position;
170    }
171
172    /**
173     * Returns the root context of the path, which provides easy access to variables and functions.
174     *
175     * @return RootContext
176     */
177    public RootContext getRootContext() {
178        if (rootContext == null) {
179            rootContext = parentContext.getRootContext();
180        }
181        return rootContext;
182    }
183
184    /**
185     * Returns the first encountered Pointer that matches the current context's criteria.
186     *
187     * @return Pointer
188     */
189    public Pointer getSingleNodePointer() {
190        reset();
191        while (nextSet()) {
192            if (nextNode()) {
193                return getCurrentNodePointer();
194            }
195        }
196        return null;
197    }
198
199    /**
200     * Typically returns the NodeSet by calling getNodeSet(), but will be overridden for contexts that more naturally produce individual values, e.g.
201     * VariableContext
202     *
203     * @return Object
204     */
205    public Object getValue() {
206        return getNodeSet();
207    }
208
209    /**
210     * Returns true if there are mode nodes matching the context's constraints.
211     *
212     * @return boolean
213     */
214    @Override
215    public boolean hasNext() {
216        if (pointerIterator != null) {
217            return pointerIterator.hasNext();
218        }
219        if (getDocumentOrder() != 0) {
220            return constructIterator();
221        }
222        if (!done && !hasPerformedIteratorStep) {
223            performIteratorStep();
224        }
225        return !done;
226    }
227
228    /**
229     * Even if this context has the natural ordering and therefore does not require collecting and sorting all nodes prior to returning them, such operation may
230     * be required for any child context.
231     *
232     * @return boolean
233     */
234    public boolean isChildOrderingRequired() {
235        // Default behavior: if this context needs to be ordered,
236        // the children need to be ordered too
237        return getDocumentOrder() != 0;
238    }
239
240    /**
241     * Returns the next node pointer in the context
242     *
243     * @return Object
244     */
245    @Override
246    public Object next() {
247        if (pointerIterator != null) {
248            return pointerIterator.next();
249        }
250        if (getDocumentOrder() != 0) {
251            if (!constructIterator()) {
252                throw new NoSuchElementException();
253            }
254            return pointerIterator.next();
255        }
256        if (!done && !hasPerformedIteratorStep) {
257            performIteratorStep();
258        }
259        if (done) {
260            throw new NoSuchElementException();
261        }
262        hasPerformedIteratorStep = false;
263        return getCurrentNodePointer();
264    }
265
266    /**
267     * Returns true if there is another object in the current set. Switches the current position and node to the next object.
268     *
269     * @return boolean
270     */
271    public abstract boolean nextNode();
272
273    /**
274     * Returns true if there is another sets of objects to interate over. Resets the current position and node.
275     *
276     * @return boolean
277     */
278    public boolean nextSet() {
279        reset(); // Restart iteration within the set
280        // Most of the time you have one set per parent node
281        // First time this method is called, we should look for
282        // the first parent set that contains at least one node.
283        if (!startedSetIteration) {
284            startedSetIteration = true;
285            while (parentContext.nextSet()) {
286                if (parentContext.nextNode()) {
287                    return true;
288                }
289            }
290            return false;
291        }
292        // In subsequent calls, we see if the parent context
293        // has any nodes left in the current set
294        if (parentContext.nextNode()) {
295            return true;
296        }
297        // If not, we look for the next set that contains
298        // at least one node
299        while (parentContext.nextSet()) {
300            if (parentContext.nextNode()) {
301                return true;
302            }
303        }
304        return false;
305    }
306
307    /**
308     * Moves the iterator forward by one position
309     */
310    private void performIteratorStep() {
311        done = true;
312        if (position != 0 && nextNode()) {
313            done = false;
314        } else {
315            while (nextSet()) {
316                if (nextNode()) {
317                    done = false;
318                    break;
319                }
320            }
321        }
322        hasPerformedIteratorStep = true;
323    }
324
325    /**
326     * Operation is not supported
327     *
328     * @throws UnsupportedOperationException Always thrown.
329     */
330    @Override
331    public void remove() {
332        throw new UnsupportedOperationException("JXPath iterators cannot remove nodes");
333    }
334
335    /**
336     * Sets current position = 0, which is the pre-iteration state.
337     */
338    public void reset() {
339        position = 0;
340    }
341
342    /**
343     * Moves the current position to the specified index. Used with integer predicates to quickly get to the n'th element of the node set. Returns false if the
344     * position is out of the node set range. You can call it with 0 as the position argument to restart the iteration.
345     *
346     * @param position to set
347     * @return boolean
348     */
349    public boolean setPosition(final int position) {
350        this.position = position;
351        return true;
352    }
353
354    /**
355     * Sort a list of pointers based on document order.
356     *
357     * @param l the list to sort.
358     */
359    protected void sortPointers(final List l) {
360        switch (getDocumentOrder()) {
361        case 1:
362            Collections.sort(l);
363            break;
364        case -1:
365            Collections.sort(l, ReverseComparator.INSTANCE);
366            break;
367        default:
368            break;
369        }
370    }
371
372    @Override
373    public String toString() {
374        final Pointer ptr = getContextNodePointer();
375        return ptr == null ? "Empty expression context" : "Expression context [" + getPosition() + "] " + ptr.asPath();
376    }
377}