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 }