    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.Iterator;
    020
    021/**
    022 * An LazyIteratorChain is an Iterator that wraps a number of Iterators in a lazy manner.
    023 * <p>
    024 * This class makes multiple iterators look like one to the caller. When any
    025 * method from the Iterator interface is called, the LazyIteratorChain will delegate
    026 * to a single underlying Iterator. The LazyIteratorChain will invoke the Iterators
    027 * in sequence until all Iterators are exhausted.
    028 * </p>
    029 * <p>
    030 * The Iterators are provided by {@link #nextIterator(int)} which has to be overridden by
    031 * subclasses and allows to lazily create the Iterators as they are accessed:
    032 * </p>
    033 * <pre>
    034 * return new LazyIteratorChain&lt;String&gt;() {
    035 *     protected Iterator&lt;String&gt; nextIterator(int count) {
    036 *         return count == 1 ? Arrays.asList("foo", "bar").iterator() : null;
    037 *     }
    038 * };
    039 * </pre>
    040 * <p>
    041 * Once the inner Iterator's {@link Iterator#hasNext()} method returns false,
    042 * {@link #nextIterator(int)} will be called to obtain another iterator, and so on
    043 * until {@link #nextIterator(int)} returns null, indicating that the chain is exhausted.
    044 * </p>
    045 * <p>
    046 * NOTE: The LazyIteratorChain may contain no iterators. In this case the class will
    047 * function as an empty iterator.
    048 * </p>
    049 *
    050 * @param <E> the type of elements in this iterator.
    051 * @since 4.0
    052 */
    053public abstract class LazyIteratorChain<E> implements Iterator<E> {
    054
    055    /** The number of times {@link #next()} was already called. */
    056    private int callCounter;
    057
    058    /** Indicates that the Iterator chain has been exhausted. */
    059    private boolean chainExhausted;
    060
    061    /** The current iterator. */
    062    private Iterator<? extends E> currentIterator;
    063
    064    /**
    065     * The "last used" Iterator is the Iterator upon which next() or hasNext()
    066     * was most recently called used for the remove() operation only.
    067     */
    068    private Iterator<? extends E> lastUsedIterator;
    069
    070    /**
    071     * Constructs a new instance.
    072     */
    073    public LazyIteratorChain() {
    074        // empty
    075    }
    076
    077    /**
    078     * Return true if any Iterator in the chain has a remaining element.
    079     *
    080     * @return true if elements remain
    081     */
    082    @Override
    083    public boolean hasNext() {
    084        updateCurrentIterator();
    085        lastUsedIterator = currentIterator;
    086        return currentIterator.hasNext();
    087    }
    088
    089    /**
    090     * Returns the next element of the current Iterator
    091     *
    092     * @return element from the current Iterator
    093     * @throws java.util.NoSuchElementException if all the Iterators are exhausted
    094     */
    095    @Override
    096    public E next() {
    097        updateCurrentIterator();
    098        lastUsedIterator = currentIterator;
    099        return currentIterator.next();
    100    }
    101
    102    /**
    103     * Gets the next iterator after the previous one has been exhausted.
    104     * <p>
    105     * This method <strong>MUST</strong> return null when there are no more iterators.
    106     * </p>
    107     *
    108     * @param count the number of time this method has been called (starts with 1)
    109     * @return the next iterator, or null if there are no more.
    110     */
    111    protected abstract Iterator<? extends E> nextIterator(int count);
    112
    113    /**
    114     * Removes from the underlying collection the last element returned by the Iterator.
    115     * <p>
    116     * As with next() and hasNext(), this method calls remove() on the underlying Iterator.
    117     * Therefore, this method may throw an UnsupportedOperationException if the underlying
    118     * Iterator does not support this method.
    119     * </p>
    120     *
    121     * @throws UnsupportedOperationException if the remove operator is not
    122     *   supported by the underlying Iterator
    123     * @throws IllegalStateException if the next method has not yet been called,
    124     *   or the remove method has already been called after the last call to the next method.
    125     */
    126    @Override
    127    public void remove() {
    128        if (currentIterator == null) {
    129            updateCurrentIterator();
    130        }
    131        lastUsedIterator.remove();
    132    }
    133
    134    /**
    135     * Updates the current iterator field to ensure that the current Iterator
    136     * is not exhausted.
    137     */
    138    private void updateCurrentIterator() {
    139        if (callCounter == 0) {
    140            currentIterator = nextIterator(++callCounter);
    141            if (currentIterator == null) {
    142                currentIterator = EmptyIterator.<E>emptyIterator();
    143                chainExhausted = true;
    144            }
    145            // set last used iterator here, in case the user calls remove
    146            // before calling hasNext() or next() (although they shouldn't)
    147            lastUsedIterator = currentIterator;
    148        }
    149        while (!currentIterator.hasNext() && !chainExhausted) {
    150            final Iterator<? extends E> nextIterator = nextIterator(++callCounter);
    151            if (nextIterator != null) {
    152                currentIterator = nextIterator;
    153            } else {
    154                chainExhausted = true;
    155            }
    156        }
    157    }
    158
    159}