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 * https://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.Collection;
20 import java.util.Iterator;
21 import java.util.LinkedList;
22 import java.util.Objects;
23 import java.util.Queue;
24
25 /**
26 * An IteratorChain is an Iterator that wraps a number of Iterators.
27 * <p>
28 * This class makes multiple iterators look like one to the caller. When any
29 * method from the Iterator interface is called, the IteratorChain will delegate
30 * to a single underlying Iterator. The IteratorChain will invoke the Iterators
31 * in sequence until all Iterators are exhausted.
32 * </p>
33 * <p>
34 * Under many circumstances, linking Iterators together in this manner is more
35 * efficient (and convenient) than reading out the contents of each Iterator
36 * into a List and creating a new Iterator.
37 * </p>
38 * <p>
39 * Calling a method that adds new Iterator <i>after a method in the Iterator
40 * interface has been called</i> will result in an UnsupportedOperationException.
41 * </p>
42 * <p>
43 * NOTE: As from version 3.0, the IteratorChain may contain no iterators. In
44 * this case the class will function as an empty iterator.
45 * </p>
46 * <p>
47 * NOTE: As from version 4.0, the IteratorChain stores the iterators in a queue
48 * and removes any reference to them as soon as they are not used anymore. Thus,
49 * the methods {@code setIterator(Iterator)} and {@code getIterators()} have been
50 * removed and {@link #size()} will return the number of remaining iterators in
51 * the queue.
52 * </p>
53 *
54 * @param <E> The type of elements in this iterator.
55 * @since 2.1
56 */
57 public class IteratorChain<E> implements Iterator<E> {
58
59 /** The chain of iterators */
60 private final Queue<Iterator<? extends E>> iteratorQueue = new LinkedList<>();
61
62 /** The current iterator */
63 private Iterator<? extends E> currentIterator;
64
65 /**
66 * The "last used" Iterator is the Iterator upon which next()
67 * was most recently called used for the remove() operation only
68 */
69 private Iterator<? extends E> lastUsedIterator;
70
71 /**
72 * ComparatorChain is "locked" after the first time compare(Object, Object)
73 * is called
74 */
75 private boolean isLocked;
76
77 /**
78 * Contains the result of the last hasNext() call until next() is invoked
79 */
80 private Boolean cachedHasNextValue;
81
82 /**
83 * Constructs an IteratorChain with no Iterators.
84 * <p>
85 * You will normally use {@link #addIterator(Iterator)} to add some
86 * iterators after using this constructor.
87 * </p>
88 */
89 public IteratorChain() {
90 }
91
92 /**
93 * Constructs a new {@code IteratorChain} over the collection of
94 * iterators.
95 * <p>
96 * This method takes a collection of iterators. The newly constructed
97 * iterator will iterate through each one of the input iterators in turn.
98 * </p>
99 *
100 * @param iteratorQueue The collection of iterators, not null
101 * @throws NullPointerException if iterators collection is or contains null
102 * @throws ClassCastException if iterators collection doesn't contain an
103 * iterator
104 */
105 public IteratorChain(final Collection<? extends Iterator<? extends E>> iteratorQueue) {
106 for (final Iterator<? extends E> iterator : iteratorQueue) {
107 addIterator(iterator);
108 }
109 }
110
111 /**
112 * Constructs an IteratorChain with a single Iterator.
113 * <p>
114 * This method takes one iterator. The newly constructed iterator will
115 * iterate through that iterator. Thus calling this constructor on its own
116 * will have no effect other than decorating the input iterator.
117 * </p>
118 * <p>
119 * You will normally use {@link #addIterator(Iterator)} to add some more
120 * iterators after using this constructor.
121 * </p>
122 *
123 * @param iterator The first child iterator in the IteratorChain, not null
124 * @throws NullPointerException if the iterator is null
125 */
126 public IteratorChain(final Iterator<? extends E> iterator) {
127 addIterator(iterator);
128 }
129
130 /**
131 * Constructs a new {@code IteratorChain} over the array of iterators.
132 * <p>
133 * This method takes an array of iterators. The newly constructed iterator
134 * will iterate through each one of the input iterators in turn.
135 * </p>
136 *
137 * @param iteratorQueue The array of iterators, not null
138 * @throws NullPointerException if iterators array is or contains null
139 */
140 public IteratorChain(final Iterator<? extends E>... iteratorQueue) {
141 for (final Iterator<? extends E> element : iteratorQueue) {
142 addIterator(element);
143 }
144 }
145
146 /**
147 * Constructs a new {@code IteratorChain} over the two given iterators.
148 * <p>
149 * This method takes two iterators. The newly constructed iterator will
150 * iterate through each one of the input iterators in turn.
151 * </p>
152 *
153 * @param first The first child iterator in the IteratorChain, not null
154 * @param second The second child iterator in the IteratorChain, not null
155 * @throws NullPointerException if either iterator is null
156 */
157 public IteratorChain(final Iterator<? extends E> first, final Iterator<? extends E> second) {
158 addIterator(first);
159 addIterator(second);
160 }
161
162 /**
163 * Add an Iterator to the end of the chain
164 *
165 * @param iterator Iterator to add
166 * @throws IllegalStateException if I've already started iterating
167 * @throws NullPointerException if the iterator is null
168 */
169 public void addIterator(final Iterator<? extends E> iterator) {
170 checkLocked();
171 Objects.requireNonNull(iterator, "iterator");
172 if (iterator instanceof UnmodifiableIterator) {
173 final Iterator<? extends E> underlyingIterator = ((UnmodifiableIterator) iterator).unwrap();
174 if (underlyingIterator instanceof IteratorChain) {
175 // in case it is an IteratorChain, wrap every underlying iterators as unmodifiable
176 // multiple rechainings would otherwise lead to exponential growing number of function calls
177 // when the iteratorChain gets used.
178 for (final Iterator<? extends E> nestedIterator : ((IteratorChain<? extends E>) underlyingIterator).iteratorQueue) {
179 iteratorQueue.add(UnmodifiableIterator.unmodifiableIterator(nestedIterator));
180 }
181 } else {
182 // we don't know anything about the underlying iterator, simply add it here
183 iteratorQueue.add(iterator);
184 }
185 } else if (iterator instanceof IteratorChain) {
186 // add the wrapped iterators directly instead of reusing the given instance
187 // multiple rechainings would otherwise lead to exponential growing number of function calls
188 // when the iteratorChain gets used.
189 iteratorQueue.addAll(((IteratorChain) iterator).iteratorQueue);
190 } else {
191 // arbitrary other iterator
192 iteratorQueue.add(iterator);
193 }
194 }
195
196 /**
197 * Checks whether the iterator chain is now locked and in use.
198 */
199 private void checkLocked() {
200 if (isLocked) {
201 throw new UnsupportedOperationException("IteratorChain cannot be changed after the first use of a method from the Iterator interface");
202 }
203 }
204
205 /**
206 * Return true if any Iterator in the IteratorChain has a remaining element.
207 *
208 * @return true if elements remain
209 */
210 @Override
211 public boolean hasNext() {
212 lockChain();
213 if (cachedHasNextValue == null) {
214 updateCurrentIterator();
215 }
216 return cachedHasNextValue;
217 }
218
219 /**
220 * Determine if modifications can still be made to the IteratorChain.
221 * IteratorChains cannot be modified once they have executed a method from
222 * the Iterator interface.
223 *
224 * @return true if IteratorChain cannot be modified, false if it can
225 */
226 public boolean isLocked() {
227 return isLocked;
228 }
229
230 /**
231 * Lock the chain so no more iterators can be added. This must be called
232 * from all Iterator interface methods.
233 */
234 private void lockChain() {
235 if (!isLocked) {
236 isLocked = true;
237 }
238 }
239
240 /**
241 * Returns the next Object of the current Iterator
242 *
243 * @return Object from the current Iterator
244 * @throws java.util.NoSuchElementException if all the Iterators are
245 * exhausted
246 */
247 @Override
248 public E next() {
249 lockChain();
250 if (cachedHasNextValue == null) {
251 updateCurrentIterator();
252 }
253 lastUsedIterator = currentIterator;
254 cachedHasNextValue = null;
255 return currentIterator.next();
256 }
257
258 /**
259 * Removes from the underlying collection the last element returned by the
260 * Iterator. As with next() and hasNext(), this method calls remove() on the
261 * underlying Iterator. Therefore, this method may throw an
262 * UnsupportedOperationException if the underlying Iterator does not support
263 * this method.
264 *
265 * @throws UnsupportedOperationException if the remove operator is not
266 * supported by the underlying Iterator
267 * @throws IllegalStateException if the next method has not yet been called,
268 * or the remove method has already been called after the last call to the
269 * next method.
270 */
271 @Override
272 public void remove() {
273 lockChain();
274 if (lastUsedIterator == null) {
275 throw new IllegalStateException("remove() has been invoked without next()");
276 }
277 lastUsedIterator.remove();
278 lastUsedIterator = null; // must never be used twice without next() being invoked
279 }
280
281 /**
282 * Returns the remaining number of Iterators in the current IteratorChain.
283 *
284 * @return Iterator count
285 */
286 public int size() {
287 return iteratorQueue.size();
288 }
289
290 /**
291 * Updates the current iterator field to ensure that the current Iterator is
292 * not exhausted
293 */
294 protected void updateCurrentIterator() {
295 if (currentIterator == null) {
296 if (iteratorQueue.isEmpty()) {
297 currentIterator = EmptyIterator.<E>emptyIterator();
298 } else {
299 currentIterator = iteratorQueue.remove();
300 }
301 }
302 while (true) {
303 cachedHasNextValue = currentIterator.hasNext();
304 if (cachedHasNextValue) {
305 break;
306 }
307 if (iteratorQueue.isEmpty()) {
308 break;
309 }
310 currentIterator = iteratorQueue.remove();
311 }
312 }
313 }