    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    }