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