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}