    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}