001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.collections.iterators;
018
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.Iterator;
022 import java.util.List;
023
024 import org.apache.commons.collections.list.UnmodifiableList;
025
026 /**
027 * An IteratorChain is an Iterator that wraps a number of Iterators.
028 * <p>
029 * This class makes multiple iterators look like one to the caller
030 * When any method from the Iterator interface is called, the IteratorChain
031 * will delegate to a single underlying Iterator. The IteratorChain will
032 * invoke the Iterators in sequence until all Iterators are exhausted.
033 * <p>
034 * Under many circumstances, linking Iterators together in this manner is
035 * more efficient (and convenient) than reading out the contents of each
036 * Iterator into a List and creating a new Iterator.
037 * <p>
038 * Calling a method that adds new Iterator<i>after a method in the Iterator
039 * interface has been called</i> will result in an UnsupportedOperationException.
040 * Subclasses should <i>take care</i> to not alter the underlying List of Iterators.
041 * <p>
042 * NOTE: As from version 3.0, the IteratorChain may contain no
043 * iterators. In this case the class will function as an empty iterator.
044 *
045 * @since Commons Collections 2.1
046 * @version $Revision: 647116 $ $Date: 2008-04-11 12:23:08 +0100 (Fri, 11 Apr 2008) $
047 *
048 * @author Morgan Delagrange
049 * @author Stephen Colebourne
050 */
051 public class IteratorChain implements Iterator {
052
053 /** The chain of iterators */
054 protected final List iteratorChain = new ArrayList();
055 /** The index of the current iterator */
056 protected int currentIteratorIndex = 0;
057 /** The current iterator */
058 protected Iterator currentIterator = null;
059 /**
060 * The "last used" Iterator is the Iterator upon which
061 * next() or hasNext() was most recently called
062 * used for the remove() operation only
063 */
064 protected Iterator lastUsedIterator = null;
065 /**
066 * ComparatorChain is "locked" after the first time
067 * compare(Object,Object) is called
068 */
069 protected boolean isLocked = false;
070
071 //-----------------------------------------------------------------------
072 /**
073 * Construct an IteratorChain with no Iterators.
074 * <p>
075 * You will normally use {@link #addIterator(Iterator)} to add
076 * some iterators after using this constructor.
077 */
078 public IteratorChain() {
079 super();
080 }
081
082 /**
083 * Construct an IteratorChain with a single Iterator.
084 *
085 * @param iterator first Iterator in the IteratorChain
086 * @throws NullPointerException if the iterator is null
087 */
088 public IteratorChain(Iterator iterator) {
089 super();
090 addIterator(iterator);
091 }
092
093 /**
094 * Constructs a new <code>IteratorChain</code> over the two
095 * given iterators.
096 *
097 * @param a the first child iterator
098 * @param b the second child iterator
099 * @throws NullPointerException if either iterator is null
100 */
101 public IteratorChain(Iterator a, Iterator b) {
102 super();
103 addIterator(a);
104 addIterator(b);
105 }
106
107 /**
108 * Constructs a new <code>IteratorChain</code> over the array
109 * of iterators.
110 *
111 * @param iterators the array of iterators
112 * @throws NullPointerException if iterators array is or contains null
113 */
114 public IteratorChain(Iterator[] iterators) {
115 super();
116 for (int i = 0; i < iterators.length; i++) {
117 addIterator(iterators[i]);
118 }
119 }
120
121 /**
122 * Constructs a new <code>IteratorChain</code> over the collection
123 * of iterators.
124 *
125 * @param iterators the collection of iterators
126 * @throws NullPointerException if iterators collection is or contains null
127 * @throws ClassCastException if iterators collection doesn't contain an iterator
128 */
129 public IteratorChain(Collection iterators) {
130 super();
131 for (Iterator it = iterators.iterator(); it.hasNext();) {
132 Iterator item = (Iterator) it.next();
133 addIterator(item);
134 }
135 }
136
137 //-----------------------------------------------------------------------
138 /**
139 * Add an Iterator to the end of the chain
140 *
141 * @param iterator Iterator to add
142 * @throws IllegalStateException if I've already started iterating
143 * @throws NullPointerException if the iterator is null
144 */
145 public void addIterator(Iterator iterator) {
146 checkLocked();
147 if (iterator == null) {
148 throw new NullPointerException("Iterator must not be null");
149 }
150 iteratorChain.add(iterator);
151 }
152
153 /**
154 * Set the Iterator at the given index
155 *
156 * @param index index of the Iterator to replace
157 * @param iterator Iterator to place at the given index
158 * @throws IndexOutOfBoundsException if index < 0 or index > size()
159 * @throws IllegalStateException if I've already started iterating
160 * @throws NullPointerException if the iterator is null
161 */
162 public void setIterator(int index, Iterator iterator) throws IndexOutOfBoundsException {
163 checkLocked();
164 if (iterator == null) {
165 throw new NullPointerException("Iterator must not be null");
166 }
167 iteratorChain.set(index, iterator);
168 }
169
170 /**
171 * Get the list of Iterators (unmodifiable)
172 *
173 * @return the unmodifiable list of iterators added
174 */
175 public List getIterators() {
176 return UnmodifiableList.decorate(iteratorChain);
177 }
178
179 /**
180 * Number of Iterators in the current IteratorChain.
181 *
182 * @return Iterator count
183 */
184 public int size() {
185 return iteratorChain.size();
186 }
187
188 /**
189 * Determine if modifications can still be made to the IteratorChain.
190 * IteratorChains cannot be modified once they have executed a method
191 * from the Iterator interface.
192 *
193 * @return true if IteratorChain cannot be modified, false if it can
194 */
195 public boolean isLocked() {
196 return isLocked;
197 }
198
199 /**
200 * Checks whether the iterator chain is now locked and in use.
201 */
202 private void checkLocked() {
203 if (isLocked == true) {
204 throw new UnsupportedOperationException("IteratorChain cannot be changed after the first use of a method from the Iterator interface");
205 }
206 }
207
208 /**
209 * Lock the chain so no more iterators can be added.
210 * This must be called from all Iterator interface methods.
211 */
212 private void lockChain() {
213 if (isLocked == false) {
214 isLocked = true;
215 }
216 }
217
218 /**
219 * Updates the current iterator field to ensure that the current Iterator
220 * is not exhausted
221 */
222 protected void updateCurrentIterator() {
223 if (currentIterator == null) {
224 if (iteratorChain.isEmpty()) {
225 currentIterator = EmptyIterator.INSTANCE;
226 } else {
227 currentIterator = (Iterator) iteratorChain.get(0);
228 }
229 // set last used iterator here, in case the user calls remove
230 // before calling hasNext() or next() (although they shouldn't)
231 lastUsedIterator = currentIterator;
232 }
233
234 while (currentIterator.hasNext() == false && currentIteratorIndex < iteratorChain.size() - 1) {
235 currentIteratorIndex++;
236 currentIterator = (Iterator) iteratorChain.get(currentIteratorIndex);
237 }
238 }
239
240 //-----------------------------------------------------------------------
241 /**
242 * Return true if any Iterator in the IteratorChain has a remaining element.
243 *
244 * @return true if elements remain
245 */
246 public boolean hasNext() {
247 lockChain();
248 updateCurrentIterator();
249 lastUsedIterator = currentIterator;
250
251 return currentIterator.hasNext();
252 }
253
254 /**
255 * Returns the next Object of the current Iterator
256 *
257 * @return Object from the current Iterator
258 * @throws java.util.NoSuchElementException if all the Iterators are exhausted
259 */
260 public Object next() {
261 lockChain();
262 updateCurrentIterator();
263 lastUsedIterator = currentIterator;
264
265 return currentIterator.next();
266 }
267
268 /**
269 * Removes from the underlying collection the last element
270 * returned by the Iterator. As with next() and hasNext(),
271 * this method calls remove() on the underlying Iterator.
272 * Therefore, this method may throw an
273 * UnsupportedOperationException if the underlying
274 * Iterator does not support this method.
275 *
276 * @throws UnsupportedOperationException
277 * if the remove operator is not supported by the underlying Iterator
278 * @throws IllegalStateException
279 * if the next method has not yet been called, or the remove method has
280 * already been called after the last call to the next method.
281 */
282 public void remove() {
283 lockChain();
284 if (currentIterator == null) {
285 updateCurrentIterator();
286 }
287 lastUsedIterator.remove();
288 }
289
290 }