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}