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    *      https://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  import java.util.NoSuchElementException;
21  import java.util.Objects;
22  import java.util.Queue;
23  
24  /**
25   * Decorates an iterator to support one-element lookahead while iterating.
26   * <p>
27   * The decorator supports the removal operation, but an {@link IllegalStateException} will be thrown if {@link #remove()} is called directly after a call to
28   * {@link #peek()} or {@link #element()}.
29   * </p>
30   *
31   * @param <E> The type of elements returned by this iterator.
32   * @since 4.0
33   */
34  public class PeekingIterator<E> implements Iterator<E> {
35  
36      /**
37       * Decorates the specified iterator to support one-element lookahead.
38       * <p>
39       * If the iterator is already a {@link PeekingIterator} it is returned directly.
40       * </p>
41       *
42       * @param <E>      the element type
43       * @param iterator The iterator to decorate
44       * @return A new peeking iterator
45       * @throws NullPointerException if the iterator is null
46       */
47      public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) {
48          Objects.requireNonNull(iterator, "iterator");
49          if (iterator instanceof PeekingIterator<?>) {
50              @SuppressWarnings("unchecked") // safe cast
51              final PeekingIterator<E> it = (PeekingIterator<E>) iterator;
52              return it;
53          }
54          return new PeekingIterator<>(iterator);
55      }
56  
57      /** The iterator being decorated. */
58      private final Iterator<? extends E> iterator;
59  
60      /** Indicates that the decorated iterator is exhausted. */
61      private boolean exhausted;
62  
63      /** Indicates if the lookahead slot is filled. */
64      private boolean slotFilled;
65  
66      /** The current slot for lookahead. */
67      private E slot;
68  
69      /**
70       * Constructs a new instance.
71       *
72       * @param iterator The iterator to decorate
73       */
74      public PeekingIterator(final Iterator<? extends E> iterator) {
75          this.iterator = iterator;
76      }
77  
78      /**
79       * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned.
80       * <p>
81       * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
82       * element() or {@link #peek()} has been called after the most recent invocation of {@link #next()}
83       * </p>
84       *
85       * @return The next element from the iterator
86       * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}
87       */
88      public E element() {
89          fill();
90          if (exhausted) {
91              throw new NoSuchElementException();
92          }
93          return slot;
94      }
95  
96      private void fill() {
97          if (exhausted || slotFilled) {
98              return;
99          }
100         if (iterator.hasNext()) {
101             slot = iterator.next();
102             slotFilled = true;
103         } else {
104             exhausted = true;
105             slot = null;
106             slotFilled = false;
107         }
108     }
109 
110     @Override
111     public boolean hasNext() {
112         if (exhausted) {
113             return false;
114         }
115         return slotFilled || iterator.hasNext();
116     }
117 
118     /**
119      * Returns the next element in iteration.
120      * <p>
121      * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
122      * {@link #element()} or {@link #peek()} has been called after the most recent invocation of {@link #next()}.
123      * </p>
124      *
125      * @return The next element from the iterator
126      * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}.
127      */
128     @Override
129     public E next() {
130         if (!hasNext()) {
131             throw new NoSuchElementException();
132         }
133         final E x = slotFilled ? slot : iterator.next();
134         // reset the lookahead slot
135         slot = null;
136         slotFilled = false;
137         return x;
138     }
139 
140     /**
141      * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned.
142      * <p>
143      * Note: this method does not throw a {@link NoSuchElementException} if the iterator is already exhausted. If you want such a behavior, use
144      * {@link #element()} instead.
145      * </p>
146      * <p>
147      * The rationale behind this is to follow the {@link Queue} interface which uses the same terminology.
148      * </p>
149      * <p>
150      * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
151      * {@link #element()} or peek() has been called after the most recent invocation of {@link #next()}.
152      * </p>
153      *
154      * @return The next element from the iterator
155      */
156     public E peek() {
157         fill();
158         return exhausted ? null : slot;
159     }
160 
161     /**
162      * {@inheritDoc}
163      *
164      * @throws IllegalStateException if {@link #peek()} or {@link #element()} has been called prior to the call to {@link #remove()}.
165      */
166     @Override
167     public void remove() {
168         if (slotFilled) {
169             throw new IllegalStateException("peek() or element() called before remove()");
170         }
171         iterator.remove();
172     }
173 
174 }