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.Iterator;
20
21 /**
22 * An LazyIteratorChain is an Iterator that wraps a number of Iterators in a lazy manner.
23 * <p>
24 * This class makes multiple iterators look like one to the caller. When any
25 * method from the Iterator interface is called, the LazyIteratorChain will delegate
26 * to a single underlying Iterator. The LazyIteratorChain will invoke the Iterators
27 * in sequence until all Iterators are exhausted.
28 * </p>
29 * <p>
30 * The Iterators are provided by {@link #nextIterator(int)} which has to be overridden by
31 * subclasses and allows to lazily create the Iterators as they are accessed:
32 * </p>
33 * <pre>
34 * return new LazyIteratorChain<String>() {
35 * protected Iterator<String> nextIterator(int count) {
36 * return count == 1 ? Arrays.asList("foo", "bar").iterator() : null;
37 * }
38 * };
39 * </pre>
40 * <p>
41 * Once the inner Iterator's {@link Iterator#hasNext()} method returns false,
42 * {@link #nextIterator(int)} will be called to obtain another iterator, and so on
43 * until {@link #nextIterator(int)} returns null, indicating that the chain is exhausted.
44 * </p>
45 * <p>
46 * NOTE: The LazyIteratorChain may contain no iterators. In this case the class will
47 * function as an empty iterator.
48 * </p>
49 *
50 * @param <E> the type of elements in this iterator.
51 * @since 4.0
52 */
53 public abstract class LazyIteratorChain<E> implements Iterator<E> {
54
55 /** The number of times {@link #next()} was already called. */
56 private int callCounter;
57
58 /** Indicates that the Iterator chain has been exhausted. */
59 private boolean chainExhausted;
60
61 /** The current iterator. */
62 private Iterator<? extends E> currentIterator;
63
64 /**
65 * The "last used" Iterator is the Iterator upon which next() or hasNext()
66 * was most recently called used for the remove() operation only.
67 */
68 private Iterator<? extends E> lastUsedIterator;
69
70 /**
71 * Constructs a new instance.
72 */
73 public LazyIteratorChain() {
74 // empty
75 }
76
77 /**
78 * Return true if any Iterator in the chain has a remaining element.
79 *
80 * @return true if elements remain
81 */
82 @Override
83 public boolean hasNext() {
84 updateCurrentIterator();
85 lastUsedIterator = currentIterator;
86 return currentIterator.hasNext();
87 }
88
89 /**
90 * Returns the next element of the current Iterator
91 *
92 * @return element from the current Iterator
93 * @throws java.util.NoSuchElementException if all the Iterators are exhausted
94 */
95 @Override
96 public E next() {
97 updateCurrentIterator();
98 lastUsedIterator = currentIterator;
99 return currentIterator.next();
100 }
101
102 /**
103 * Gets the next iterator after the previous one has been exhausted.
104 * <p>
105 * This method <strong>MUST</strong> return null when there are no more iterators.
106 * </p>
107 *
108 * @param count the number of time this method has been called (starts with 1)
109 * @return the next iterator, or null if there are no more.
110 */
111 protected abstract Iterator<? extends E> nextIterator(int count);
112
113 /**
114 * Removes from the underlying collection the last element returned by the Iterator.
115 * <p>
116 * As with next() and hasNext(), this method calls remove() on the underlying Iterator.
117 * Therefore, this method may throw an UnsupportedOperationException if the underlying
118 * Iterator does not support this method.
119 * </p>
120 *
121 * @throws UnsupportedOperationException if the remove operator is not
122 * supported by the underlying Iterator
123 * @throws IllegalStateException if the next method has not yet been called,
124 * or the remove method has already been called after the last call to the next method.
125 */
126 @Override
127 public void remove() {
128 if (currentIterator == null) {
129 updateCurrentIterator();
130 }
131 lastUsedIterator.remove();
132 }
133
134 /**
135 * Updates the current iterator field to ensure that the current Iterator
136 * is not exhausted.
137 */
138 private void updateCurrentIterator() {
139 if (callCounter == 0) {
140 currentIterator = nextIterator(++callCounter);
141 if (currentIterator == null) {
142 currentIterator = EmptyIterator.<E>emptyIterator();
143 chainExhausted = true;
144 }
145 // set last used iterator here, in case the user calls remove
146 // before calling hasNext() or next() (although they shouldn't)
147 lastUsedIterator = currentIterator;
148 }
149 while (!currentIterator.hasNext() && !chainExhausted) {
150 final Iterator<? extends E> nextIterator = nextIterator(++callCounter);
151 if (nextIterator != null) {
152 currentIterator = nextIterator;
153 } else {
154 chainExhausted = true;
155 }
156 }
157 }
158
159 }