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