View Javadoc
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  
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.NoSuchElementException;
23  import java.util.Objects;
24  
25  import org.apache.commons.collections4.FluentIterable;
26  
27  /**
28   * Provides an interleaved iteration over the elements contained in a
29   * collection of Iterators.
30   * <p>
31   * Given two {@link Iterator} instances {@code A} and {@code B}, the
32   * {@link #next} method on this iterator will switch between {@code A.next()}
33   * and {@code B.next()} until both iterators are exhausted.
34   * </p>
35   *
36   * @param <E> the type of elements returned by this iterator.
37   * @since 4.1
38   */
39  public class ZippingIterator<E> implements Iterator<E> {
40  
41      /** The {@link Iterator}s to evaluate. */
42      private final Iterator<Iterator<? extends E>> iterators;
43  
44      /** The next iterator to use for next(). */
45      private Iterator<? extends E> nextIterator;
46  
47      /** The last iterator which was used for next(). */
48      private Iterator<? extends E> lastReturned;
49  
50      /**
51       * Constructs a new {@code ZippingIterator} that will provide
52       * interleaved iteration of the specified iterators.
53       *
54       * @param iterators  the array of iterators
55       * @throws NullPointerException if any iterator is null
56       */
57      public ZippingIterator(final Iterator<? extends E>... iterators) {
58          // create a mutable list to be able to remove exhausted iterators
59          final List<Iterator<? extends E>> list = new ArrayList<>();
60          for (final Iterator<? extends E> iterator : iterators) {
61              Objects.requireNonNull(iterator, "iterator");
62              list.add(iterator);
63          }
64          this.iterators = FluentIterable.of(list).loop().iterator();
65      }
66  
67      /**
68       * Constructs a new {@code ZippingIterator} that will provide
69       * interleaved iteration over the two given iterators.
70       *
71       * @param a  the first child iterator
72       * @param b  the second child iterator
73       * @throws NullPointerException if either iterator is null
74       */
75      @SuppressWarnings("unchecked")
76      public ZippingIterator(final Iterator<? extends E> a, final Iterator<? extends E> b) {
77          this(new Iterator[] {a, b});
78      }
79  
80      /**
81       * Constructs a new {@code ZippingIterator} that will provide
82       * interleaved iteration over the three given iterators.
83       *
84       * @param a  the first child iterator
85       * @param b  the second child iterator
86       * @param c  the third child iterator
87       * @throws NullPointerException if either iterator is null
88       */
89      @SuppressWarnings("unchecked")
90      public ZippingIterator(final Iterator<? extends E> a,
91                             final Iterator<? extends E> b,
92                             final Iterator<? extends E> c) {
93          this(new Iterator[] {a, b, c});
94      }
95  
96      /**
97       * Returns {@code true} if any child iterator has remaining elements.
98       *
99       * @return true if this iterator has remaining elements
100      */
101     @Override
102     public boolean hasNext() {
103         // the next iterator has already been determined
104         // this might happen if hasNext() is called multiple
105         if (nextIterator != null) {
106             return true;
107         }
108 
109         while (iterators.hasNext()) {
110             final Iterator<? extends E> childIterator = iterators.next();
111             if (childIterator.hasNext()) {
112                 nextIterator = childIterator;
113                 return true;
114             }
115             // iterator is exhausted, remove it
116             iterators.remove();
117         }
118         return false;
119     }
120 
121     /**
122      * Returns the next element from a child iterator.
123      *
124      * @return the next interleaved element
125      * @throws NoSuchElementException if no child iterator has any more elements
126      */
127     @Override
128     public E next() throws NoSuchElementException {
129         if (!hasNext()) {
130             throw new NoSuchElementException();
131         }
132 
133         final E val = nextIterator.next();
134         lastReturned = nextIterator;
135         nextIterator = null;
136         return val;
137     }
138 
139     /**
140      * Removes the last returned element from the child iterator that produced it.
141      *
142      * @throws IllegalStateException if there is no last returned element, or if
143      *   the last returned element has already been removed
144      */
145     @Override
146     public void remove() {
147         if (lastReturned == null) {
148             throw new IllegalStateException("No value can be removed at present");
149         }
150         lastReturned.remove();
151         lastReturned = null;
152     }
153 
154 }