View Javadoc

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 &lt; 0 or index &gt; 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 }