Home | History | Annotate | Download | only in xpath
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one
      3  * or more contributor license agreements. See the NOTICE file
      4  * distributed with this work for additional information
      5  * regarding copyright ownership. The ASF licenses this file
      6  * to you under the Apache License, Version 2.0 (the  "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *     http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 /*
     19  * $Id: NodeSet.java 468655 2006-10-28 07:12:06Z minchau $
     20  */
     21 package org.apache.xpath;
     22 
     23 import org.apache.xalan.res.XSLMessages;
     24 import org.apache.xml.utils.DOM2Helper;
     25 import org.apache.xpath.axes.ContextNodeList;
     26 import org.apache.xpath.res.XPATHErrorResources;
     27 
     28 import org.w3c.dom.DOMException;
     29 import org.w3c.dom.Node;
     30 import org.w3c.dom.NodeList;
     31 import org.w3c.dom.traversal.NodeFilter;
     32 import org.w3c.dom.traversal.NodeIterator;
     33 
     34 
     35 /**
     36  * <p>The NodeSet class can act as either a NodeVector,
     37  * NodeList, or NodeIterator.  However, in order for it to
     38  * act as a NodeVector or NodeList, it's required that
     39  * setShouldCacheNodes(true) be called before the first
     40  * nextNode() is called, in order that nodes can be added
     41  * as they are fetched.  Derived classes that implement iterators
     42  * must override runTo(int index), in order that they may
     43  * run the iteration to the given index. </p>
     44  *
     45  * <p>Note that we directly implement the DOM's NodeIterator
     46  * interface. We do not emulate all the behavior of the
     47  * standard NodeIterator. In particular, we do not guarantee
     48  * to present a "live view" of the document ... but in XSLT,
     49  * the source document should never be mutated, so this should
     50  * never be an issue.</p>
     51  *
     52  * <p>Thought: Should NodeSet really implement NodeList and NodeIterator,
     53  * or should there be specific subclasses of it which do so? The
     54  * advantage of doing it all here is that all NodeSets will respond
     55  * to the same calls; the disadvantage is that some of them may return
     56  * less-than-enlightening results when you do so.</p>
     57  * @xsl.usage advanced
     58  */
     59 public class NodeSet
     60         implements NodeList, NodeIterator, Cloneable, ContextNodeList
     61 {
     62 
     63   /**
     64    * Create an empty nodelist.
     65    */
     66   public NodeSet()
     67   {
     68     m_blocksize = 32;
     69     m_mapSize = 0;
     70   }
     71 
     72   /**
     73    * Create an empty, using the given block size.
     74    *
     75    * @param blocksize Size of blocks to allocate
     76    */
     77   public NodeSet(int blocksize)
     78   {
     79     m_blocksize = blocksize;
     80     m_mapSize = 0;
     81   }
     82 
     83   /**
     84    * Create a NodeSet, and copy the members of the
     85    * given nodelist into it.
     86    *
     87    * @param nodelist List of Nodes to be made members of the new set.
     88    */
     89   public NodeSet(NodeList nodelist)
     90   {
     91 
     92     this(32);
     93 
     94     addNodes(nodelist);
     95   }
     96 
     97   /**
     98    * Create a NodeSet, and copy the members of the
     99    * given NodeSet into it.
    100    *
    101    * @param nodelist Set of Nodes to be made members of the new set.
    102    */
    103   public NodeSet(NodeSet nodelist)
    104   {
    105 
    106     this(32);
    107 
    108     addNodes((NodeIterator) nodelist);
    109   }
    110 
    111   /**
    112    * Create a NodeSet, and copy the members of the
    113    * given NodeIterator into it.
    114    *
    115    * @param ni Iterator which yields Nodes to be made members of the new set.
    116    */
    117   public NodeSet(NodeIterator ni)
    118   {
    119 
    120     this(32);
    121 
    122     addNodes(ni);
    123   }
    124 
    125   /**
    126    * Create a NodeSet which contains the given Node.
    127    *
    128    * @param node Single node to be added to the new set.
    129    */
    130   public NodeSet(Node node)
    131   {
    132 
    133     this(32);
    134 
    135     addNode(node);
    136   }
    137 
    138   /**
    139    * @return The root node of the Iterator, as specified when it was created.
    140    * For non-Iterator NodeSets, this will be null.
    141    */
    142   public Node getRoot()
    143   {
    144     return null;
    145   }
    146 
    147   /**
    148    * Get a cloned Iterator, and reset its state to the beginning of the
    149    * iteration.
    150    *
    151    * @return a new NodeSet of the same type, having the same state...
    152    * except that the reset() operation has been called.
    153    *
    154    * @throws CloneNotSupportedException if this subclass of NodeSet
    155    * does not support the clone() operation.
    156    */
    157   public NodeIterator cloneWithReset() throws CloneNotSupportedException
    158   {
    159 
    160     NodeSet clone = (NodeSet) clone();
    161 
    162     clone.reset();
    163 
    164     return clone;
    165   }
    166 
    167   /**
    168    * Reset the iterator. May have no effect on non-iterator Nodesets.
    169    */
    170   public void reset()
    171   {
    172     m_next = 0;
    173   }
    174 
    175   /**
    176    *  This attribute determines which node types are presented via the
    177    * iterator. The available set of constants is defined in the
    178    * <code>NodeFilter</code> interface. For NodeSets, the mask has been
    179    * hardcoded to show all nodes except EntityReference nodes, which have
    180    * no equivalent in the XPath data model.
    181    *
    182    * @return integer used as a bit-array, containing flags defined in
    183    * the DOM's NodeFilter class. The value will be
    184    * <code>SHOW_ALL & ~SHOW_ENTITY_REFERENCE</code>, meaning that
    185    * only entity references are suppressed.
    186    */
    187   public int getWhatToShow()
    188   {
    189     return NodeFilter.SHOW_ALL & ~NodeFilter.SHOW_ENTITY_REFERENCE;
    190   }
    191 
    192   /**
    193    * The filter object used to screen nodes. Filters are applied to
    194    * further reduce (and restructure) the NodeIterator's view of the
    195    * document. In our case, we will be using hardcoded filters built
    196    * into our iterators... but getFilter() is part of the DOM's
    197    * NodeIterator interface, so we have to support it.
    198    *
    199    * @return null, which is slightly misleading. True, there is no
    200    * user-written filter object, but in fact we are doing some very
    201    * sophisticated custom filtering. A DOM purist might suggest
    202    * returning a placeholder object just to indicate that this is
    203    * not going to return all nodes selected by whatToShow.
    204    */
    205   public NodeFilter getFilter()
    206   {
    207     return null;
    208   }
    209 
    210   /**
    211    *  The value of this flag determines whether the children of entity
    212    * reference nodes are visible to the iterator. If false, they will be
    213    * skipped over.
    214    * <br> To produce a view of the document that has entity references
    215    * expanded and does not expose the entity reference node itself, use the
    216    * whatToShow flags to hide the entity reference node and set
    217    * expandEntityReferences to true when creating the iterator. To produce
    218    * a view of the document that has entity reference nodes but no entity
    219    * expansion, use the whatToShow flags to show the entity reference node
    220    * and set expandEntityReferences to false.
    221    *
    222    * @return true for all iterators based on NodeSet, meaning that the
    223    * contents of EntityRefrence nodes may be returned (though whatToShow
    224    * says that the EntityReferences themselves are not shown.)
    225    */
    226   public boolean getExpandEntityReferences()
    227   {
    228     return true;
    229   }
    230 
    231   /**
    232    *  Returns the next node in the set and advances the position of the
    233    * iterator in the set. After a NodeIterator is created, the first call
    234    * to nextNode() returns the first node in the set.
    235    * @return  The next <code>Node</code> in the set being iterated over, or
    236    *   <code>null</code> if there are no more members in that set.
    237    * @throws DOMException
    238    *    INVALID_STATE_ERR: Raised if this method is called after the
    239    *   <code>detach</code> method was invoked.
    240    */
    241   public Node nextNode() throws DOMException
    242   {
    243 
    244     if ((m_next) < this.size())
    245     {
    246       Node next = this.elementAt(m_next);
    247 
    248       m_next++;
    249 
    250       return next;
    251     }
    252     else
    253       return null;
    254   }
    255 
    256   /**
    257    *  Returns the previous node in the set and moves the position of the
    258    * iterator backwards in the set.
    259    * @return  The previous <code>Node</code> in the set being iterated over,
    260    *   or<code>null</code> if there are no more members in that set.
    261    * @throws DOMException
    262    *    INVALID_STATE_ERR: Raised if this method is called after the
    263    *   <code>detach</code> method was invoked.
    264    * @throws RuntimeException thrown if this NodeSet is not of
    265    * a cached type, and hence doesn't know what the previous node was.
    266    */
    267   public Node previousNode() throws DOMException
    268   {
    269 
    270     if (!m_cacheNodes)
    271       throw new RuntimeException(
    272         XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_CANNOT_ITERATE, null)); //"This NodeSet can not iterate to a previous node!");
    273 
    274     if ((m_next - 1) > 0)
    275     {
    276       m_next--;
    277 
    278       return this.elementAt(m_next);
    279     }
    280     else
    281       return null;
    282   }
    283 
    284   /**
    285    * Detaches the iterator from the set which it iterated over, releasing
    286    * any computational resources and placing the iterator in the INVALID
    287    * state. After<code>detach</code> has been invoked, calls to
    288    * <code>nextNode</code> or<code>previousNode</code> will raise the
    289    * exception INVALID_STATE_ERR.
    290    * <p>
    291    * This operation is a no-op in NodeSet, and will not cause
    292    * INVALID_STATE_ERR to be raised by later operations.
    293    * </p>
    294    */
    295   public void detach(){}
    296 
    297   /**
    298    * Tells if this NodeSet is "fresh", in other words, if
    299    * the first nextNode() that is called will return the
    300    * first node in the set.
    301    *
    302    * @return true if nextNode() would return the first node in the set,
    303    * false if it would return a later one.
    304    */
    305   public boolean isFresh()
    306   {
    307     return (m_next == 0);
    308   }
    309 
    310   /**
    311    * If an index is requested, NodeSet will call this method
    312    * to run the iterator to the index.  By default this sets
    313    * m_next to the index.  If the index argument is -1, this
    314    * signals that the iterator should be run to the end.
    315    *
    316    * @param index Position to advance (or retreat) to, with
    317    * 0 requesting the reset ("fresh") position and -1 (or indeed
    318    * any out-of-bounds value) requesting the final position.
    319    * @throws RuntimeException thrown if this NodeSet is not
    320    * one of the types which supports indexing/counting.
    321    */
    322   public void runTo(int index)
    323   {
    324 
    325     if (!m_cacheNodes)
    326       throw new RuntimeException(
    327         XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_CANNOT_INDEX, null)); //"This NodeSet can not do indexing or counting functions!");
    328 
    329     if ((index >= 0) && (m_next < m_firstFree))
    330       m_next = index;
    331     else
    332       m_next = m_firstFree - 1;
    333   }
    334 
    335   /**
    336    * Returns the <code>index</code>th item in the collection. If
    337    * <code>index</code> is greater than or equal to the number of nodes in
    338    * the list, this returns <code>null</code>.
    339    *
    340    * TODO: What happens if index is out of range?
    341    *
    342    * @param index Index into the collection.
    343    * @return The node at the <code>index</code>th position in the
    344    *   <code>NodeList</code>, or <code>null</code> if that is not a valid
    345    *   index.
    346    */
    347   public Node item(int index)
    348   {
    349 
    350     runTo(index);
    351 
    352     return (Node) this.elementAt(index);
    353   }
    354 
    355   /**
    356    * The number of nodes in the list. The range of valid child node indices is
    357    * 0 to <code>length-1</code> inclusive. Note that this operation requires
    358    * finding all the matching nodes, which may defeat attempts to defer
    359    * that work.
    360    *
    361    * @return integer indicating how many nodes are represented by this list.
    362    */
    363   public int getLength()
    364   {
    365 
    366     runTo(-1);
    367 
    368     return this.size();
    369   }
    370 
    371   /**
    372    * Add a node to the NodeSet. Not all types of NodeSets support this
    373    * operation
    374    *
    375    * @param n Node to be added
    376    * @throws RuntimeException thrown if this NodeSet is not of
    377    * a mutable type.
    378    */
    379   public void addNode(Node n)
    380   {
    381 
    382     if (!m_mutable)
    383       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    384 
    385     this.addElement(n);
    386   }
    387 
    388   /**
    389    * Insert a node at a given position.
    390    *
    391    * @param n Node to be added
    392    * @param pos Offset at which the node is to be inserted,
    393    * with 0 being the first position.
    394    * @throws RuntimeException thrown if this NodeSet is not of
    395    * a mutable type.
    396    */
    397   public void insertNode(Node n, int pos)
    398   {
    399 
    400     if (!m_mutable)
    401       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    402 
    403     insertElementAt(n, pos);
    404   }
    405 
    406   /**
    407    * Remove a node.
    408    *
    409    * @param n Node to be added
    410    * @throws RuntimeException thrown if this NodeSet is not of
    411    * a mutable type.
    412    */
    413   public void removeNode(Node n)
    414   {
    415 
    416     if (!m_mutable)
    417       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    418 
    419     this.removeElement(n);
    420   }
    421 
    422   /**
    423    * Copy NodeList members into this nodelist, adding in
    424    * document order.  If a node is null, don't add it.
    425    *
    426    * @param nodelist List of nodes which should now be referenced by
    427    * this NodeSet.
    428    * @throws RuntimeException thrown if this NodeSet is not of
    429    * a mutable type.
    430    */
    431   public void addNodes(NodeList nodelist)
    432   {
    433 
    434     if (!m_mutable)
    435       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    436 
    437     if (null != nodelist)  // defensive to fix a bug that Sanjiva reported.
    438     {
    439       int nChildren = nodelist.getLength();
    440 
    441       for (int i = 0; i < nChildren; i++)
    442       {
    443         Node obj = nodelist.item(i);
    444 
    445         if (null != obj)
    446         {
    447           addElement(obj);
    448         }
    449       }
    450     }
    451 
    452     // checkDups();
    453   }
    454 
    455   /**
    456    * <p>Copy NodeList members into this nodelist, adding in
    457    * document order.  Only genuine node references will be copied;
    458    * nulls appearing in the source NodeSet will
    459    * not be added to this one. </p>
    460    *
    461    * <p> In case you're wondering why this function is needed: NodeSet
    462    * implements both NodeIterator and NodeList. If this method isn't
    463    * provided, Java can't decide which of those to use when addNodes()
    464    * is invoked. Providing the more-explicit match avoids that
    465    * ambiguity.)</p>
    466    *
    467    * @param ns NodeSet whose members should be merged into this NodeSet.
    468    * @throws RuntimeException thrown if this NodeSet is not of
    469    * a mutable type.
    470    */
    471   public void addNodes(NodeSet ns)
    472   {
    473 
    474     if (!m_mutable)
    475       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    476 
    477     addNodes((NodeIterator) ns);
    478   }
    479 
    480   /**
    481    * Copy NodeList members into this nodelist, adding in
    482    * document order.  Null references are not added.
    483    *
    484    * @param iterator NodeIterator which yields the nodes to be added.
    485    * @throws RuntimeException thrown if this NodeSet is not of
    486    * a mutable type.
    487    */
    488   public void addNodes(NodeIterator iterator)
    489   {
    490 
    491     if (!m_mutable)
    492       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    493 
    494     if (null != iterator)  // defensive to fix a bug that Sanjiva reported.
    495     {
    496       Node obj;
    497 
    498       while (null != (obj = iterator.nextNode()))
    499       {
    500         addElement(obj);
    501       }
    502     }
    503 
    504     // checkDups();
    505   }
    506 
    507   /**
    508    * Copy NodeList members into this nodelist, adding in
    509    * document order.  If a node is null, don't add it.
    510    *
    511    * @param nodelist List of nodes to be added
    512    * @param support The XPath runtime context.
    513    * @throws RuntimeException thrown if this NodeSet is not of
    514    * a mutable type.
    515    */
    516   public void addNodesInDocOrder(NodeList nodelist, XPathContext support)
    517   {
    518 
    519     if (!m_mutable)
    520       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    521 
    522     int nChildren = nodelist.getLength();
    523 
    524     for (int i = 0; i < nChildren; i++)
    525     {
    526       Node node = nodelist.item(i);
    527 
    528       if (null != node)
    529       {
    530         addNodeInDocOrder(node, support);
    531       }
    532     }
    533   }
    534 
    535   /**
    536    * Copy NodeList members into this nodelist, adding in
    537    * document order.  If a node is null, don't add it.
    538    *
    539    * @param iterator NodeIterator which yields the nodes to be added.
    540    * @param support The XPath runtime context.
    541    * @throws RuntimeException thrown if this NodeSet is not of
    542    * a mutable type.
    543    */
    544   public void addNodesInDocOrder(NodeIterator iterator, XPathContext support)
    545   {
    546 
    547     if (!m_mutable)
    548       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    549 
    550     Node node;
    551 
    552     while (null != (node = iterator.nextNode()))
    553     {
    554       addNodeInDocOrder(node, support);
    555     }
    556   }
    557 
    558   /**
    559    * Add the node list to this node set in document order.
    560    *
    561    * @param start index.
    562    * @param end index.
    563    * @param testIndex index.
    564    * @param nodelist The nodelist to add.
    565    * @param support The XPath runtime context.
    566    *
    567    * @return false always.
    568    * @throws RuntimeException thrown if this NodeSet is not of
    569    * a mutable type.
    570    */
    571   private boolean addNodesInDocOrder(int start, int end, int testIndex,
    572                                      NodeList nodelist, XPathContext support)
    573   {
    574 
    575     if (!m_mutable)
    576       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    577 
    578     boolean foundit = false;
    579     int i;
    580     Node node = nodelist.item(testIndex);
    581 
    582     for (i = end; i >= start; i--)
    583     {
    584       Node child = (Node) elementAt(i);
    585 
    586       if (child == node)
    587       {
    588         i = -2;  // Duplicate, suppress insert
    589 
    590         break;
    591       }
    592 
    593       if (!DOM2Helper.isNodeAfter(node, child))
    594       {
    595         insertElementAt(node, i + 1);
    596 
    597         testIndex--;
    598 
    599         if (testIndex > 0)
    600         {
    601           boolean foundPrev = addNodesInDocOrder(0, i, testIndex, nodelist,
    602                                                  support);
    603 
    604           if (!foundPrev)
    605           {
    606             addNodesInDocOrder(i, size() - 1, testIndex, nodelist, support);
    607           }
    608         }
    609 
    610         break;
    611       }
    612     }
    613 
    614     if (i == -1)
    615     {
    616       insertElementAt(node, 0);
    617     }
    618 
    619     return foundit;
    620   }
    621 
    622   /**
    623    * Add the node into a vector of nodes where it should occur in
    624    * document order.
    625    * @param node The node to be added.
    626    * @param test true if we should test for doc order
    627    * @param support The XPath runtime context.
    628    * @return insertIndex.
    629    * @throws RuntimeException thrown if this NodeSet is not of
    630    * a mutable type.
    631    */
    632   public int addNodeInDocOrder(Node node, boolean test, XPathContext support)
    633   {
    634 
    635     if (!m_mutable)
    636       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    637 
    638     int insertIndex = -1;
    639 
    640     if (test)
    641     {
    642 
    643       // This needs to do a binary search, but a binary search
    644       // is somewhat tough because the sequence test involves
    645       // two nodes.
    646       int size = size(), i;
    647 
    648       for (i = size - 1; i >= 0; i--)
    649       {
    650         Node child = (Node) elementAt(i);
    651 
    652         if (child == node)
    653         {
    654           i = -2;  // Duplicate, suppress insert
    655 
    656           break;
    657         }
    658 
    659         if (!DOM2Helper.isNodeAfter(node, child))
    660         {
    661           break;
    662         }
    663       }
    664 
    665       if (i != -2)
    666       {
    667         insertIndex = i + 1;
    668 
    669         insertElementAt(node, insertIndex);
    670       }
    671     }
    672     else
    673     {
    674       insertIndex = this.size();
    675 
    676       boolean foundit = false;
    677 
    678       for (int i = 0; i < insertIndex; i++)
    679       {
    680         if (this.item(i).equals(node))
    681         {
    682           foundit = true;
    683 
    684           break;
    685         }
    686       }
    687 
    688       if (!foundit)
    689         addElement(node);
    690     }
    691 
    692     // checkDups();
    693     return insertIndex;
    694   }  // end addNodeInDocOrder(Vector v, Object obj)
    695 
    696   /**
    697    * Add the node into a vector of nodes where it should occur in
    698    * document order.
    699    * @param node The node to be added.
    700    * @param support The XPath runtime context.
    701    *
    702    * @return The index where it was inserted.
    703    * @throws RuntimeException thrown if this NodeSet is not of
    704    * a mutable type.
    705    */
    706   public int addNodeInDocOrder(Node node, XPathContext support)
    707   {
    708 
    709     if (!m_mutable)
    710       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    711 
    712     return addNodeInDocOrder(node, true, support);
    713   }  // end addNodeInDocOrder(Vector v, Object obj)
    714 
    715 
    716   /** If this node is being used as an iterator, the next index that nextNode()
    717    *  will return.  */
    718   transient protected int m_next = 0;
    719 
    720   /**
    721    * Get the current position, which is one less than
    722    * the next nextNode() call will retrieve.  i.e. if
    723    * you call getCurrentPos() and the return is 0, the next
    724    * fetch will take place at index 1.
    725    *
    726    * @return The the current position index.
    727    */
    728   public int getCurrentPos()
    729   {
    730     return m_next;
    731   }
    732 
    733   /**
    734    * Set the current position in the node set.
    735    * @param i Must be a valid index.
    736    * @throws RuntimeException thrown if this NodeSet is not of
    737    * a cached type, and thus doesn't permit indexed access.
    738    */
    739   public void setCurrentPos(int i)
    740   {
    741 
    742     if (!m_cacheNodes)
    743       throw new RuntimeException(
    744         XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_CANNOT_INDEX, null)); //"This NodeSet can not do indexing or counting functions!");
    745 
    746     m_next = i;
    747   }
    748 
    749   /**
    750    * Return the last fetched node.  Needed to support the UnionPathIterator.
    751    *
    752    * @return the last fetched node.
    753    * @throws RuntimeException thrown if this NodeSet is not of
    754    * a cached type, and thus doesn't permit indexed access.
    755    */
    756   public Node getCurrentNode()
    757   {
    758 
    759     if (!m_cacheNodes)
    760       throw new RuntimeException(
    761         XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_CANNOT_INDEX, null)); //"This NodeSet can not do indexing or counting functions!");
    762 
    763     int saved = m_next;
    764     Node n = (m_next < m_firstFree) ? elementAt(m_next) : null;
    765     m_next = saved; // HACK: I think this is a bit of a hack.  -sb
    766     return n;
    767   }
    768 
    769   /** True if this list can be mutated.  */
    770   transient protected boolean m_mutable = true;
    771 
    772   /** True if this list is cached.
    773    *  @serial  */
    774   transient protected boolean m_cacheNodes = true;
    775 
    776   /**
    777    * Get whether or not this is a cached node set.
    778    *
    779    *
    780    * @return True if this list is cached.
    781    */
    782   public boolean getShouldCacheNodes()
    783   {
    784     return m_cacheNodes;
    785   }
    786 
    787   /**
    788    * If setShouldCacheNodes(true) is called, then nodes will
    789    * be cached.  They are not cached by default. This switch must
    790    * be set before the first call to nextNode is made, to ensure
    791    * that all nodes are cached.
    792    *
    793    * @param b true if this node set should be cached.
    794    * @throws RuntimeException thrown if an attempt is made to
    795    * request caching after we've already begun stepping through the
    796    * nodes in this set.
    797   */
    798   public void setShouldCacheNodes(boolean b)
    799   {
    800 
    801     if (!isFresh())
    802       throw new RuntimeException(
    803         XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANNOT_CALL_SETSHOULDCACHENODE, null)); //"Can not call setShouldCacheNodes after nextNode has been called!");
    804 
    805     m_cacheNodes = b;
    806     m_mutable = true;
    807   }
    808 
    809 
    810   transient private int m_last = 0;
    811 
    812   public int getLast()
    813   {
    814     return m_last;
    815   }
    816 
    817   public void setLast(int last)
    818   {
    819     m_last = last;
    820   }
    821 
    822   /** Size of blocks to allocate.
    823    *  @serial          */
    824   private int m_blocksize;
    825 
    826   /** Array of nodes this points to.
    827    *  @serial          */
    828   Node m_map[];
    829 
    830   /** Number of nodes in this NodeVector.
    831    *  @serial          */
    832   protected int m_firstFree = 0;
    833 
    834   /** Size of the array this points to.
    835    *  @serial           */
    836   private int m_mapSize;  // lazy initialization
    837 
    838   /**
    839    * Get a cloned LocPathIterator.
    840    *
    841    * @return A clone of this
    842    *
    843    * @throws CloneNotSupportedException
    844    */
    845   public Object clone() throws CloneNotSupportedException
    846   {
    847 
    848     NodeSet clone = (NodeSet) super.clone();
    849 
    850     if ((null != this.m_map) && (this.m_map == clone.m_map))
    851     {
    852       clone.m_map = new Node[this.m_map.length];
    853 
    854       System.arraycopy(this.m_map, 0, clone.m_map, 0, this.m_map.length);
    855     }
    856 
    857     return clone;
    858   }
    859 
    860   /**
    861    * Get the length of the list.
    862    *
    863    * @return Number of nodes in this NodeVector
    864    */
    865   public int size()
    866   {
    867     return m_firstFree;
    868   }
    869 
    870   /**
    871    * Append a Node onto the vector.
    872    *
    873    * @param value Node to add to the vector
    874    */
    875   public void addElement(Node value)
    876   {
    877     if (!m_mutable)
    878       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
    879 
    880     if ((m_firstFree + 1) >= m_mapSize)
    881     {
    882       if (null == m_map)
    883       {
    884         m_map = new Node[m_blocksize];
    885         m_mapSize = m_blocksize;
    886       }
    887       else
    888       {
    889         m_mapSize += m_blocksize;
    890 
    891         Node newMap[] = new Node[m_mapSize];
    892 
    893         System.arraycopy(m_map, 0, newMap, 0, m_firstFree + 1);
    894 
    895         m_map = newMap;
    896       }
    897     }
    898 
    899     m_map[m_firstFree] = value;
    900 
    901     m_firstFree++;
    902   }
    903 
    904   /**
    905    * Append a Node onto the vector.
    906    *
    907    * @param value Node to add to the vector
    908    */
    909   public final void push(Node value)
    910   {
    911 
    912     int ff = m_firstFree;
    913 
    914     if ((ff + 1) >= m_mapSize)
    915     {
    916       if (null == m_map)
    917       {
    918         m_map = new Node[m_blocksize];
    919         m_mapSize = m_blocksize;
    920       }
    921       else
    922       {
    923         m_mapSize += m_blocksize;
    924 
    925         Node newMap[] = new Node[m_mapSize];
    926 
    927         System.arraycopy(m_map, 0, newMap, 0, ff + 1);
    928 
    929         m_map = newMap;
    930       }
    931     }
    932 
    933     m_map[ff] = value;
    934 
    935     ff++;
    936 
    937     m_firstFree = ff;
    938   }
    939 
    940   /**
    941    * Pop a node from the tail of the vector and return the result.
    942    *
    943    * @return the node at the tail of the vector
    944    */
    945   public final Node pop()
    946   {
    947 
    948     m_firstFree--;
    949 
    950     Node n = m_map[m_firstFree];
    951 
    952     m_map[m_firstFree] = null;
    953 
    954     return n;
    955   }
    956 
    957   /**
    958    * Pop a node from the tail of the vector and return the
    959    * top of the stack after the pop.
    960    *
    961    * @return The top of the stack after it's been popped
    962    */
    963   public final Node popAndTop()
    964   {
    965 
    966     m_firstFree--;
    967 
    968     m_map[m_firstFree] = null;
    969 
    970     return (m_firstFree == 0) ? null : m_map[m_firstFree - 1];
    971   }
    972 
    973   /**
    974    * Pop a node from the tail of the vector.
    975    */
    976   public final void popQuick()
    977   {
    978 
    979     m_firstFree--;
    980 
    981     m_map[m_firstFree] = null;
    982   }
    983 
    984   /**
    985    * Return the node at the top of the stack without popping the stack.
    986    * Special purpose method for TransformerImpl, pushElemTemplateElement.
    987    * Performance critical.
    988    *
    989    * @return Node at the top of the stack or null if stack is empty.
    990    */
    991   public final Node peepOrNull()
    992   {
    993     return ((null != m_map) && (m_firstFree > 0))
    994            ? m_map[m_firstFree - 1] : null;
    995   }
    996 
    997   /**
    998    * Push a pair of nodes into the stack.
    999    * Special purpose method for TransformerImpl, pushElemTemplateElement.
   1000    * Performance critical.
   1001    *
   1002    * @param v1 First node to add to vector
   1003    * @param v2 Second node to add to vector
   1004    */
   1005   public final void pushPair(Node v1, Node v2)
   1006   {
   1007 
   1008     if (null == m_map)
   1009     {
   1010       m_map = new Node[m_blocksize];
   1011       m_mapSize = m_blocksize;
   1012     }
   1013     else
   1014     {
   1015       if ((m_firstFree + 2) >= m_mapSize)
   1016       {
   1017         m_mapSize += m_blocksize;
   1018 
   1019         Node newMap[] = new Node[m_mapSize];
   1020 
   1021         System.arraycopy(m_map, 0, newMap, 0, m_firstFree);
   1022 
   1023         m_map = newMap;
   1024       }
   1025     }
   1026 
   1027     m_map[m_firstFree] = v1;
   1028     m_map[m_firstFree + 1] = v2;
   1029     m_firstFree += 2;
   1030   }
   1031 
   1032   /**
   1033    * Pop a pair of nodes from the tail of the stack.
   1034    * Special purpose method for TransformerImpl, pushElemTemplateElement.
   1035    * Performance critical.
   1036    */
   1037   public final void popPair()
   1038   {
   1039 
   1040     m_firstFree -= 2;
   1041     m_map[m_firstFree] = null;
   1042     m_map[m_firstFree + 1] = null;
   1043   }
   1044 
   1045   /**
   1046    * Set the tail of the stack to the given node.
   1047    * Special purpose method for TransformerImpl, pushElemTemplateElement.
   1048    * Performance critical.
   1049    *
   1050    * @param n Node to set at the tail of vector
   1051    */
   1052   public final void setTail(Node n)
   1053   {
   1054     m_map[m_firstFree - 1] = n;
   1055   }
   1056 
   1057   /**
   1058    * Set the given node one position from the tail.
   1059    * Special purpose method for TransformerImpl, pushElemTemplateElement.
   1060    * Performance critical.
   1061    *
   1062    * @param n Node to set
   1063    */
   1064   public final void setTailSub1(Node n)
   1065   {
   1066     m_map[m_firstFree - 2] = n;
   1067   }
   1068 
   1069   /**
   1070    * Return the node at the tail of the vector without popping
   1071    * Special purpose method for TransformerImpl, pushElemTemplateElement.
   1072    * Performance critical.
   1073    *
   1074    * @return Node at the tail of the vector
   1075    */
   1076   public final Node peepTail()
   1077   {
   1078     return m_map[m_firstFree - 1];
   1079   }
   1080 
   1081   /**
   1082    * Return the node one position from the tail without popping.
   1083    * Special purpose method for TransformerImpl, pushElemTemplateElement.
   1084    * Performance critical.
   1085    *
   1086    * @return Node one away from the tail
   1087    */
   1088   public final Node peepTailSub1()
   1089   {
   1090     return m_map[m_firstFree - 2];
   1091   }
   1092 
   1093   /**
   1094    * Inserts the specified node in this vector at the specified index.
   1095    * Each component in this vector with an index greater or equal to
   1096    * the specified index is shifted upward to have an index one greater
   1097    * than the value it had previously.
   1098    *
   1099    * @param value Node to insert
   1100    * @param at Position where to insert
   1101    */
   1102   public void insertElementAt(Node value, int at)
   1103   {
   1104     if (!m_mutable)
   1105       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
   1106 
   1107     if (null == m_map)
   1108     {
   1109       m_map = new Node[m_blocksize];
   1110       m_mapSize = m_blocksize;
   1111     }
   1112     else if ((m_firstFree + 1) >= m_mapSize)
   1113     {
   1114       m_mapSize += m_blocksize;
   1115 
   1116       Node newMap[] = new Node[m_mapSize];
   1117 
   1118       System.arraycopy(m_map, 0, newMap, 0, m_firstFree + 1);
   1119 
   1120       m_map = newMap;
   1121     }
   1122 
   1123     if (at <= (m_firstFree - 1))
   1124     {
   1125       System.arraycopy(m_map, at, m_map, at + 1, m_firstFree - at);
   1126     }
   1127 
   1128     m_map[at] = value;
   1129 
   1130     m_firstFree++;
   1131   }
   1132 
   1133   /**
   1134    * Append the nodes to the list.
   1135    *
   1136    * @param nodes NodeVector to append to this list
   1137    */
   1138   public void appendNodes(NodeSet nodes)
   1139   {
   1140 
   1141     int nNodes = nodes.size();
   1142 
   1143     if (null == m_map)
   1144     {
   1145       m_mapSize = nNodes + m_blocksize;
   1146       m_map = new Node[m_mapSize];
   1147     }
   1148     else if ((m_firstFree + nNodes) >= m_mapSize)
   1149     {
   1150       m_mapSize += (nNodes + m_blocksize);
   1151 
   1152       Node newMap[] = new Node[m_mapSize];
   1153 
   1154       System.arraycopy(m_map, 0, newMap, 0, m_firstFree + nNodes);
   1155 
   1156       m_map = newMap;
   1157     }
   1158 
   1159     System.arraycopy(nodes.m_map, 0, m_map, m_firstFree, nNodes);
   1160 
   1161     m_firstFree += nNodes;
   1162   }
   1163 
   1164   /**
   1165    * Inserts the specified node in this vector at the specified index.
   1166    * Each component in this vector with an index greater or equal to
   1167    * the specified index is shifted upward to have an index one greater
   1168    * than the value it had previously.
   1169    */
   1170   public void removeAllElements()
   1171   {
   1172 
   1173     if (null == m_map)
   1174       return;
   1175 
   1176     for (int i = 0; i < m_firstFree; i++)
   1177     {
   1178       m_map[i] = null;
   1179     }
   1180 
   1181     m_firstFree = 0;
   1182   }
   1183 
   1184   /**
   1185    * Removes the first occurrence of the argument from this vector.
   1186    * If the object is found in this vector, each component in the vector
   1187    * with an index greater or equal to the object's index is shifted
   1188    * downward to have an index one smaller than the value it had
   1189    * previously.
   1190    *
   1191    * @param s Node to remove from the list
   1192    *
   1193    * @return True if the node was successfully removed
   1194    */
   1195   public boolean removeElement(Node s)
   1196   {
   1197     if (!m_mutable)
   1198       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
   1199 
   1200     if (null == m_map)
   1201       return false;
   1202 
   1203     for (int i = 0; i < m_firstFree; i++)
   1204     {
   1205       Node node = m_map[i];
   1206 
   1207       if ((null != node) && node.equals(s))
   1208       {
   1209         if (i < m_firstFree - 1)
   1210           System.arraycopy(m_map, i + 1, m_map, i, m_firstFree - i - 1);
   1211 
   1212         m_firstFree--;
   1213         m_map[m_firstFree] = null;
   1214 
   1215         return true;
   1216       }
   1217     }
   1218 
   1219     return false;
   1220   }
   1221 
   1222   /**
   1223    * Deletes the component at the specified index. Each component in
   1224    * this vector with an index greater or equal to the specified
   1225    * index is shifted downward to have an index one smaller than
   1226    * the value it had previously.
   1227    *
   1228    * @param i Index of node to remove
   1229    */
   1230   public void removeElementAt(int i)
   1231   {
   1232 
   1233     if (null == m_map)
   1234       return;
   1235 
   1236     if (i >= m_firstFree)
   1237       throw new ArrayIndexOutOfBoundsException(i + " >= " + m_firstFree);
   1238     else if (i < 0)
   1239       throw new ArrayIndexOutOfBoundsException(i);
   1240 
   1241     if (i < m_firstFree - 1)
   1242       System.arraycopy(m_map, i + 1, m_map, i, m_firstFree - i - 1);
   1243 
   1244     m_firstFree--;
   1245     m_map[m_firstFree] = null;
   1246   }
   1247 
   1248   /**
   1249    * Sets the component at the specified index of this vector to be the
   1250    * specified object. The previous component at that position is discarded.
   1251    *
   1252    * The index must be a value greater than or equal to 0 and less
   1253    * than the current size of the vector.
   1254    *
   1255    * @param node Node to set
   1256    * @param index Index of where to set the node
   1257    */
   1258   public void setElementAt(Node node, int index)
   1259   {
   1260     if (!m_mutable)
   1261       throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
   1262 
   1263     if (null == m_map)
   1264     {
   1265       m_map = new Node[m_blocksize];
   1266       m_mapSize = m_blocksize;
   1267     }
   1268 
   1269     m_map[index] = node;
   1270   }
   1271 
   1272   /**
   1273    * Get the nth element.
   1274    *
   1275    * @param i Index of node to get
   1276    *
   1277    * @return Node at specified index
   1278    */
   1279   public Node elementAt(int i)
   1280   {
   1281 
   1282     if (null == m_map)
   1283       return null;
   1284 
   1285     return m_map[i];
   1286   }
   1287 
   1288   /**
   1289    * Tell if the table contains the given node.
   1290    *
   1291    * @param s Node to look for
   1292    *
   1293    * @return True if the given node was found.
   1294    */
   1295   public boolean contains(Node s)
   1296   {
   1297     runTo(-1);
   1298 
   1299     if (null == m_map)
   1300       return false;
   1301 
   1302     for (int i = 0; i < m_firstFree; i++)
   1303     {
   1304       Node node = m_map[i];
   1305 
   1306       if ((null != node) && node.equals(s))
   1307         return true;
   1308     }
   1309 
   1310     return false;
   1311   }
   1312 
   1313   /**
   1314    * Searches for the first occurence of the given argument,
   1315    * beginning the search at index, and testing for equality
   1316    * using the equals method.
   1317    *
   1318    * @param elem Node to look for
   1319    * @param index Index of where to start the search
   1320    * @return the index of the first occurrence of the object
   1321    * argument in this vector at position index or later in the
   1322    * vector; returns -1 if the object is not found.
   1323    */
   1324   public int indexOf(Node elem, int index)
   1325   {
   1326     runTo(-1);
   1327 
   1328     if (null == m_map)
   1329       return -1;
   1330 
   1331     for (int i = index; i < m_firstFree; i++)
   1332     {
   1333       Node node = m_map[i];
   1334 
   1335       if ((null != node) && node.equals(elem))
   1336         return i;
   1337     }
   1338 
   1339     return -1;
   1340   }
   1341 
   1342   /**
   1343    * Searches for the first occurence of the given argument,
   1344    * beginning the search at index, and testing for equality
   1345    * using the equals method.
   1346    *
   1347    * @param elem Node to look for
   1348    * @return the index of the first occurrence of the object
   1349    * argument in this vector at position index or later in the
   1350    * vector; returns -1 if the object is not found.
   1351    */
   1352   public int indexOf(Node elem)
   1353   {
   1354     runTo(-1);
   1355 
   1356     if (null == m_map)
   1357       return -1;
   1358 
   1359     for (int i = 0; i < m_firstFree; i++)
   1360     {
   1361       Node node = m_map[i];
   1362 
   1363       if ((null != node) && node.equals(elem))
   1364         return i;
   1365     }
   1366 
   1367     return -1;
   1368   }
   1369 
   1370 }
   1371