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 * @version $Id: PeekingIterator.html 972421 2015-11-14 20:00:04Z tn $
032 */
033public class PeekingIterator<E> implements Iterator<E> {
034
035    /** The iterator being decorated. */
036    private final Iterator<? extends E> iterator;
037
038    /** Indicates that the decorated iterator is exhausted. */
039    private boolean exhausted = false;
040
041    /** Indicates if the lookahead slot is filled. */
042    private boolean slotFilled = false;
043
044    /** The current slot for lookahead. */
045    private E slot;
046
047    //-----------------------------------------------------------------------
048    /**
049     * Decorates the specified iterator to support one-element lookahead.
050     * <p>
051     * If the iterator is already a {@link PeekingIterator} it is returned directly.
052     *
053     * @param <E>  the element type
054     * @param iterator  the iterator to decorate
055     * @return a new peeking iterator
056     * @throws IllegalArgumentException if the iterator is null
057     */
058    public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) {
059        if (iterator == null) {
060            throw new IllegalArgumentException("Iterator must not be null");
061        }
062        if (iterator instanceof PeekingIterator<?>) {
063            @SuppressWarnings("unchecked") // safe cast
064            final PeekingIterator<E> it = (PeekingIterator<E>) iterator;
065            return it;
066        }
067        return new PeekingIterator<E>(iterator);
068    }
069
070    //-----------------------------------------------------------------------
071
072    /**
073     * Constructor.
074     *
075     * @param iterator  the iterator to decorate
076     */
077    public PeekingIterator(final Iterator<? extends E> iterator) {
078        this.iterator = iterator;
079    }
080
081    private void fill() {
082        if (exhausted || slotFilled) {
083            return;
084        }
085        if (iterator.hasNext()) {
086            slot = iterator.next();
087            slotFilled = true;
088        } else {
089            exhausted = true;
090            slot = null;
091            slotFilled = false;
092        }
093    }
094
095    //-----------------------------------------------------------------------
096    public boolean hasNext() {
097        if (exhausted) {
098            return false;
099        }
100        return slotFilled ? true : 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    public E next() {
136        if (!hasNext()) {
137            throw new NoSuchElementException();
138        }
139        final E x = slotFilled ? slot : iterator.next();
140        // reset the lookahead slot
141        slot = null;
142        slotFilled = false;
143        return x;
144    }
145
146    /**
147     * {@inheritDoc}
148     *
149     * @throws IllegalStateException if {@link #peek()} or {@link #element()} has been called
150     *   prior to the call to {@link #remove()}
151     */
152    public void remove() {
153        if (slotFilled) {
154            throw new IllegalStateException();
155        }
156        iterator.remove();
157    }
158
159}