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 }