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 */ 017 package org.apache.commons.collections.iterators; 018 019 import java.util.ArrayList; 020 import java.util.Collection; 021 import java.util.Iterator; 022 import java.util.List; 023 024 import org.apache.commons.collections.list.UnmodifiableList; 025 026 /** 027 * An IteratorChain is an Iterator that wraps a number of Iterators. 028 * <p> 029 * This class makes multiple iterators look like one to the caller 030 * When any method from the Iterator interface is called, the IteratorChain 031 * will delegate to a single underlying Iterator. The IteratorChain will 032 * invoke the Iterators in sequence until all Iterators are exhausted. 033 * <p> 034 * Under many circumstances, linking Iterators together in this manner is 035 * more efficient (and convenient) than reading out the contents of each 036 * Iterator into a List and creating a new Iterator. 037 * <p> 038 * Calling a method that adds new Iterator<i>after a method in the Iterator 039 * interface has been called</i> will result in an UnsupportedOperationException. 040 * Subclasses should <i>take care</i> to not alter the underlying List of Iterators. 041 * <p> 042 * NOTE: As from version 3.0, the IteratorChain may contain no 043 * iterators. In this case the class will function as an empty iterator. 044 * 045 * @since Commons Collections 2.1 046 * @version $Revision: 647116 $ $Date: 2008-04-11 12:23:08 +0100 (Fri, 11 Apr 2008) $ 047 * 048 * @author Morgan Delagrange 049 * @author Stephen Colebourne 050 */ 051 public class IteratorChain implements Iterator { 052 053 /** The chain of iterators */ 054 protected final List iteratorChain = new ArrayList(); 055 /** The index of the current iterator */ 056 protected int currentIteratorIndex = 0; 057 /** The current iterator */ 058 protected Iterator currentIterator = null; 059 /** 060 * The "last used" Iterator is the Iterator upon which 061 * next() or hasNext() was most recently called 062 * used for the remove() operation only 063 */ 064 protected Iterator lastUsedIterator = null; 065 /** 066 * ComparatorChain is "locked" after the first time 067 * compare(Object,Object) is called 068 */ 069 protected 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 076 * some iterators after using this constructor. 077 */ 078 public IteratorChain() { 079 super(); 080 } 081 082 /** 083 * Construct an IteratorChain with a single Iterator. 084 * 085 * @param iterator first Iterator in the IteratorChain 086 * @throws NullPointerException if the iterator is null 087 */ 088 public IteratorChain(Iterator iterator) { 089 super(); 090 addIterator(iterator); 091 } 092 093 /** 094 * Constructs a new <code>IteratorChain</code> over the two 095 * given iterators. 096 * 097 * @param a the first child iterator 098 * @param b the second child iterator 099 * @throws NullPointerException if either iterator is null 100 */ 101 public IteratorChain(Iterator a, Iterator b) { 102 super(); 103 addIterator(a); 104 addIterator(b); 105 } 106 107 /** 108 * Constructs a new <code>IteratorChain</code> over the array 109 * of iterators. 110 * 111 * @param iterators the array of iterators 112 * @throws NullPointerException if iterators array is or contains null 113 */ 114 public IteratorChain(Iterator[] iterators) { 115 super(); 116 for (int i = 0; i < iterators.length; i++) { 117 addIterator(iterators[i]); 118 } 119 } 120 121 /** 122 * Constructs a new <code>IteratorChain</code> over the collection 123 * of iterators. 124 * 125 * @param iterators the collection of iterators 126 * @throws NullPointerException if iterators collection is or contains null 127 * @throws ClassCastException if iterators collection doesn't contain an iterator 128 */ 129 public IteratorChain(Collection iterators) { 130 super(); 131 for (Iterator it = iterators.iterator(); it.hasNext();) { 132 Iterator item = (Iterator) it.next(); 133 addIterator(item); 134 } 135 } 136 137 //----------------------------------------------------------------------- 138 /** 139 * Add an Iterator to the end of the chain 140 * 141 * @param iterator Iterator to add 142 * @throws IllegalStateException if I've already started iterating 143 * @throws NullPointerException if the iterator is null 144 */ 145 public void addIterator(Iterator iterator) { 146 checkLocked(); 147 if (iterator == null) { 148 throw new NullPointerException("Iterator must not be null"); 149 } 150 iteratorChain.add(iterator); 151 } 152 153 /** 154 * Set the Iterator at the given index 155 * 156 * @param index index of the Iterator to replace 157 * @param iterator Iterator to place at the given index 158 * @throws IndexOutOfBoundsException if index < 0 or index > size() 159 * @throws IllegalStateException if I've already started iterating 160 * @throws NullPointerException if the iterator is null 161 */ 162 public void setIterator(int index, Iterator iterator) throws IndexOutOfBoundsException { 163 checkLocked(); 164 if (iterator == null) { 165 throw new NullPointerException("Iterator must not be null"); 166 } 167 iteratorChain.set(index, iterator); 168 } 169 170 /** 171 * Get the list of Iterators (unmodifiable) 172 * 173 * @return the unmodifiable list of iterators added 174 */ 175 public List getIterators() { 176 return UnmodifiableList.decorate(iteratorChain); 177 } 178 179 /** 180 * Number of Iterators in the current IteratorChain. 181 * 182 * @return Iterator count 183 */ 184 public int size() { 185 return iteratorChain.size(); 186 } 187 188 /** 189 * Determine if modifications can still be made to the IteratorChain. 190 * IteratorChains cannot be modified once they have executed a method 191 * from the Iterator interface. 192 * 193 * @return true if IteratorChain cannot be modified, false if it can 194 */ 195 public boolean isLocked() { 196 return isLocked; 197 } 198 199 /** 200 * Checks whether the iterator chain is now locked and in use. 201 */ 202 private void checkLocked() { 203 if (isLocked == true) { 204 throw new UnsupportedOperationException("IteratorChain cannot be changed after the first use of a method from the Iterator interface"); 205 } 206 } 207 208 /** 209 * Lock the chain so no more iterators can be added. 210 * This must be called from all Iterator interface methods. 211 */ 212 private void lockChain() { 213 if (isLocked == false) { 214 isLocked = true; 215 } 216 } 217 218 /** 219 * Updates the current iterator field to ensure that the current Iterator 220 * is not exhausted 221 */ 222 protected void updateCurrentIterator() { 223 if (currentIterator == null) { 224 if (iteratorChain.isEmpty()) { 225 currentIterator = EmptyIterator.INSTANCE; 226 } else { 227 currentIterator = (Iterator) iteratorChain.get(0); 228 } 229 // set last used iterator here, in case the user calls remove 230 // before calling hasNext() or next() (although they shouldn't) 231 lastUsedIterator = currentIterator; 232 } 233 234 while (currentIterator.hasNext() == false && currentIteratorIndex < iteratorChain.size() - 1) { 235 currentIteratorIndex++; 236 currentIterator = (Iterator) iteratorChain.get(currentIteratorIndex); 237 } 238 } 239 240 //----------------------------------------------------------------------- 241 /** 242 * Return true if any Iterator in the IteratorChain has a remaining element. 243 * 244 * @return true if elements remain 245 */ 246 public boolean hasNext() { 247 lockChain(); 248 updateCurrentIterator(); 249 lastUsedIterator = currentIterator; 250 251 return currentIterator.hasNext(); 252 } 253 254 /** 255 * Returns the next Object of the current Iterator 256 * 257 * @return Object from the current Iterator 258 * @throws java.util.NoSuchElementException if all the Iterators are exhausted 259 */ 260 public Object next() { 261 lockChain(); 262 updateCurrentIterator(); 263 lastUsedIterator = currentIterator; 264 265 return currentIterator.next(); 266 } 267 268 /** 269 * Removes from the underlying collection the last element 270 * returned by the Iterator. As with next() and hasNext(), 271 * this method calls remove() on the underlying Iterator. 272 * Therefore, this method may throw an 273 * UnsupportedOperationException if the underlying 274 * Iterator does not support this method. 275 * 276 * @throws UnsupportedOperationException 277 * if the remove operator is not supported by the underlying Iterator 278 * @throws IllegalStateException 279 * if the next method has not yet been called, or the remove method has 280 * already been called after the last call to the next method. 281 */ 282 public void remove() { 283 lockChain(); 284 if (currentIterator == null) { 285 updateCurrentIterator(); 286 } 287 lastUsedIterator.remove(); 288 } 289 290 }