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.Iterator; 20 21 /** 22 * An LazyIteratorChain is an Iterator that wraps a number of Iterators in a lazy manner. 23 * <p> 24 * This class makes multiple iterators look like one to the caller. When any 25 * method from the Iterator interface is called, the LazyIteratorChain will delegate 26 * to a single underlying Iterator. The LazyIteratorChain will invoke the Iterators 27 * in sequence until all Iterators are exhausted. 28 * </p> 29 * <p> 30 * The Iterators are provided by {@link #nextIterator(int)} which has to be overridden by 31 * subclasses and allows to lazily create the Iterators as they are accessed: 32 * </p> 33 * <pre> 34 * return new LazyIteratorChain<String>() { 35 * protected Iterator<String> nextIterator(int count) { 36 * return count == 1 ? Arrays.asList("foo", "bar").iterator() : null; 37 * } 38 * }; 39 * </pre> 40 * <p> 41 * Once the inner Iterator's {@link Iterator#hasNext()} method returns false, 42 * {@link #nextIterator(int)} will be called to obtain another iterator, and so on 43 * until {@link #nextIterator(int)} returns null, indicating that the chain is exhausted. 44 * </p> 45 * <p> 46 * NOTE: The LazyIteratorChain may contain no iterators. In this case the class will 47 * function as an empty iterator. 48 * </p> 49 * 50 * @param <E> the type of elements in this iterator. 51 * @since 4.0 52 */ 53 public abstract class LazyIteratorChain<E> implements Iterator<E> { 54 55 /** The number of times {@link #next()} was already called. */ 56 private int callCounter; 57 58 /** Indicates that the Iterator chain has been exhausted. */ 59 private boolean chainExhausted; 60 61 /** The current iterator. */ 62 private Iterator<? extends E> currentIterator; 63 64 /** 65 * The "last used" Iterator is the Iterator upon which next() or hasNext() 66 * was most recently called used for the remove() operation only. 67 */ 68 private Iterator<? extends E> lastUsedIterator; 69 70 /** 71 * Constructs a new instance. 72 */ 73 public LazyIteratorChain() { 74 // empty 75 } 76 77 /** 78 * Return true if any Iterator in the chain has a remaining element. 79 * 80 * @return true if elements remain 81 */ 82 @Override 83 public boolean hasNext() { 84 updateCurrentIterator(); 85 lastUsedIterator = currentIterator; 86 return currentIterator.hasNext(); 87 } 88 89 /** 90 * Returns the next element of the current Iterator 91 * 92 * @return element from the current Iterator 93 * @throws java.util.NoSuchElementException if all the Iterators are exhausted 94 */ 95 @Override 96 public E next() { 97 updateCurrentIterator(); 98 lastUsedIterator = currentIterator; 99 return currentIterator.next(); 100 } 101 102 /** 103 * Gets the next iterator after the previous one has been exhausted. 104 * <p> 105 * This method <strong>MUST</strong> return null when there are no more iterators. 106 * </p> 107 * 108 * @param count the number of time this method has been called (starts with 1) 109 * @return the next iterator, or null if there are no more. 110 */ 111 protected abstract Iterator<? extends E> nextIterator(int count); 112 113 /** 114 * Removes from the underlying collection the last element returned by the Iterator. 115 * <p> 116 * As with next() and hasNext(), this method calls remove() on the underlying Iterator. 117 * Therefore, this method may throw an UnsupportedOperationException if the underlying 118 * Iterator does not support this method. 119 * </p> 120 * 121 * @throws UnsupportedOperationException if the remove operator is not 122 * supported by the underlying Iterator 123 * @throws IllegalStateException if the next method has not yet been called, 124 * or the remove method has already been called after the last call to the next method. 125 */ 126 @Override 127 public void remove() { 128 if (currentIterator == null) { 129 updateCurrentIterator(); 130 } 131 lastUsedIterator.remove(); 132 } 133 134 /** 135 * Updates the current iterator field to ensure that the current Iterator 136 * is not exhausted. 137 */ 138 private void updateCurrentIterator() { 139 if (callCounter == 0) { 140 currentIterator = nextIterator(++callCounter); 141 if (currentIterator == null) { 142 currentIterator = EmptyIterator.<E>emptyIterator(); 143 chainExhausted = true; 144 } 145 // set last used iterator here, in case the user calls remove 146 // before calling hasNext() or next() (although they shouldn't) 147 lastUsedIterator = currentIterator; 148 } 149 while (!currentIterator.hasNext() && !chainExhausted) { 150 final Iterator<? extends E> nextIterator = nextIterator(++callCounter); 151 if (nextIterator != null) { 152 currentIterator = nextIterator; 153 } else { 154 chainExhausted = true; 155 } 156 } 157 } 158 159 }