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