001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.iterators; 018 019import java.util.ArrayList; 020import java.util.BitSet; 021import java.util.Collection; 022import java.util.Comparator; 023import java.util.Iterator; 024import java.util.List; 025import java.util.NoSuchElementException; 026 027import org.apache.commons.collections4.list.UnmodifiableList; 028 029 030/** 031 * Provides an ordered iteration over the elements contained in a collection of 032 * ordered Iterators. 033 * <p> 034 * Given two ordered {@link Iterator} instances <code>A</code> and 035 * <code>B</code>, the {@link #next} method on this iterator will return the 036 * lesser of <code>A.next()</code> and <code>B.next()</code>. 037 * 038 * @since 2.1 039 */ 040public class CollatingIterator<E> implements Iterator<E> { 041 042 /** The {@link Comparator} used to evaluate order. */ 043 private Comparator<? super E> comparator = null; 044 045 /** The list of {@link Iterator}s to evaluate. */ 046 private List<Iterator<? extends E>> iterators = null; 047 048 /** {@link Iterator#next Next} objects peeked from each iterator. */ 049 private List<E> values = null; 050 051 /** Whether or not each {@link #values} element has been set. */ 052 private BitSet valueSet = null; 053 054 /** 055 * Index of the {@link #iterators iterator} from whom the last returned 056 * value was obtained. 057 */ 058 private int lastReturned = -1; 059 060 // Constructors 061 // ---------------------------------------------------------------------- 062 /** 063 * Constructs a new <code>CollatingIterator</code>. A comparator must be 064 * set by calling {@link #setComparator(Comparator)} before invoking 065 * {@link #hasNext()}, or {@link #next()} for the first time. Child 066 * iterators will have to be manually added using the 067 * {@link #addIterator(Iterator)} method. 068 */ 069 public CollatingIterator() { 070 this(null, 2); 071 } 072 073 /** 074 * Constructs a new <code>CollatingIterator</code> that will used the 075 * specified comparator for ordering. Child iterators will have to be 076 * manually added using the {@link #addIterator(Iterator)} method. 077 * 078 * @param comp the comparator to use to sort; must not be null, 079 * unless you'll be invoking {@link #setComparator(Comparator)} later on. 080 */ 081 public CollatingIterator(final Comparator<? super E> comp) { 082 this(comp, 2); 083 } 084 085 /** 086 * Constructs a new <code>CollatingIterator</code> that will used the 087 * specified comparator for ordering and have the specified initial 088 * capacity. Child iterators will have to be manually added using the 089 * {@link #addIterator(Iterator)} method. 090 * 091 * @param comp the comparator to use to sort; must not be null, 092 * unless you'll be invoking {@link #setComparator(Comparator)} later on. 093 * @param initIterCapacity the initial capacity for the internal list of 094 * child iterators 095 */ 096 public CollatingIterator(final Comparator<? super E> comp, final int initIterCapacity) { 097 iterators = new ArrayList<>(initIterCapacity); 098 setComparator(comp); 099 } 100 101 /** 102 * Constructs a new <code>CollatingIterator</code> that will use the 103 * specified comparator to provide ordered iteration over the two given 104 * iterators. 105 * 106 * @param comp the comparator to use to sort; must not be null, 107 * unless you'll be invoking {@link #setComparator(Comparator)} later on. 108 * @param a the first child ordered iterator 109 * @param b the second child ordered iterator 110 * @throws NullPointerException if either iterator is null 111 */ 112 public CollatingIterator(final Comparator<? super E> comp, final Iterator<? extends E> a, 113 final Iterator<? extends E> b) { 114 this(comp, 2); 115 addIterator(a); 116 addIterator(b); 117 } 118 119 /** 120 * Constructs a new <code>CollatingIterator</code> that will use the 121 * specified comparator to provide ordered iteration over the array of 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 iterators the array of iterators 127 * @throws NullPointerException if iterators array is or contains null 128 */ 129 public CollatingIterator(final Comparator<? super E> comp, final Iterator<? extends E>[] iterators) { 130 this(comp, iterators.length); 131 for (final Iterator<? extends E> iterator : iterators) { 132 addIterator(iterator); 133 } 134 } 135 136 /** 137 * Constructs a new <code>CollatingIterator</code> that will use the 138 * specified comparator to provide ordered iteration over the collection of 139 * iterators. 140 * 141 * @param comp the comparator to use to sort; must not be null, 142 * unless you'll be invoking {@link #setComparator(Comparator)} later on. 143 * @param iterators the collection of iterators 144 * @throws NullPointerException if the iterators collection is or contains null 145 * @throws ClassCastException if the iterators collection contains an 146 * element that's not an {@link Iterator} 147 */ 148 public CollatingIterator(final Comparator<? super E> comp, final Collection<Iterator<? extends E>> iterators) { 149 this(comp, iterators.size()); 150 for (final Iterator<? extends E> iterator : iterators) { 151 addIterator(iterator); 152 } 153 } 154 155 // Public Methods 156 // ---------------------------------------------------------------------- 157 /** 158 * Adds the given {@link Iterator} to the iterators being collated. 159 * 160 * @param iterator the iterator to add to the collation, must not be null 161 * @throws IllegalStateException if iteration has started 162 * @throws NullPointerException if the iterator is null 163 */ 164 public void addIterator(final Iterator<? extends E> iterator) { 165 checkNotStarted(); 166 if (iterator == null) { 167 throw new NullPointerException("Iterator must not be null"); 168 } 169 iterators.add(iterator); 170 } 171 172 /** 173 * Sets the iterator at the given index. 174 * 175 * @param index index of the Iterator to replace 176 * @param iterator Iterator to place at the given index 177 * @throws IndexOutOfBoundsException if index < 0 or index > size() 178 * @throws IllegalStateException if iteration has started 179 * @throws NullPointerException if the iterator is null 180 */ 181 public void setIterator(final int index, final Iterator<? extends E> iterator) { 182 checkNotStarted(); 183 if (iterator == null) { 184 throw new NullPointerException("Iterator must not be null"); 185 } 186 iterators.set(index, iterator); 187 } 188 189 /** 190 * Gets the list of Iterators (unmodifiable). 191 * 192 * @return the unmodifiable list of iterators added 193 */ 194 public List<Iterator<? extends E>> getIterators() { 195 return UnmodifiableList.unmodifiableList(iterators); 196 } 197 198 /** 199 * Gets the {@link Comparator} by which collatation occurs. 200 * 201 * @return the {@link Comparator} 202 */ 203 public Comparator<? super E> getComparator() { 204 return comparator; 205 } 206 207 /** 208 * Sets the {@link Comparator} by which collation occurs. If you 209 * would like to use the natural sort order (or, in other words, 210 * if the elements in the iterators are implementing the 211 * {@link java.lang.Comparable} interface), then use the 212 * {@link org.apache.commons.collections4.comparators.ComparableComparator}. 213 * 214 * @param comp the {@link Comparator} to set 215 * @throws IllegalStateException if iteration has started 216 */ 217 public void setComparator(final Comparator<? super E> comp) { 218 checkNotStarted(); 219 comparator = comp; 220 } 221 222 // Iterator Methods 223 // ------------------------------------------------------------------- 224 /** 225 * Returns <code>true</code> if any child iterator has remaining elements. 226 * 227 * @return true if this iterator has remaining elements 228 */ 229 @Override 230 public boolean hasNext() { 231 start(); 232 return anyValueSet(valueSet) || anyHasNext(iterators); 233 } 234 235 /** 236 * Returns the next ordered element from a child iterator. 237 * 238 * @return the next ordered element 239 * @throws NoSuchElementException if no child iterator has any more elements 240 */ 241 @Override 242 public E next() throws NoSuchElementException { 243 if (hasNext() == false) { 244 throw new NoSuchElementException(); 245 } 246 final int leastIndex = least(); 247 if (leastIndex == -1) { 248 throw new NoSuchElementException(); 249 } 250 final E val = values.get(leastIndex); 251 clear(leastIndex); 252 lastReturned = leastIndex; 253 return val; 254 } 255 256 /** 257 * Removes the last returned element from the child iterator that produced it. 258 * 259 * @throws IllegalStateException if there is no last returned element, or if 260 * the last returned element has already been removed 261 */ 262 @Override 263 public void remove() { 264 if (lastReturned == -1) { 265 throw new IllegalStateException("No value can be removed at present"); 266 } 267 iterators.get(lastReturned).remove(); 268 } 269 270 /** 271 * Returns the index of the iterator that returned the last element. 272 * 273 * @return the index of the iterator that returned the last element 274 * @throws IllegalStateException if there is no last returned element 275 */ 276 public int getIteratorIndex() { 277 if (lastReturned == -1) { 278 throw new IllegalStateException("No value has been returned yet"); 279 } 280 281 return lastReturned; 282 } 283 284 // Private Methods 285 // ------------------------------------------------------------------- 286 /** 287 * Initializes the collating state if it hasn't been already. 288 */ 289 private void start() { 290 if (values == null) { 291 values = new ArrayList<>(iterators.size()); 292 valueSet = new BitSet(iterators.size()); 293 for (int i = 0; i < iterators.size(); i++) { 294 values.add(null); 295 valueSet.clear(i); 296 } 297 } 298 } 299 300 /** 301 * Sets the {@link #values} and {@link #valueSet} attributes at position 302 * <i>i</i> to the next value of the {@link #iterators iterator} at position 303 * <i>i</i>, or clear them if the <i>i</i><sup>th</sup> iterator has no next 304 * value. 305 * 306 * @return {@code false} iff there was no value to set 307 */ 308 private boolean set(final int i) { 309 final Iterator<? extends E> it = iterators.get(i); 310 if (it.hasNext()) { 311 values.set(i, it.next()); 312 valueSet.set(i); 313 return true; 314 } 315 values.set(i, null); 316 valueSet.clear(i); 317 return false; 318 } 319 320 /** 321 * Clears the {@link #values} and {@link #valueSet} attributes at position 322 * <i>i</i>. 323 */ 324 private void clear(final int i) { 325 values.set(i, null); 326 valueSet.clear(i); 327 } 328 329 /** 330 * Throws {@link IllegalStateException} if iteration has started via 331 * {@link #start}. 332 * 333 * @throws IllegalStateException if iteration started 334 */ 335 private void checkNotStarted() throws IllegalStateException { 336 if (values != null) { 337 throw new IllegalStateException("Can't do that after next or hasNext has been called."); 338 } 339 } 340 341 /** 342 * Returns the index of the least element in {@link #values}, 343 * {@link #set(int) setting} any uninitialized values. 344 * 345 * @throws NullPointerException if no comparator is set 346 */ 347 private int least() { 348 int leastIndex = -1; 349 E leastObject = null; 350 for (int i = 0; i < values.size(); i++) { 351 if (valueSet.get(i) == false) { 352 set(i); 353 } 354 if (valueSet.get(i)) { 355 if (leastIndex == -1) { 356 leastIndex = i; 357 leastObject = values.get(i); 358 } else { 359 final E curObject = values.get(i); 360 if (comparator == null) { 361 throw new NullPointerException("You must invoke setComparator() to set a comparator first."); 362 } 363 if (comparator.compare(curObject, leastObject) < 0) { 364 leastObject = curObject; 365 leastIndex = i; 366 } 367 } 368 } 369 } 370 return leastIndex; 371 } 372 373 /** 374 * Returns <code>true</code> iff any bit in the given set is 375 * <code>true</code>. 376 */ 377 private boolean anyValueSet(final BitSet set) { 378 for (int i = 0; i < set.size(); i++) { 379 if (set.get(i)) { 380 return true; 381 } 382 } 383 return false; 384 } 385 386 /** 387 * Returns <code>true</code> iff any {@link Iterator} in the given list has 388 * a next value. 389 */ 390 private boolean anyHasNext(final List<Iterator<? extends E>> iters) { 391 for (final Iterator<? extends E> iterator : iters) { 392 if (iterator.hasNext()) { 393 return true; 394 } 395 } 396 return false; 397 } 398 399}