LazyIteratorChain.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.iterators;

  18. import java.util.Iterator;

  19. /**
  20.  * An LazyIteratorChain is an Iterator that wraps a number of Iterators in a lazy manner.
  21.  * <p>
  22.  * This class makes multiple iterators look like one to the caller. When any
  23.  * method from the Iterator interface is called, the LazyIteratorChain will delegate
  24.  * to a single underlying Iterator. The LazyIteratorChain will invoke the Iterators
  25.  * in sequence until all Iterators are exhausted.
  26.  * </p>
  27.  * <p>
  28.  * The Iterators are provided by {@link #nextIterator(int)} which has to be overridden by
  29.  * subclasses and allows to lazily create the Iterators as they are accessed:
  30.  * </p>
  31.  * <pre>
  32.  * return new LazyIteratorChain&lt;String&gt;() {
  33.  *     protected Iterator&lt;String&gt; nextIterator(int count) {
  34.  *         return count == 1 ? Arrays.asList("foo", "bar").iterator() : null;
  35.  *     }
  36.  * };
  37.  * </pre>
  38.  * <p>
  39.  * Once the inner Iterator's {@link Iterator#hasNext()} method returns false,
  40.  * {@link #nextIterator(int)} will be called to obtain another iterator, and so on
  41.  * until {@link #nextIterator(int)} returns null, indicating that the chain is exhausted.
  42.  * </p>
  43.  * <p>
  44.  * NOTE: The LazyIteratorChain may contain no iterators. In this case the class will
  45.  * function as an empty iterator.
  46.  * </p>
  47.  *
  48.  * @param <E> the type of elements in this iterator.
  49.  * @since 4.0
  50.  */
  51. public abstract class LazyIteratorChain<E> implements Iterator<E> {

  52.     /** The number of times {@link #next()} was already called. */
  53.     private int callCounter;

  54.     /** Indicates that the Iterator chain has been exhausted. */
  55.     private boolean chainExhausted;

  56.     /** The current iterator. */
  57.     private Iterator<? extends E> currentIterator;

  58.     /**
  59.      * The "last used" Iterator is the Iterator upon which next() or hasNext()
  60.      * was most recently called used for the remove() operation only.
  61.      */
  62.     private Iterator<? extends E> lastUsedIterator;

  63.     /**
  64.      * Constructs a new instance.
  65.      */
  66.     public LazyIteratorChain() {
  67.         // empty
  68.     }

  69.     /**
  70.      * Return true if any Iterator in the chain has a remaining element.
  71.      *
  72.      * @return true if elements remain
  73.      */
  74.     @Override
  75.     public boolean hasNext() {
  76.         updateCurrentIterator();
  77.         lastUsedIterator = currentIterator;
  78.         return currentIterator.hasNext();
  79.     }

  80.     /**
  81.      * Returns the next element of the current Iterator
  82.      *
  83.      * @return element from the current Iterator
  84.      * @throws java.util.NoSuchElementException if all the Iterators are exhausted
  85.      */
  86.     @Override
  87.     public E next() {
  88.         updateCurrentIterator();
  89.         lastUsedIterator = currentIterator;
  90.         return currentIterator.next();
  91.     }

  92.     /**
  93.      * Gets the next iterator after the previous one has been exhausted.
  94.      * <p>
  95.      * This method <strong>MUST</strong> return null when there are no more iterators.
  96.      * </p>
  97.      *
  98.      * @param count the number of time this method has been called (starts with 1)
  99.      * @return the next iterator, or null if there are no more.
  100.      */
  101.     protected abstract Iterator<? extends E> nextIterator(int count);

  102.     /**
  103.      * Removes from the underlying collection the last element returned by the Iterator.
  104.      * <p>
  105.      * As with next() and hasNext(), this method calls remove() on the underlying Iterator.
  106.      * Therefore, this method may throw an UnsupportedOperationException if the underlying
  107.      * Iterator does not support this method.
  108.      * </p>
  109.      *
  110.      * @throws UnsupportedOperationException if the remove operator is not
  111.      *   supported by the underlying Iterator
  112.      * @throws IllegalStateException if the next method has not yet been called,
  113.      *   or the remove method has already been called after the last call to the next method.
  114.      */
  115.     @Override
  116.     public void remove() {
  117.         if (currentIterator == null) {
  118.             updateCurrentIterator();
  119.         }
  120.         lastUsedIterator.remove();
  121.     }

  122.     /**
  123.      * Updates the current iterator field to ensure that the current Iterator
  124.      * is not exhausted.
  125.      */
  126.     private void updateCurrentIterator() {
  127.         if (callCounter == 0) {
  128.             currentIterator = nextIterator(++callCounter);
  129.             if (currentIterator == null) {
  130.                 currentIterator = EmptyIterator.<E>emptyIterator();
  131.                 chainExhausted = true;
  132.             }
  133.             // set last used iterator here, in case the user calls remove
  134.             // before calling hasNext() or next() (although they shouldn't)
  135.             lastUsedIterator = currentIterator;
  136.         }
  137.         while (!currentIterator.hasNext() && !chainExhausted) {
  138.             final Iterator<? extends E> nextIterator = nextIterator(++callCounter);
  139.             if (nextIterator != null) {
  140.                 currentIterator = nextIterator;
  141.             } else {
  142.                 chainExhausted = true;
  143.             }
  144.         }
  145.     }

  146. }