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