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.collections4.iterators;
18  
19  import java.util.Collection;
20  import java.util.Iterator;
21  import java.util.LinkedList;
22  import java.util.Objects;
23  import java.util.Queue;
24  
25  /**
26   * An IteratorChain is an Iterator that wraps a number of Iterators.
27   * <p>
28   * This class makes multiple iterators look like one to the caller. When any
29   * method from the Iterator interface is called, the IteratorChain will delegate
30   * to a single underlying Iterator. The IteratorChain will invoke the Iterators
31   * in sequence until all Iterators are exhausted.
32   * <p>
33   * Under many circumstances, linking Iterators together in this manner is more
34   * efficient (and convenient) than reading out the contents of each Iterator
35   * into a List and creating a new Iterator.
36   * <p>
37   * Calling a method that adds new Iterator <i>after a method in the Iterator
38   * interface has been called</i> will result in an UnsupportedOperationException.
39   * <p>
40   * NOTE: As from version 3.0, the IteratorChain may contain no iterators. In
41   * this case the class will function as an empty iterator.
42   * <p>
43   * NOTE: As from version 4.0, the IteratorChain stores the iterators in a queue
44   * and removes any reference to them as soon as they are not used anymore. Thus,
45   * the methods {@code setIterator(Iterator)} and {@code getIterators()} have been
46   * removed and {@link #size()} will return the number of remaining iterators in
47   * the queue.
48   *
49   * @param <E> the type of elements in this iterator.
50   * @since 2.1
51   */
52  public class IteratorChain<E> implements Iterator<E> {
53  
54      /** The chain of iterators */
55      private final Queue<Iterator<? extends E>> iteratorChain = new LinkedList<>();
56  
57      /** The current iterator */
58      private Iterator<? extends E> currentIterator;
59  
60      /**
61       * The "last used" Iterator is the Iterator upon which next() or hasNext()
62       * was most recently called used for the remove() operation only
63       */
64      private Iterator<? extends E> lastUsedIterator;
65  
66      /**
67       * ComparatorChain is "locked" after the first time compare(Object,Object)
68       * is called
69       */
70      private boolean isLocked;
71  
72      /**
73       * Constructs 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      }
80  
81      /**
82       * Constructs a new {@code IteratorChain} over the collection of
83       * iterators.
84       * <p>
85       * This method takes a collection of iterators. The newly constructed
86       * iterator will iterate through each one of the input iterators in turn.
87       *
88       * @param iteratorChain the collection of iterators, not null
89       * @throws NullPointerException if iterators collection is or contains null
90       * @throws ClassCastException if iterators collection doesn't contain an
91       * iterator
92       */
93      public IteratorChain(final Collection<Iterator<? extends E>> iteratorChain) {
94          for (final Iterator<? extends E> iterator : iteratorChain) {
95              addIterator(iterator);
96          }
97      }
98  
99      /**
100      * Constructs an IteratorChain with a single Iterator.
101      * <p>
102      * This method takes one iterator. The newly constructed iterator will
103      * iterate through that iterator. Thus calling this constructor on its own
104      * will have no effect other than decorating the input iterator.
105      * <p>
106      * You will normally use {@link #addIterator(Iterator)} to add some more
107      * iterators after using this constructor.
108      *
109      * @param iterator the first child iterator in the IteratorChain, not null
110      * @throws NullPointerException if the iterator is null
111      */
112     public IteratorChain(final Iterator<? extends E> iterator) {
113         addIterator(iterator);
114     }
115 
116     /**
117      * Constructs a new {@code IteratorChain} 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         for (final Iterator<? extends E> element : iteratorChain) {
127             addIterator(element);
128         }
129     }
130 
131     /**
132      * Constructs a new {@code IteratorChain} over the two given iterators.
133      * <p>
134      * This method takes two iterators. The newly constructed iterator will
135      * iterate through each one of the input iterators in turn.
136      *
137      * @param first the first child iterator in the IteratorChain, not null
138      * @param second the second child iterator in the IteratorChain, not null
139      * @throws NullPointerException if either iterator is null
140      */
141     public IteratorChain(final Iterator<? extends E> first, final Iterator<? extends E> second) {
142         addIterator(first);
143         addIterator(second);
144     }
145 
146     /**
147      * Add an Iterator to the end of the chain
148      *
149      * @param iterator Iterator to add
150      * @throws IllegalStateException if I've already started iterating
151      * @throws NullPointerException if the iterator is null
152      */
153     public void addIterator(final Iterator<? extends E> iterator) {
154         checkLocked();
155         iteratorChain.add(Objects.requireNonNull(iterator, "iterator"));
156     }
157 
158     /**
159      * Checks whether the iterator chain is now locked and in use.
160      */
161     private void checkLocked() {
162         if (isLocked) {
163             throw new UnsupportedOperationException(
164                     "IteratorChain cannot be changed after the first use of a method from the Iterator interface");
165         }
166     }
167 
168     /**
169      * Return true if any Iterator in the IteratorChain has a remaining element.
170      *
171      * @return true if elements remain
172      */
173     @Override
174     public boolean hasNext() {
175         lockChain();
176         updateCurrentIterator();
177         lastUsedIterator = currentIterator;
178 
179         return currentIterator.hasNext();
180     }
181 
182     /**
183      * Determine if modifications can still be made to the IteratorChain.
184      * IteratorChains cannot be modified once they have executed a method from
185      * the Iterator interface.
186      *
187      * @return true if IteratorChain cannot be modified, false if it can
188      */
189     public boolean isLocked() {
190         return isLocked;
191     }
192 
193     /**
194      * Lock the chain so no more iterators can be added. This must be called
195      * from all Iterator interface methods.
196      */
197     private void lockChain() {
198         if (!isLocked) {
199             isLocked = true;
200         }
201     }
202 
203     /**
204      * Returns the next Object of the current Iterator
205      *
206      * @return Object from the current Iterator
207      * @throws java.util.NoSuchElementException if all the Iterators are
208      * exhausted
209      */
210     @Override
211     public E next() {
212         lockChain();
213         updateCurrentIterator();
214         lastUsedIterator = currentIterator;
215 
216         return currentIterator.next();
217     }
218 
219     /**
220      * Removes from the underlying collection the last element returned by the
221      * Iterator. As with next() and hasNext(), this method calls remove() on the
222      * underlying Iterator. Therefore, this method may throw an
223      * UnsupportedOperationException if the underlying Iterator does not support
224      * this method.
225      *
226      * @throws UnsupportedOperationException if the remove operator is not
227      * supported by the underlying Iterator
228      * @throws IllegalStateException if the next method has not yet been called,
229      * or the remove method has already been called after the last call to the
230      * next method.
231      */
232     @Override
233     public void remove() {
234         lockChain();
235         if (currentIterator == null) {
236             updateCurrentIterator();
237         }
238         lastUsedIterator.remove();
239     }
240 
241     /**
242      * Returns the remaining number of Iterators in the current IteratorChain.
243      *
244      * @return Iterator count
245      */
246     public int size() {
247         return iteratorChain.size();
248     }
249 
250     /**
251      * Updates the current iterator field to ensure that the current Iterator is
252      * not exhausted
253      */
254     protected void updateCurrentIterator() {
255         if (currentIterator == null) {
256             if (iteratorChain.isEmpty()) {
257                 currentIterator = EmptyIterator.<E>emptyIterator();
258             } else {
259                 currentIterator = iteratorChain.remove();
260             }
261             // set last used iterator here, in case the user calls remove
262             // before calling hasNext() or next() (although they shouldn't)
263             lastUsedIterator = currentIterator;
264         }
265 
266         while (!currentIterator.hasNext() && !iteratorChain.isEmpty()) {
267             currentIterator = iteratorChain.remove();
268         }
269     }
270 
271 }