/* * Copyright 1999-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: LocPathIterator.java,v 1.41 2004/02/17 04:32:08 minchau Exp $ */ package com.sun.org.apache.xpath.internal.axes; import com.sun.org.apache.xalan.internal.res.XSLMessages; import com.sun.org.apache.xml.internal.dtm.DTM; import com.sun.org.apache.xml.internal.dtm.DTMFilter; import com.sun.org.apache.xml.internal.dtm.DTMIterator; import com.sun.org.apache.xml.internal.dtm.DTMManager; import com.sun.org.apache.xml.internal.utils.PrefixResolver; import com.sun.org.apache.xpath.internal.ExpressionOwner; import com.sun.org.apache.xpath.internal.XPathContext; import com.sun.org.apache.xpath.internal.XPathVisitor; import com.sun.org.apache.xpath.internal.compiler.Compiler; import com.sun.org.apache.xpath.internal.objects.XNodeSet; import com.sun.org.apache.xpath.internal.objects.XObject; import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; /** * This class extends NodeSetDTM, which implements NodeIterator, * and fetches nodes one at a time in document order based on a XPath * * @xsl.usage advanced */ public abstract class LocPathIterator extends PredicatedNodeTest implements Cloneable, DTMIterator, java.io.Serializable, PathComponent { /** * Create a LocPathIterator object. * * @param nscontext The namespace context for this iterator, * should be OK if null. */ protected LocPathIterator() { } /** * Create a LocPathIterator object. * * @param nscontext The namespace context for this iterator, * should be OK if null. */ protected LocPathIterator(PrefixResolver nscontext) { setLocPathIterator(this); m_prefixResolver = nscontext; } /** * Create a LocPathIterator object, including creation * of step walkers from the opcode list, and call back * into the Compiler to create predicate expressions. * * @param compiler The Compiler which is creating * this expression. * @param opPos The position of this iterator in the * opcode list from the compiler. * * @throws javax.xml.transform.TransformerException */ protected LocPathIterator(Compiler compiler, int opPos, int analysis) throws javax.xml.transform.TransformerException { this(compiler, opPos, analysis, true); } /** * Create a LocPathIterator object, including creation * of step walkers from the opcode list, and call back * into the Compiler to create predicate expressions. * * @param compiler The Compiler which is creating * this expression. * @param opPos The position of this iterator in the * opcode list from the compiler. * @param shouldLoadWalkers True if walkers should be * loaded, or false if this is a derived iterator and * it doesn't wish to load child walkers. * * @throws javax.xml.transform.TransformerException */ protected LocPathIterator( Compiler compiler, int opPos, int analysis, boolean shouldLoadWalkers) throws javax.xml.transform.TransformerException { setLocPathIterator(this); } /** * Get the analysis bits for this walker, as defined in the WalkerFactory. * @return One of WalkerFactory#BIT_DESCENDANT, etc. */ public int getAnalysisBits() { int axis = getAxis(); int bit = WalkerFactory.getAnalysisBitFromAxes(axis); return bit; } /** * Read the object from a serialization stream. * * @param stream Input stream to read from * * @throws java.io.IOException * @throws javax.xml.transform.TransformerException */ private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, javax.xml.transform.TransformerException { try { stream.defaultReadObject(); m_clones = new IteratorPool(this); } catch (ClassNotFoundException cnfe) { throw new javax.xml.transform.TransformerException(cnfe); } } /** * Set the environment in which this iterator operates, which should provide: * a node (the context node... same value as "root" defined below) * a pair of non-zero positive integers (the context position and the context size) * a set of variable bindings * a function library * the set of namespace declarations in scope for the expression. * *

At this time the exact implementation of this environment is application * dependent. Probably a proper interface will be created fairly soon.

* * @param environment The environment object. */ public void setEnvironment(Object environment) { // no-op for now. } /** * Get an instance of a DTM that "owns" a node handle. Since a node * iterator may be passed without a DTMManager, this allows the * caller to easily get the DTM using just the iterator. * * @param nodeHandle the nodeHandle. * * @return a non-null DTM reference. */ public DTM getDTM(int nodeHandle) { // %OPT% return m_execContext.getDTM(nodeHandle); } /** * Get an instance of the DTMManager. Since a node * iterator may be passed without a DTMManager, this allows the * caller to easily get the DTMManager using just the iterator. * * @return a non-null DTMManager reference. */ public DTMManager getDTMManager() { return m_execContext.getDTMManager(); } /** * Execute this iterator, meaning create a clone that can * store state, and initialize it for fast execution from * the current runtime state. When this is called, no actual * query from the current context node is performed. * * @param xctxt The XPath execution context. * * @return An XNodeSet reference that holds this iterator. * * @throws javax.xml.transform.TransformerException */ public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException { XNodeSet iter = new XNodeSet((LocPathIterator)m_clones.getInstance()); iter.setRoot(xctxt.getCurrentNode(), xctxt); return iter; } /** * Execute an expression in the XPath runtime context, and return the * result of the expression. * * * @param xctxt The XPath runtime context. * @param handler The target content handler. * * @return The result of the expression in the form of a XObject. * * @throws javax.xml.transform.TransformerException if a runtime exception * occurs. * @throws org.xml.sax.SAXException */ public void executeCharsToContentHandler( XPathContext xctxt, org.xml.sax.ContentHandler handler) throws javax.xml.transform.TransformerException, org.xml.sax.SAXException { LocPathIterator clone = (LocPathIterator)m_clones.getInstance(); int current = xctxt.getCurrentNode(); clone.setRoot(current, xctxt); int node = clone.nextNode(); DTM dtm = clone.getDTM(node); clone.detach(); if(node != DTM.NULL) { dtm.dispatchCharactersEvents(node, handler, false); } } /** * Given an select expression and a context, evaluate the XPath * and return the resulting iterator. * * @param xctxt The execution context. * @param contextNode The node that "." expresses. * @param namespaceContext The context in which namespaces in the * XPath are supposed to be expanded. * * @throws TransformerException thrown if the active ProblemListener decides * the error condition is severe enough to halt processing. * * @throws javax.xml.transform.TransformerException * @xsl.usage experimental */ public DTMIterator asIterator( XPathContext xctxt, int contextNode) throws javax.xml.transform.TransformerException { XNodeSet iter = new XNodeSet((LocPathIterator)m_clones.getInstance()); iter.setRoot(contextNode, xctxt); return iter; } /** * Tell if the expression is a nodeset expression. * * @return true if the expression can be represented as a nodeset. */ public boolean isNodesetExpr() { return true; } /** * Return the first node out of the nodeset, if this expression is * a nodeset expression. This is the default implementation for * nodesets. Derived classes should try and override this and return a * value without having to do a clone operation. * @param xctxt The XPath runtime context. * @return the first node out of the nodeset, or DTM.NULL. */ public int asNode(XPathContext xctxt) throws javax.xml.transform.TransformerException { DTMIterator iter = (DTMIterator)m_clones.getInstance(); int current = xctxt.getCurrentNode(); iter.setRoot(current, xctxt); int next = iter.nextNode(); // m_clones.freeInstance(iter); iter.detach(); return next; } /** * Evaluate this operation directly to a boolean. * * @param xctxt The runtime execution context. * * @return The result of the operation as a boolean. * * @throws javax.xml.transform.TransformerException */ public boolean bool(XPathContext xctxt) throws javax.xml.transform.TransformerException { return (asNode(xctxt) != DTM.NULL); } /** * Set if this is an iterator at the upper level of * the XPath. * * @param b true if this location path is at the top level of the * expression. * @xsl.usage advanced */ public void setIsTopLevel(boolean b) { m_isTopLevel = b; } /** * Get if this is an iterator at the upper level of * the XPath. * * @return true if this location path is at the top level of the * expression. * @xsl.usage advanced */ public boolean getIsTopLevel() { return m_isTopLevel; } /** * Initialize the context values for this expression * after it is cloned. * * @param execContext The XPath runtime context for this * transformation. */ public void setRoot(int context, Object environment) { m_context = context; XPathContext xctxt = (XPathContext)environment; m_execContext = xctxt; m_cdtm = xctxt.getDTM(context); m_currentContextNode = context; // only if top level? // Yech, shouldn't have to do this. -sb if(null == m_prefixResolver) m_prefixResolver = xctxt.getNamespaceContext(); m_lastFetched = DTM.NULL; m_foundLast = false; m_pos = 0; m_length = -1; if (m_isTopLevel) this.m_stackFrame = xctxt.getVarStack().getStackFrame(); // reset(); } /** * Set the next position index of this iterator. * * @param next A value greater than or equal to zero that indicates the next * node position to fetch. */ protected void setNextPosition(int next) { assertion(false, "setNextPosition not supported in this iterator!"); } /** * Get the current position, which is one less than * the next nextNode() call will retrieve. i.e. if * you call getCurrentPos() and the return is 0, the next * fetch will take place at index 1. * * @return A value greater than or equal to zero that indicates the next * node position to fetch. */ public final int getCurrentPos() { return m_pos; } /** * If setShouldCacheNodes(true) is called, then nodes will * be cached. They are not cached by default. * * @param b True if this iterator should cache nodes. */ public void setShouldCacheNodes(boolean b) { assertion(false, "setShouldCacheNodes not supported by this iterater!"); } /** * Tells if this iterator can have nodes added to it or set via * the setItem(int node, int index) method. * * @return True if the nodelist can be mutated. */ public boolean isMutable() { return false; } /** * Set the current position in the node set. * * @param i Must be a valid index greater * than or equal to zero and less than m_cachedNodes.size(). */ public void setCurrentPos(int i) { assertion(false, "setCurrentPos not supported by this iterator!"); } /** * Increment the current position in the node set. */ public void incrementCurrentPos() { m_pos++; } /** * Get the length of the cached nodes. * *

Note: for the moment at least, this only returns * the size of the nodes that have been fetched to date, * it doesn't attempt to run to the end to make sure we * have found everything. This should be reviewed.

* * @return The size of the current cache list. */ public int size() { assertion(false, "size() not supported by this iterator!"); return 0; } /** * Returns the index th item in the collection. If * index is greater than or equal to the number of nodes in * the list, this returns null . * @param index Index into the collection. * @return The node at the index th position in the * NodeList , or null if that is not a valid * index. */ public int item(int index) { assertion(false, "item(int index) not supported by this iterator!"); return 0; } /** * Sets the node at the specified index of this vector to be the * specified node. The previous component at that position is discarded. * *

The index must be a value greater than or equal to 0 and less * than the current size of the vector. * The iterator must be in cached mode.

* *

Meant to be used for sorted iterators.

* * @param node Node to set * @param index Index of where to set the node */ public void setItem(int node, int index) { assertion(false, "setItem not supported by this iterator!"); } /** * The number of nodes in the list. The range of valid child node indices * is 0 to length-1 inclusive. * * @return The number of nodes in the list, always greater or equal to zero. */ public int getLength() { // Tell if this is being called from within a predicate. boolean isPredicateTest = (this == m_execContext.getSubContextList()); // And get how many total predicates are part of this step. int predCount = getPredicateCount(); // If we have already calculated the length, and the current predicate // is the first predicate, then return the length. We don't cache // the anything but the length of the list to the first predicate. if (-1 != m_length && isPredicateTest && m_predicateIndex < 1) return m_length; // I'm a bit worried about this one, since it doesn't have the // checks found above. I suspect it's fine. -sb if (m_foundLast) return m_pos; // Create a clone, and count from the current position to the end // of the list, not taking into account the current predicate and // predicates after the current one. int pos = (m_predicateIndex >= 0) ? getProximityPosition() : m_pos; LocPathIterator clone; try { clone = (LocPathIterator) clone(); } catch (CloneNotSupportedException cnse) { return -1; } // We want to clip off the last predicate, but only if we are a sub // context node list, NOT if we are a context list. See pos68 test, // also test against bug4638. if (predCount > 0 && isPredicateTest) { // Don't call setPredicateCount, because it clones and is slower. clone.m_predCount = m_predicateIndex; // The line above used to be: // clone.m_predCount = predCount - 1; // ...which looks like a dumb bug to me. -sb } int next; while (DTM.NULL != (next = clone.nextNode())) { pos++; } if (isPredicateTest && m_predicateIndex < 1) m_length = pos; return pos; } /** * Tells if this NodeSetDTM is "fresh", in other words, if * the first nextNode() that is called will return the * first node in the set. * * @return true of nextNode has not been called. */ public boolean isFresh() { return (m_pos == 0); } /** * Returns the previous node in the set and moves the position of the * iterator backwards in the set. * @return The previous Node in the set being iterated over, * ornull if there are no more members in that set. */ public int previousNode() { throw new RuntimeException( XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_CANNOT_ITERATE, null)); //"This NodeSetDTM can not iterate to a previous node!"); } /** * This attribute determines which node types are presented via the * iterator. The available set of constants is defined in the * NodeFilter interface. * *

This is somewhat useless at this time, since it doesn't * really return information that tells what this iterator will * show. It is here only to fullfill the DOM NodeIterator * interface.

* * @return For now, always NodeFilter.SHOW_ALL & ~NodeFilter.SHOW_ENTITY_REFERENCE. * @see org.w3c.dom.traversal.NodeIterator */ public int getWhatToShow() { // TODO: ?? return DTMFilter.SHOW_ALL & ~DTMFilter.SHOW_ENTITY_REFERENCE; } /** * The filter used to screen nodes. Not used at this time, * this is here only to fullfill the DOM NodeIterator * interface. * * @return Always null. * @see org.w3c.dom.traversal.NodeIterator */ public DTMFilter getFilter() { return null; } /** * The root node of the Iterator, as specified when it was created. * * @return The "root" of this iterator, which, in XPath terms, * is the node context for this iterator. */ public int getRoot() { return m_context; } /** * The value of this flag determines whether the children of entity * reference nodes are visible to the iterator. If false, they will be * skipped over. *
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 iterator. 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 Always true, since entity reference nodes are not * visible in the XPath model. */ public boolean getExpandEntityReferences() { return true; } /** Control over whether it is OK for detach to reset the iterator. */ protected boolean m_allowDetach = true; /** * Specify if it's OK for detach to release the iterator for reuse. * * @param allowRelease true if it is OK for detach to release this iterator * for pooling. */ public void allowDetachToRelease(boolean allowRelease) { m_allowDetach = allowRelease; } /** * Detaches the iterator from the set which it iterated over, releasing * any computational resources and placing the iterator in the INVALID * state. Afterdetach has been invoked, calls to * nextNode orpreviousNode will raise the * exception INVALID_STATE_ERR. */ public void detach() { if(m_allowDetach) { // sb: allow reusing of cached nodes when possible? // m_cachedNodes = null; m_execContext = null; // m_prefixResolver = null; sb: Why would this ever want to be null? m_cdtm = null; m_length = -1; m_pos = 0; m_lastFetched = DTM.NULL; m_context = DTM.NULL; m_currentContextNode = DTM.NULL; m_clones.freeInstance(this); } } /** * Reset the iterator. */ public void reset() { assertion(false, "This iterator can not reset!"); } /** * Get a cloned Iterator that is reset to the beginning * of the query. * * @return A cloned NodeIterator set of the start of the query. * * @throws CloneNotSupportedException */ public DTMIterator cloneWithReset() throws CloneNotSupportedException { LocPathIterator clone; // clone = (LocPathIterator) clone(); clone = (LocPathIterator)m_clones.getInstanceOrThrow(); clone.m_execContext = m_execContext; clone.m_cdtm = m_cdtm; clone.m_context = m_context; clone.m_currentContextNode = m_currentContextNode; clone.m_stackFrame = m_stackFrame; // clone.reset(); return clone; } // /** // * Get a cloned LocPathIterator that holds the same // * position as this iterator. // * // * @return A clone of this iterator that holds the same node position. // * // * @throws CloneNotSupportedException // */ // public Object clone() throws CloneNotSupportedException // { // // LocPathIterator clone = (LocPathIterator) super.clone(); // // return clone; // } /** * Returns the next node in the set and advances the position of the * iterator in the set. After a NodeIterator is created, the first call * to nextNode() returns the first node in the set. * @return The next Node in the set being iterated over, or * null if there are no more members in that set. */ public abstract int nextNode(); /** * Bottleneck the return of a next node, to make returns * easier from nextNode(). * * @param nextNode The next node found, may be null. * * @return The same node that was passed as an argument. */ protected int returnNextNode(int nextNode) { if (DTM.NULL != nextNode) { m_pos++; } m_lastFetched = nextNode; if (DTM.NULL == nextNode) m_foundLast = true; return nextNode; } /** * Return the last fetched node. Needed to support the UnionPathIterator. * * @return The last fetched node, or null if the last fetch was null. */ public int getCurrentNode() { return m_lastFetched; } /** * If an index is requested, NodeSetDTM will call this method * to run the iterator to the index. By default this sets * m_next to the index. If the index argument is -1, this * signals that the iterator should be run to the end. * * @param index The index to run to, or -1 if the iterator * should run to the end. */ public void runTo(int index) { if (m_foundLast || ((index >= 0) && (index <= getCurrentPos()))) return; int n; if (-1 == index) { while (DTM.NULL != (n = nextNode())); } else { while (DTM.NULL != (n = nextNode())) { if (getCurrentPos() >= index) break; } } } /** * Tells if we've found the last node yet. * * @return true if the last nextNode returned null. */ public final boolean getFoundLast() { return m_foundLast; } /** * The XPath execution context we are operating on. * * @return XPath execution context this iterator is operating on, * or null if setRoot has not been called. */ public final XPathContext getXPathContext() { return m_execContext; } /** * The node context for the iterator. * * @return The node context, same as getRoot(). */ public final int getContext() { return m_context; } /** * The node context from where the expression is being * executed from (i.e. for current() support). * * @return The top-level node context of the entire expression. */ public final int getCurrentContextNode() { return m_currentContextNode; } /** * Set the current context node for this iterator. * * @param n Must be a non-null reference to the node context. */ public final void setCurrentContextNode(int n) { m_currentContextNode = n; } // /** // * Set the current context node for this iterator. // * // * @param n Must be a non-null reference to the node context. // */ // public void setRoot(int n) // { // m_context = n; // m_cdtm = m_execContext.getDTM(n); // } /** * Return the saved reference to the prefix resolver that * was in effect when this iterator was created. * * @return The prefix resolver or this iterator, which may be null. */ public final PrefixResolver getPrefixResolver() { if(null == m_prefixResolver) { m_prefixResolver = (PrefixResolver)getExpressionOwner(); } return m_prefixResolver; } // /** // * Get the analysis pattern built by the WalkerFactory. // * // * @return The analysis pattern built by the WalkerFactory. // */ // int getAnalysis() // { // return m_analysis; // } // /** // * Set the analysis pattern built by the WalkerFactory. // * // * @param a The analysis pattern built by the WalkerFactory. // */ // void setAnalysis(int a) // { // m_analysis = a; // } /** * @see com.sun.org.apache.xpath.internal.XPathVisitable#callVisitors(ExpressionOwner, XPathVisitor) */ public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) { if(visitor.visitLocationPath(owner, this)) { visitor.visitStep(owner, this); callPredicateVisitors(visitor); } } //============= State Data ============= /** * The pool for cloned iterators. Iterators need to be cloned * because the hold running state, and thus the original iterator * expression from the stylesheet pool can not be used. */ transient protected IteratorPool m_clones = new IteratorPool(this); /** * The dtm of the context node. Careful about using this... it may not * be the dtm of the current node. */ transient protected DTM m_cdtm; /** * The stack frame index for this iterator. */ transient int m_stackFrame = -1; /** * Value determined at compile time, indicates that this is an * iterator at the top level of the expression, rather than inside * a predicate. * @serial */ private boolean m_isTopLevel = false; /** The last node that was fetched, usually by nextNode. */ transient public int m_lastFetched = DTM.NULL; /** * The context node for this iterator, which doesn't change through * the course of the iteration. */ transient protected int m_context = DTM.NULL; /** * The node context from where the expression is being * executed from (i.e. for current() support). Different * from m_context in that this is the context for the entire * expression, rather than the context for the subexpression. */ transient protected int m_currentContextNode = DTM.NULL; /** * The current position of the context node. */ transient protected int m_pos = 0; transient protected int m_length = -1; /** * Fast access to the current prefix resolver. It isn't really * clear that this is needed. * @serial */ private PrefixResolver m_prefixResolver; /** * The XPathContext reference, needed for execution of many * operations. */ transient protected XPathContext m_execContext; /** * Returns true if all the nodes in the iteration well be returned in document * order. * * @return true as a default. */ public boolean isDocOrdered() { return true; } /** * Returns the axis being iterated, if it is known. * * @return Axis.CHILD, etc., or -1 if the axis is not known or is of multiple * types. */ public int getAxis() { return -1; } // /** // * The analysis pattern built by the WalkerFactory. // * TODO: Move to LocPathIterator. // * @see com.sun.org.apache.xpath.internal.axes.WalkerFactory // * @serial // */ // protected int m_analysis = 0x00000000; /** * @see PredicatedNodeTest#getLastPos(XPathContext) */ public int getLastPos(XPathContext xctxt) { return getLength(); } }