001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.iterators;
018
019import java.util.Iterator;
020import java.util.NoSuchElementException;
021import java.util.Objects;
022import java.util.Queue;
023
024/**
025 * Decorates an iterator to support one-element lookahead while iterating.
026 * <p>
027 * The decorator supports the removal operation, but an {@link IllegalStateException} will be thrown if {@link #remove()} is called directly after a call to
028 * {@link #peek()} or {@link #element()}.
029 * </p>
030 *
031 * @param <E> The type of elements returned by this iterator.
032 * @since 4.0
033 */
034public class PeekingIterator<E> implements Iterator<E> {
035
036    /**
037     * Decorates the specified iterator to support one-element lookahead.
038     * <p>
039     * If the iterator is already a {@link PeekingIterator} it is returned directly.
040     * </p>
041     *
042     * @param <E>      the element type
043     * @param iterator The iterator to decorate
044     * @return A new peeking iterator
045     * @throws NullPointerException if the iterator is null
046     */
047    public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) {
048        Objects.requireNonNull(iterator, "iterator");
049        if (iterator instanceof PeekingIterator<?>) {
050            @SuppressWarnings("unchecked") // safe cast
051            final PeekingIterator<E> it = (PeekingIterator<E>) iterator;
052            return it;
053        }
054        return new PeekingIterator<>(iterator);
055    }
056
057    /** The iterator being decorated. */
058    private final Iterator<? extends E> iterator;
059
060    /** Indicates that the decorated iterator is exhausted. */
061    private boolean exhausted;
062
063    /** Indicates if the lookahead slot is filled. */
064    private boolean slotFilled;
065
066    /** The current slot for lookahead. */
067    private E slot;
068
069    /**
070     * Constructs a new instance.
071     *
072     * @param iterator The iterator to decorate
073     */
074    public PeekingIterator(final Iterator<? extends E> iterator) {
075        this.iterator = iterator;
076    }
077
078    /**
079     * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned.
080     * <p>
081     * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
082     * element() or {@link #peek()} has been called after the most recent invocation of {@link #next()}
083     * </p>
084     *
085     * @return The next element from the iterator
086     * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}
087     */
088    public E element() {
089        fill();
090        if (exhausted) {
091            throw new NoSuchElementException();
092        }
093        return slot;
094    }
095
096    private void fill() {
097        if (exhausted || slotFilled) {
098            return;
099        }
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}