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 }