PeekingIterator.java

  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. import java.util.Iterator;
  19. import java.util.NoSuchElementException;
  20. import java.util.Objects;

  21. /**
  22.  * Decorates an iterator to support one-element lookahead while iterating.
  23.  * <p>
  24.  * The decorator supports the removal operation, but an {@link IllegalStateException} will be thrown if {@link #remove()} is called directly after a call to
  25.  * {@link #peek()} or {@link #element()}.
  26.  * </p>
  27.  *
  28.  * @param <E> the type of elements returned by this iterator.
  29.  * @since 4.0
  30.  */
  31. public class PeekingIterator<E> implements Iterator<E> {

  32.     /**
  33.      * Decorates the specified iterator to support one-element lookahead.
  34.      * <p>
  35.      * If the iterator is already a {@link PeekingIterator} it is returned directly.
  36.      * </p>
  37.      *
  38.      * @param <E>      the element type
  39.      * @param iterator the iterator to decorate
  40.      * @return a new peeking iterator
  41.      * @throws NullPointerException if the iterator is null
  42.      */
  43.     public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) {
  44.         Objects.requireNonNull(iterator, "iterator");
  45.         if (iterator instanceof PeekingIterator<?>) {
  46.             @SuppressWarnings("unchecked") // safe cast
  47.             final PeekingIterator<E> it = (PeekingIterator<E>) iterator;
  48.             return it;
  49.         }
  50.         return new PeekingIterator<>(iterator);
  51.     }

  52.     /** The iterator being decorated. */
  53.     private final Iterator<? extends E> iterator;

  54.     /** Indicates that the decorated iterator is exhausted. */
  55.     private boolean exhausted;

  56.     /** Indicates if the lookahead slot is filled. */
  57.     private boolean slotFilled;

  58.     /** The current slot for lookahead. */
  59.     private E slot;

  60.     /**
  61.      * Constructs a new instance.
  62.      *
  63.      * @param iterator the iterator to decorate
  64.      */
  65.     public PeekingIterator(final Iterator<? extends E> iterator) {
  66.         this.iterator = iterator;
  67.     }

  68.     /**
  69.      * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned.
  70.      * <p>
  71.      * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
  72.      * element() or {@link #peek()} has been called after the most recent invocation of {@link #next()}
  73.      * </p>
  74.      *
  75.      * @return the next element from the iterator
  76.      * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}
  77.      */
  78.     public E element() {
  79.         fill();
  80.         if (exhausted) {
  81.             throw new NoSuchElementException();
  82.         }
  83.         return slot;
  84.     }

  85.     private void fill() {
  86.         if (exhausted || slotFilled) {
  87.             return;
  88.         }
  89.         if (iterator.hasNext()) {
  90.             slot = iterator.next();
  91.             slotFilled = true;
  92.         } else {
  93.             exhausted = true;
  94.             slot = null;
  95.             slotFilled = false;
  96.         }
  97.     }

  98.     @Override
  99.     public boolean hasNext() {
  100.         if (exhausted) {
  101.             return false;
  102.         }
  103.         return slotFilled || iterator.hasNext();
  104.     }

  105.     /**
  106.      * Returns the next element in iteration.
  107.      * <p>
  108.      * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
  109.      * {@link #element()} or {@link #peek()} has been called after the most recent invocation of {@link #next()}.
  110.      * </p>
  111.      *
  112.      * @return the next element from the iterator
  113.      * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}.
  114.      */
  115.     @Override
  116.     public E next() {
  117.         if (!hasNext()) {
  118.             throw new NoSuchElementException();
  119.         }
  120.         final E x = slotFilled ? slot : iterator.next();
  121.         // reset the lookahead slot
  122.         slot = null;
  123.         slotFilled = false;
  124.         return x;
  125.     }

  126.     /**
  127.      * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned.
  128.      * <p>
  129.      * Note: this method does not throw a {@link NoSuchElementException} if the iterator is already exhausted. If you want such a behavior, use
  130.      * {@link #element()} instead.
  131.      * </p>
  132.      * <p>
  133.      * The rationale behind this is to follow the {@link java.util.Queue} interface which uses the same terminology.
  134.      * </p>
  135.      * <p>
  136.      * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if
  137.      * {@link #element()} or peek() has been called after the most recent invocation of {@link #next()}.
  138.      * </p>
  139.      *
  140.      * @return the next element from the iterator
  141.      */
  142.     public E peek() {
  143.         fill();
  144.         return exhausted ? null : slot;
  145.     }

  146.     /**
  147.      * {@inheritDoc}
  148.      *
  149.      * @throws IllegalStateException if {@link #peek()} or {@link #element()} has been called prior to the call to {@link #remove()}.
  150.      */
  151.     @Override
  152.     public void remove() {
  153.         if (slotFilled) {
  154.             throw new IllegalStateException("peek() or element() called before remove()");
  155.         }
  156.         iterator.remove();
  157.     }

  158. }