001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.commons.collections4.iterators;
019
020import java.util.Iterator;
021import java.util.NoSuchElementException;
022
023/**
024 * Decorates an iterator to support one-element lookahead while iterating.
025 * <p>
026 * The decorator supports the removal operation, but an {@link IllegalStateException}
027 * will be thrown if {@link #remove()} is called directly after a call to
028 * {@link #peek()} or {@link #element()}.
029 *
030 * @since 4.0
031 */
032public class PeekingIterator<E> implements Iterator<E> {
033
034    /** The iterator being decorated. */
035    private final Iterator<? extends E> iterator;
036
037    /** Indicates that the decorated iterator is exhausted. */
038    private boolean exhausted = false;
039
040    /** Indicates if the lookahead slot is filled. */
041    private boolean slotFilled = false;
042
043    /** The current slot for lookahead. */
044    private E slot;
045
046    //-----------------------------------------------------------------------
047    /**
048     * Decorates the specified iterator to support one-element lookahead.
049     * <p>
050     * If the iterator is already a {@link PeekingIterator} it is returned directly.
051     *
052     * @param <E>  the element type
053     * @param iterator  the iterator to decorate
054     * @return a new peeking iterator
055     * @throws NullPointerException if the iterator is null
056     */
057    public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) {
058        if (iterator == null) {
059            throw new NullPointerException("Iterator must not be null");
060        }
061        if (iterator instanceof PeekingIterator<?>) {
062            @SuppressWarnings("unchecked") // safe cast
063            final PeekingIterator<E> it = (PeekingIterator<E>) iterator;
064            return it;
065        }
066        return new PeekingIterator<>(iterator);
067    }
068
069    //-----------------------------------------------------------------------
070
071    /**
072     * Constructor.
073     *
074     * @param iterator  the iterator to decorate
075     */
076    public PeekingIterator(final Iterator<? extends E> iterator) {
077        this.iterator = iterator;
078    }
079
080    private void fill() {
081        if (exhausted || slotFilled) {
082            return;
083        }
084        if (iterator.hasNext()) {
085            slot = iterator.next();
086            slotFilled = true;
087        } else {
088            exhausted = true;
089            slot = null;
090            slotFilled = false;
091        }
092    }
093
094    //-----------------------------------------------------------------------
095    @Override
096    public boolean hasNext() {
097        if (exhausted) {
098            return false;
099        }
100        return slotFilled || iterator.hasNext();
101    }
102
103    /**
104     * Returns the next element in iteration without advancing the underlying iterator.
105     * If the iterator is already exhausted, null will be returned.
106     * <p>
107     * Note: this method does not throw a {@link NoSuchElementException} if the iterator
108     * is already exhausted. If you want such a behavior, use {@link #element()} instead.
109     * <p>
110     * The rationale behind this is to follow the {@link java.util.Queue} interface
111     * which uses the same terminology.
112     *
113     * @return the next element from the iterator
114     */
115    public E peek() {
116        fill();
117        return exhausted ? null : slot;
118    }
119
120    /**
121     * Returns the next element in iteration without advancing the underlying iterator.
122     * If the iterator is already exhausted, null will be returned.
123     *
124     * @return the next element from the iterator
125     * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}
126     */
127    public E element() {
128        fill();
129        if (exhausted) {
130            throw new NoSuchElementException();
131        }
132        return slot;
133    }
134
135    @Override
136    public E next() {
137        if (!hasNext()) {
138            throw new NoSuchElementException();
139        }
140        final E x = slotFilled ? slot : iterator.next();
141        // reset the lookahead slot
142        slot = null;
143        slotFilled = false;
144        return x;
145    }
146
147    /**
148     * {@inheritDoc}
149     *
150     * @throws IllegalStateException if {@link #peek()} or {@link #element()} has been called
151     *   prior to the call to {@link #remove()}
152     */
153    @Override
154    public void remove() {
155        if (slotFilled) {
156            throw new IllegalStateException("peek() or element() called before remove()");
157        }
158        iterator.remove();
159    }
160
161}