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.Collection; 20 import java.util.Iterator; 21 import java.util.LinkedList; 22 import java.util.Objects; 23 import java.util.Queue; 24 25 /** 26 * An IteratorChain is an Iterator that wraps a number of Iterators. 27 * <p> 28 * This class makes multiple iterators look like one to the caller. When any 29 * method from the Iterator interface is called, the IteratorChain will delegate 30 * to a single underlying Iterator. The IteratorChain will invoke the Iterators 31 * in sequence until all Iterators are exhausted. 32 * </p> 33 * <p> 34 * Under many circumstances, linking Iterators together in this manner is more 35 * efficient (and convenient) than reading out the contents of each Iterator 36 * into a List and creating a new Iterator. 37 * </p> 38 * <p> 39 * Calling a method that adds new Iterator <i>after a method in the Iterator 40 * interface has been called</i> will result in an UnsupportedOperationException. 41 * </p> 42 * <p> 43 * NOTE: As from version 3.0, the IteratorChain may contain no iterators. In 44 * this case the class will function as an empty iterator. 45 * </p> 46 * <p> 47 * NOTE: As from version 4.0, the IteratorChain stores the iterators in a queue 48 * and removes any reference to them as soon as they are not used anymore. Thus, 49 * the methods {@code setIterator(Iterator)} and {@code getIterators()} have been 50 * removed and {@link #size()} will return the number of remaining iterators in 51 * the queue. 52 * </p> 53 * 54 * @param <E> the type of elements in this iterator. 55 * @since 2.1 56 */ 57 public class IteratorChain<E> implements Iterator<E> { 58 59 /** The chain of iterators */ 60 private final Queue<Iterator<? extends E>> iteratorQueue = new LinkedList<>(); 61 62 /** The current iterator */ 63 private Iterator<? extends E> currentIterator; 64 65 /** 66 * The "last used" Iterator is the Iterator upon which next() or hasNext() 67 * was most recently called used for the remove() operation only 68 */ 69 private Iterator<? extends E> lastUsedIterator; 70 71 /** 72 * ComparatorChain is "locked" after the first time compare(Object,Object) 73 * is called 74 */ 75 private boolean isLocked; 76 77 /** 78 * Constructs an IteratorChain with no Iterators. 79 * <p> 80 * You will normally use {@link #addIterator(Iterator)} to add some 81 * iterators after using this constructor. 82 * </p> 83 */ 84 public IteratorChain() { 85 } 86 87 /** 88 * Constructs a new {@code IteratorChain} over the collection of 89 * iterators. 90 * <p> 91 * This method takes a collection of iterators. The newly constructed 92 * iterator will iterate through each one of the input iterators in turn. 93 * </p> 94 * 95 * @param iteratorQueue the collection of iterators, not null 96 * @throws NullPointerException if iterators collection is or contains null 97 * @throws ClassCastException if iterators collection doesn't contain an 98 * iterator 99 */ 100 public IteratorChain(final Collection<? extends Iterator<? extends E>> iteratorQueue) { 101 for (final Iterator<? extends E> iterator : iteratorQueue) { 102 addIterator(iterator); 103 } 104 } 105 106 /** 107 * Constructs an IteratorChain with a single Iterator. 108 * <p> 109 * This method takes one iterator. The newly constructed iterator will 110 * iterate through that iterator. Thus calling this constructor on its own 111 * will have no effect other than decorating the input iterator. 112 * </p> 113 * <p> 114 * You will normally use {@link #addIterator(Iterator)} to add some more 115 * iterators after using this constructor. 116 * </p> 117 * 118 * @param iterator the first child iterator in the IteratorChain, not null 119 * @throws NullPointerException if the iterator is null 120 */ 121 public IteratorChain(final Iterator<? extends E> iterator) { 122 addIterator(iterator); 123 } 124 125 /** 126 * Constructs a new {@code IteratorChain} over the array of iterators. 127 * <p> 128 * This method takes an array of iterators. The newly constructed iterator 129 * will iterate through each one of the input iterators in turn. 130 * </p> 131 * 132 * @param iteratorQueue the array of iterators, not null 133 * @throws NullPointerException if iterators array is or contains null 134 */ 135 public IteratorChain(final Iterator<? extends E>... iteratorQueue) { 136 for (final Iterator<? extends E> element : iteratorQueue) { 137 addIterator(element); 138 } 139 } 140 141 /** 142 * Constructs a new {@code IteratorChain} over the two given iterators. 143 * <p> 144 * This method takes two iterators. The newly constructed iterator will 145 * iterate through each one of the input iterators in turn. 146 * </p> 147 * 148 * @param first the first child iterator in the IteratorChain, not null 149 * @param second the second child iterator in the IteratorChain, not null 150 * @throws NullPointerException if either iterator is null 151 */ 152 public IteratorChain(final Iterator<? extends E> first, final Iterator<? extends E> second) { 153 addIterator(first); 154 addIterator(second); 155 } 156 157 /** 158 * Add an Iterator to the end of the chain 159 * 160 * @param iterator Iterator to add 161 * @throws IllegalStateException if I've already started iterating 162 * @throws NullPointerException if the iterator is null 163 */ 164 public void addIterator(final Iterator<? extends E> iterator) { 165 checkLocked(); 166 iteratorQueue.add(Objects.requireNonNull(iterator, "iterator")); 167 } 168 169 /** 170 * Checks whether the iterator chain is now locked and in use. 171 */ 172 private void checkLocked() { 173 if (isLocked) { 174 throw new UnsupportedOperationException("IteratorChain cannot be changed after the first use of a method from the Iterator interface"); 175 } 176 } 177 178 /** 179 * Return true if any Iterator in the IteratorChain has a remaining element. 180 * 181 * @return true if elements remain 182 */ 183 @Override 184 public boolean hasNext() { 185 lockChain(); 186 updateCurrentIterator(); 187 lastUsedIterator = currentIterator; 188 return currentIterator.hasNext(); 189 } 190 191 /** 192 * Determine if modifications can still be made to the IteratorChain. 193 * IteratorChains cannot be modified once they have executed a method from 194 * the Iterator interface. 195 * 196 * @return true if IteratorChain cannot be modified, false if it can 197 */ 198 public boolean isLocked() { 199 return isLocked; 200 } 201 202 /** 203 * Lock the chain so no more iterators can be added. This must be called 204 * from all Iterator interface methods. 205 */ 206 private void lockChain() { 207 if (!isLocked) { 208 isLocked = true; 209 } 210 } 211 212 /** 213 * Returns the next Object of the current Iterator 214 * 215 * @return Object from the current Iterator 216 * @throws java.util.NoSuchElementException if all the Iterators are 217 * exhausted 218 */ 219 @Override 220 public E next() { 221 lockChain(); 222 updateCurrentIterator(); 223 lastUsedIterator = currentIterator; 224 225 return currentIterator.next(); 226 } 227 228 /** 229 * Removes from the underlying collection the last element returned by the 230 * Iterator. As with next() and hasNext(), this method calls remove() on the 231 * underlying Iterator. Therefore, this method may throw an 232 * UnsupportedOperationException if the underlying Iterator does not support 233 * this method. 234 * 235 * @throws UnsupportedOperationException if the remove operator is not 236 * supported by the underlying Iterator 237 * @throws IllegalStateException if the next method has not yet been called, 238 * or the remove method has already been called after the last call to the 239 * next method. 240 */ 241 @Override 242 public void remove() { 243 lockChain(); 244 if (currentIterator == null) { 245 updateCurrentIterator(); 246 } 247 lastUsedIterator.remove(); 248 } 249 250 /** 251 * Returns the remaining number of Iterators in the current IteratorChain. 252 * 253 * @return Iterator count 254 */ 255 public int size() { 256 return iteratorQueue.size(); 257 } 258 259 /** 260 * Updates the current iterator field to ensure that the current Iterator is 261 * not exhausted 262 */ 263 protected void updateCurrentIterator() { 264 if (currentIterator == null) { 265 if (iteratorQueue.isEmpty()) { 266 currentIterator = EmptyIterator.<E>emptyIterator(); 267 } else { 268 currentIterator = iteratorQueue.remove(); 269 } 270 // set last used iterator here, in case the user calls remove 271 // before calling hasNext() or next() (although they shouldn't) 272 lastUsedIterator = currentIterator; 273 } 274 while (!currentIterator.hasNext() && !iteratorQueue.isEmpty()) { 275 currentIterator = iteratorQueue.remove(); 276 } 277 } 278 279 }