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    }