| /* |
| * Copyright (C) 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.common.collect; |
| |
| import static com.google.common.collect.Iterators.peekingIterator; |
| import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; |
| import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; |
| import com.google.common.collect.testing.IteratorTester; |
| |
| import junit.framework.TestCase; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import static java.util.Collections.emptyList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| |
| /** |
| * Unit test for {@link PeekingIterator}. |
| * |
| * @author Mick Killianey |
| */ |
| @SuppressWarnings("serial") // No serialization is used in this test |
| public class PeekingIteratorTest extends TestCase { |
| |
| /** |
| * Version of {@link IteratorTester} that compares an iterator over |
| * a given collection of elements (used as the reference iterator) |
| * against a {@code PeekingIterator} that *wraps* such an iterator |
| * (used as the target iterator). |
| * |
| * <p>This IteratorTester makes copies of the master so that it can |
| * later verify that {@link PeekingIterator#remove()} removes the |
| * same elements as the reference's iterator {@code #remove()}. |
| */ |
| private static class PeekingIteratorTester<T> extends IteratorTester<T> { |
| private Iterable<T> master; |
| private List<T> targetList; |
| |
| public PeekingIteratorTester(Collection<T> master) { |
| super(master.size() + 3, MODIFIABLE, master, |
| IteratorTester.KnownOrder.KNOWN_ORDER); |
| this.master = master; |
| } |
| @Override protected Iterator<T> newTargetIterator() { |
| // make copy from master to verify later |
| targetList = Lists.newArrayList(master); |
| Iterator<T> iterator = targetList.iterator(); |
| return Iterators.peekingIterator(iterator); |
| } |
| @Override protected void verify(List<T> elements) { |
| // verify same objects were removed from reference and target |
| assertEquals(elements, targetList); |
| } |
| } |
| |
| private <T> void actsLikeIteratorHelper(final List<T> list) throws Exception { |
| // Check with modifiable copies of the list |
| new PeekingIteratorTester<T>(list).test(); |
| |
| // Check with unmodifiable lists |
| new IteratorTester<T>(list.size() * 2 + 2, UNMODIFIABLE, list, |
| IteratorTester.KnownOrder.KNOWN_ORDER) { |
| @Override protected Iterator<T> newTargetIterator() { |
| Iterator<T> iterator = Collections.unmodifiableList(list).iterator(); |
| return Iterators.peekingIterator(iterator); |
| } |
| }.test(); |
| } |
| |
| public void testPeekingIteratorBehavesLikeIteratorOnEmptyIterable() |
| throws Exception { |
| actsLikeIteratorHelper(Collections.emptyList()); |
| } |
| |
| public void testPeekingIteratorBehavesLikeIteratorOnSingletonIterable() |
| throws Exception { |
| actsLikeIteratorHelper(Collections.singletonList(new Object())); |
| } |
| |
| public void testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable() |
| throws Exception { |
| actsLikeIteratorHelper(Lists.newArrayList("A", "B", "C")); |
| } |
| |
| public void testPeekingIteratorAcceptsNullElements() throws Exception { |
| actsLikeIteratorHelper(Lists.newArrayList(null, "A", null)); |
| } |
| |
| public void testPeekOnEmptyList() { |
| List<?> list = Collections.emptyList(); |
| Iterator<?> iterator = list.iterator(); |
| PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); |
| |
| try { |
| peekingIterator.peek(); |
| fail("Should throw NoSuchElementException if nothing to peek()"); |
| } catch (NoSuchElementException e) { /* expected */ } |
| } |
| |
| public void testPeekDoesntChangeIteration() { |
| List<?> list = Lists.newArrayList("A", "B", "C"); |
| Iterator<?> iterator = list.iterator(); |
| PeekingIterator<?> peekingIterator = |
| Iterators.peekingIterator(iterator); |
| |
| assertEquals("Should be able to peek() at first element", |
| "A", peekingIterator.peek()); |
| assertEquals("Should be able to peek() first element multiple times", |
| "A", peekingIterator.peek()); |
| assertEquals("next() should still return first element after peeking", |
| "A", peekingIterator.next()); |
| |
| assertEquals("Should be able to peek() at middle element", |
| "B", peekingIterator.peek()); |
| assertEquals("Should be able to peek() middle element multiple times", |
| "B", peekingIterator.peek()); |
| assertEquals("next() should still return middle element after peeking", |
| "B", peekingIterator.next()); |
| |
| assertEquals("Should be able to peek() at last element", |
| "C", peekingIterator.peek()); |
| assertEquals("Should be able to peek() last element multiple times", |
| "C", peekingIterator.peek()); |
| assertEquals("next() should still return last element after peeking", |
| "C", peekingIterator.next()); |
| |
| try { |
| peekingIterator.peek(); |
| fail("Should throw exception if no next to peek()"); |
| } catch (NoSuchElementException e) { /* expected */ } |
| try { |
| peekingIterator.peek(); |
| fail("Should continue to throw exception if no next to peek()"); |
| } catch (NoSuchElementException e) { /* expected */ } |
| try { |
| peekingIterator.next(); |
| fail("next() should still throw exception after the end of iteration"); |
| } catch (NoSuchElementException e) { /* expected */ } |
| } |
| |
| public void testCantRemoveAfterPeek() { |
| List<String> list = Lists.newArrayList("A", "B", "C"); |
| Iterator<String> iterator = list.iterator(); |
| PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); |
| |
| assertEquals("A", peekingIterator.next()); |
| assertEquals("B", peekingIterator.peek()); |
| |
| /* Should complain on attempt to remove() after peek(). */ |
| try { |
| peekingIterator.remove(); |
| fail("remove() should throw IllegalStateException after a peek()"); |
| } catch (IllegalStateException e) { /* expected */ } |
| |
| assertEquals("After remove() throws exception, peek should still be ok", |
| "B", peekingIterator.peek()); |
| |
| /* Should recover to be able to remove() after next(). */ |
| assertEquals("B", peekingIterator.next()); |
| peekingIterator.remove(); |
| assertEquals("Should have removed an element", 2, list.size()); |
| assertFalse("Second element should be gone", list.contains("B")); |
| } |
| |
| static class ThrowsAtEndException extends RuntimeException { /* nothing */ } |
| |
| /** |
| * This Iterator claims to have more elements than the underlying |
| * iterable, but when you try to fetch the extra elements, it throws |
| * an unchecked exception. |
| */ |
| static class ThrowsAtEndIterator<E> implements Iterator<E> { |
| Iterator<E> iterator; |
| public ThrowsAtEndIterator(Iterable<E> iterable) { |
| this.iterator = iterable.iterator(); |
| } |
| public boolean hasNext() { |
| return true; // pretend that you have more... |
| } |
| public E next() { |
| // ...but throw an unchecked exception when you ask for it. |
| if (!iterator.hasNext()) { |
| throw new ThrowsAtEndException(); |
| } |
| return iterator.next(); |
| } |
| public void remove() { |
| iterator.remove(); |
| } |
| } |
| |
| public void testPeekingIteratorDoesntAdvancePrematurely() throws Exception { |
| /* |
| * This test will catch problems where the underlying iterator |
| * throws a RuntimeException when retrieving the nth element. |
| * |
| * If the PeekingIterator is caching elements too aggressively, |
| * it may throw the exception on the (n-1)th element (oops!). |
| */ |
| |
| /* Checks the case where the first element throws an exception. */ |
| |
| List<Integer> list = emptyList(); |
| Iterator<Integer> iterator = |
| peekingIterator(new ThrowsAtEndIterator<Integer>(list)); |
| assertNextThrows(iterator); |
| |
| /* Checks the case where a later element throws an exception. */ |
| |
| list = Lists.newArrayList(1, 2); |
| iterator = peekingIterator(new ThrowsAtEndIterator<Integer>(list)); |
| assertTrue(iterator.hasNext()); |
| iterator.next(); |
| assertTrue(iterator.hasNext()); |
| iterator.next(); |
| assertNextThrows(iterator); |
| } |
| |
| private void assertNextThrows(Iterator<?> iterator) { |
| try { |
| iterator.next(); |
| fail(); |
| } catch (ThrowsAtEndException expected) { |
| } |
| } |
| } |