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