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