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 < 0 or index > 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 }