ZippingIterator.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.ArrayList;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.NoSuchElementException;
  22. import java.util.Objects;

  23. import org.apache.commons.collections4.FluentIterable;

  24. /**
  25.  * Provides an interleaved iteration over the elements contained in a
  26.  * collection of Iterators.
  27.  * <p>
  28.  * Given two {@link Iterator} instances {@code A} and {@code B}, the
  29.  * {@link #next} method on this iterator will switch between {@code A.next()}
  30.  * and {@code B.next()} until both iterators are exhausted.
  31.  * </p>
  32.  *
  33.  * @param <E> the type of elements returned by this iterator.
  34.  * @since 4.1
  35.  */
  36. public class ZippingIterator<E> implements Iterator<E> {

  37.     /** The {@link Iterator}s to evaluate. */
  38.     private final Iterator<Iterator<? extends E>> iterators;

  39.     /** The next iterator to use for next(). */
  40.     private Iterator<? extends E> nextIterator;

  41.     /** The last iterator which was used for next(). */
  42.     private Iterator<? extends E> lastReturned;

  43.     /**
  44.      * Constructs a new {@code ZippingIterator} that will provide
  45.      * interleaved iteration of the specified iterators.
  46.      *
  47.      * @param iterators  the array of iterators
  48.      * @throws NullPointerException if any iterator is null
  49.      */
  50.     public ZippingIterator(final Iterator<? extends E>... iterators) {
  51.         // create a mutable list to be able to remove exhausted iterators
  52.         final List<Iterator<? extends E>> list = new ArrayList<>();
  53.         for (final Iterator<? extends E> iterator : iterators) {
  54.             Objects.requireNonNull(iterator, "iterator");
  55.             list.add(iterator);
  56.         }
  57.         this.iterators = FluentIterable.of(list).loop().iterator();
  58.     }

  59.     /**
  60.      * Constructs a new {@code ZippingIterator} that will provide
  61.      * interleaved iteration over the two given iterators.
  62.      *
  63.      * @param a  the first child iterator
  64.      * @param b  the second child iterator
  65.      * @throws NullPointerException if either iterator is null
  66.      */
  67.     @SuppressWarnings("unchecked")
  68.     public ZippingIterator(final Iterator<? extends E> a, final Iterator<? extends E> b) {
  69.         this(new Iterator[] {a, b});
  70.     }

  71.     /**
  72.      * Constructs a new {@code ZippingIterator} that will provide
  73.      * interleaved iteration over the three given iterators.
  74.      *
  75.      * @param a  the first child iterator
  76.      * @param b  the second child iterator
  77.      * @param c  the third child iterator
  78.      * @throws NullPointerException if either iterator is null
  79.      */
  80.     @SuppressWarnings("unchecked")
  81.     public ZippingIterator(final Iterator<? extends E> a,
  82.                            final Iterator<? extends E> b,
  83.                            final Iterator<? extends E> c) {
  84.         this(new Iterator[] {a, b, c});
  85.     }

  86.     /**
  87.      * Returns {@code true} if any child iterator has remaining elements.
  88.      *
  89.      * @return true if this iterator has remaining elements
  90.      */
  91.     @Override
  92.     public boolean hasNext() {
  93.         // the next iterator has already been determined
  94.         // this might happen if hasNext() is called multiple
  95.         if (nextIterator != null) {
  96.             return true;
  97.         }

  98.         while (iterators.hasNext()) {
  99.             final Iterator<? extends E> childIterator = iterators.next();
  100.             if (childIterator.hasNext()) {
  101.                 nextIterator = childIterator;
  102.                 return true;
  103.             }
  104.             // iterator is exhausted, remove it
  105.             iterators.remove();
  106.         }
  107.         return false;
  108.     }

  109.     /**
  110.      * Returns the next element from a child iterator.
  111.      *
  112.      * @return the next interleaved element
  113.      * @throws NoSuchElementException if no child iterator has any more elements
  114.      */
  115.     @Override
  116.     public E next() throws NoSuchElementException {
  117.         if (!hasNext()) {
  118.             throw new NoSuchElementException();
  119.         }

  120.         final E val = nextIterator.next();
  121.         lastReturned = nextIterator;
  122.         nextIterator = null;
  123.         return val;
  124.     }

  125.     /**
  126.      * Removes the last returned element from the child iterator that produced it.
  127.      *
  128.      * @throws IllegalStateException if there is no last returned element, or if
  129.      *   the last returned element has already been removed
  130.      */
  131.     @Override
  132.     public void remove() {
  133.         if (lastReturned == null) {
  134.             throw new IllegalStateException("No value can be removed at present");
  135.         }
  136.         lastReturned.remove();
  137.         lastReturned = null;
  138.     }

  139. }