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