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