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