| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 1999 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Xalan" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation and was |
| * originally based on software copyright (c) 1999, Lotus |
| * Development Corporation., http://www.lotus.com. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| package org.apache.xpath.axes; |
| |
| import java.util.Stack; |
| import java.util.Vector; |
| |
| // Xalan imports |
| import org.apache.xpath.axes.LocPathIterator; |
| import org.apache.xml.utils.PrefixResolver; |
| import org.apache.xpath.axes.SubContextList; |
| import org.apache.xpath.compiler.Compiler; |
| import org.apache.xpath.compiler.OpCodes; |
| import org.apache.xpath.DOMHelper; |
| import org.apache.xpath.Expression; |
| import org.apache.xpath.objects.XObject; |
| import org.apache.xpath.patterns.NodeTestFilter; |
| import org.apache.xpath.patterns.NodeTest; |
| import org.apache.xpath.XPathContext; |
| import org.apache.xpath.XPath; |
| |
| import org.apache.xml.dtm.DTM; |
| import org.apache.xml.dtm.DTMIterator; |
| import org.apache.xml.dtm.DTMFilter; |
| |
| import org.apache.xml.utils.XMLString; |
| |
| /** |
| * Serves as common interface for axes Walkers, and stores common |
| * state variables. |
| */ |
| public abstract class AxesWalker extends PredicatedNodeTest |
| implements Cloneable , DTMFilter |
| { |
| |
| /** |
| * Construct an AxesWalker using a LocPathIterator. |
| * |
| * @param locPathIterator non-null reference to the parent iterator. |
| */ |
| public AxesWalker(LocPathIterator locPathIterator) |
| { |
| super( locPathIterator ); |
| } |
| |
| /** |
| * Initialize an AxesWalker during the parse of the XPath expression. |
| * |
| * @param compiler The Compiler object that has information about this |
| * walker in the op map. |
| * @param opPos The op code position of this location step. |
| * @param stepType The type of location step. |
| * |
| * @throws javax.xml.transform.TransformerException |
| */ |
| public void init(Compiler compiler, int opPos, int stepType) |
| throws javax.xml.transform.TransformerException |
| { |
| |
| // int nodeTestOpPos = compiler.getFirstChildPosOfStep(opPos); |
| m_stepType = stepType; |
| |
| switch (stepType) |
| { |
| case OpCodes.OP_VARIABLE : |
| case OpCodes.OP_EXTFUNCTION : |
| case OpCodes.OP_FUNCTION : |
| case OpCodes.OP_GROUP : |
| m_argLen = compiler.getArgLength(opPos); |
| break; |
| default : |
| m_argLen = compiler.getArgLengthOfStep(opPos); |
| } |
| |
| initPredicateInfo(compiler, opPos); |
| |
| // int testType = compiler.getOp(nodeTestOpPos); |
| } |
| |
| /** |
| * Get a cloned AxesWalker. |
| * |
| * @return A new AxesWalker that can be used without mutating this one. |
| * |
| * @throws CloneNotSupportedException |
| */ |
| public Object clone() throws CloneNotSupportedException |
| { |
| // Do not access the location path itterator during this operation! |
| |
| AxesWalker clone = (AxesWalker) super.clone(); |
| |
| //clone.setCurrentNode(clone.m_root); |
| |
| // clone.m_isFresh = true; |
| |
| return clone; |
| } |
| |
| /** |
| * Do a deep clone of this walker, including next and previous walkers. |
| * If the this AxesWalker is on the clone list, don't clone but |
| * return the already cloned version. |
| * |
| * @param cloneOwner non-null reference to the cloned location path |
| * iterator to which this clone will be added. |
| * @param cloneList non-null vector of sources in odd elements, and the |
| * corresponding clones in even vectors. |
| * |
| * @return non-null clone, which may be a new clone, or may be a clone |
| * contained on the cloneList. |
| */ |
| AxesWalker cloneDeep(LocPathIterator cloneOwner, Vector cloneList) |
| throws CloneNotSupportedException |
| { |
| AxesWalker clone = findClone(this, cloneList); |
| if(null != clone) |
| return clone; |
| clone = (AxesWalker)this.clone(); |
| clone.setLocPathIterator(cloneOwner); |
| if(null != cloneList) |
| { |
| cloneList.addElement(this); |
| cloneList.addElement(clone); |
| } |
| |
| if(m_lpi.m_lastUsedWalker == this) |
| cloneOwner.m_lastUsedWalker = clone; |
| |
| if(null != m_nextWalker) |
| clone.m_nextWalker = m_nextWalker.cloneDeep(cloneOwner, cloneList); |
| |
| // If you don't check for the cloneList here, you'll go into an |
| // recursive infinate loop. |
| if(null != cloneList) |
| { |
| if(null != m_prevWalker) |
| clone.m_prevWalker = m_prevWalker.cloneDeep(cloneOwner, cloneList); |
| } |
| else |
| { |
| if(null != m_nextWalker) |
| clone.m_nextWalker.m_prevWalker = clone; |
| } |
| return clone; |
| } |
| |
| /** |
| * Find a clone that corresponds to the key argument. |
| * |
| * @param key The original AxesWalker for which there may be a clone. |
| * @param cloneList vector of sources in odd elements, and the |
| * corresponding clones in even vectors, may be null. |
| * |
| * @return A clone that corresponds to the key, or null if key not found. |
| */ |
| static AxesWalker findClone(AxesWalker key, Vector cloneList) |
| { |
| if(null != cloneList) |
| { |
| // First, look for clone on list. |
| int n = cloneList.size(); |
| for (int i = 0; i < n; i+=2) |
| { |
| if(key == cloneList.elementAt(i)) |
| return (AxesWalker)cloneList.elementAt(i+1); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Tell if this expression or it's subexpressions can traverse outside |
| * the current subtree. |
| * |
| * @return true if traversal outside the context node's subtree can occur. |
| */ |
| public boolean canTraverseOutsideSubtree() |
| { |
| if(super.canTraverseOutsideSubtree()) |
| return true; |
| if(null != m_nextWalker) |
| return m_nextWalker.canTraverseOutsideSubtree(); |
| return false; |
| } |
| |
| /** |
| * The the step type op code. |
| * |
| * |
| * @return An integer that represents an axes traversal opcode found in |
| * {@link org.apache.xpath.compiler.OpCodes}. |
| */ |
| protected int getStepType() |
| { |
| return m_stepType; |
| } |
| |
| /** |
| * Get the argument length of the location step in the opcode map. |
| * TODO: Can this be removed since it is only valuable at compile time? |
| * |
| * @return The argument length of the location step in the opcode map. |
| */ |
| protected int getArgLen() |
| { |
| return m_argLen; |
| } |
| |
| /** |
| * Tell if the given node is a parent of the |
| * step context, or the step context node itself. |
| * |
| * @param n The node being tested. |
| * |
| * @return true if n is a parent of the step context, or the step context |
| * itself. |
| */ |
| boolean isAncestorOfRootContext(int n) |
| { |
| |
| int parent = m_root; |
| |
| DTM dtm = getDTM(parent); |
| while (DTM.NULL != (parent = dtm.getParent(parent))) |
| { |
| if (parent == n) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| //=============== TreeWalker Implementation =============== |
| |
| /** |
| * The root node of the TreeWalker, as specified in setRoot(int root). |
| * Note that this may actually be below the current node. |
| * |
| * @return The context node of the step. |
| */ |
| public int getRoot() |
| { |
| return m_root; |
| } |
| |
| /** |
| * Set the root node of the TreeWalker. |
| * (Not part of the DOM2 TreeWalker interface). |
| * |
| * @param root The context node of this step. |
| */ |
| public void setRoot(int root) |
| { |
| // %OPT% Get this directly from the lpi. |
| m_dtm = m_lpi.getXPathContext().getDTM(root); |
| m_isFresh = true; |
| m_isDone = false; |
| m_root = root; |
| m_currentNode = root; |
| m_prevReturned = DTM.NULL; |
| |
| if (DTM.NULL == root) |
| { |
| throw new RuntimeException( |
| "\n !!!! Error! Setting the root of a walker to null!!!"); |
| } |
| |
| resetProximityPositions(); |
| } |
| |
| /** |
| * The node at which the TreeWalker is currently positioned. |
| * <br> The value must not be null. Alterations to the DOM tree may cause |
| * the current node to no longer be accepted by the TreeWalker's |
| * associated filter. currentNode may also be explicitly set to any node, |
| * whether or not it is within the subtree specified by the root node or |
| * would be accepted by the filter and whatToShow flags. Further |
| * traversal occurs relative to currentNode even if it is not part of the |
| * current view by applying the filters in the requested direction (not |
| * changing currentNode where no traversal is possible). |
| * |
| * @return The node at which the TreeWalker is currently positioned, only null |
| * if setRoot has not yet been called. |
| */ |
| public final int getCurrentNode() |
| { |
| return m_currentNode; |
| } |
| |
| /** |
| * Set the current node. |
| * |
| * @param currentNode The current itteration node, should not be null. |
| */ |
| public void setCurrentNode(int currentNode) |
| { |
| m_currentNode = currentNode; |
| } |
| |
| /** |
| * Set the current node if it's not null. |
| * |
| * @param currentNode The current node or null. |
| * @return The node passed in. |
| */ |
| protected int setCurrentIfNotNull(int currentNode) |
| { |
| |
| if (DTM.NULL != currentNode) |
| m_currentNode = currentNode; |
| |
| return currentNode; |
| } |
| |
| /** |
| * The filter used to screen nodes. |
| * |
| * @return This AxesWalker. |
| */ |
| public DTMFilter getFilter() |
| { |
| return this; |
| } |
| |
| /** |
| * The value of this flag determines whether the children of entity |
| * reference nodes are visible to the TreeWalker. If false, they will be |
| * skipped over. |
| * <br> To produce a view of the document that has entity references |
| * expanded and does not expose the entity reference node itself, use the |
| * whatToShow flags to hide the entity reference node and set |
| * expandEntityReferences to true when creating the TreeWalker. To |
| * produce a view of the document that has entity reference nodes but no |
| * entity expansion, use the whatToShow flags to show the entity |
| * reference node and set expandEntityReferences to false. |
| * |
| * @return true. |
| */ |
| public boolean getExpandEntityReferences() |
| { |
| return true; |
| } |
| |
| /** |
| * Moves to and returns the closest visible ancestor node of the current |
| * node. If the search for parentNode attempts to step upward from the |
| * TreeWalker's root node, or if it fails to find a visible ancestor |
| * node, this method retains the current position and returns null. |
| * @return The new parent node, or null if the current node has no parent |
| * in the TreeWalker's logical view. |
| */ |
| public int parentNode() |
| { |
| return DTM.NULL; |
| } |
| |
| /** |
| * Moves the <code>TreeWalker</code> to the first visible child of the |
| * current node, and returns the new node. If the current node has no |
| * visible children, returns <code>null</code> , and retains the current |
| * node. |
| * @return The new node, or <code>null</code> if the current node has no |
| * visible children in the TreeWalker's logical view. |
| */ |
| public int firstChild() |
| { |
| return DTM.NULL; |
| } |
| |
| /** |
| * Moves the <code>TreeWalker</code> to the next sibling of the current |
| * node, and returns the new node. If the current node has no visible |
| * next sibling, returns <code>null</code> , and retains the current node. |
| * @return The new node, or <code>null</code> if the current node has no |
| * next sibling in the TreeWalker's logical view. |
| */ |
| public int nextSibling() |
| { |
| return DTM.NULL; |
| } |
| |
| /** |
| * Moves the <code>TreeWalker</code> to the last visible child of the |
| * current node, and returns the new node. If the current node has no |
| * visible children, returns <code>null</code> , and retains the current |
| * node. |
| * @return The new node, or <code>null</code> if the current node has no |
| * children in the TreeWalker's logical view. |
| */ |
| public int lastChild() |
| { |
| |
| // We may need to support this... |
| throw new RuntimeException("lastChild not supported!"); |
| } |
| |
| /** |
| * Moves the <code>TreeWalker</code> to the previous sibling of the |
| * current node, and returns the new node. If the current node has no |
| * visible previous sibling, returns <code>null</code> , and retains the |
| * current node. |
| * @return The new node, or <code>null</code> if the current node has no |
| * previous sibling in the TreeWalker's logical view. |
| */ |
| public int previousSibling() |
| { |
| throw new RuntimeException("previousSibling not supported!"); |
| } |
| |
| /** |
| * Moves the <code>TreeWalker</code> to the previous visible node in |
| * document order relative to the current node, and returns the new node. |
| * If the current node has no previous node, or if the search for |
| * previousNode attempts to step upward from the TreeWalker's root node, |
| * returns <code>null</code> , and retains the current node. |
| * @return The new node, or <code>null</code> if the current node has no |
| * previous node in the TreeWalker's logical view. |
| */ |
| public int previousNode() |
| { |
| throw new RuntimeException("previousNode not supported!"); |
| } |
| |
| /** |
| * Set the next walker in the location step chain. |
| * |
| * |
| * @param walker Reference to AxesWalker derivative, or may be null. |
| */ |
| public void setNextWalker(AxesWalker walker) |
| { |
| m_nextWalker = walker; |
| } |
| |
| /** |
| * Get the next walker in the location step chain. |
| * |
| * |
| * @return Reference to AxesWalker derivative, or null. |
| */ |
| public AxesWalker getNextWalker() |
| { |
| return m_nextWalker; |
| } |
| |
| /** |
| * Set or clear the previous walker reference in the location step chain. |
| * |
| * |
| * @param walker Reference to previous walker reference in the location |
| * step chain, or null. |
| */ |
| public void setPrevWalker(AxesWalker walker) |
| { |
| m_prevWalker = walker; |
| } |
| |
| /** |
| * Get the previous walker reference in the location step chain. |
| * |
| * |
| * @return Reference to previous walker reference in the location |
| * step chain, or null. |
| */ |
| public AxesWalker getPrevWalker() |
| { |
| return m_prevWalker; |
| } |
| |
| /** |
| * Diagnostic string for this walker. |
| * |
| * @return Diagnostic string for this walker. |
| */ |
| public String toString() |
| { |
| |
| Class cl = this.getClass(); |
| String clName = cl.getName(); |
| java.util.StringTokenizer tokenizer = |
| new java.util.StringTokenizer(clName, "."); |
| |
| while (tokenizer.hasMoreTokens()) |
| { |
| clName = tokenizer.nextToken(); |
| } |
| |
| String rootName; |
| String currentNodeName; |
| |
| rootName = (DTM.NULL == m_root) |
| ? "null" |
| : getDTM(m_root).getNodeName(m_root) + "{" |
| + (m_root+1) + "}"; |
| currentNodeName = |
| (DTM.NULL == m_currentNode) |
| ? "null" |
| : getDTM(m_currentNode).getNodeName(m_currentNode) + "{" |
| + (m_currentNode+1) + "}"; |
| |
| return clName + "[" + rootName + "][" + currentNodeName + "]"; |
| } |
| |
| /** |
| * This is simply a way to bottle-neck the return of the next node, for |
| * diagnostic purposes. |
| * |
| * @param n Node to return, or null. |
| * |
| * @return The argument. |
| */ |
| private int returnNextNode(int n) |
| { |
| |
| if (DEBUG_LOCATED && (DTM.NULL != n)) |
| { |
| printDebug("RETURN --->" + nodeToString(n)); |
| } |
| else if (DEBUG_LOCATED) |
| { |
| printDebug("RETURN --->null"); |
| } |
| |
| return n; |
| } |
| |
| /** |
| * Print a diagnostics string, adding a line break before the print. |
| * |
| * @param s String to print. |
| */ |
| private void printDebug(String s) |
| { |
| |
| if (DEBUG) |
| { |
| System.out.print("\n"); |
| |
| if (DTM.NULL != m_currentNode) |
| { |
| try |
| { |
| int depth = getDTM(m_currentNode).getLevel(m_currentNode); |
| |
| for (int i = 0; i < depth; i++) |
| { |
| System.out.print(" "); |
| } |
| } |
| catch (ClassCastException cce){} |
| } |
| |
| System.out.print(s); |
| } |
| } |
| |
| /** |
| * Do a diagnostics dump of an entire subtree. |
| * |
| * @param node The top of the subtree. |
| * @param indent The amount to begin the indenting at. |
| */ |
| private void dumpAll(int node, int indent) |
| { |
| |
| for (int i = 0; i < indent; i++) |
| { |
| System.out.print(" "); |
| } |
| |
| System.out.print(nodeToString(node)); |
| |
| if (DTM.TEXT_NODE == getDTM(node).getNodeType(node)) |
| { |
| XMLString value = getDTM(node).getStringValue(node); |
| |
| if (null != value) |
| { |
| System.out.print("+= -->" + value.trim()); |
| } |
| } |
| |
| System.out.println(""); |
| |
| DTM dtm = getDTM(node); |
| for (int attr = dtm.getFirstAttribute(node); attr != DTM.NULL; |
| attr = dtm.getNextAttribute(attr)) |
| { |
| for (int k = 0; k < indent; k++) |
| { |
| System.out.print(" "); |
| } |
| |
| System.out.print("attr -->"); |
| System.out.print(nodeToString(attr)); |
| |
| XMLString value = dtm.getStringValue(attr); |
| |
| if (null != value) |
| { |
| System.out.print("+= -->" + value.trim()); |
| } |
| |
| System.out.println(""); |
| } |
| |
| for (int child = dtm.getFirstChild(node); DTM.NULL != child; |
| child = dtm.getNextSibling(child)) |
| { |
| dumpAll(child, indent + 1); |
| } |
| } |
| |
| /** |
| * Print a diagnostic string without adding a line break. |
| * |
| * @param s The string to print. |
| */ |
| private void printDebugAdd(String s) |
| { |
| |
| if (DEBUG) |
| { |
| System.out.print("; " + s); |
| } |
| } |
| |
| /** |
| * Diagnostics. |
| */ |
| private void printEntryDebug() |
| { |
| |
| if (true && DEBUG_TRAVERSAL) |
| { |
| System.out.print("\n============================\n"); |
| |
| if (DTM.NULL != m_currentNode) |
| { |
| try |
| { |
| int depth = getDTM(m_currentNode).getLevel(m_currentNode); |
| |
| for (int i = 0; i < depth; i++) |
| { |
| System.out.print("+"); |
| } |
| } |
| catch (ClassCastException cce){} |
| } |
| |
| System.out.print(" " + this.toString() + ", " |
| + nodeToString(this.m_currentNode)); |
| printWaiters(); |
| } |
| } |
| |
| /** |
| * Diagnostics. |
| */ |
| private void printWaiters() |
| { |
| |
| if (DEBUG_WAITING) |
| { |
| int nWaiting = m_lpi.getWaitingCount(); |
| |
| for (int i = m_lpi.m_waitingBottom; i < nWaiting; i++) |
| { |
| AxesWalker ws = (AxesWalker) m_lpi.getWaiting(i); |
| |
| printDebug("[" + ws.toString() + " WAITING... ]"); |
| } |
| |
| printDebug("Waiting count: " + nWaiting); |
| } |
| } |
| |
| /** |
| * Tell what's the maximum level this axes can descend to. This method is |
| * meant to be overloaded by derived classes. |
| * |
| * @return An estimation of the maximum level this axes can descend to. |
| */ |
| protected int getLevelMax() |
| { |
| return 0; |
| } |
| |
| /** |
| * Tell what's the next level this axes can descend to. |
| * |
| * @return An estimation of the next level that this walker will traverse to. |
| */ |
| protected int getNextLevelAmount() |
| { |
| return m_nextLevelAmount; |
| } |
| |
| /** |
| * Tell if it's OK to traverse to the next node, following document |
| * order, or if the walker should wait for a condition to occur. |
| * |
| * @param prevStepWalker The previous walker in the location path. |
| * @param testWalker The walker being tested, but the state may not be intact, |
| * so only static information can be obtained from it. |
| * @param currentTestNode The current node being testing. |
| * @param nextLevelAmount An estimation of the next level to traverse to. |
| * |
| * @return True if it's OK for testWalker to traverse to nextLevelAmount. |
| */ |
| protected boolean checkOKToTraverse(AxesWalker prevStepWalker, |
| AxesWalker testWalker, |
| int currentTestNode, |
| int nextLevelAmount) |
| { |
| |
| int level = getDTM(currentTestNode).getLevel(currentTestNode); |
| |
| // Is this always the context node of the test walker? |
| int prevNode = prevStepWalker.m_currentNode; |
| |
| // Can the previous walker go past the one being tested? |
| if (DEBUG_WAITING) |
| printDebug("[prevStepWalker.getLevelMax():" |
| + prevStepWalker.getLevelMax() + " > level:" + level + "?]"); |
| |
| boolean ok; |
| |
| if (!prevStepWalker.m_isDone && prevStepWalker.getLevelMax() > level) |
| { |
| |
| // Is (prevStepWalker.m_currentNode > the currentTestNode)? |
| // (Sorry about the reverse logic). |
| boolean isNodeAfter = !getDTM(prevNode).isNodeAfter(prevNode, currentTestNode); |
| |
| if (DEBUG_WAITING) |
| printDebug("[isNodeAfter:" + isNodeAfter + "?]"); |
| |
| if (isNodeAfter) |
| { |
| int prevStepLevel = getDTM(prevNode).getLevel(prevNode); |
| |
| // If the previous step walker is below us in the tree, |
| // then we have to wait until it pops back up to our level, |
| // (if it ever does). |
| if (DEBUG_WAITING) |
| printDebug("[prevStepLevel:" + prevStepLevel + " <= (level:" |
| + level + "+nextLevelAmount:" + nextLevelAmount + "):" |
| + (level + nextLevelAmount) + "?]"); |
| |
| if (prevStepLevel > (level + nextLevelAmount)) |
| { |
| |
| // if next step is down, then ok = true, else |
| // if next step is horizontal, then we have to wait. |
| ok = false; |
| } |
| else |
| ok = true; |
| } |
| else |
| ok = false; |
| } |
| else |
| ok = true; |
| |
| if (DEBUG_WAITING) |
| printDebug("checkOKToTraverse = " + ok); |
| |
| return ok; |
| } |
| |
| /** |
| * Check if any walkers need to fire before the given walker. If they |
| * do, then the given walker will be put on the waiting list, and the |
| * waiting walker will be returned. |
| * @param walker The walker that is about to call nextNode(), or null. |
| * @return walker argument or new walker. |
| */ |
| AxesWalker checkWaiting(AxesWalker walker) |
| { |
| |
| // printDebug("checkWaiting: "+walker.toString()+", "+nodeToString(walker.m_currentNode)); |
| if ((null != walker) && (DTM.NULL == walker.m_currentNode)) |
| return walker; |
| |
| int nWaiting = m_lpi.getWaitingCount(); |
| |
| for (int i = m_lpi.m_waitingBottom; i < nWaiting; i++) |
| { |
| AxesWalker ws = (AxesWalker) m_lpi.getWaiting(i); |
| AxesWalker prevStepWalker = ws.m_prevWalker; |
| |
| if (null != prevStepWalker) |
| { |
| if (DEBUG_WAITING) |
| printDebug("Calling checkOKToTraverse(" + prevStepWalker.toString() |
| + ", " + ws.toString() + ", .);"); |
| |
| if (checkOKToTraverse(prevStepWalker, ws, ws.m_currentNode, |
| ws.m_nextLevelAmount)) |
| { |
| if (null != walker) |
| { |
| AxesWalker deferedWalker = walker; |
| |
| if (!isWaiting(deferedWalker)) |
| { |
| addToWaitList(deferedWalker); |
| } |
| } |
| |
| walker = ws; |
| |
| m_lpi.removeFromWaitList(walker); |
| |
| if (DEBUG_WAITING) |
| printDebug("[And using WAITING on " + ws.toString()); |
| |
| walker.printEntryDebug(); |
| |
| m_didSwitch = true; |
| |
| break; |
| } |
| } |
| } |
| |
| return walker; |
| } |
| |
| /** |
| * We have to do something to get things moving along, |
| * so get the earliest (in doc order) waiter. |
| * |
| * @return the earliest (in doc order) waiting walker. |
| */ |
| private AxesWalker getEarliestWaiting() |
| { |
| |
| DOMHelper dh = m_lpi.getDOMHelper(); |
| AxesWalker first = null; |
| int nWaiting = m_lpi.getWaitingCount(); |
| |
| for (int i = m_lpi.m_waitingBottom; i < nWaiting; i++) |
| { |
| AxesWalker ws = (AxesWalker) m_lpi.getWaiting(i); |
| |
| if (first == null) |
| first = ws; |
| else |
| { |
| if (!getDTM(ws.m_currentNode).isNodeAfter(ws.m_currentNode, first.m_currentNode)) |
| first = ws; |
| } |
| } |
| |
| if (null != first) |
| { |
| m_lpi.removeFromWaitList(first); |
| |
| if (DEBUG_WAITING) |
| printDebug("[(getEarliestWaiting)Using WAITING on " |
| + first.toString()); |
| |
| first.printEntryDebug(); |
| } |
| |
| return first; |
| } |
| |
| /** |
| * Tell if the given walker is already on the waiting list. |
| * |
| * @param walker Reference to walker that is the subject of the test. |
| * |
| * @return True if the walker argument is on the waiting list. |
| */ |
| boolean isWaiting(AxesWalker walker) |
| { |
| |
| int nWaiting = m_lpi.getWaitingCount(); |
| |
| for (int i = m_lpi.m_waitingBottom; i < nWaiting; i++) |
| { |
| AxesWalker ws = (AxesWalker) m_lpi.getWaiting(i); |
| |
| if (ws == walker) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private final void addToWaitList(AxesWalker walker) |
| { |
| if (DEBUG_WAITING) |
| printDebug("[Moving " + walker.toString() + ", " |
| + nodeToString(walker.m_currentNode) |
| + " to WAITING list]"); |
| |
| m_lpi.addToWaitList(walker); |
| } |
| |
| /** |
| * Check if a given walker needs to wait for the previous walker to |
| * catch up. |
| * |
| * @param walker The walker being checked. |
| * |
| * @return The walker or the previous walker. |
| */ |
| AxesWalker checkNeedsToWait(AxesWalker walker) |
| { |
| |
| AxesWalker prevWalker = walker.m_prevWalker; |
| |
| if (null != prevWalker) |
| { |
| if (DEBUG_WAITING) |
| printDebug("Calling checkOKToTraverse(" + prevWalker.toString() |
| + ", " + walker.toString() + ", .);"); |
| |
| if (!checkOKToTraverse(prevWalker, walker, walker.m_currentNode, |
| walker.m_nextLevelAmount)) |
| { |
| if (DEBUG_WAITING) |
| printDebug("[Adding " + walker.toString() + " to WAITING list"); |
| |
| if (isWaiting(walker)) |
| { |
| try |
| { |
| if (DEBUG_WAITING) |
| printDebug("checkNeedsToWait.clone: " + walker.toString()); |
| |
| addToWaitList((AxesWalker) walker.clone()); |
| } |
| catch (CloneNotSupportedException cnse){} |
| } |
| else |
| addToWaitList(walker); |
| |
| walker = walker.m_prevWalker; |
| |
| if (DEBUG_WAITING) |
| walker.printEntryDebug(); |
| } |
| } |
| |
| return walker; |
| } |
| |
| /** |
| * Get the next node in document order on the axes. |
| * |
| * @return the next node in document order on the axes, or null. |
| */ |
| protected int getNextNode() |
| { |
| |
| if (m_isFresh) |
| m_isFresh = false; |
| |
| int current = this.getCurrentNode(); |
| |
| // %NODETESTFILTER% |
| // if (current.isSupported(FEATURE_NODETESTFILTER, "1.0")) |
| // ((NodeTestFilter) current).setNodeTest(this); |
| |
| int next = this.firstChild(); |
| |
| while (DTM.NULL == next) |
| { |
| next = this.nextSibling(); |
| |
| if (DTM.NULL == next) |
| { |
| int p = this.parentNode(); |
| |
| if (DTM.NULL == p) |
| break; |
| } |
| } |
| |
| if (DTM.NULL == next) |
| this.m_isDone = true; |
| |
| // System.out.println("Returning: "+this); |
| return next; |
| } |
| |
| /** |
| * Moves the <code>TreeWalker</code> to the next visible node in document |
| * order relative to the current node, and returns the new node. If the |
| * current node has no next node, or if the search for nextNode attempts |
| * to step upward from the TreeWalker's root node, returns |
| * <code>null</code> , and retains the current node. |
| * @return The new node, or <code>null</code> if the current node has no |
| * next node in the TreeWalker's logical view. |
| */ |
| public int nextNode() |
| { |
| |
| if (DEBUG_TRAVERSAL &&!m_didDumpAll) |
| { |
| m_didDumpAll = true; |
| |
| // Node doc = (Node.DOCUMENT_NODE == m_root.getNodeType()) ? m_root : m_root.getOwnerDocument(); |
| // dumpAll(doc, 0); |
| } |
| |
| int nextNode = DTM.NULL; |
| AxesWalker walker = m_lpi.getLastUsedWalker(); |
| |
| // DOMHelper dh = m_lpi.getDOMHelper(); |
| // walker.printEntryDebug(); |
| m_didSwitch = false; |
| |
| boolean processWaiters = true; |
| |
| do |
| { |
| while (true) |
| { |
| |
| // Check to see if there's any walkers that need to execute first. |
| if (processWaiters) |
| { |
| AxesWalker waiting = checkWaiting(walker); |
| |
| if (m_didSwitch) |
| { |
| m_didSwitch = false; |
| walker = waiting; |
| } |
| else if (null != walker) |
| { |
| waiting = checkNeedsToWait(walker); |
| |
| if (waiting != walker) |
| { |
| walker = waiting; |
| |
| continue; |
| } |
| } |
| } |
| else |
| processWaiters = true; |
| |
| if (null == walker) |
| break; |
| |
| nextNode = walker.getNextNode(); |
| |
| if (DEBUG_TRAVERSAL) |
| walker.printDebug(walker.toString() + "--NEXT->" |
| + nodeToString(nextNode) + ")"); |
| |
| if (DTM.NULL == nextNode) |
| { |
| |
| // AxesWalker prev = walker; ?? -sb |
| walker = walker.m_prevWalker; |
| |
| if (null != walker) |
| walker.printEntryDebug(); |
| else |
| { |
| walker = getEarliestWaiting(); |
| |
| if (null != walker) |
| { |
| processWaiters = false; |
| |
| continue; |
| } |
| } |
| } |
| else |
| { |
| if (walker.acceptNode(nextNode) != DTMIterator.FILTER_ACCEPT) |
| { |
| if (DEBUG_TRAVERSAL) |
| printDebugAdd("[FILTER_SKIP]"); |
| |
| continue; |
| } |
| else |
| { |
| if (DEBUG_TRAVERSAL) |
| printDebugAdd("[FILTER_ACCEPT]"); |
| } |
| |
| if (null == walker.m_nextWalker) |
| { |
| |
| // walker.pushState(); |
| if (DEBUG_TRAVERSAL) |
| printDebug("May be returning: " + nodeToString(nextNode)); |
| |
| if (DEBUG_TRAVERSAL && (DTM.NULL != m_prevReturned)) |
| printDebugAdd(", m_prevReturned: " |
| + nodeToString(m_prevReturned)); |
| |
| m_lpi.setLastUsedWalker(walker); |
| |
| // return walker.returnNextNode(nextNode); |
| break; |
| } |
| else |
| { |
| AxesWalker prev = walker; |
| |
| walker = walker.m_nextWalker; |
| |
| /* |
| if((walker.getRoot() != null) && |
| prev.getLevelMax() >= walker.getLevelMax()) // bogus, but might be ok |
| */ |
| if (isWaiting(walker)) |
| { |
| try |
| { |
| walker = (AxesWalker) walker.clone(); |
| |
| // walker.pushState(); |
| // System.out.println("AxesWalker - Calling setRoot(1)"); |
| walker.setRoot(nextNode); |
| |
| if (DEBUG_WAITING) |
| printDebug("clone: " + walker.toString()); |
| } |
| catch (CloneNotSupportedException cnse){} |
| } |
| else |
| { |
| |
| // System.out.println("AxesWalker - Calling setRoot(2)"); |
| walker.setRoot(nextNode); |
| } |
| |
| walker.m_prevWalker = prev; |
| |
| walker.printEntryDebug(); |
| |
| continue; |
| } |
| } // if(null != nextNode) |
| } // while(null != walker) |
| } |
| |
| // Not sure what is going on here, but we were loosing |
| // the next node in the nodeset because it's coming from a |
| // different document. |
| while ( |
| (DTM.NULL != nextNode) && (DTM.NULL != m_prevReturned) |
| && getDTM(nextNode).getDocument() == getDTM(m_prevReturned).getDocument() |
| && getDTM(nextNode).isNodeAfter(nextNode, m_prevReturned)); |
| |
| m_prevReturned = nextNode; |
| |
| if (DEBUG_LOCATED) |
| return returnNextNode(nextNode); |
| else |
| return nextNode; |
| } |
| |
| //============= End TreeWalker Implementation ============= |
| |
| /** |
| * Get the index of the last node that can be itterated to. |
| * |
| * |
| * @param xctxt XPath runtime context. |
| * |
| * @return the index of the last node that can be itterated to. |
| */ |
| public int getLastPos(XPathContext xctxt) |
| { |
| |
| int pos = getProximityPosition(); |
| |
| AxesWalker walker; |
| |
| try |
| { |
| walker = (AxesWalker) clone(); |
| } |
| catch (CloneNotSupportedException cnse) |
| { |
| return -1; |
| } |
| |
| walker.setPredicateCount(walker.getPredicateCount() - 1); |
| walker.setNextWalker(null); |
| walker.setPrevWalker(null); |
| |
| LocPathIterator lpi = walker.getLocPathIterator(); |
| AxesWalker savedWalker = lpi.getLastUsedWalker(); |
| |
| try |
| { |
| lpi.setLastUsedWalker(walker); |
| |
| int next; |
| |
| while (DTM.NULL != (next = walker.nextNode())) |
| { |
| pos++; |
| } |
| |
| // TODO: Should probably save this in the iterator. |
| } |
| finally |
| { |
| lpi.setLastUsedWalker(savedWalker); |
| } |
| |
| // System.out.println("pos: "+pos); |
| return pos; |
| } |
| |
| /** |
| * Tell if this is a special type of walker compatible with ChildWalkerMultiStep. |
| * |
| * @return true this is a special type of walker compatible with ChildWalkerMultiStep. |
| */ |
| protected boolean isFastWalker() |
| { |
| return false; |
| } |
| |
| /** |
| * Test whether a specified node is visible in the logical view of a |
| * <code>DTMIterator</code>. Normally, this function |
| * will be called by the implementation of <code>DTMIterator</code>; |
| * it is not normally called directly from |
| * user code. |
| * |
| * @param nodeHandle int Handle of the node. |
| * @param whatToShow one of SHOW_XXX values. |
| * @return one of FILTER_ACCEPT, FILTER_REJECT, or FILTER_SKIP. |
| */ |
| public short acceptNode(int nodeHandle, int whatToShow) |
| { |
| return DTMIterator.FILTER_ACCEPT; // %TBD% |
| } |
| |
| /** |
| * Test whether a specified node is visible in the logical view of a |
| * <code>DTMIterator</code>. Normally, this function |
| * will be called by the implementation of <code>DTMIterator</code>; |
| * it is not normally called directly from |
| * user code. |
| * |
| * @param nodeHandle int Handle of the node. |
| * @param whatToShow one of SHOW_XXX values. |
| * @param expandedName a value defining the exanded name as defined in |
| * the DTM interface. Wild cards will be defined |
| * by 0xFFFF in the high word and/or in the low word. |
| * @return one of FILTER_ACCEPT, FILTER_REJECT, or FILTER_SKIP. |
| */ |
| public short acceptNode(int nodeHandle, int whatToShow, int expandedName) |
| { |
| return DTMIterator.FILTER_ACCEPT; // %TBD% |
| } |
| |
| //============= Static Data ============= |
| |
| // These are useful to enable if you want to turn diagnostics messages |
| // on or off temporarily from another module. |
| // public static boolean DEBUG = false; |
| // public static boolean DEBUG_WAITING = false; |
| // public static boolean DEBUG_TRAVERSAL = false; |
| // public static boolean DEBUG_LOCATED = false; |
| // public static boolean DEBUG_PREDICATECOUNTING = false; |
| |
| /** General static debug flag. Setting this to false will suppress some |
| * of the output messages caused by the other debug categories. */ |
| static final boolean DEBUG = false; |
| |
| /** If true, diagnostic messages about the waiting queue will be posted. */ |
| static final boolean DEBUG_WAITING = false; |
| |
| /** If true, diagnostic messages about the tree traversal will be posted. */ |
| static final boolean DEBUG_TRAVERSAL = false; |
| |
| /** If true, diagnostic messages about the nodes that have |
| * been 'located' will be posted. */ |
| static final boolean DEBUG_LOCATED = false; |
| |
| /** For diagnostic purposes, tells if we already did a subtree dump. */ |
| static boolean m_didDumpAll = false; |
| |
| /** String passed to {@link org.w3c.dom.Node#isSupported} to see if it implements |
| * a {@link org.apache.xpath.patterns.NodeTestFilter} interface. */ |
| public static final String FEATURE_NODETESTFILTER = "NodeTestFilter"; |
| |
| //============= State Data ============= |
| |
| /** |
| * The DTM for the root. This can not be used, or must be changed, |
| * for the filter walker, or any walker that can have nodes |
| * from multiple documents. |
| * Never, ever, access this value without going through getDTM(int node). |
| */ |
| private DTM m_dtm; |
| |
| /** |
| * Set the DTM for this walker. |
| * |
| * @param dtm Non-null reference to a DTM. |
| */ |
| public void setDefaultDTM(DTM dtm) |
| { |
| m_dtm = dtm; |
| } |
| |
| /** |
| * Get the DTM for this walker. |
| * |
| * @return Non-null reference to a DTM. |
| */ |
| public DTM getDTM(int node) |
| { |
| // |
| return m_lpi.getXPathContext().getDTM(node); |
| } |
| |
| /** |
| * The root node of the TreeWalker, as specified when it was created. |
| */ |
| transient int m_root = DTM.NULL; |
| |
| /** |
| * The node at which the TreeWalker is currently positioned. |
| */ |
| transient int m_currentNode = DTM.NULL; |
| |
| /** The node last returned from nextNode(). */ |
| transient int m_prevReturned = DTM.NULL; |
| |
| /** |
| * The arg length of the XPath step. Does not change after the constructor. |
| * TODO: Can this be removed since it is only valuable at compile time? |
| * @serial |
| */ |
| private int m_argLen; |
| |
| /** |
| * The step type of the XPath step. Does not change after the constructor. |
| * @serial |
| */ |
| private int m_stepType; |
| |
| /** Fairly short lived flag to tell if we switched to a waiting walker. */ |
| transient private boolean m_didSwitch = false; |
| |
| /** True if this walker has found it's last node. */ |
| transient boolean m_isDone = false; |
| |
| /** True if an itteration has not begun. */ |
| transient boolean m_isFresh; |
| |
| /** An estimation of the next level that this walker will traverse to. Not |
| * always accurate. */ |
| transient protected int m_nextLevelAmount; |
| |
| /** The next walker in the location step chain. |
| * @serial */ |
| protected AxesWalker m_nextWalker; |
| |
| /** The previous walker in the location step chain, or null. |
| * @serial */ |
| AxesWalker m_prevWalker; |
| |
| } |