/* | 
|
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. | 
|
*/  | 
|
/*  | 
|
* Licensed to the Apache Software Foundation (ASF) under one or more  | 
|
* contributor license agreements. See the NOTICE file distributed with  | 
|
* this work for additional information regarding copyright ownership.  | 
|
* The ASF licenses this file to You 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.  | 
|
*/  | 
|
package com.sun.org.apache.xpath.internal.axes;  | 
|
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.NodeVector;  | 
|
import com.sun.org.apache.xml.internal.utils.QName;  | 
|
import com.sun.org.apache.xpath.internal.NodeSetDTM;  | 
|
import com.sun.org.apache.xpath.internal.XPathContext;  | 
|
import com.sun.org.apache.xpath.internal.objects.XObject;  | 
|
import java.util.List;  | 
|
/** | 
|
 * This class is the dynamic wrapper for a Xalan DTMIterator instance, and | 
|
 * provides random access capabilities. | 
|
 * | 
|
 * @LastModified: Oct 2017 | 
|
*/  | 
|
public class NodeSequence extends XObject  | 
|
implements DTMIterator, Cloneable, PathComponent  | 
|
{ | 
|
static final long serialVersionUID = 3866261934726581044L;  | 
|
  /** 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;  | 
|
  /** | 
|
   * A cache of a list of nodes obtained from the iterator so far. | 
|
   * This list is appended to until the iterator is exhausted and | 
|
   * the cache is complete. | 
|
   * <p> | 
|
   * Multiple NodeSequence objects may share the same cache. | 
|
*/  | 
|
private IteratorCache m_cache;  | 
|
  /** | 
|
   * If this iterator needs to cache nodes that are fetched, they | 
|
   * are stored in the Vector in the generic object. | 
|
*/  | 
|
protected NodeVector getVector() {  | 
|
NodeVector nv = (m_cache != null) ? m_cache.getVector() : null;  | 
|
return nv;  | 
|
}  | 
|
  /** | 
|
   * Get the cache (if any) of nodes obtained from | 
|
   * the iterator so far. Note that the cache keeps | 
|
   * growing until the iterator is walked to exhaustion, | 
|
   * at which point the cache is "complete". | 
|
*/  | 
|
  private IteratorCache getCache() { | 
|
return m_cache;  | 
|
}  | 
|
  /** | 
|
   * Set the vector where nodes will be cached. | 
|
*/  | 
|
protected void SetVector(NodeVector v)  | 
|
  { | 
|
setObject(v);  | 
|
}  | 
|
  /** | 
|
   * If the iterator needs to cache nodes as they are fetched, | 
|
   * then this method returns true. | 
|
*/  | 
|
public boolean hasCache()  | 
|
  { | 
|
final NodeVector nv = getVector();  | 
|
return (nv != null);  | 
|
}  | 
|
  /** | 
|
   * If this NodeSequence has a cache, and that cache is | 
|
   * fully populated then this method returns true, otherwise | 
|
   * if there is no cache or it is not complete it returns false. | 
|
*/  | 
|
  private boolean cacheComplete() { | 
|
final boolean complete;  | 
|
if (m_cache != null) {  | 
|
complete = m_cache.isComplete();  | 
|
      } else { | 
|
complete = false;  | 
|
}  | 
|
return complete;  | 
|
}  | 
|
  /** | 
|
   * If this NodeSequence has a cache, mark that it is complete. | 
|
   * This method should be called after the iterator is exhausted. | 
|
*/  | 
|
  private void markCacheComplete() { | 
|
NodeVector nv = getVector();  | 
|
if (nv != null) {  | 
|
m_cache.setCacheComplete(true);  | 
|
}  | 
|
}  | 
|
  /** | 
|
   * 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. | 
|
*/  | 
|
private 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 nodeVector | 
|
*/  | 
|
public NodeSequence(Object nodeVector)  | 
|
  { | 
|
super(nodeVector);  | 
|
if (nodeVector instanceof NodeVector) {  | 
|
SetVector((NodeVector) 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. | 
|
*/  | 
|
private NodeSequence(DTMManager dtmMgr)  | 
|
  { | 
|
super(new NodeVector());  | 
|
m_last = 0;  | 
|
m_dtmMgr = dtmMgr;  | 
|
}  | 
|
  /** | 
|
   * Create a new NodeSequence in an invalid (null) state. | 
|
*/  | 
|
public NodeSequence()  | 
|
  { | 
|
return;  | 
|
}  | 
|
  /** | 
|
   * @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 root is DTM.NULL, then something's wrong with the context | 
|
if (nodeHandle == DTM.NULL)  | 
|
        { | 
|
throw new RuntimeException("Unable to evaluate expression using " +  | 
|
                    "this context"); | 
|
}  | 
|
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)  | 
|
    { | 
|
        // There is a cache | 
|
if(m_next < vec.size())  | 
|
        { | 
|
            // The node is in the cache, so just return it. | 
|
int next = vec.elementAt(m_next);  | 
|
m_next++;  | 
|
return next;  | 
|
}  | 
|
else if(cacheComplete() || (-1 != m_last) || (null == m_iter))  | 
|
        { | 
|
m_next++;  | 
|
return DTM.NULL;  | 
|
}  | 
|
}  | 
|
if (null == m_iter)  | 
|
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  | 
|
    { | 
|
// We have exhausted the iterator, and if there is a cache  | 
|
// it must have all nodes in it by now, so let the cache  | 
|
        // know that it is complete. | 
|
markCacheComplete();  | 
|
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() && index < 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)  | 
|
        { | 
|
int oldNode = vec.elementAt(index);  | 
|
if (oldNode != node && m_cache.useCount() > 1) {  | 
|
            /* If we are going to set the node at the given index | 
|
             * to a different value, and the cache is shared | 
|
             * (has a use count greater than 1) | 
|
             * then make a copy of the cache and use it | 
|
             * so we don't overwrite the value for other | 
|
             * users of the cache. | 
|
*/  | 
|
IteratorCache newCache = new IteratorCache();  | 
|
final NodeVector nv;  | 
|
            try { | 
|
nv = (NodeVector) vec.clone();  | 
|
} catch (CloneNotSupportedException e) {  | 
|
                // This should never happen | 
|
e.printStackTrace();  | 
|
RuntimeException rte = new RuntimeException(e.getMessage());  | 
|
throw rte;  | 
|
}  | 
|
newCache.setVector(nv);  | 
|
newCache.setCacheComplete(true);  | 
|
m_cache = newCache;  | 
|
vec = nv;  | 
|
            // Keep our superclass informed of the current NodeVector | 
|
super.setObject(nv);  | 
|
/* When we get to here the new cache has  | 
|
* a use count of 1 and when setting a  | 
|
* bunch of values on the same NodeSequence,  | 
|
* such as when sorting, we will keep setting  | 
|
* values in that same copy which has a use count of 1.  | 
|
*/  | 
|
}  | 
|
vec.setElementAt(node, index);  | 
|
m_last = vec.size();  | 
|
}  | 
|
else  | 
|
m_iter.setItem(node, index);  | 
|
}  | 
|
  /** | 
|
   * @see DTMIterator#getLength() | 
|
*/  | 
|
public int getLength()  | 
|
  { | 
|
IteratorCache cache = getCache();  | 
|
if(cache != null)  | 
|
        { | 
|
        // Nodes from the iterator are cached | 
|
if (cache.isComplete()) {  | 
|
// All of the nodes from the iterator are cached  | 
|
            // so just return the number of nodes in the cache | 
|
NodeVector nv = cache.getVector();  | 
|
return nv.size();  | 
|
}  | 
|
// If this NodeSequence wraps a mutable nodeset, then  | 
|
// m_last will not reflect the size of the nodeset if  | 
|
        // it has been mutated... | 
|
if (m_iter instanceof NodeSetDTM)  | 
|
        { | 
|
return m_iter.getLength();  | 
|
}  | 
|
if(-1 == m_last)  | 
|
                { | 
|
int pos = m_next;  | 
|
runTo(-1);  | 
|
m_next = pos;  | 
|
}  | 
|
return m_last;  | 
|
}  | 
|
else  | 
|
        { | 
|
return (-1 == m_last) ? (m_last = m_iter.getLength()) : m_last;  | 
|
}  | 
|
}  | 
|
  /** | 
|
   * Note: Not a deep clone. | 
|
   * @see DTMIterator#cloneWithReset() | 
|
*/  | 
|
public DTMIterator cloneWithReset() throws CloneNotSupportedException  | 
|
  { | 
|
NodeSequence seq = (NodeSequence)super.clone();  | 
|
seq.m_next = 0;  | 
|
if (m_cache != null) {  | 
|
// In making this clone of an iterator we are making  | 
|
// another NodeSequence object it has a reference  | 
|
// to the same IteratorCache object as the original  | 
|
// so we need to remember that more than one  | 
|
        // NodeSequence object shares the cache. | 
|
m_cache.increaseUseCount();  | 
|
}  | 
|
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  | 
|
  { | 
|
NodeSequence clone = (NodeSequence) super.clone();  | 
|
if (null != m_iter) clone.m_iter = (DTMIterator) m_iter.clone();  | 
|
if (m_cache != null) {  | 
|
// In making this clone of an iterator we are making  | 
|
// another NodeSequence object it has a reference  | 
|
// to the same IteratorCache object as the original  | 
|
// so we need to remember that more than one  | 
|
              // NodeSequence object shares the cache. | 
|
m_cache.increaseUseCount();  | 
|
}  | 
|
return 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 org.apache.xpath.Expression#fixupVariables(Vector, int) | 
|
*/  | 
|
public void fixupVariables(List<QName> vars, int globalsSize)  | 
|
  { | 
|
super.fixupVariables(vars, globalsSize);  | 
|
}  | 
|
  /** | 
|
   * Add the node into a vector of nodes where it should occur in | 
|
   * document order. | 
|
   * @param node The node to be added. | 
|
   * @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(List<QName> v, Object obj)  | 
|
   /** | 
|
    * It used to be that many locations in the code simply | 
|
    * did an assignment to this.m_obj directly, rather than | 
|
    * calling the setObject(Object) method. The problem is | 
|
    * that our super-class would be updated on what the | 
|
    * cache associated with this NodeSequence, but | 
|
    * we wouldn't know ourselves. | 
|
    * <p> | 
|
    * All setting of m_obj is done through setObject() now, | 
|
    * and this method over-rides the super-class method. | 
|
    * So now we are in the loop have an opportunity | 
|
    * to update some caching information. | 
|
    * | 
|
*/  | 
|
protected void setObject(Object obj) {  | 
|
if (obj instanceof NodeVector) {  | 
|
// Keep our superclass informed of the current NodeVector  | 
|
           // ... if we don't the smoketest fails (don't know why). | 
|
super.setObject(obj);  | 
|
           // A copy of the code of what SetVector() would do. | 
|
NodeVector v = (NodeVector)obj;  | 
|
if (m_cache != null) {  | 
|
m_cache.setVector(v);  | 
|
} else if (v!=null) {  | 
|
m_cache = new IteratorCache();  | 
|
m_cache.setVector(v);  | 
|
}  | 
|
} else if (obj instanceof IteratorCache) {  | 
|
IteratorCache cache = (IteratorCache) obj;  | 
|
m_cache = cache;  | 
|
m_cache.increaseUseCount();  | 
|
           // Keep our superclass informed of the current NodeVector | 
|
super.setObject(cache.getVector());  | 
|
       } else { | 
|
super.setObject(obj);  | 
|
}  | 
|
}  | 
|
   /** | 
|
    * Each NodeSequence object has an iterator which is "walked". | 
|
    * As an iterator is walked one obtains nodes from it. | 
|
    * As those nodes are obtained they may be cached, making | 
|
    * the next walking of a copy or clone of the iterator faster. | 
|
    * This field (m_cache) is a reference to such a cache, | 
|
    * which is populated as the iterator is walked. | 
|
    * <p> | 
|
    * Note that multiple NodeSequence objects may hold a | 
|
    * reference to the same cache, and also | 
|
    * (and this is important) the same iterator. | 
|
    * The iterator and its cache may be shared among | 
|
    * many NodeSequence objects. | 
|
    * <p> | 
|
    * If one of the NodeSequence objects walks ahead | 
|
    * of the others it fills in the cache. | 
|
    * As the others NodeSequence objects catch up they | 
|
    * get their values from | 
|
    * the cache rather than the iterator itself, so | 
|
    * the iterator is only ever walked once and everyone | 
|
    * benefits from the cache. | 
|
    * <p> | 
|
    * At some point the cache may be | 
|
    * complete due to walking to the end of one of | 
|
    * the copies of the iterator, and the cache is | 
|
    * then marked as "complete". | 
|
    * and the cache will have no more nodes added to it. | 
|
    * <p> | 
|
    * Its use-count is the number of NodeSequence objects that use it. | 
|
*/  | 
|
   private final static class IteratorCache { | 
|
       /** | 
|
        * A list of nodes already obtained from the iterator. | 
|
        * As the iterator is walked the nodes obtained from | 
|
        * it are appended to this list. | 
|
        * <p> | 
|
        * Both an iterator and its corresponding cache can | 
|
        * be shared by multiple NodeSequence objects. | 
|
        * <p> | 
|
        * For example, consider three NodeSequence objects | 
|
        * ns1, ns2 and ns3 doing such sharing, and the | 
|
        * nodes to be obtaind from the iterator being | 
|
        * the sequence { 33, 11, 44, 22, 55 }. | 
|
        * <p> | 
|
        * If ns3.nextNode() is called 3 times the the | 
|
        * underlying iterator will have walked through | 
|
        * 33, 11, 55 and these three nodes will have been put | 
|
        * in the cache. | 
|
        * <p> | 
|
        * If ns2.nextNode() is called 2 times it will return | 
|
        * 33 and 11 from the cache, leaving the iterator alone. | 
|
        * <p> | 
|
        * If ns1.nextNode() is called 6 times it will return | 
|
        * 33 and 11 from the cache, then get 44, 22, 55 from | 
|
        * the iterator, and appending 44, 22, 55 to the cache. | 
|
        * On the sixth call it is found that the iterator is | 
|
        * exhausted and the cache is marked complete. | 
|
        * <p> | 
|
        * Should ns2 or ns3 have nextNode() called they will | 
|
        * know that the cache is complete, and they will | 
|
        * obtain all subsequent nodes from the cache. | 
|
        * <p> | 
|
        * Note that the underlying iterator, though shared | 
|
        * is only ever walked once. | 
|
*/  | 
|
private NodeVector m_vec2;  | 
|
        /** | 
|
         * true if the associated iterator is exhausted and | 
|
         * all nodes obtained from it are in the cache. | 
|
*/  | 
|
private boolean m_isComplete2;  | 
|
private int m_useCount2;  | 
|
        IteratorCache() { | 
|
m_vec2 = null;  | 
|
m_isComplete2 = false;  | 
|
m_useCount2 = 1;  | 
|
return;  | 
|
}  | 
|
        /** | 
|
         * Returns count of how many NodeSequence objects share this | 
|
         * IteratorCache object. | 
|
*/  | 
|
        private int useCount() { | 
|
return m_useCount2;  | 
|
}  | 
|
        /** | 
|
         * This method is called when yet another | 
|
         * NodeSequence object uses, or shares | 
|
         * this same cache. | 
|
         * | 
|
*/  | 
|
        private void increaseUseCount() { | 
|
if (m_vec2 != null)  | 
|
m_useCount2++;  | 
|
}  | 
|
        /** | 
|
         * Sets the NodeVector that holds the | 
|
         * growing list of nodes as they are appended | 
|
         * to the cached list. | 
|
*/  | 
|
private void setVector(NodeVector nv) {  | 
|
m_vec2 = nv;  | 
|
m_useCount2 = 1;  | 
|
}  | 
|
        /** | 
|
         * Get the cached list of nodes obtained from | 
|
         * the iterator so far. | 
|
*/  | 
|
private NodeVector getVector() {  | 
|
return m_vec2;  | 
|
}  | 
|
        /** | 
|
         * Call this method with 'true' if the | 
|
         * iterator is exhausted and the cached list | 
|
         * is complete, or no longer growing. | 
|
*/  | 
|
        private void setCacheComplete(boolean b) { | 
|
m_isComplete2 = b;  | 
|
}  | 
|
        /** | 
|
         * Returns true if no cache is complete | 
|
         * and immutable. | 
|
*/  | 
|
        private boolean isComplete() { | 
|
return m_isComplete2;  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Get the cached list of nodes appended with | 
|
     * values obtained from the iterator as | 
|
     * a NodeSequence is walked when its | 
|
     * nextNode() method is called. | 
|
*/  | 
|
    protected IteratorCache getIteratorCache() { | 
|
return m_cache;  | 
|
}  | 
|
}  |