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