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.axes;
019
020import java.util.Stack;
021
022import org.apache.commons.jxpath.Pointer;
023import org.apache.commons.jxpath.ri.Compiler;
024import org.apache.commons.jxpath.ri.EvalContext;
025import org.apache.commons.jxpath.ri.compiler.NodeTest;
026import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
027import org.apache.commons.jxpath.ri.model.NodeIterator;
028import org.apache.commons.jxpath.ri.model.NodePointer;
029
030/**
031 * An EvalContext that walks the "descendant::" and "descendant-or-self::" axes.
032 */
033public class DescendantContext extends EvalContext {
034
035    private static final NodeTest ELEMENT_NODE_TEST = new NodeTypeTest(Compiler.NODE_TYPE_NODE);
036    private final NodeTest nodeTest;
037    private boolean setStarted;
038    private Stack<NodeIterator> stack;
039    private NodePointer currentNodePointer;
040    private final boolean includeSelf;
041
042    /**
043     * Constructs a new DescendantContext.
044     *
045     * @param parentContext parent context
046     * @param includeSelf   whether to include this node
047     * @param nodeTest      test
048     */
049    public DescendantContext(final EvalContext parentContext, final boolean includeSelf, final NodeTest nodeTest) {
050        super(parentContext);
051        this.includeSelf = includeSelf;
052        this.nodeTest = nodeTest;
053    }
054
055    @Override
056    public NodePointer getCurrentNodePointer() {
057        if (position == 0 && !setPosition(1)) {
058            return null;
059        }
060        return currentNodePointer;
061    }
062
063    @Override
064    public boolean isChildOrderingRequired() {
065        return true;
066    }
067
068    /**
069     * Checks if we are reentering a bean we have already seen and if so returns true to prevent infinite recursion.
070     *
071     * @return boolean
072     */
073    private boolean isRecursive() {
074        final Object node = currentNodePointer.getNode();
075        for (int i = stack.size() - 1; --i >= 0;) {
076            final NodeIterator it = stack.get(i);
077            final Pointer pointer = it.getNodePointer();
078            if (pointer != null && pointer.getNode() == node) {
079                return true;
080            }
081        }
082        return false;
083    }
084
085    @Override
086    public boolean nextNode() {
087        if (!setStarted) {
088            setStarted = true;
089            if (stack == null) {
090                stack = new Stack<>();
091            } else {
092                stack.clear();
093            }
094            currentNodePointer = parentContext.getCurrentNodePointer();
095            if (currentNodePointer != null) {
096                if (!currentNodePointer.isLeaf()) {
097                    stack.push(currentNodePointer.childIterator(ELEMENT_NODE_TEST, false, null));
098                }
099                if (includeSelf && currentNodePointer.testNode(nodeTest)) {
100                    position++;
101                    return true;
102                }
103            }
104        }
105        while (!stack.isEmpty()) {
106            final NodeIterator it = stack.peek();
107            if (it.setPosition(it.getPosition() + 1)) {
108                currentNodePointer = it.getNodePointer();
109                if (!isRecursive()) {
110                    if (!currentNodePointer.isLeaf()) {
111                        stack.push(currentNodePointer.childIterator(ELEMENT_NODE_TEST, false, null));
112                    }
113                    if (currentNodePointer.testNode(nodeTest)) {
114                        position++;
115                        return true;
116                    }
117                }
118            } else {
119                // We get here only if the name test failed
120                // and the iterator ended
121                stack.pop();
122            }
123        }
124        return false;
125    }
126
127    @Override
128    public void reset() {
129        super.reset();
130        setStarted = false;
131    }
132
133    @Override
134    public boolean setPosition(final int position) {
135        if (position < this.position) {
136            reset();
137        }
138        while (this.position < position) {
139            if (!nextNode()) {
140                return false;
141            }
142        }
143        return true;
144    }
145}