001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.commons.collections.iterators;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.Iterator;
022    import java.util.List;
023    
024    import org.apache.commons.collections.list.UnmodifiableList;
025    
026    /**
027     * An IteratorChain is an Iterator that wraps a number of Iterators.
028     * <p>
029     * This class makes multiple iterators look like one to the caller
030     * When any method from the Iterator interface is called, the IteratorChain
031     * will delegate to a single underlying Iterator. The IteratorChain will
032     * invoke the Iterators in sequence until all Iterators are exhausted.
033     * <p>
034     * Under many circumstances, linking Iterators together in this manner is
035     * more efficient (and convenient) than reading out the contents of each
036     * Iterator into a List and creating a new Iterator.
037     * <p>
038     * Calling a method that adds new Iterator<i>after a method in the Iterator
039     * interface has been called</i> will result in an UnsupportedOperationException.
040     * Subclasses should <i>take care</i> to not alter the underlying List of Iterators.
041     * <p>
042     * NOTE: As from version 3.0, the IteratorChain may contain no
043     * iterators. In this case the class will function as an empty iterator.
044     *
045     * @since Commons Collections 2.1
046     * @version $Revision: 647116 $ $Date: 2008-04-11 12:23:08 +0100 (Fri, 11 Apr 2008) $
047     *
048     * @author Morgan Delagrange
049     * @author Stephen Colebourne
050     */
051    public class IteratorChain implements Iterator {
052    
053        /** The chain of iterators */
054        protected final List iteratorChain = new ArrayList();
055        /** The index of the current iterator */
056        protected int currentIteratorIndex = 0;
057        /** The current iterator */
058        protected Iterator currentIterator = null;
059        /**
060         * The "last used" Iterator is the Iterator upon which
061         * next() or hasNext() was most recently called
062         * used for the remove() operation only
063         */
064        protected Iterator lastUsedIterator = null;
065        /**
066         * ComparatorChain is "locked" after the first time
067         * compare(Object,Object) is called
068         */
069        protected boolean isLocked = false;
070    
071        //-----------------------------------------------------------------------
072        /**
073         * Construct an IteratorChain with no Iterators.
074         * <p>
075         * You will normally use {@link #addIterator(Iterator)} to add
076         * some iterators after using this constructor.
077         */
078        public IteratorChain() {
079            super();
080        }
081    
082        /**
083         * Construct an IteratorChain with a single Iterator.
084         *
085         * @param iterator first Iterator in the IteratorChain
086         * @throws NullPointerException if the iterator is null
087         */
088        public IteratorChain(Iterator iterator) {
089            super();
090            addIterator(iterator);
091        }
092    
093        /**
094         * Constructs a new <code>IteratorChain</code> over the two
095         * given iterators.
096         *
097         * @param a  the first child iterator
098         * @param b  the second child iterator
099         * @throws NullPointerException if either iterator is null
100         */
101        public IteratorChain(Iterator a, Iterator b) {
102            super();
103            addIterator(a);
104            addIterator(b);
105        }
106    
107        /**
108         * Constructs a new <code>IteratorChain</code> over the array
109         * of iterators.
110         *
111         * @param iterators  the array of iterators
112         * @throws NullPointerException if iterators array is or contains null
113         */
114        public IteratorChain(Iterator[] iterators) {
115            super();
116            for (int i = 0; i < iterators.length; i++) {
117                addIterator(iterators[i]);
118            }
119        }
120    
121        /**
122         * Constructs a new <code>IteratorChain</code> over the collection
123         * of iterators.
124         *
125         * @param iterators  the collection of iterators
126         * @throws NullPointerException if iterators collection is or contains null
127         * @throws ClassCastException if iterators collection doesn't contain an iterator
128         */
129        public IteratorChain(Collection iterators) {
130            super();
131            for (Iterator it = iterators.iterator(); it.hasNext();) {
132                Iterator item = (Iterator) it.next();
133                addIterator(item);
134            }
135        }
136        
137        //-----------------------------------------------------------------------
138        /**
139         * Add an Iterator to the end of the chain
140         *
141         * @param iterator Iterator to add
142         * @throws IllegalStateException if I've already started iterating
143         * @throws NullPointerException if the iterator is null
144         */
145        public void addIterator(Iterator iterator) {
146            checkLocked();
147            if (iterator == null) {
148                throw new NullPointerException("Iterator must not be null");
149            }
150            iteratorChain.add(iterator);
151        }
152    
153        /**
154         * Set the Iterator at the given index
155         *
156         * @param index      index of the Iterator to replace
157         * @param iterator   Iterator to place at the given index
158         * @throws IndexOutOfBoundsException if index &lt; 0 or index &gt; size()
159         * @throws IllegalStateException if I've already started iterating
160         * @throws NullPointerException if the iterator is null
161         */
162        public void setIterator(int index, Iterator iterator) throws IndexOutOfBoundsException {
163            checkLocked();
164            if (iterator == null) {
165                throw new NullPointerException("Iterator must not be null");
166            }
167            iteratorChain.set(index, iterator);
168        }
169    
170        /**
171         * Get the list of Iterators (unmodifiable)
172         *
173         * @return the unmodifiable list of iterators added
174         */
175        public List getIterators() {
176            return UnmodifiableList.decorate(iteratorChain);
177        }
178    
179        /**
180         * Number of Iterators in the current IteratorChain.
181         *
182         * @return Iterator count
183         */
184        public int size() {
185            return iteratorChain.size();
186        }
187    
188        /**
189         * Determine if modifications can still be made to the IteratorChain.
190         * IteratorChains cannot be modified once they have executed a method
191         * from the Iterator interface.
192         *
193         * @return true if IteratorChain cannot be modified, false if it can
194         */
195        public boolean isLocked() {
196            return isLocked;
197        }
198    
199        /**
200         * Checks whether the iterator chain is now locked and in use.
201         */
202        private void checkLocked() {
203            if (isLocked == true) {
204                throw new UnsupportedOperationException("IteratorChain cannot be changed after the first use of a method from the Iterator interface");
205            }
206        }
207    
208        /**
209         * Lock the chain so no more iterators can be added.
210         * This must be called from all Iterator interface methods.
211         */
212        private void lockChain() {
213            if (isLocked == false) {
214                isLocked = true;
215            }
216        }
217    
218        /**
219         * Updates the current iterator field to ensure that the current Iterator
220         * is not exhausted
221         */
222        protected void updateCurrentIterator() {
223            if (currentIterator == null) {
224                if (iteratorChain.isEmpty()) {
225                    currentIterator = EmptyIterator.INSTANCE;
226                } else {
227                    currentIterator = (Iterator) iteratorChain.get(0);
228                }
229                // set last used iterator here, in case the user calls remove
230                // before calling hasNext() or next() (although they shouldn't)
231                lastUsedIterator = currentIterator;
232            }
233    
234            while (currentIterator.hasNext() == false && currentIteratorIndex < iteratorChain.size() - 1) {
235                currentIteratorIndex++;
236                currentIterator = (Iterator) iteratorChain.get(currentIteratorIndex);
237            }
238        }
239    
240        //-----------------------------------------------------------------------
241        /**
242         * Return true if any Iterator in the IteratorChain has a remaining element.
243         *
244         * @return true if elements remain
245         */
246        public boolean hasNext() {
247            lockChain();
248            updateCurrentIterator();
249            lastUsedIterator = currentIterator;
250    
251            return currentIterator.hasNext();
252        }
253    
254        /**
255         * Returns the next Object of the current Iterator
256         *
257         * @return Object from the current Iterator
258         * @throws java.util.NoSuchElementException if all the Iterators are exhausted
259         */
260        public Object next() {
261            lockChain();
262            updateCurrentIterator();
263            lastUsedIterator = currentIterator;
264    
265            return currentIterator.next();
266        }
267    
268        /**
269         * Removes from the underlying collection the last element
270         * returned by the Iterator.  As with next() and hasNext(),
271         * this method calls remove() on the underlying Iterator.
272         * Therefore, this method may throw an
273         * UnsupportedOperationException if the underlying
274         * Iterator does not support this method.
275         *
276         * @throws UnsupportedOperationException
277         *   if the remove operator is not supported by the underlying Iterator
278         * @throws IllegalStateException
279         *   if the next method has not yet been called, or the remove method has
280         *   already been called after the last call to the next method.
281         */
282        public void remove() {
283            lockChain();
284            if (currentIterator == null) {
285                updateCurrentIterator();
286            }
287            lastUsedIterator.remove();
288        }
289    
290    }