| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 2002 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.io.Serializable; |
| |
| import java.util.Vector; |
| import javax.xml.transform.TransformerException; |
| import org.apache.xml.dtm.DTM; |
| import org.apache.xml.dtm.DTMIterator; |
| import org.apache.xml.dtm.DTMManager; |
| import org.apache.xml.dtm.DTMFilter; |
| import org.apache.xml.utils.NodeVector; |
| import org.apache.xpath.Expression; |
| import org.apache.xpath.XPathContext; |
| import org.apache.xpath.objects.XObject; |
| |
| /** |
| * This class is the dynamic wrapper for a Xalan DTMIterator instance, and |
| * provides random access capabilities. |
| */ |
| public class NodeSequence extends XObject |
| implements DTMIterator, Cloneable, PathComponent |
| { |
| /** The index of the last node in the iteration. */ |
| protected int m_last = -1; |
| |
| /** |
| * The index of the next node to be fetched. Useful if this |
| * is a cached iterator, and is being used as random access |
| * NodeList. |
| */ |
| protected int m_next = 0; |
| |
| /** |
| * If this iterator needs to cache nodes that are fetched, they |
| * are stored in the Vector in the generic object. |
| */ |
| protected NodeVector getVector() |
| { |
| return (NodeVector)m_obj; |
| } |
| |
| /** |
| * Set the vector where nodes will be stored. |
| */ |
| protected void SetVector(NodeVector v) |
| { |
| m_obj = v; |
| } |
| |
| |
| /** |
| * If this iterator needs to cache nodes that are fetched, they |
| * are stored here. |
| */ |
| public boolean hasCache() |
| { |
| return (m_obj != null); |
| } |
| |
| |
| /** |
| * The functional iterator that fetches nodes. |
| */ |
| protected DTMIterator m_iter; |
| |
| /** |
| * Set the functional iterator that fetches nodes. |
| * @param iter The iterator that is to be contained. |
| */ |
| public final void setIter(DTMIterator iter) |
| { |
| m_iter = iter; |
| } |
| |
| /** |
| * Get the functional iterator that fetches nodes. |
| * @return The contained iterator. |
| */ |
| public final DTMIterator getContainedIter() |
| { |
| return m_iter; |
| } |
| |
| /** |
| * The DTMManager to use if we're using a NodeVector only. |
| * We may well want to do away with this, and store it in the NodeVector. |
| */ |
| protected DTMManager m_dtmMgr; |
| |
| // ==== Constructors ==== |
| |
| /** |
| * Create a new NodeSequence from a (already cloned) iterator. |
| * |
| * @param iter Cloned (not static) DTMIterator. |
| * @param context The initial context node. |
| * @param xctxt The execution context. |
| * @param shouldCacheNodes True if this sequence can random access. |
| */ |
| public NodeSequence(DTMIterator iter, int context, XPathContext xctxt, boolean shouldCacheNodes) |
| { |
| setIter(iter); |
| setRoot(context, xctxt); |
| setShouldCacheNodes(shouldCacheNodes); |
| } |
| |
| /** |
| * Create a new NodeSequence from a (already cloned) iterator. |
| * |
| * @param iter Cloned (not static) DTMIterator. |
| */ |
| public NodeSequence(Object nodeVector) |
| { |
| super(nodeVector); |
| if(null != nodeVector) |
| { |
| assertion(nodeVector instanceof NodeVector, |
| "Must have a NodeVector as the object for NodeSequence!"); |
| if(nodeVector instanceof DTMIterator) |
| { |
| setIter((DTMIterator)nodeVector); |
| m_last = ((DTMIterator)nodeVector).getLength(); |
| } |
| |
| } |
| } |
| |
| /** |
| * Construct an empty XNodeSet object. This is used to create a mutable |
| * nodeset to which random nodes may be added. |
| */ |
| public NodeSequence(DTMManager dtmMgr) |
| { |
| super(new NodeVector()); |
| m_last = 0; |
| m_dtmMgr = dtmMgr; |
| } |
| |
| |
| /** |
| * Create a new NodeSequence in an invalid (null) state. |
| */ |
| public NodeSequence() |
| { |
| } |
| |
| |
| /** |
| * @see DTMIterator#getDTM(int) |
| */ |
| public DTM getDTM(int nodeHandle) |
| { |
| DTMManager mgr = getDTMManager(); |
| if(null != mgr) |
| return getDTMManager().getDTM(nodeHandle); |
| else |
| { |
| assertion(false, "Can not get a DTM Unless a DTMManager has been set!"); |
| return null; |
| } |
| } |
| |
| /** |
| * @see DTMIterator#getDTMManager() |
| */ |
| public DTMManager getDTMManager() |
| { |
| return m_dtmMgr; |
| } |
| |
| /** |
| * @see DTMIterator#getRoot() |
| */ |
| public int getRoot() |
| { |
| if(null != m_iter) |
| return m_iter.getRoot(); |
| else |
| { |
| // NodeSetDTM will call this, and so it's not a good thing to throw |
| // an assertion here. |
| // assertion(false, "Can not get the root from a non-iterated NodeSequence!"); |
| return DTM.NULL; |
| } |
| } |
| |
| /** |
| * @see DTMIterator#setRoot(int, Object) |
| */ |
| public void setRoot(int nodeHandle, Object environment) |
| { |
| if(null != m_iter) |
| { |
| XPathContext xctxt = (XPathContext)environment; |
| m_dtmMgr = xctxt.getDTMManager(); |
| m_iter.setRoot(nodeHandle, environment); |
| if(!m_iter.isDocOrdered()) |
| { |
| if(!hasCache()) |
| setShouldCacheNodes(true); |
| runTo(-1); |
| m_next=0; |
| } |
| } |
| else |
| assertion(false, "Can not setRoot on a non-iterated NodeSequence!"); |
| } |
| |
| /** |
| * @see DTMIterator#reset() |
| */ |
| public void reset() |
| { |
| m_next = 0; |
| // not resetting the iterator on purpose!!! |
| } |
| |
| /** |
| * @see DTMIterator#getWhatToShow() |
| */ |
| public int getWhatToShow() |
| { |
| return hasCache() ? (DTMFilter.SHOW_ALL & ~DTMFilter.SHOW_ENTITY_REFERENCE) |
| : m_iter.getWhatToShow(); |
| } |
| |
| /** |
| * @see DTMIterator#getExpandEntityReferences() |
| */ |
| public boolean getExpandEntityReferences() |
| { |
| if(null != m_iter) |
| return m_iter.getExpandEntityReferences(); |
| else |
| return true; |
| } |
| |
| /** |
| * @see DTMIterator#nextNode() |
| */ |
| public int nextNode() |
| { |
| // If the cache is on, and the node has already been found, then |
| // just return from the list. |
| NodeVector vec = getVector(); |
| if (null != vec) |
| { |
| if(m_next < vec.size()) |
| { |
| int next = vec.elementAt(m_next); |
| m_next++; |
| return next; |
| } |
| else if((-1 != m_last) || (null == m_iter)) |
| { |
| m_next++; |
| return DTM.NULL; |
| } |
| } |
| |
| |
| int next = m_iter.nextNode(); |
| if(DTM.NULL != next) |
| { |
| if(hasCache()) |
| { |
| if(m_iter.isDocOrdered()) |
| { |
| getVector().addElement(next); |
| m_next++; |
| } |
| else |
| { |
| int insertIndex = addNodeInDocOrder(next); |
| if(insertIndex >= 0) |
| m_next++; |
| } |
| } |
| else |
| m_next++; |
| } |
| else |
| { |
| m_last = m_next; |
| m_next++; |
| } |
| |
| return next; |
| } |
| |
| /** |
| * @see DTMIterator#previousNode() |
| */ |
| public int previousNode() |
| { |
| if(hasCache()) |
| { |
| if(m_next <= 0) |
| return DTM.NULL; |
| else |
| { |
| m_next--; |
| return item(m_next); |
| } |
| } |
| else |
| { |
| int n = m_iter.previousNode(); |
| m_next = m_iter.getCurrentPos(); |
| return m_next; |
| } |
| } |
| |
| /** |
| * @see DTMIterator#detach() |
| */ |
| public void detach() |
| { |
| if(null != m_iter) |
| m_iter.detach(); |
| super.detach(); |
| } |
| |
| /** |
| * Calling this with a value of false will cause the nodeset |
| * to be cached. |
| * @see DTMIterator#allowDetachToRelease(boolean) |
| */ |
| public void allowDetachToRelease(boolean allowRelease) |
| { |
| if((false == allowRelease) && !hasCache()) |
| { |
| setShouldCacheNodes(true); |
| } |
| |
| if(null != m_iter) |
| m_iter.allowDetachToRelease(allowRelease); |
| super.allowDetachToRelease(allowRelease); |
| } |
| |
| /** |
| * @see DTMIterator#getCurrentNode() |
| */ |
| public int getCurrentNode() |
| { |
| if(hasCache()) |
| { |
| int currentIndex = m_next-1; |
| NodeVector vec = getVector(); |
| if((currentIndex >= 0) && (currentIndex < vec.size())) |
| return vec.elementAt(currentIndex); |
| else |
| return DTM.NULL; |
| } |
| |
| if(null != m_iter) |
| { |
| return m_iter.getCurrentNode(); |
| } |
| else |
| return DTM.NULL; |
| } |
| |
| /** |
| * @see DTMIterator#isFresh() |
| */ |
| public boolean isFresh() |
| { |
| return (0 == m_next); |
| } |
| |
| /** |
| * @see DTMIterator#setShouldCacheNodes(boolean) |
| */ |
| public void setShouldCacheNodes(boolean b) |
| { |
| if (b) |
| { |
| if(!hasCache()) |
| { |
| SetVector(new NodeVector()); |
| } |
| // else |
| // getVector().RemoveAllNoClear(); // Is this good? |
| } |
| else |
| SetVector(null); |
| } |
| |
| /** |
| * @see DTMIterator#isMutable() |
| */ |
| public boolean isMutable() |
| { |
| return hasCache(); // though may be surprising if it also has an iterator! |
| } |
| |
| /** |
| * @see DTMIterator#getCurrentPos() |
| */ |
| public int getCurrentPos() |
| { |
| return m_next; |
| } |
| |
| /** |
| * @see DTMIterator#runTo(int) |
| */ |
| public void runTo(int index) |
| { |
| int n; |
| |
| if (-1 == index) |
| { |
| int pos = m_next; |
| while (DTM.NULL != (n = nextNode())); |
| m_next = pos; |
| } |
| else if(m_next == index) |
| { |
| return; |
| } |
| else if(hasCache() && m_next < getVector().size()) |
| { |
| m_next = index; |
| } |
| else if((null == getVector()) && (index < m_next)) |
| { |
| while ((m_next >= index) && DTM.NULL != (n = previousNode())); |
| } |
| else |
| { |
| while ((m_next < index) && DTM.NULL != (n = nextNode())); |
| } |
| |
| } |
| |
| /** |
| * @see DTMIterator#setCurrentPos(int) |
| */ |
| public void setCurrentPos(int i) |
| { |
| runTo(i); |
| } |
| |
| /** |
| * @see DTMIterator#item(int) |
| */ |
| public int item(int index) |
| { |
| setCurrentPos(index); |
| int n = nextNode(); |
| m_next = index; |
| return n; |
| } |
| |
| /** |
| * @see DTMIterator#setItem(int, int) |
| */ |
| public void setItem(int node, int index) |
| { |
| NodeVector vec = getVector(); |
| if(null != vec) |
| { |
| vec.setElementAt(node, index); |
| m_last = vec.size(); |
| } |
| else |
| m_iter.setItem(node, index); |
| } |
| |
| /** |
| * @see DTMIterator#getLength() |
| */ |
| public int getLength() |
| { |
| if(hasCache()) |
| { |
| if(-1 == m_last) |
| { |
| int pos = m_next; |
| runTo(-1); |
| m_next = pos; |
| } |
| return m_last; |
| } |
| else |
| { |
| return m_iter.getLength(); |
| } |
| } |
| |
| /** |
| * Note: Not a deep clone. |
| * @see DTMIterator#cloneWithReset() |
| */ |
| public DTMIterator cloneWithReset() throws CloneNotSupportedException |
| { |
| NodeSequence seq = (NodeSequence)super.clone(); |
| seq.m_next = 0; |
| return seq; |
| } |
| |
| /** |
| * Get a clone of this iterator, but don't reset the iteration in the |
| * process, so that it may be used from the current position. |
| * Note: Not a deep clone. |
| * |
| * @return A clone of this object. |
| * |
| * @throws CloneNotSupportedException |
| */ |
| public Object clone() throws CloneNotSupportedException |
| { |
| return super.clone(); |
| } |
| |
| |
| /** |
| * @see DTMIterator#isDocOrdered() |
| */ |
| public boolean isDocOrdered() |
| { |
| if(null != m_iter) |
| return m_iter.isDocOrdered(); |
| else |
| return true; // can't be sure? |
| } |
| |
| /** |
| * @see DTMIterator#getAxis() |
| */ |
| public int getAxis() |
| { |
| if(null != m_iter) |
| return m_iter.getAxis(); |
| else |
| { |
| assertion(false, "Can not getAxis from a non-iterated node sequence!"); |
| return 0; |
| } |
| } |
| |
| /** |
| * @see PathComponent#getAnalysisBits() |
| */ |
| public int getAnalysisBits() |
| { |
| if((null != m_iter) && (m_iter instanceof PathComponent)) |
| return ((PathComponent)m_iter).getAnalysisBits(); |
| else |
| return 0; |
| } |
| |
| /** |
| * @see Expression#fixupVariables(Vector, int) |
| */ |
| public void fixupVariables(Vector vars, int globalsSize) |
| { |
| super.fixupVariables(vars, globalsSize); |
| } |
| |
| /** |
| * Add the node into a vector of nodes where it should occur in |
| * document order. |
| * @param v Vector of nodes, presumably containing Nodes |
| * @param obj Node object. |
| * |
| * @param node The node to be added. |
| * @param test true if we should test for doc order |
| * @param support The XPath runtime context. |
| * @return insertIndex. |
| * @throws RuntimeException thrown if this NodeSetDTM is not of |
| * a mutable type. |
| */ |
| protected int addNodeInDocOrder(int node) |
| { |
| assertion(hasCache(), "addNodeInDocOrder must be done on a mutable sequence!"); |
| |
| int insertIndex = -1; |
| |
| NodeVector vec = getVector(); |
| |
| // This needs to do a binary search, but a binary search |
| // is somewhat tough because the sequence test involves |
| // two nodes. |
| int size = vec.size(), i; |
| |
| for (i = size - 1; i >= 0; i--) |
| { |
| int child = vec.elementAt(i); |
| |
| if (child == node) |
| { |
| i = -2; // Duplicate, suppress insert |
| |
| break; |
| } |
| |
| DTM dtm = m_dtmMgr.getDTM(node); |
| if (!dtm.isNodeAfter(node, child)) |
| { |
| break; |
| } |
| } |
| |
| if (i != -2) |
| { |
| insertIndex = i + 1; |
| |
| vec.insertElementAt(node, insertIndex); |
| } |
| |
| // checkDups(); |
| return insertIndex; |
| } // end addNodeInDocOrder(Vector v, Object obj) |
| } |
| |