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 * https://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.Collection; 020import java.util.Iterator; 021import java.util.LinkedList; 022import java.util.Objects; 023import java.util.Queue; 024 025/** 026 * An IteratorChain is an Iterator that wraps a number of Iterators. 027 * <p> 028 * This class makes multiple iterators look like one to the caller. When any 029 * method from the Iterator interface is called, the IteratorChain will delegate 030 * to a single underlying Iterator. The IteratorChain will invoke the Iterators 031 * in sequence until all Iterators are exhausted. 032 * </p> 033 * <p> 034 * Under many circumstances, linking Iterators together in this manner is more 035 * efficient (and convenient) than reading out the contents of each Iterator 036 * into a List and creating a new Iterator. 037 * </p> 038 * <p> 039 * Calling a method that adds new Iterator <i>after a method in the Iterator 040 * interface has been called</i> will result in an UnsupportedOperationException. 041 * </p> 042 * <p> 043 * NOTE: As from version 3.0, the IteratorChain may contain no iterators. In 044 * this case the class will function as an empty iterator. 045 * </p> 046 * <p> 047 * NOTE: As from version 4.0, the IteratorChain stores the iterators in a queue 048 * and removes any reference to them as soon as they are not used anymore. Thus, 049 * the methods {@code setIterator(Iterator)} and {@code getIterators()} have been 050 * removed and {@link #size()} will return the number of remaining iterators in 051 * the queue. 052 * </p> 053 * 054 * @param <E> The type of elements in this iterator. 055 * @since 2.1 056 */ 057public class IteratorChain<E> implements Iterator<E> { 058 059 /** The chain of iterators */ 060 private final Queue<Iterator<? extends E>> iteratorQueue = new LinkedList<>(); 061 062 /** The current iterator */ 063 private Iterator<? extends E> currentIterator; 064 065 /** 066 * The "last used" Iterator is the Iterator upon which next() 067 * was most recently called used for the remove() operation only 068 */ 069 private Iterator<? extends E> lastUsedIterator; 070 071 /** 072 * ComparatorChain is "locked" after the first time compare(Object, Object) 073 * is called 074 */ 075 private boolean isLocked; 076 077 /** 078 * Contains the result of the last hasNext() call until next() is invoked 079 */ 080 private Boolean cachedHasNextValue; 081 082 /** 083 * Constructs an IteratorChain with no Iterators. 084 * <p> 085 * You will normally use {@link #addIterator(Iterator)} to add some 086 * iterators after using this constructor. 087 * </p> 088 */ 089 public IteratorChain() { 090 } 091 092 /** 093 * Constructs a new {@code IteratorChain} over the collection of 094 * iterators. 095 * <p> 096 * This method takes a collection of iterators. The newly constructed 097 * iterator will iterate through each one of the input iterators in turn. 098 * </p> 099 * 100 * @param iteratorQueue The collection of iterators, not null 101 * @throws NullPointerException if iterators collection is or contains null 102 * @throws ClassCastException if iterators collection doesn't contain an 103 * iterator 104 */ 105 public IteratorChain(final Collection<? extends Iterator<? extends E>> iteratorQueue) { 106 for (final Iterator<? extends E> iterator : iteratorQueue) { 107 addIterator(iterator); 108 } 109 } 110 111 /** 112 * Constructs an IteratorChain with a single Iterator. 113 * <p> 114 * This method takes one iterator. The newly constructed iterator will 115 * iterate through that iterator. Thus calling this constructor on its own 116 * will have no effect other than decorating the input iterator. 117 * </p> 118 * <p> 119 * You will normally use {@link #addIterator(Iterator)} to add some more 120 * iterators after using this constructor. 121 * </p> 122 * 123 * @param iterator The first child iterator in the IteratorChain, not null 124 * @throws NullPointerException if the iterator is null 125 */ 126 public IteratorChain(final Iterator<? extends E> iterator) { 127 addIterator(iterator); 128 } 129 130 /** 131 * Constructs a new {@code IteratorChain} over the array of iterators. 132 * <p> 133 * This method takes an array of iterators. The newly constructed iterator 134 * will iterate through each one of the input iterators in turn. 135 * </p> 136 * 137 * @param iteratorQueue The array of iterators, not null 138 * @throws NullPointerException if iterators array is or contains null 139 */ 140 public IteratorChain(final Iterator<? extends E>... iteratorQueue) { 141 for (final Iterator<? extends E> element : iteratorQueue) { 142 addIterator(element); 143 } 144 } 145 146 /** 147 * Constructs a new {@code IteratorChain} over the two given iterators. 148 * <p> 149 * This method takes two iterators. The newly constructed iterator will 150 * iterate through each one of the input iterators in turn. 151 * </p> 152 * 153 * @param first The first child iterator in the IteratorChain, not null 154 * @param second The second child iterator in the IteratorChain, not null 155 * @throws NullPointerException if either iterator is null 156 */ 157 public IteratorChain(final Iterator<? extends E> first, final Iterator<? extends E> second) { 158 addIterator(first); 159 addIterator(second); 160 } 161 162 /** 163 * Add an Iterator to the end of the chain 164 * 165 * @param iterator Iterator to add 166 * @throws IllegalStateException if I've already started iterating 167 * @throws NullPointerException if the iterator is null 168 */ 169 public void addIterator(final Iterator<? extends E> iterator) { 170 checkLocked(); 171 Objects.requireNonNull(iterator, "iterator"); 172 if (iterator instanceof UnmodifiableIterator) { 173 final Iterator<? extends E> underlyingIterator = ((UnmodifiableIterator) iterator).unwrap(); 174 if (underlyingIterator instanceof IteratorChain) { 175 // in case it is an IteratorChain, wrap every underlying iterators as unmodifiable 176 // multiple rechainings would otherwise lead to exponential growing number of function calls 177 // when the iteratorChain gets used. 178 for (final Iterator<? extends E> nestedIterator : ((IteratorChain<? extends E>) underlyingIterator).iteratorQueue) { 179 iteratorQueue.add(UnmodifiableIterator.unmodifiableIterator(nestedIterator)); 180 } 181 } else { 182 // we don't know anything about the underlying iterator, simply add it here 183 iteratorQueue.add(iterator); 184 } 185 } else if (iterator instanceof IteratorChain) { 186 // add the wrapped iterators directly instead of reusing the given instance 187 // multiple rechainings would otherwise lead to exponential growing number of function calls 188 // when the iteratorChain gets used. 189 iteratorQueue.addAll(((IteratorChain) iterator).iteratorQueue); 190 } else { 191 // arbitrary other iterator 192 iteratorQueue.add(iterator); 193 } 194 } 195 196 /** 197 * Checks whether the iterator chain is now locked and in use. 198 */ 199 private void checkLocked() { 200 if (isLocked) { 201 throw new UnsupportedOperationException("IteratorChain cannot be changed after the first use of a method from the Iterator interface"); 202 } 203 } 204 205 /** 206 * Return true if any Iterator in the IteratorChain has a remaining element. 207 * 208 * @return true if elements remain 209 */ 210 @Override 211 public boolean hasNext() { 212 lockChain(); 213 if (cachedHasNextValue == null) { 214 updateCurrentIterator(); 215 } 216 return cachedHasNextValue; 217 } 218 219 /** 220 * Determine if modifications can still be made to the IteratorChain. 221 * IteratorChains cannot be modified once they have executed a method from 222 * the Iterator interface. 223 * 224 * @return true if IteratorChain cannot be modified, false if it can 225 */ 226 public boolean isLocked() { 227 return isLocked; 228 } 229 230 /** 231 * Lock the chain so no more iterators can be added. This must be called 232 * from all Iterator interface methods. 233 */ 234 private void lockChain() { 235 if (!isLocked) { 236 isLocked = true; 237 } 238 } 239 240 /** 241 * Returns the next Object of the current Iterator 242 * 243 * @return Object from the current Iterator 244 * @throws java.util.NoSuchElementException if all the Iterators are 245 * exhausted 246 */ 247 @Override 248 public E next() { 249 lockChain(); 250 if (cachedHasNextValue == null) { 251 updateCurrentIterator(); 252 } 253 lastUsedIterator = currentIterator; 254 cachedHasNextValue = null; 255 return currentIterator.next(); 256 } 257 258 /** 259 * Removes from the underlying collection the last element returned by the 260 * Iterator. As with next() and hasNext(), this method calls remove() on the 261 * underlying Iterator. Therefore, this method may throw an 262 * UnsupportedOperationException if the underlying Iterator does not support 263 * this method. 264 * 265 * @throws UnsupportedOperationException if the remove operator is not 266 * supported by the underlying Iterator 267 * @throws IllegalStateException if the next method has not yet been called, 268 * or the remove method has already been called after the last call to the 269 * next method. 270 */ 271 @Override 272 public void remove() { 273 lockChain(); 274 if (lastUsedIterator == null) { 275 throw new IllegalStateException("remove() has been invoked without next()"); 276 } 277 lastUsedIterator.remove(); 278 lastUsedIterator = null; // must never be used twice without next() being invoked 279 } 280 281 /** 282 * Returns the remaining number of Iterators in the current IteratorChain. 283 * 284 * @return Iterator count 285 */ 286 public int size() { 287 return iteratorQueue.size(); 288 } 289 290 /** 291 * Updates the current iterator field to ensure that the current Iterator is 292 * not exhausted 293 */ 294 protected void updateCurrentIterator() { 295 if (currentIterator == null) { 296 if (iteratorQueue.isEmpty()) { 297 currentIterator = EmptyIterator.<E>emptyIterator(); 298 } else { 299 currentIterator = iteratorQueue.remove(); 300 } 301 } 302 while (true) { 303 cachedHasNextValue = currentIterator.hasNext(); 304 if (cachedHasNextValue) { 305 break; 306 } 307 if (iteratorQueue.isEmpty()) { 308 break; 309 } 310 currentIterator = iteratorQueue.remove(); 311 } 312 } 313}