View Javadoc

1   /*
2    *  Copyright 1999-2004 The Apache Software Foundation
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package net.sf.collections15.iterators;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Iterator;
21  import java.util.List;
22  
23  import net.sf.collections15.list.UnmodifiableList;
24  
25  
26  /***
27   * An <code>IteratorChain</code> is an <code>Iterator</code> that wraps a number
28   * of <code>Iterator</code>s.
29   * <p>
30   * This class makes multiple iterators look like one to the caller
31   * When any method from the <code>Iterator</code> interface is called, the
32   * <code>IteratorChain</code> will delegate to a single underlying
33   * <code>Iterator</code>. The <code>IteratorChain</code> will invoke the
34   * <code>Iterator</code>s in sequence until all <code>Iterator</code>s are
35   * exhausted.
36   * <p>
37   * Under many circumstances, linking <code>Iterator</code>s together in this
38   * manner is more efficient (and convenient) than reading out the contents of
39   * each <code>Iterator</code> into a <code>List</code> and creating a new
40   * <code>Iterator</code>.
41   * <p>
42   * Calling a method that adds new <code>Iterator</code> <i>after a method in the
43   * <code>Iterator<code> interface has been called</i> will result in an
44   * <code>UnsupportedOperationException</code>. Subclasses should <i>take
45   * care</i> to not alter the underlying <code>List</code> of
46   * <code>Iterator</code>s.
47   * <p>
48   * NOTE: As from version 3.0, the IteratorChain may contain no
49   * iterators. In this case the class will function as an empty iterator.
50   * 
51   * @since Commons Collections 2.1
52   * @version $Revision: 1.1 $ $Date: 2005/02/27 18:48:37 $
53   * 
54   * @author Morgan Delagrange
55   * @author Stephen Colebourne
56   * @author Mauro Franceschini (port to 5.0)
57   */
58  public class IteratorChain<E> implements Iterator<E> {
59      // Instance fields
60      // -------------------------------------------------------------------------
61      
62  	/*** The chain of iterators */
63      protected final List<Iterator<E>> iteratorChain = 
64          new ArrayList<Iterator<E>>();
65      
66      /*** The index of the current iterator */
67      protected int currentIteratorIndex = 0;
68      
69      /*** The current iterator */
70      protected Iterator<E> currentIterator = null;
71      
72      /***
73       * The "last used" <code>Iterator</code> is the <code>Iterator</code> upon
74       * which <code>next()</code> or <code>hasNext()</code> was most recently
75       * called used for the <code>remove()</code> operation only.
76       */
77      protected Iterator<E> lastUsedIterator = null;
78      
79      /***
80       * <code>IteratorChain</code> is "locked" after the first time
81       * <code>next()</code> is called.
82       */
83      protected boolean isLocked = false;
84  
85      // Constructors
86      // -------------------------------------------------------------------------
87      
88      /***
89       * Construct an <code>IteratorChain</code> with no <code>Iterator</code>s.
90       * <p>
91       * You will normally use {@link #addIterator(Iterator)} to add some
92       * iterators after using this constructor.
93       */
94      public IteratorChain() {
95          super();
96      }
97  
98      /***
99       * Construct an <code>IteratorChain</code> with a list of
100      * <code>Iterator</code>s.
101      * 
102      * @param iterators the iterators to add to the <code>IteratorChain</code>
103      * 
104      * @throws NullPointerException if one of the provided iterator is
105      *  <code>null</code>
106      */
107     public IteratorChain(final Iterator<E>... iterators) {
108         super();
109         for (Iterator<E> iterator : iterators) {
110             addIterator(iterator);
111         }
112     }
113 
114     /***
115      * Constructs a new <code>IteratorChain</code> over the collection of
116      * iterators.
117      *
118      * @param iterators the collection of iterators
119      * 
120      * @throws NullPointerException if iterators collection is or contains
121      *  <code>null</code>
122      */
123     public IteratorChain(final Collection<Iterator<E>> iterators) {
124         for (Iterator<E> iterator : iterators) {
125             addIterator(iterator);
126         }
127     }
128     
129     // Public methods
130     // -------------------------------------------------------------------------
131     
132     /***
133      * Add an <code>Iterator</code> to the end of the chain. 
134      * 
135      * @param iterator the <code>Iterator</code> to add
136      * 
137      * @throws IllegalStateException if I've already started iterating
138      * @throws NullPointerException if the iterator is <code>null</code>
139      */
140     public void addIterator(final Iterator<E> iterator) {
141         checkLocked();
142         if (iterator == null) {
143             throw new NullPointerException("Iterator must not be null");
144         }
145         iteratorChain.add(iterator);
146     }
147 
148     /***
149      * Set the <code>Iterator</code> at the given index     
150      * 
151      * @param index the index of the <code>Iterator</code> to replace
152      * @param iterator <code>Iterator</code> to place at the given index
153      * 
154      * @throws IndexOutOfBoundsException if index &lt; 0 or index &gt; size()
155      * @throws IllegalStateException if I've already started iterating
156      * @throws NullPointerException if the iterator is <code>null</code>
157      */
158     public void setIterator(int index, Iterator<E> iterator)
159             throws IndexOutOfBoundsException {
160         checkLocked();
161         if (iterator == null) {
162             throw new NullPointerException("Iterator must not be null");
163         }
164         iteratorChain.set(index, iterator);
165     }
166 
167     /***
168      * Get the list of <code>Iterator</code>s (unmodifiable)
169      * 
170      * @return the unmodifiable list of iterators added
171      */
172     public List<Iterator<E>> getIterators() {
173         return UnmodifiableList.decorate(iteratorChain);
174     }
175 
176     /***
177      * Number of <code>Iterator</code>s in the current
178      * <code>IteratorChain</code>.
179      * 
180      * @return the <code>Iterator</code> count
181      */
182     public int size() {
183         return iteratorChain.size();
184     }
185 
186     /***
187      * Determine if modifications can still be made to the
188      * <code>IteratorChain</code>.
189      * <p>
190      * <code>IteratorChain</code>s cannot be modified once they have executed a
191      * method from the <code>Iterator</code> interface.
192      * 
193      * @return <code>true</code> if the <code>IteratorChain</code> cannot be
194      *  modified, <code>false</code> if it can 
195      */
196     public boolean isLocked() {
197         return isLocked;
198     }
199 
200     /***
201      * Checks whether the iterator chain is now locked and in use.
202      */
203     private void checkLocked() {
204         if (isLocked == true) {
205             throw new UnsupportedOperationException("IteratorChain cannot be " +
206                     "changed after the first use of a method from the " +
207                     "Iterator interface");
208         }
209     }
210 
211     /***
212      * Lock the chain so no more iterators can be added.
213      * <p>
214      * This must be called from all <code>Iterator</code> interface methods.
215      */
216     private void lockChain() {
217         if (isLocked == false) {
218             isLocked = true;
219         }
220     }
221 
222     /***
223      * Updates the current iterator field to ensure that the current
224      * <code>Iterator</code> is not exhausted
225      */
226     protected void updateCurrentIterator() {
227         if (currentIterator == null) {
228             if (iteratorChain.isEmpty()) {
229                 // @todo How to manage the EmptyIterator.INSTANCE warning?
230                 currentIterator = EmptyIterator.INSTANCE;
231             } else {
232                 currentIterator = iteratorChain.get(0);
233             }
234             // set last used iterator here, in case the user calls remove
235             // before calling hasNext() or next() (although they shouldn't)
236             lastUsedIterator = currentIterator;
237         }
238 
239         while (currentIterator.hasNext() == false &&
240                currentIteratorIndex < iteratorChain.size() - 1) {
241             currentIteratorIndex++;
242             currentIterator = iteratorChain.get(currentIteratorIndex);
243         }
244     }
245 
246     // Iterator interface methods
247     // -------------------------------------------------------------------------
248     
249     /***
250      * Return <code>true</code> if any <code>Iterator</code> in the
251      * <code>IteratorChain</code> has a remaining element.
252      * 
253      * @return <code>true</code> if elements remain
254      */
255     public boolean hasNext() {
256         lockChain();
257         updateCurrentIterator();
258         lastUsedIterator = currentIterator;
259 
260         return currentIterator.hasNext();
261     }
262 
263     /***
264      * Returns the next object of the current <code>Iterator</code>.
265      * 
266      * @return object from the current <code>Iterator</code>
267      * 
268      * @throws java.util.NoSuchElementException if all the
269      *  <code>Iterator</code>s are exhausted
270      */
271     public E next() {
272         lockChain();
273         updateCurrentIterator();
274         lastUsedIterator = currentIterator;
275 
276         return currentIterator.next();
277     }
278 
279     /***
280      * Removes from the underlying collection the last element 
281      * returned by the <code>Iterator</code>.
282      * <p>
283      * As with <code>next()</code> and <code>hasNext()</code>, this method calls
284      * <code>remove()</code> on the underlying <code>Iterator</code>. Therefore,
285      * this method may throw an <code>UnsupportedOperationException</code> if
286      * the underlying <code>Iterator</code> does not support this method. 
287      * 
288      * @throws UnsupportedOperationException if the remove operator is not
289      *  supported by the underlying <code>Iterator</code>
290      * @throws IllegalStateException if the next method has not yet been called,
291      *  or the remove method has already been called after the last call to the
292      *  next method
293      */
294     public void remove() {
295         lockChain();
296         updateCurrentIterator();
297 
298         lastUsedIterator.remove();
299     }
300 
301 }