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 */
017package org.apache.commons.collections4.iterators;
018
019import java.util.Collection;
020import java.util.Iterator;
021import java.util.LinkedList;
022import java.util.Queue;
023
024/**
025 * An IteratorChain is an Iterator that wraps a number of Iterators.
026 * <p>
027 * This class makes multiple iterators look like one to the caller. When any
028 * method from the Iterator interface is called, the IteratorChain will delegate
029 * to a single underlying Iterator. The IteratorChain will invoke the Iterators
030 * in sequence until all Iterators are exhausted.
031 * <p>
032 * Under many circumstances, linking Iterators together in this manner is more
033 * efficient (and convenient) than reading out the contents of each Iterator
034 * into a List and creating a new Iterator.
035 * <p>
036 * Calling a method that adds new Iterator <i>after a method in the Iterator
037 * interface has been called</i> will result in an UnsupportedOperationException.
038 * <p>
039 * NOTE: As from version 3.0, the IteratorChain may contain no iterators. In
040 * this case the class will function as an empty iterator.
041 * <p>
042 * NOTE: As from version 4.0, the IteratorChain stores the iterators in a queue
043 * and removes any reference to them as soon as they are not used anymore. Thus
044 * the methods {@code setIterator(Iterator)} and {@code getIterators()} have been
045 * removed and {@link #size()} will return the number of remaining iterators in
046 * the queue.
047 *
048 * @since 2.1
049 * @version $Id: IteratorChain.html 972421 2015-11-14 20:00:04Z tn $
050 */
051public class IteratorChain<E> implements Iterator<E> {
052
053    /** The chain of iterators */
054    private final Queue<Iterator<? extends E>> iteratorChain = new LinkedList<Iterator<? extends E>>();
055
056    /** The current iterator */
057    private Iterator<? extends E> currentIterator = null;
058
059    /**
060     * The "last used" Iterator is the Iterator upon which next() or hasNext()
061     * was most recently called used for the remove() operation only
062     */
063    private Iterator<? extends E> lastUsedIterator = null;
064
065    /**
066     * ComparatorChain is "locked" after the first time compare(Object,Object)
067     * is called
068     */
069    private 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 some
076     * iterators after using this constructor.
077     */
078    public IteratorChain() {
079        super();
080    }
081
082    /**
083     * Construct an IteratorChain with a single Iterator.
084     * <p>
085     * This method takes one iterator. The newly constructed iterator will
086     * iterate through that iterator. Thus calling this constructor on its own
087     * will have no effect other than decorating the input iterator.
088     * <p>
089     * You will normally use {@link #addIterator(Iterator)} to add some more
090     * iterators after using this constructor.
091     *
092     * @param iterator the first child iterator in the IteratorChain, not null
093     * @throws NullPointerException if the iterator is null
094     */
095    public IteratorChain(final Iterator<? extends E> iterator) {
096        super();
097        addIterator(iterator);
098    }
099
100    /**
101     * Constructs a new <code>IteratorChain</code> over the two given iterators.
102     * <p>
103     * This method takes two iterators. The newly constructed iterator will
104     * iterate through each one of the input iterators in turn.
105     *
106     * @param first the first child iterator in the IteratorChain, not null
107     * @param second the second child iterator in the IteratorChain, not null
108     * @throws NullPointerException if either iterator is null
109     */
110    public IteratorChain(final Iterator<? extends E> first, final Iterator<? extends E> second) {
111        super();
112        addIterator(first);
113        addIterator(second);
114    }
115
116    /**
117     * Constructs a new <code>IteratorChain</code> over the array of iterators.
118     * <p>
119     * This method takes an array of iterators. The newly constructed iterator
120     * will iterate through each one of the input iterators in turn.
121     *
122     * @param iteratorChain the array of iterators, not null
123     * @throws NullPointerException if iterators array is or contains null
124     */
125    public IteratorChain(final Iterator<? extends E>... iteratorChain) {
126        super();
127        for (final Iterator<? extends E> element : iteratorChain) {
128            addIterator(element);
129        }
130    }
131
132    /**
133     * Constructs a new <code>IteratorChain</code> over the collection of
134     * iterators.
135     * <p>
136     * This method takes a collection of iterators. The newly constructed
137     * iterator will iterate through each one of the input iterators in turn.
138     *
139     * @param iteratorChain the collection of iterators, not null
140     * @throws NullPointerException if iterators collection is or contains null
141     * @throws ClassCastException if iterators collection doesn't contain an
142     * iterator
143     */
144    public IteratorChain(final Collection<Iterator<? extends E>> iteratorChain) {
145        super();
146        for (final Iterator<? extends E> iterator : iteratorChain) {
147            addIterator(iterator);
148        }
149    }
150
151    //-----------------------------------------------------------------------
152    /**
153     * Add an Iterator to the end of the chain
154     *
155     * @param iterator Iterator to add
156     * @throws IllegalStateException if I've already started iterating
157     * @throws NullPointerException if the iterator is null
158     */
159    public void addIterator(final Iterator<? extends E> iterator) {
160        checkLocked();
161        if (iterator == null) {
162            throw new NullPointerException("Iterator must not be null");
163        }
164        iteratorChain.add(iterator);
165    }
166
167    /**
168     * Returns the remaining number of Iterators in the current IteratorChain.
169     *
170     * @return Iterator count
171     */
172    public int size() {
173        return iteratorChain.size();
174    }
175
176    /**
177     * Determine if modifications can still be made to the IteratorChain.
178     * IteratorChains cannot be modified once they have executed a method from
179     * the Iterator interface.
180     *
181     * @return true if IteratorChain cannot be modified, false if it can
182     */
183    public boolean isLocked() {
184        return isLocked;
185    }
186
187    /**
188     * Checks whether the iterator chain is now locked and in use.
189     */
190    private void checkLocked() {
191        if (isLocked == true) {
192            throw new UnsupportedOperationException(
193                    "IteratorChain cannot be changed after the first use of a method from the Iterator interface");
194        }
195    }
196
197    /**
198     * Lock the chain so no more iterators can be added. This must be called
199     * from all Iterator interface methods.
200     */
201    private void lockChain() {
202        if (isLocked == false) {
203            isLocked = true;
204        }
205    }
206
207    /**
208     * Updates the current iterator field to ensure that the current Iterator is
209     * not exhausted
210     */
211    protected void updateCurrentIterator() {
212        if (currentIterator == null) {
213            if (iteratorChain.isEmpty()) {
214                currentIterator = EmptyIterator.<E> emptyIterator();
215            } else {
216                currentIterator = iteratorChain.remove();
217            }
218            // set last used iterator here, in case the user calls remove
219            // before calling hasNext() or next() (although they shouldn't)
220            lastUsedIterator = currentIterator;
221        }
222
223        while (currentIterator.hasNext() == false && !iteratorChain.isEmpty()) {
224            currentIterator = iteratorChain.remove();
225        }
226    }
227
228    //-----------------------------------------------------------------------
229    /**
230     * Return true if any Iterator in the IteratorChain has a remaining element.
231     *
232     * @return true if elements remain
233     */
234    public boolean hasNext() {
235        lockChain();
236        updateCurrentIterator();
237        lastUsedIterator = currentIterator;
238
239        return currentIterator.hasNext();
240    }
241
242    /**
243     * Returns the next Object of the current Iterator
244     *
245     * @return Object from the current Iterator
246     * @throws java.util.NoSuchElementException if all the Iterators are
247     * exhausted
248     */
249    public E next() {
250        lockChain();
251        updateCurrentIterator();
252        lastUsedIterator = currentIterator;
253
254        return currentIterator.next();
255    }
256
257    /**
258     * Removes from the underlying collection the last element returned by the
259     * Iterator. As with next() and hasNext(), this method calls remove() on the
260     * underlying Iterator. Therefore, this method may throw an
261     * UnsupportedOperationException if the underlying Iterator does not support
262     * this method.
263     *
264     * @throws UnsupportedOperationException if the remove operator is not
265     * supported by the underlying Iterator
266     * @throws IllegalStateException if the next method has not yet been called,
267     * or the remove method has already been called after the last call to the
268     * next method.
269     */
270    public void remove() {
271        lockChain();
272        if (currentIterator == null) {
273            updateCurrentIterator();
274        }
275        lastUsedIterator.remove();
276    }
277
278}