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