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.ArrayList;
20  import java.util.BitSet;
21  import java.util.Collection;
22  import java.util.Comparator;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.NoSuchElementException;
26  import java.util.Objects;
27  
28  import org.apache.commons.collections4.list.UnmodifiableList;
29  
30  /**
31   * Provides an ordered iteration over the elements contained in a collection of
32   * ordered Iterators.
33   * <p>
34   * Given two ordered {@link Iterator} instances {@code A} and
35   * {@code B}, the {@link #next} method on this iterator will return the
36   * lesser of {@code A.next()} and {@code B.next()}.
37   * </p>
38   *
39   * @param <E> the type of elements returned by this iterator.
40   * @since 2.1
41   */
42  public class CollatingIterator<E> implements Iterator<E> {
43  
44      /** The {@link Comparator} used to evaluate order. */
45      private Comparator<? super E> comparator;
46  
47      /** The list of {@link Iterator}s to evaluate. */
48      private final List<Iterator<? extends E>> iterators;
49  
50      /** {@link Iterator#next Next} objects peeked from each iterator. */
51      private List<E> values;
52  
53      /** Whether or not each {@link #values} element has been set. */
54      private BitSet valueSet;
55  
56      /**
57       * Index of the {@link #iterators iterator} from whom the last returned
58       * value was obtained.
59       */
60      private int lastReturned = -1;
61  
62      /**
63       * Constructs a new {@code CollatingIterator}. A comparator must be
64       * set by calling {@link #setComparator(Comparator)} before invoking
65       * {@link #hasNext()}, or {@link #next()} for the first time. Child
66       * iterators will have to be manually added using the
67       * {@link #addIterator(Iterator)} method.
68       */
69      public CollatingIterator() {
70          this(null, 2);
71      }
72  
73      /**
74       * Constructs a new {@code CollatingIterator} that will use the
75       * specified comparator for ordering. Child iterators will have to be
76       * manually added using the {@link #addIterator(Iterator)} method.
77       *
78       * @param comp the comparator to use to sort; must not be null,
79       *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
80       */
81      public CollatingIterator(final Comparator<? super E> comp) {
82          this(comp, 2);
83      }
84  
85      /**
86       * Constructs a new {@code CollatingIterator} that will use the
87       * specified comparator to provide ordered iteration over the collection of
88       * iterators.
89       *
90       * @param comp the comparator to use to sort; must not be null,
91       *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
92       * @param iterators the collection of iterators
93       * @throws NullPointerException if the iterators collection is or contains null
94       * @throws ClassCastException if the iterators collection contains an
95       *   element that's not an {@link Iterator}
96       */
97      public CollatingIterator(final Comparator<? super E> comp, final Collection<Iterator<? extends E>> iterators) {
98          this(comp, iterators.size());
99          for (final Iterator<? extends E> iterator : iterators) {
100             addIterator(iterator);
101         }
102     }
103 
104     /**
105      * Constructs a new {@code CollatingIterator} that will use the
106      * specified comparator for ordering and have the specified initial
107      * capacity. Child iterators will have to be manually added using the
108      * {@link #addIterator(Iterator)} method.
109      *
110      * @param comp the comparator to use to sort; must not be null,
111      *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
112      * @param initIterCapacity the initial capacity for the internal list of
113      *   child iterators
114      */
115     public CollatingIterator(final Comparator<? super E> comp, final int initIterCapacity) {
116         iterators = new ArrayList<>(initIterCapacity);
117         setComparator(comp);
118     }
119 
120     /**
121      * Constructs a new {@code CollatingIterator} that will use the
122      * specified comparator to provide ordered iteration over the two given
123      * iterators.
124      *
125      * @param comp the comparator to use to sort; must not be null,
126      *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
127      * @param a the first child ordered iterator
128      * @param b the second child ordered iterator
129      * @throws NullPointerException if either iterator is null
130      */
131     public CollatingIterator(final Comparator<? super E> comp, final Iterator<? extends E> a,
132                              final Iterator<? extends E> b) {
133         this(comp, 2);
134         addIterator(a);
135         addIterator(b);
136     }
137 
138     /**
139      * Constructs a new {@code CollatingIterator} that will use the
140      * specified comparator to provide ordered iteration over the array of
141      * iterators.
142      *
143      * @param comp the comparator to use to sort; must not be null,
144      *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
145      * @param iterators the array of iterators
146      * @throws NullPointerException if iterators array is or contains null
147      */
148     public CollatingIterator(final Comparator<? super E> comp, final Iterator<? extends E>[] iterators) {
149         this(comp, iterators.length);
150         for (final Iterator<? extends E> iterator : iterators) {
151             addIterator(iterator);
152         }
153     }
154 
155     /**
156      * Adds the given {@link Iterator} to the iterators being collated.
157      *
158      * @param iterator the iterator to add to the collation, must not be null
159      * @throws IllegalStateException if iteration has started
160      * @throws NullPointerException if the iterator is null
161      */
162     public void addIterator(final Iterator<? extends E> iterator) {
163         checkNotStarted();
164         Objects.requireNonNull(iterator, "iterator");
165         iterators.add(iterator);
166     }
167 
168     /**
169      * Returns {@code true} iff any {@link Iterator} in the given list has
170      * a next value.
171      */
172     private boolean anyHasNext(final List<Iterator<? extends E>> iterators) {
173         for (final Iterator<? extends E> iterator : iterators) {
174             if (iterator.hasNext()) {
175                 return true;
176             }
177         }
178         return false;
179     }
180 
181     /**
182      * Returns {@code true} iff any bit in the given set is
183      * {@code true}.
184      */
185     private boolean anyValueSet(final BitSet set) {
186         for (int i = 0; i < set.size(); i++) {
187             if (set.get(i)) {
188                 return true;
189             }
190         }
191         return false;
192     }
193 
194     /**
195      * Throws {@link IllegalStateException} if iteration has started via
196      * {@link #start}.
197      *
198      * @throws IllegalStateException if iteration started
199      */
200     private void checkNotStarted() throws IllegalStateException {
201         if (values != null) {
202             throw new IllegalStateException("Can't do that after next or hasNext has been called.");
203         }
204     }
205 
206     /**
207      * Clears the {@link #values} and {@link #valueSet} attributes at position
208      * <em>i</em>.
209      */
210     private void clear(final int i) {
211         values.set(i, null);
212         valueSet.clear(i);
213     }
214 
215     /**
216      * Gets the {@link Comparator} by which collation occurs.
217      *
218      * @return the {@link Comparator}
219      */
220     public Comparator<? super E> getComparator() {
221         return comparator;
222     }
223 
224     /**
225      * Returns the index of the iterator that returned the last element.
226      *
227      * @return the index of the iterator that returned the last element
228      * @throws IllegalStateException if there is no last returned element
229      */
230     public int getIteratorIndex() {
231         if (lastReturned == -1) {
232             throw new IllegalStateException("No value has been returned yet");
233         }
234 
235         return lastReturned;
236     }
237 
238     /**
239      * Gets the list of Iterators (unmodifiable).
240      *
241      * @return the unmodifiable list of iterators added
242      */
243     public List<Iterator<? extends E>> getIterators() {
244         return UnmodifiableList.unmodifiableList(iterators);
245     }
246 
247     /**
248      * Returns {@code true} if any child iterator has remaining elements.
249      *
250      * @return true if this iterator has remaining elements
251      */
252     @Override
253     public boolean hasNext() {
254         start();
255         return anyValueSet(valueSet) || anyHasNext(iterators);
256     }
257 
258     /**
259      * Returns the index of the least element in {@link #values},
260      * {@link #set(int) setting} any uninitialized values.
261      *
262      * @throws NullPointerException if no comparator is set
263      */
264     private int least() {
265         int leastIndex = -1;
266         E leastObject = null;
267         for (int i = 0; i < values.size(); i++) {
268             if (!valueSet.get(i)) {
269                 set(i);
270             }
271             if (valueSet.get(i)) {
272                 if (leastIndex == -1) {
273                     leastIndex = i;
274                     leastObject = values.get(i);
275                 } else {
276                     final E curObject = values.get(i);
277                     Objects.requireNonNull(comparator, "You must invoke setComparator() to set a comparator first.");
278                     if (comparator.compare(curObject, leastObject) < 0) {
279                         leastObject = curObject;
280                         leastIndex = i;
281                     }
282                 }
283             }
284         }
285         return leastIndex;
286     }
287 
288     /**
289      * Returns the next ordered element from a child iterator.
290      *
291      * @return the next ordered element
292      * @throws NoSuchElementException if no child iterator has any more elements
293      */
294     @Override
295     public E next() throws NoSuchElementException {
296         if (!hasNext()) {
297             throw new NoSuchElementException();
298         }
299         final int leastIndex = least();
300         if (leastIndex == -1) {
301             throw new NoSuchElementException();
302         }
303         final E val = values.get(leastIndex);
304         clear(leastIndex);
305         lastReturned = leastIndex;
306         return val;
307     }
308 
309     /**
310      * Removes the last returned element from the child iterator that produced it.
311      *
312      * @throws IllegalStateException if there is no last returned element, or if
313      * the last returned element has already been removed
314      */
315     @Override
316     public void remove() {
317         if (lastReturned == -1) {
318             throw new IllegalStateException("No value can be removed at present");
319         }
320         iterators.get(lastReturned).remove();
321     }
322 
323     /**
324      * Sets the {@link #values} and {@link #valueSet} attributes at position
325      * <em>i</em> to the next value of the {@link #iterators iterator} at position
326      * <em>i</em>, or clear them if the <em>i</em><sup>th</sup> iterator has no next
327      * value.
328      *
329      * @return {@code false} iff there was no value to set
330      */
331     private boolean set(final int index) {
332         final Iterator<? extends E> it = iterators.get(index);
333         if (it.hasNext()) {
334             values.set(index, it.next());
335             valueSet.set(index);
336             return true;
337         }
338         values.set(index, null);
339         valueSet.clear(index);
340         return false;
341     }
342 
343     /**
344      * Sets the {@link Comparator} by which collation occurs. If you
345      * would like to use the natural sort order (or, in other words,
346      * if the elements in the iterators are implementing the
347      * {@link Comparable} interface), then use the
348      * {@link org.apache.commons.collections4.comparators.ComparableComparator}.
349      *
350      * @param comp the {@link Comparator} to set
351      * @throws IllegalStateException if iteration has started
352      */
353     public void setComparator(final Comparator<? super E> comp) {
354         checkNotStarted();
355         comparator = comp;
356     }
357 
358     /**
359      * Sets the iterator at the given index.
360      *
361      * @param index index of the Iterator to replace
362      * @param iterator Iterator to place at the given index
363      * @throws IndexOutOfBoundsException if index &lt; 0 or index &gt;= size()
364      * @throws IllegalStateException if iteration has started
365      * @throws NullPointerException if the iterator is null
366      */
367     public void setIterator(final int index, final Iterator<? extends E> iterator) {
368         checkNotStarted();
369         Objects.requireNonNull(iterator, "iterator");
370         iterators.set(index, iterator);
371     }
372 
373     /**
374      * Initializes the collating state if it hasn't been already.
375      */
376     private void start() {
377         if (values == null) {
378             values = new ArrayList<>(iterators.size());
379             valueSet = new BitSet(iterators.size());
380             for (int i = 0; i < iterators.size(); i++) {
381                 values.add(null);
382                 valueSet.clear(i);
383             }
384         }
385     }
386 
387 }