1 /* 2 * Copyright (C) 2008 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import static com.google.common.collect.Iterators.peekingIterator; 20 import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; 21 import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; 22 import static java.util.Collections.emptyList; 23 24 import com.google.common.annotations.GwtCompatible; 25 import com.google.common.annotations.GwtIncompatible; 26 import com.google.common.collect.testing.IteratorTester; 27 28 import junit.framework.TestCase; 29 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.Iterator; 33 import java.util.List; 34 import java.util.NoSuchElementException; 35 36 /** 37 * Unit test for {@link PeekingIterator}. 38 * 39 * @author Mick Killianey 40 */ 41 @SuppressWarnings("serial") // No serialization is used in this test 42 @GwtCompatible(emulated = true) 43 public class PeekingIteratorTest extends TestCase { 44 45 /** 46 * Version of {@link IteratorTester} that compares an iterator over 47 * a given collection of elements (used as the reference iterator) 48 * against a {@code PeekingIterator} that *wraps* such an iterator 49 * (used as the target iterator). 50 * 51 * <p>This IteratorTester makes copies of the master so that it can 52 * later verify that {@link PeekingIterator#remove()} removes the 53 * same elements as the reference's iterator {@code #remove()}. 54 */ 55 private static class PeekingIteratorTester<T> extends IteratorTester<T> { 56 private Iterable<T> master; 57 private List<T> targetList; 58 PeekingIteratorTester(Collection<T> master)59 public PeekingIteratorTester(Collection<T> master) { 60 super(master.size() + 3, MODIFIABLE, master, 61 IteratorTester.KnownOrder.KNOWN_ORDER); 62 this.master = master; 63 } newTargetIterator()64 @Override protected Iterator<T> newTargetIterator() { 65 // make copy from master to verify later 66 targetList = Lists.newArrayList(master); 67 Iterator<T> iterator = targetList.iterator(); 68 return Iterators.peekingIterator(iterator); 69 } verify(List<T> elements)70 @Override protected void verify(List<T> elements) { 71 // verify same objects were removed from reference and target 72 assertEquals(elements, targetList); 73 } 74 } 75 actsLikeIteratorHelper(final List<T> list)76 private <T> void actsLikeIteratorHelper(final List<T> list) { 77 // Check with modifiable copies of the list 78 new PeekingIteratorTester<T>(list).test(); 79 80 // Check with unmodifiable lists 81 new IteratorTester<T>(list.size() * 2 + 2, UNMODIFIABLE, list, 82 IteratorTester.KnownOrder.KNOWN_ORDER) { 83 @Override protected Iterator<T> newTargetIterator() { 84 Iterator<T> iterator = Collections.unmodifiableList(list).iterator(); 85 return Iterators.peekingIterator(iterator); 86 } 87 }.test(); 88 } 89 testPeekingIteratorBehavesLikeIteratorOnEmptyIterable()90 public void testPeekingIteratorBehavesLikeIteratorOnEmptyIterable() { 91 actsLikeIteratorHelper(Collections.emptyList()); 92 } 93 testPeekingIteratorBehavesLikeIteratorOnSingletonIterable()94 public void testPeekingIteratorBehavesLikeIteratorOnSingletonIterable() { 95 actsLikeIteratorHelper(Collections.singletonList(new Object())); 96 } 97 98 // TODO(cpovirk): instead of skipping, use a smaller number of steps 99 @GwtIncompatible("works but takes 5 minutes to run") testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable()100 public void testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable() { 101 actsLikeIteratorHelper(Lists.newArrayList("A", "B", "C")); 102 } 103 104 @GwtIncompatible("works but takes 5 minutes to run") testPeekingIteratorAcceptsNullElements()105 public void testPeekingIteratorAcceptsNullElements() { 106 actsLikeIteratorHelper(Lists.newArrayList(null, "A", null)); 107 } 108 testPeekOnEmptyList()109 public void testPeekOnEmptyList() { 110 List<?> list = Collections.emptyList(); 111 Iterator<?> iterator = list.iterator(); 112 PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); 113 114 try { 115 peekingIterator.peek(); 116 fail("Should throw NoSuchElementException if nothing to peek()"); 117 } catch (NoSuchElementException e) { /* expected */ } 118 } 119 testPeekDoesntChangeIteration()120 public void testPeekDoesntChangeIteration() { 121 List<?> list = Lists.newArrayList("A", "B", "C"); 122 Iterator<?> iterator = list.iterator(); 123 PeekingIterator<?> peekingIterator = 124 Iterators.peekingIterator(iterator); 125 126 assertEquals("Should be able to peek() at first element", 127 "A", peekingIterator.peek()); 128 assertEquals("Should be able to peek() first element multiple times", 129 "A", peekingIterator.peek()); 130 assertEquals("next() should still return first element after peeking", 131 "A", peekingIterator.next()); 132 133 assertEquals("Should be able to peek() at middle element", 134 "B", peekingIterator.peek()); 135 assertEquals("Should be able to peek() middle element multiple times", 136 "B", peekingIterator.peek()); 137 assertEquals("next() should still return middle element after peeking", 138 "B", peekingIterator.next()); 139 140 assertEquals("Should be able to peek() at last element", 141 "C", peekingIterator.peek()); 142 assertEquals("Should be able to peek() last element multiple times", 143 "C", peekingIterator.peek()); 144 assertEquals("next() should still return last element after peeking", 145 "C", peekingIterator.next()); 146 147 try { 148 peekingIterator.peek(); 149 fail("Should throw exception if no next to peek()"); 150 } catch (NoSuchElementException e) { /* expected */ } 151 try { 152 peekingIterator.peek(); 153 fail("Should continue to throw exception if no next to peek()"); 154 } catch (NoSuchElementException e) { /* expected */ } 155 try { 156 peekingIterator.next(); 157 fail("next() should still throw exception after the end of iteration"); 158 } catch (NoSuchElementException e) { /* expected */ } 159 } 160 testCantRemoveAfterPeek()161 public void testCantRemoveAfterPeek() { 162 List<String> list = Lists.newArrayList("A", "B", "C"); 163 Iterator<String> iterator = list.iterator(); 164 PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); 165 166 assertEquals("A", peekingIterator.next()); 167 assertEquals("B", peekingIterator.peek()); 168 169 /* Should complain on attempt to remove() after peek(). */ 170 try { 171 peekingIterator.remove(); 172 fail("remove() should throw IllegalStateException after a peek()"); 173 } catch (IllegalStateException e) { /* expected */ } 174 175 assertEquals("After remove() throws exception, peek should still be ok", 176 "B", peekingIterator.peek()); 177 178 /* Should recover to be able to remove() after next(). */ 179 assertEquals("B", peekingIterator.next()); 180 peekingIterator.remove(); 181 assertEquals("Should have removed an element", 2, list.size()); 182 assertFalse("Second element should be gone", list.contains("B")); 183 } 184 185 static class ThrowsAtEndException extends RuntimeException { /* nothing */ } 186 187 /** 188 * This Iterator claims to have more elements than the underlying 189 * iterable, but when you try to fetch the extra elements, it throws 190 * an unchecked exception. 191 */ 192 static class ThrowsAtEndIterator<E> implements Iterator<E> { 193 Iterator<E> iterator; ThrowsAtEndIterator(Iterable<E> iterable)194 public ThrowsAtEndIterator(Iterable<E> iterable) { 195 this.iterator = iterable.iterator(); 196 } 197 @Override hasNext()198 public boolean hasNext() { 199 return true; // pretend that you have more... 200 } 201 @Override next()202 public E next() { 203 // ...but throw an unchecked exception when you ask for it. 204 if (!iterator.hasNext()) { 205 throw new ThrowsAtEndException(); 206 } 207 return iterator.next(); 208 } 209 @Override remove()210 public void remove() { 211 iterator.remove(); 212 } 213 } 214 testPeekingIteratorDoesntAdvancePrematurely()215 public void testPeekingIteratorDoesntAdvancePrematurely() throws Exception { 216 /* 217 * This test will catch problems where the underlying iterator 218 * throws a RuntimeException when retrieving the nth element. 219 * 220 * If the PeekingIterator is caching elements too aggressively, 221 * it may throw the exception on the (n-1)th element (oops!). 222 */ 223 224 /* Checks the case where the first element throws an exception. */ 225 226 List<Integer> list = emptyList(); 227 Iterator<Integer> iterator = 228 peekingIterator(new ThrowsAtEndIterator<Integer>(list)); 229 assertNextThrows(iterator); 230 231 /* Checks the case where a later element throws an exception. */ 232 233 list = Lists.newArrayList(1, 2); 234 iterator = peekingIterator(new ThrowsAtEndIterator<Integer>(list)); 235 assertTrue(iterator.hasNext()); 236 iterator.next(); 237 assertTrue(iterator.hasNext()); 238 iterator.next(); 239 assertNextThrows(iterator); 240 } 241 assertNextThrows(Iterator<?> iterator)242 private void assertNextThrows(Iterator<?> iterator) { 243 try { 244 iterator.next(); 245 fail(); 246 } catch (ThrowsAtEndException expected) { 247 } 248 } 249 } 250