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