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