    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}