1/*
2 * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20/*
21 * $Id: KeyIndex.java,v 1.6 2006/06/19 19:49:02 spericas Exp $
22 */
23
24package com.sun.org.apache.xalan.internal.xsltc.dom;
25
26import com.sun.org.apache.xalan.internal.xsltc.DOM;
27import com.sun.org.apache.xalan.internal.xsltc.DOMEnhancedForDTM;
28import com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary;
29import com.sun.org.apache.xalan.internal.xsltc.util.IntegerArray;
30import com.sun.org.apache.xml.internal.dtm.Axis;
31import com.sun.org.apache.xml.internal.dtm.DTM;
32import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
33import com.sun.org.apache.xml.internal.dtm.ref.DTMAxisIteratorBase;
34import java.util.HashMap;
35import java.util.Map;
36import java.util.StringTokenizer;
37
38/**
39 * Stores mappings of key values or IDs to DTM nodes.
40 * <em>Use of an instance of this class as a {@link DTMAxisIterator} is
41 * <b>deprecated.</b></em>
42 * @author Morten Jorgensen
43 * @author Santiago Pericas-Geertsen
44 */
45public class KeyIndex extends DTMAxisIteratorBase {
46
47    /**
48     * A mapping between values and nodesets for the current document.  Used
49     * only while building keys.
50     */
51    private Map<String, IntegerArray> _index;
52
53    /**
54     * The document node currently being processed.  Used only while building
55     * keys.
56     */
57    private int _currentDocumentNode = DTM.NULL;
58
59    /**
60     * A mapping from a document node to the mapping between values and nodesets
61     */
62    private Map<Integer, Map> _rootToIndexMap = new HashMap<>();
63
64    /**
65     * The node set associated to the current value passed
66     * to lookupKey();
67     */
68    private IntegerArray _nodes = null;
69
70    /**
71     * The XSLTC DOM object if this KeyIndex is being used to implement the
72     * id() function.
73     */
74    private DOM        _dom;
75
76    private DOMEnhancedForDTM    _enhancedDOM;
77
78    /**
79     * Store position after call to setMark()
80     */
81    private int _markedPosition = 0;
82
83    public KeyIndex(int dummy) {
84    }
85
86    public void setRestartable(boolean flag) {
87    }
88
89    /**
90     * Adds a node to the node list for a given value. Nodes will
91     * always be added in document order.
92     */
93    public void add(String value, int node, int rootNode) {
94        if (_currentDocumentNode != rootNode) {
95            _currentDocumentNode = rootNode;
96            _index = new HashMap<>();
97            _rootToIndexMap.put(rootNode, _index);
98        }
99
100        IntegerArray nodes = _index.get(value);
101
102        if (nodes == null) {
103             nodes = new IntegerArray();
104            _index.put(value, nodes);
105            nodes.add(node);
106
107        // Because nodes are added in document order,
108        // duplicates can be eliminated easily at this stage.
109        } else if (node != nodes.at(nodes.cardinality() - 1)) {
110            nodes.add(node);
111        }
112    }
113
114    /**
115     * Merge the current value's nodeset set by lookupKey() with _nodes.
116     * @deprecated
117     */
118    @Deprecated
119    public void merge(KeyIndex other) {
120        if (other == null) return;
121
122        if (other._nodes != null) {
123            if (_nodes == null) {
124                _nodes = (IntegerArray)other._nodes.clone();
125            }
126            else {
127                _nodes.merge(other._nodes);
128            }
129        }
130    }
131
132    /**
133     * This method must be called by the code generated by the id() function
134     * prior to returning the node iterator. The lookup code for key() and
135     * id() differ in the way the lookup value can be whitespace separated
136     * list of tokens for the id() function, but a single string for the
137     * key() function.
138     * @deprecated
139     */
140    @Deprecated
141    public void lookupId(Object value) {
142        // Clear _nodes array
143        _nodes = null;
144
145        final StringTokenizer values = new StringTokenizer((String) value,
146                                                           " \n\t");
147        while (values.hasMoreElements()) {
148            final String token = (String) values.nextElement();
149            IntegerArray nodes = _index.get(token);
150
151            if (nodes == null && _enhancedDOM != null
152                && _enhancedDOM.hasDOMSource()) {
153                nodes = getDOMNodeById(token);
154            }
155
156            if (nodes == null) continue;
157
158            if (_nodes == null) {
159                 nodes = (IntegerArray)nodes.clone();
160                _nodes = nodes;
161            }
162            else {
163                _nodes.merge(nodes);
164            }
165        }
166    }
167
168    /**
169     * Return an IntegerArray for the DOM Node which has the given id.
170     *
171     * @param id The id
172     * @return A IntegerArray representing the Node whose id is the given value.
173     */
174    public IntegerArray getDOMNodeById(String id) {
175        IntegerArray nodes = null;
176
177        if (_enhancedDOM != null) {
178            int ident = _enhancedDOM.getElementById(id);
179
180            if (ident != DTM.NULL) {
181                Integer root = _enhancedDOM.getDocument();
182                Map<String, IntegerArray> index = _rootToIndexMap.get(root);
183
184                if (index == null) {
185                    index = new HashMap<>();
186                    _rootToIndexMap.put(root, index);
187                } else {
188                    nodes = index.get(id);
189                }
190
191                if (nodes == null) {
192                    nodes = new IntegerArray();
193                    index.put(id, nodes);
194                }
195
196                nodes.add(_enhancedDOM.getNodeHandle(ident));
197            }
198        }
199
200        return nodes;
201    }
202
203    /**
204     * <p>This method must be called by the code generated by the key() function
205     * prior to returning the node iterator.</p>
206     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
207     * <b>deprecated.</b></em></p>
208     * @deprecated
209     */
210    @Deprecated
211    public void lookupKey(Object value) {
212        IntegerArray nodes = _index.get(value);
213        _nodes = (nodes != null) ? (IntegerArray) nodes.clone() : null;
214        _position = 0;
215    }
216
217    /**
218     * <p>Callers should not call next() after it returns END.</p>
219     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
220     * <b>deprecated.</b></em></p>
221     * @deprecated
222     */
223    @Deprecated
224    public int next() {
225        if (_nodes == null) return DTMAxisIterator.END;
226
227        return (_position < _nodes.cardinality()) ?
228            _dom.getNodeHandle(_nodes.at(_position++)) : DTMAxisIterator.END;
229    }
230
231    /**
232     * Given a context node and the argument to the XPath <code>id</code>
233     * function, checks whether the context node is in the set of nodes that
234     * results from that reference to the <code>id</code> function.  This is
235     * used in the implementation of <code>id</code> patterns.
236     *
237     * @param node The context node
238     * @param value The argument to the <code>id</code> function
239     * @return <code>1</code> if the context node is in the set of nodes
240     *         returned by the reference to the <code>id</code> function;
241     *         <code>0</code>, otherwise
242     */
243    public int containsID(int node, Object value) {
244        final String string = (String)value;
245        int rootHandle = _dom.getAxisIterator(Axis.ROOT)
246                                 .setStartNode(node).next();
247
248        // Get the mapping table for the document containing the context node
249        Map<String, IntegerArray> index =
250            _rootToIndexMap.get(rootHandle);
251
252        // Split argument to id function into XML whitespace separated tokens
253        final StringTokenizer values = new StringTokenizer(string, " \n\t");
254
255        while (values.hasMoreElements()) {
256            final String token = (String) values.nextElement();
257            IntegerArray nodes = null;
258
259            if (index != null) {
260                nodes = index.get(token);
261            }
262
263            // If input was from W3C DOM, use DOM's getElementById to do
264            // the look-up.
265            if (nodes == null && _enhancedDOM != null
266                && _enhancedDOM.hasDOMSource()) {
267                nodes = getDOMNodeById(token);
268            }
269
270            // Did we find the context node in the set of nodes?
271            if (nodes != null && nodes.indexOf(node) >= 0) {
272                return 1;
273            }
274        }
275
276        // Didn't find the context node in the set of nodes returned by id
277        return 0;
278    }
279
280    /**
281     * <p>Given a context node and the second argument to the XSLT
282     * <code>key</code> function, checks whether the context node is in the
283     * set of nodes that results from that reference to the <code>key</code>
284     * function.  This is used in the implementation of key patterns.</p>
285     * <p>This particular {@link KeyIndex} object is the result evaluating the
286     * first argument to the <code>key</code> function, so it's not taken into
287     * any further account.</p>
288     *
289     * @param node The context node
290     * @param value The second argument to the <code>key</code> function
291     * @return <code>1</code> if and only if the context node is in the set of
292     *         nodes returned by the reference to the <code>key</code> function;
293     *         <code>0</code>, otherwise
294     */
295    public int containsKey(int node, Object value) {
296        int rootHandle = _dom.getAxisIterator(Axis.ROOT)
297                                 .setStartNode(node).next();
298
299        // Get the mapping table for the document containing the context node
300        Map<String,IntegerArray> index =
301                    _rootToIndexMap.get(rootHandle);
302
303        // Check whether the context node is present in the set of nodes
304        // returned by the key function
305        if (index != null) {
306            final IntegerArray nodes = index.get(value);
307            return (nodes != null && nodes.indexOf(node) >= 0) ? 1 : 0;
308        }
309
310        // The particular key name identifies no nodes in this document
311        return 0;
312    }
313
314    /**
315     * <p>Resets the iterator to the last start node.</p>
316     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
317     * <b>deprecated.</b></em></p>
318     * @deprecated
319     */
320    @Deprecated
321    public DTMAxisIterator reset() {
322        _position = 0;
323        return this;
324    }
325
326    /**
327     * <p>Returns the number of elements in this iterator.</p>
328     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
329     * <b>deprecated.</b></em></p>
330     * @deprecated
331     */
332    @Deprecated
333    public int getLast() {
334        return (_nodes == null) ? 0 : _nodes.cardinality();
335    }
336
337    /**
338     * <p>Returns the position of the current node in the set.</p>
339     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
340     * <b>deprecated.</b></em></p>
341     * @deprecated
342     */
343    @Deprecated
344    public int getPosition() {
345        return _position;
346    }
347
348    /**
349     * <p>Remembers the current node for the next call to gotoMark().</p>
350     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
351     * <b>deprecated.</b></em></p>
352     * @deprecated
353     */
354    @Deprecated
355    public void setMark() {
356        _markedPosition = _position;
357    }
358
359    /**
360     * <p>Restores the current node remembered by setMark().</p>
361     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
362     * <b>deprecated.</b></em></p>
363     * @deprecated
364     */
365    @Deprecated
366    public void gotoMark() {
367        _position = _markedPosition;
368    }
369
370    /**
371     * <p>Set start to END should 'close' the iterator,
372     * i.e. subsequent call to next() should return END.</p>
373     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
374     * <b>deprecated.</b></em></p>
375     * @deprecated
376     */
377    @Deprecated
378    public DTMAxisIterator setStartNode(int start) {
379        if (start == DTMAxisIterator.END) {
380            _nodes = null;
381        }
382        else if (_nodes != null) {
383            _position = 0;
384        }
385        return (DTMAxisIterator) this;
386    }
387
388    /**
389     * <p>Get start to END should 'close' the iterator,
390     * i.e. subsequent call to next() should return END.</p>
391     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
392     * <b>deprecated.</b></em></p>
393     * @deprecated
394     */
395    @Deprecated
396    public int getStartNode() {
397        return 0;
398    }
399
400    /**
401     * <p>True if this iterator has a reversed axis.</p>
402     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
403     * <b>deprecated.</b></em></p>
404     * @deprecated
405     */
406    @Deprecated
407    public boolean isReverse() {
408        return(false);
409    }
410
411    /**
412     * <p>Returns a deep copy of this iterator.</p>
413     * <p><em>Use of an instance of this class as a {@link DTMAxisIterator} is
414     * <b>deprecated.</b></em></p>
415     * @deprecated
416     */
417    @Deprecated
418    public DTMAxisIterator cloneIterator() {
419        KeyIndex other = new KeyIndex(0);
420        other._index = _index;
421        other._rootToIndexMap = _rootToIndexMap;
422        other._nodes = _nodes;
423        other._position = _position;
424        return (DTMAxisIterator) other;
425    }
426
427    public void setDom(DOM dom, int node) {
428        _dom = dom;
429
430        // If a MultiDOM, ensure _enhancedDOM is correctly set
431        // so that getElementById() works in lookupNodes below
432        if (dom instanceof MultiDOM) {
433            dom = ((MultiDOM) dom).getDTM(node);
434        }
435
436        if (dom instanceof DOMEnhancedForDTM) {
437            _enhancedDOM = (DOMEnhancedForDTM)dom;
438        }
439        else if (dom instanceof DOMAdapter) {
440            DOM idom = ((DOMAdapter)dom).getDOMImpl();
441            if (idom instanceof DOMEnhancedForDTM) {
442                _enhancedDOM = (DOMEnhancedForDTM)idom;
443            }
444        }
445    }
446
447    /**
448     * Create a {@link KeyIndexIterator} that iterates over the nodes that
449     * result from a reference to the XSLT <code>key</code> function or
450     * XPath <code>id</code> function.
451     *
452     * @param keyValue A string or iterator representing the key values or id
453     *                 references
454     * @param isKeyCall A <code>boolean</code> indicating whether the iterator
455     *                 is being created for a reference <code>key</code> or
456     *                 <code>id</code>
457     */
458    public KeyIndexIterator getKeyIndexIterator(Object keyValue,
459                                                boolean isKeyCall) {
460        if (keyValue instanceof DTMAxisIterator) {
461            return getKeyIndexIterator((DTMAxisIterator) keyValue, isKeyCall);
462        } else {
463            return getKeyIndexIterator(BasisLibrary.stringF(keyValue, _dom),
464                                       isKeyCall);
465        }
466    }
467
468    /**
469     * Create a {@link KeyIndexIterator} that iterates over the nodes that
470     * result from a reference to the XSLT <code>key</code> function or
471     * XPath <code>id</code> function.
472     *
473     * @param keyValue A string representing the key values or id
474     *                 references
475     * @param isKeyCall A <code>boolean</code> indicating whether the iterator
476     *                 is being created for a reference <code>key</code> or
477     *                 <code>id</code>
478     */
479    public KeyIndexIterator getKeyIndexIterator(String keyValue,
480                                                boolean isKeyCall) {
481        return new KeyIndexIterator(keyValue, isKeyCall);
482    }
483
484    /**
485     * Create a {@link KeyIndexIterator} that iterates over the nodes that
486     * result from a reference to the XSLT <code>key</code> function or
487     * XPath <code>id</code> function.
488     *
489     * @param keyValue An iterator representing the key values or id
490     *                 references
491     * @param isKeyCall A <code>boolean</code> indicating whether the iterator
492     *                 is being created for a reference <code>key</code> or
493     *                 <code>id</code>
494     */
495    public KeyIndexIterator getKeyIndexIterator(DTMAxisIterator keyValue,
496                                                boolean isKeyCall) {
497        return new KeyIndexIterator(keyValue, isKeyCall);
498    }
499
500    /**
501     * Used to represent an empty node set.
502     */
503    final private static IntegerArray EMPTY_NODES = new IntegerArray(0);
504
505
506    /**
507     * An iterator representing the result of a reference to either the
508     * XSLT <code>key</code> function or the XPath <code>id</code> function.
509     */
510    public class KeyIndexIterator extends MultiValuedNodeHeapIterator {
511
512        /**
513         * <p>A reference to the <code>key</code> function that only has one
514         * key value or to the <code>id</code> function that has only one string
515         * argument can be optimized to ignore the multi-valued heap.  This
516         * field will be <code>null</code> otherwise.
517         */
518        private IntegerArray _nodes;
519
520        /**
521         * <p>This field contains the iterator representing a node set key value
522         * argument to the <code>key</code> function or a node set argument
523         * to the <code>id</code> function.</p>
524         *
525         * <p>Exactly one of this field and {@link #_keyValue} must be
526         * <code>null</code>.</p>
527         */
528        private DTMAxisIterator _keyValueIterator;
529
530        /**
531         * <p>This field contains the iterator representing a non-node-set key
532         * value argument to the <code>key</code> function or a non-node-set
533         * argument to the <code>id</code> function.</p>
534         *
535         * <p>Exactly one of this field and {@link #_keyValueIterator} must be
536         * <code>null</code>.</p>
537         */
538        private String _keyValue;
539
540        /**
541         * Indicates whether this object represents the result of a reference
542         * to the <code>key</code> function (<code>true</code>) or the
543         * <code>id</code> function (<code>false</code>).
544         */
545        private boolean _isKeyIterator;
546
547        /**
548         * Represents the DTM nodes retrieved for one key value or one string
549         * argument to <code>id</code> for use as one heap node in a
550         * {@link MultiValuedNodeHeapIterator}.
551         */
552        protected class KeyIndexHeapNode
553                extends MultiValuedNodeHeapIterator.HeapNode
554        {
555            /**
556             * {@link IntegerArray} of DTM nodes retrieved for one key value.
557             * Must contain no duplicates and be stored in document order.
558             */
559            private IntegerArray _nodes;
560
561            /**
562             * Position in {@link #_nodes} array of next node to return from
563             * this heap node.
564             */
565            private int _position = 0;
566
567            /**
568             * Marked position.  Used by {@link #setMark()} and
569             * {@link #gotoMark()}
570             */
571            private int _markPosition = -1;
572
573            /**
574             * Create a heap node representing DTM nodes retrieved for one
575             * key value in a reference to the <code>key</code> function
576             * or string argument to the <code>id</code> function.
577             */
578            KeyIndexHeapNode(IntegerArray nodes) {
579                _nodes = nodes;
580            }
581
582            /**
583             * Advance to the next node represented by this {@link HeapNode}
584             *
585             * @return the next DTM node.
586             */
587            public int step() {
588                if (_position < _nodes.cardinality()) {
589                    _node = _nodes.at(_position);
590                    _position++;
591                } else {
592                    _node = DTMAxisIterator.END;
593                }
594
595                return _node;
596            }
597
598            /**
599             * Creates a deep copy of this {@link HeapNode}.  The clone is not
600             * reset from the current position of the original.
601             *
602             * @return the cloned heap node
603             */
604            public HeapNode cloneHeapNode() {
605                KeyIndexHeapNode clone =
606                        (KeyIndexHeapNode) super.cloneHeapNode();
607
608                clone._nodes = _nodes;
609                clone._position = _position;
610                clone._markPosition = _markPosition;
611
612                return clone;
613            }
614
615            /**
616             * Remembers the current node for the next call to
617             * {@link #gotoMark()}.
618             */
619            public void setMark() {
620                _markPosition = _position;
621            }
622
623            /**
624             * Restores the current node remembered by {@link #setMark()}.
625             */
626            public void gotoMark() {
627                _position = _markPosition;
628            }
629
630            /**
631             * Performs a comparison of the two heap nodes
632             *
633             * @param heapNode the heap node against which to compare
634             * @return <code>true</code> if and only if the current node for
635             *         this heap node is before the current node of the
636             *         argument heap node in document order.
637             */
638            public boolean isLessThan(HeapNode heapNode) {
639                return _node < heapNode._node;
640            }
641
642            /**
643             * <p>Sets context with respect to which this heap node is
644             * evaluated.</p>
645             * <p>This has no real effect on this kind of heap node.  Instead,
646             * the {@link KeyIndexIterator#setStartNode(int)} method should
647             * create new instances of this class to represent the effect of
648             * changing the context.</p>
649             */
650            public HeapNode setStartNode(int node) {
651                return this;
652            }
653
654            /**
655             * Reset the heap node back to its beginning.
656             */
657            public HeapNode reset() {
658                _position = 0;
659                return this;
660            }
661        }
662
663        /**
664         * Constructor used when the argument to <code>key</code> or
665         * <code>id</code> is not a node set.
666         *
667         * @param keyValue the argument to <code>key</code> or <code>id</code>
668         *                 cast to a <code>String</code>
669         * @param isKeyIterator indicates whether the constructed iterator
670         *                represents a reference to <code>key</code> or
671         *                <code>id</code>.
672         */
673        KeyIndexIterator(String keyValue, boolean isKeyIterator) {
674            _isKeyIterator = isKeyIterator;
675            _keyValue = keyValue;
676        }
677
678        /**
679         * Constructor used when the argument to <code>key</code> or
680         * <code>id</code> is a node set.
681         *
682         * @param keyValues the argument to <code>key</code> or <code>id</code>
683         * @param isKeyIterator indicates whether the constructed iterator
684         *                represents a reference to <code>key</code> or
685         *                <code>id</code>.
686         */
687        KeyIndexIterator(DTMAxisIterator keyValues, boolean isKeyIterator) {
688            _keyValueIterator = keyValues;
689            _isKeyIterator = isKeyIterator;
690        }
691
692        /**
693         * Retrieve nodes for a particular key value or a particular id
694         * argument value.
695         *
696         * @param root The root node of the document containing the context node
697         * @param keyValue The key value of id string argument value
698         * @return an {@link IntegerArray} of the resulting nodes
699         */
700        protected IntegerArray lookupNodes(int root, String keyValue) {
701            IntegerArray result = null;
702
703            // Get mapping from key values/IDs to DTM nodes for this document
704            Map<String, IntegerArray> index = _rootToIndexMap.get(root);
705
706            if (!_isKeyIterator) {
707                // For id function, tokenize argument as whitespace separated
708                // list of values and look up nodes identified by each ID.
709                final StringTokenizer values =
710                        new StringTokenizer(keyValue, " \n\t");
711
712                while (values.hasMoreElements()) {
713                    final String token = (String) values.nextElement();
714                    IntegerArray nodes = null;
715
716                    // Does the ID map to any node in the document?
717                    if (index != null) {
718                        nodes = index.get(token);
719                    }
720
721                    // If input was from W3C DOM, use DOM's getElementById to do
722                    // the look-up.
723                    if (nodes == null && _enhancedDOM != null
724                            && _enhancedDOM.hasDOMSource()) {
725                        nodes = getDOMNodeById(token);
726                    }
727
728                    // If we found any nodes, merge them into the cumulative
729                    // result
730                    if (nodes != null) {
731                        if (result == null) {
732                            result = (IntegerArray)nodes.clone();
733                        } else {
734                            result.merge(nodes);
735                        }
736                    }
737                }
738            } else if (index != null) {
739                // For key function, map key value to nodes
740                result = index.get(keyValue);
741            }
742
743            return result;
744        }
745
746        /**
747         * Set context node for the iterator.  This will cause the iterator
748         * to reset itself, reevaluate arguments to the function, look up
749         * nodes in the input and reinitialize its internal heap.
750         *
751         * @param node the context node
752         * @return A {@link DTMAxisIterator} set to the start of the iteration.
753         */
754        public DTMAxisIterator setStartNode(int node) {
755            _startNode = node;
756
757            // If the arugment to the function is a node set, set the
758            // context node on it.
759            if (_keyValueIterator != null) {
760                _keyValueIterator = _keyValueIterator.setStartNode(node);
761            }
762
763            init();
764
765            return super.setStartNode(node);
766        }
767
768        /**
769         * Get the next node in the iteration.
770         *
771         * @return The next node handle in the iteration, or END.
772         */
773        public int next() {
774            int nodeHandle;
775
776            // If at most one key value or at most one string argument to id
777            // resulted in nodes being returned, use the IntegerArray
778            // stored at _nodes directly.  This relies on the fact that the
779            // IntegerArray never includes duplicate nodes and is always stored
780            // in document order.
781            if (_nodes != null) {
782                if (_position < _nodes.cardinality()) {
783                    nodeHandle = returnNode(_nodes.at(_position));
784                } else {
785                    nodeHandle = DTMAxisIterator.END;
786                }
787            } else {
788                nodeHandle = super.next();
789            }
790
791            return nodeHandle;
792        }
793
794        /**
795         * Resets the iterator to the last start node.
796         *
797         * @return A DTMAxisIterator, which may or may not be the same as this
798         *         iterator.
799         */
800        public DTMAxisIterator reset() {
801            if (_nodes == null) {
802                init();
803            } else {
804                super.reset();
805            }
806
807            return resetPosition();
808        }
809
810        /**
811         * Evaluate the reference to the <code>key</code> or <code>id</code>
812         * function with the context specified by {@link #setStartNode(int)}
813         * and set up this iterator to iterate over the DTM nodes that are
814         * to be returned.
815         */
816        protected void init() {
817            super.init();
818            _position = 0;
819
820            // All nodes retrieved are in the same document
821            int rootHandle = _dom.getAxisIterator(Axis.ROOT)
822                                      .setStartNode(_startNode).next();
823
824            // Is the argument not a node set?
825            if (_keyValueIterator == null) {
826                // Look up nodes returned for the single string argument
827                _nodes = lookupNodes(rootHandle, _keyValue);
828
829                if (_nodes == null) {
830                    _nodes = EMPTY_NODES;
831                }
832            } else {
833                DTMAxisIterator keyValues = _keyValueIterator.reset();
834                int retrievedKeyValueIdx = 0;
835                boolean foundNodes = false;
836
837                _nodes = null;
838
839                // For each node in the node set argument, get the string value
840                // and look up the nodes returned by key or id for that string
841                // value.  If at most one string value has nodes associated,
842                // the nodes will be stored in _nodes; otherwise, the nodes
843                // will be placed in a heap.
844                for (int keyValueNode = keyValues.next();
845                     keyValueNode != DTMAxisIterator.END;
846                     keyValueNode = keyValues.next()) {
847
848                    String keyValue = BasisLibrary.stringF(keyValueNode, _dom);
849
850                    IntegerArray nodes = lookupNodes(rootHandle, keyValue);
851
852                    if (nodes != null) {
853                        if (!foundNodes) {
854                            _nodes = nodes;
855                            foundNodes = true;
856                        } else {
857                            if (_nodes != null) {
858                                addHeapNode(new KeyIndexHeapNode(_nodes));
859                                _nodes = null;
860                            }
861                            addHeapNode(new KeyIndexHeapNode(nodes));
862                        }
863                    }
864                }
865
866                if (!foundNodes) {
867                    _nodes = EMPTY_NODES;
868                }
869            }
870        }
871
872        /**
873         * Returns the number of nodes in this iterator.
874         *
875         * @return the number of nodes
876         */
877        public int getLast() {
878            // If nodes are stored in _nodes, take advantage of the fact that
879            // there are no duplicates.  Otherwise, fall back to the base heap
880            // implementaiton and hope it does a good job with this.
881            return (_nodes != null) ? _nodes.cardinality() : super.getLast();
882        }
883
884        /**
885         * Return the node at the given position.
886         *
887         * @param position The position
888         * @return The node at the given position.
889         */
890        public int getNodeByPosition(int position) {
891            int node = DTMAxisIterator.END;
892
893            // If nodes are stored in _nodes, take advantage of the fact that
894            // there are no duplicates and they are stored in document order.
895            // Otherwise, fall back to the base heap implementation to do a
896            // good job with this.
897            if (_nodes != null) {
898                if (position > 0) {
899                    if (position <= _nodes.cardinality()) {
900                        _position = position;
901                        node = _nodes.at(position-1);
902                    } else {
903                        _position = _nodes.cardinality();
904                    }
905                }
906            } else {
907                node = super.getNodeByPosition(position);
908            }
909
910            return node;
911        }
912    }
913}
914