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 * @version $Id: CollatingIterator.html 972421 2015-11-14 20:00:04Z tn $ 040 */ 041public class CollatingIterator<E> implements Iterator<E> { 042 043 /** The {@link Comparator} used to evaluate order. */ 044 private Comparator<? super E> comparator = null; 045 046 /** The list of {@link Iterator}s to evaluate. */ 047 private ArrayList<Iterator<? extends E>> iterators = null; 048 049 /** {@link Iterator#next Next} objects peeked from each iterator. */ 050 private ArrayList<E> values = null; 051 052 /** Whether or not each {@link #values} element has been set. */ 053 private BitSet valueSet = null; 054 055 /** 056 * Index of the {@link #iterators iterator} from whom the last returned 057 * value was obtained. 058 */ 059 private int lastReturned = -1; 060 061 // Constructors 062 // ---------------------------------------------------------------------- 063 /** 064 * Constructs a new <code>CollatingIterator</code>. A comparator must be 065 * set by calling {@link #setComparator(Comparator)} before invoking 066 * {@link #hasNext()}, or {@link #next()} for the first time. Child 067 * iterators will have to be manually added using the 068 * {@link #addIterator(Iterator)} method. 069 */ 070 public CollatingIterator() { 071 this(null, 2); 072 } 073 074 /** 075 * Constructs a new <code>CollatingIterator</code> that will used the 076 * specified comparator for ordering. Child iterators will have to be 077 * manually added using the {@link #addIterator(Iterator)} method. 078 * 079 * @param comp the comparator to use to sort; must not be null, 080 * unless you'll be invoking {@link #setComparator(Comparator)} 081 * later on. 082 */ 083 public CollatingIterator(final Comparator<? super E> comp) { 084 this(comp, 2); 085 } 086 087 /** 088 * Constructs a new <code>CollatingIterator</code> that will used the 089 * specified comparator for ordering and have the specified initial 090 * capacity. Child iterators will have to be manually added using the 091 * {@link #addIterator(Iterator)} method. 092 * 093 * @param comp the comparator to use to sort; must not be null, 094 * unless you'll be invoking {@link #setComparator(Comparator)} 095 * later on. 096 * @param initIterCapacity the initial capacity for the internal list of 097 * child iterators 098 */ 099 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.collections4.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}