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