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 * http://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; 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} will be thrown if {@link #remove()} is called directly after a call to 027 * {@link #peek()} or {@link #element()}. 028 * </p> 029 * 030 * @param <E> the type of elements returned by this iterator. 031 * @since 4.0 032 */ 033public class PeekingIterator<E> implements Iterator<E> { 034 035 /** 036 * Decorates the specified iterator to support one-element lookahead. 037 * <p> 038 * If the iterator is already a {@link PeekingIterator} it is returned directly. 039 * </p> 040 * 041 * @param <E> the element type 042 * @param iterator the iterator to decorate 043 * @return a new peeking iterator 044 * @throws NullPointerException if the iterator is null 045 */ 046 public static <E> PeekingIterator<E> peekingIterator(final Iterator<? extends E> iterator) { 047 Objects.requireNonNull(iterator, "iterator"); 048 if (iterator instanceof PeekingIterator<?>) { 049 @SuppressWarnings("unchecked") // safe cast 050 final PeekingIterator<E> it = (PeekingIterator<E>) iterator; 051 return it; 052 } 053 return new PeekingIterator<>(iterator); 054 } 055 056 /** The iterator being decorated. */ 057 private final Iterator<? extends E> iterator; 058 059 /** Indicates that the decorated iterator is exhausted. */ 060 private boolean exhausted; 061 062 /** Indicates if the lookahead slot is filled. */ 063 private boolean slotFilled; 064 065 /** The current slot for lookahead. */ 066 private E slot; 067 068 /** 069 * Constructs a new instance. 070 * 071 * @param iterator the iterator to decorate 072 */ 073 public PeekingIterator(final Iterator<? extends E> iterator) { 074 this.iterator = iterator; 075 } 076 077 /** 078 * Returns the next element in iteration without advancing the underlying iterator. If the iterator is already exhausted, null will be returned. 079 * <p> 080 * Note that if the underlying iterator is a {@link FilterIterator} or a {@link FilterListIterator}, the underlying predicate will <em>not</em> be tested if 081 * element() or {@link #peek()} has been called after the most recent invocation of {@link #next()} 082 * </p> 083 * 084 * @return the next element from the iterator 085 * @throws NoSuchElementException if the iterator is already exhausted according to {@link #hasNext()} 086 */ 087 public E element() { 088 fill(); 089 if (exhausted) { 090 throw new NoSuchElementException(); 091 } 092 return slot; 093 } 094 095 private void fill() { 096 if (exhausted || slotFilled) { 097 return; 098 } 099 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}