    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}