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 19 import java.util.Iterator; 20 import java.util.NoSuchElementException; 21 import java.util.Objects; 22 23 /** 24 * Decorates an iterator to support one-element lookahead while iterating. 25 * <p> 26 * The decorator supports the removal operation, but an {@link IllegalStateException} will be thrown if {@link #remove()} is called directly after a call to 27 * {@link #peek()} or {@link #element()}. 28 * </p> 29 * 30 * @param <E> the type of elements returned by this iterator. 31 * @since 4.0 32 */ 33 public class PeekingIterator<E> implements Iterator<E> { 34 35 /** 36 * Decorates the specified iterator to support one-element lookahead. 37 * <p> 38 * If the iterator is already a {@link PeekingIterator} it is returned directly. 39 * </p> 40 * 41 * @param <E> the element type 42 * @param iterator the iterator to decorate 43 * @return a new peeking iterator 44 * @throws NullPointerException if the iterator is null 45 */ 46 public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) { 47 Objects.requireNonNull(iterator, "iterator"); 48 if (iterator instanceof PeekingIterator<?>) { 49 @SuppressWarnings("unchecked") // safe cast 50 final PeekingIterator<E> it = (PeekingIterator<E>) iterator; 51 return it; 52 } 53 return new PeekingIterator<>(iterator); 54 } 55 56 /** The iterator being decorated. */ 57 private final Iterator<? extends E> iterator; 58 59 /** Indicates that the decorated iterator is exhausted. */ 60 private boolean exhausted; 61 62 /** Indicates if the lookahead slot is filled. */ 63 private boolean slotFilled; 64 65 /** The current slot for lookahead. */ 66 private E slot; 67 68 /** 69 * Constructs a new instance. 70 * 71 * @param iterator the iterator to decorate 72 */ 73 public PeekingIterator(final Iterator<? extends E> iterator) { 74 this.iterator = iterator; 75 } 76 77 /** 78 * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned. 79 * <p> 80 * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if 81 * element() or {@link #peek()} has been called after the most recent invocation of {@link #next()} 82 * </p> 83 * 84 * @return the next element from the iterator 85 * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()} 86 */ 87 public E element() { 88 fill(); 89 if (exhausted) { 90 throw new NoSuchElementException(); 91 } 92 return slot; 93 } 94 95 private void fill() { 96 if (exhausted || slotFilled) { 97 return; 98 } 99 if (iterator.hasNext()) { 100 slot = iterator.next(); 101 slotFilled = true; 102 } else { 103 exhausted = true; 104 slot = null; 105 slotFilled = false; 106 } 107 } 108 109 @Override 110 public boolean hasNext() { 111 if (exhausted) { 112 return false; 113 } 114 return slotFilled || iterator.hasNext(); 115 } 116 117 /** 118 * Returns the next element in iteration. 119 * <p> 120 * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if 121 * {@link #element()} or {@link #peek()} has been called after the most recent invocation of {@link #next()}. 122 * </p> 123 * 124 * @return the next element from the iterator 125 * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()}. 126 */ 127 @Override 128 public E next() { 129 if (!hasNext()) { 130 throw new NoSuchElementException(); 131 } 132 final E x = slotFilled ? slot : iterator.next(); 133 // reset the lookahead slot 134 slot = null; 135 slotFilled = false; 136 return x; 137 } 138 139 /** 140 * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned. 141 * <p> 142 * Note: this method does not throw a {@link NoSuchElementException} if the iterator is already exhausted. If you want such a behavior, use 143 * {@link #element()} instead. 144 * </p> 145 * <p> 146 * The rationale behind this is to follow the {@link java.util.Queue} interface which uses the same terminology. 147 * </p> 148 * <p> 149 * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if 150 * {@link #element()} or peek() has been called after the most recent invocation of {@link #next()}. 151 * </p> 152 * 153 * @return the next element from the iterator 154 */ 155 public E peek() { 156 fill(); 157 return exhausted ? null : slot; 158 } 159 160 /** 161 * {@inheritDoc} 162 * 163 * @throws IllegalStateException if {@link #peek()} or {@link #element()} has been called prior to the call to {@link #remove()}. 164 */ 165 @Override 166 public void remove() { 167 if (slotFilled) { 168 throw new IllegalStateException("peek() or element() called before remove()"); 169 } 170 iterator.remove(); 171 } 172 173 }