View Javadoc
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&lt;String&gt;() {
35   *     protected Iterator&lt;String&gt; 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 }