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 import java.util.Collection; 28 import java.util.Collections; 29 import java.util.Iterator; 30 import java.util.List; 31 import java.util.NoSuchElementException; 32 import junit.framework.TestCase; 33 34 /** 35 * Unit test for {@link PeekingIterator}. 36 * 37 * @author Mick Killianey 38 */ 39 @SuppressWarnings("serial") // No serialization is used in this test 40 @GwtCompatible(emulated = true) 41 public class PeekingIteratorTest extends TestCase { 42 43 /** 44 * Version of {@link IteratorTester} that compares an iterator over a given collection of elements 45 * (used as the reference iterator) against a {@code PeekingIterator} that *wraps* such an 46 * iterator (used as the target iterator). 47 * 48 * <p>This IteratorTester makes copies of the master so that it can later verify that {@link 49 * PeekingIterator#remove()} removes the same elements as the reference's iterator {@code 50 * #remove()}. 51 */ 52 private static class PeekingIteratorTester<T> extends IteratorTester<T> { 53 private Iterable<T> master; 54 private List<T> targetList; 55 PeekingIteratorTester(Collection<T> master)56 public PeekingIteratorTester(Collection<T> master) { 57 super(master.size() + 3, MODIFIABLE, master, IteratorTester.KnownOrder.KNOWN_ORDER); 58 this.master = master; 59 } 60 61 @Override newTargetIterator()62 protected Iterator<T> newTargetIterator() { 63 // make copy from master to verify later 64 targetList = Lists.newArrayList(master); 65 Iterator<T> iterator = targetList.iterator(); 66 return Iterators.peekingIterator(iterator); 67 } 68 69 @Override verify(List<T> elements)70 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>( 82 list.size() * 2 + 2, UNMODIFIABLE, list, IteratorTester.KnownOrder.KNOWN_ORDER) { 83 @Override 84 protected Iterator<T> newTargetIterator() { 85 Iterator<T> iterator = Collections.unmodifiableList(list).iterator(); 86 return Iterators.peekingIterator(iterator); 87 } 88 }.test(); 89 } 90 testPeekingIteratorBehavesLikeIteratorOnEmptyIterable()91 public void testPeekingIteratorBehavesLikeIteratorOnEmptyIterable() { 92 actsLikeIteratorHelper(Collections.emptyList()); 93 } 94 testPeekingIteratorBehavesLikeIteratorOnSingletonIterable()95 public void testPeekingIteratorBehavesLikeIteratorOnSingletonIterable() { 96 actsLikeIteratorHelper(Collections.singletonList(new Object())); 97 } 98 99 // TODO(cpovirk): instead of skipping, use a smaller number of steps 100 @GwtIncompatible // works but takes 5 minutes to run testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable()101 public void testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable() { 102 actsLikeIteratorHelper(Lists.newArrayList("A", "B", "C")); 103 } 104 105 @GwtIncompatible // works but takes 5 minutes to run testPeekingIteratorAcceptsNullElements()106 public void testPeekingIteratorAcceptsNullElements() { 107 actsLikeIteratorHelper(Lists.newArrayList(null, "A", null)); 108 } 109 testPeekOnEmptyList()110 public void testPeekOnEmptyList() { 111 List<?> list = Collections.emptyList(); 112 Iterator<?> iterator = list.iterator(); 113 PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); 114 115 try { 116 peekingIterator.peek(); 117 fail("Should throw NoSuchElementException if nothing to peek()"); 118 } catch (NoSuchElementException e) { 119 /* expected */ 120 } 121 } 122 testPeekDoesntChangeIteration()123 public void testPeekDoesntChangeIteration() { 124 List<?> list = Lists.newArrayList("A", "B", "C"); 125 Iterator<?> iterator = list.iterator(); 126 PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); 127 128 assertEquals("Should be able to peek() at first element", "A", peekingIterator.peek()); 129 assertEquals( 130 "Should be able to peek() first element multiple times", "A", peekingIterator.peek()); 131 assertEquals( 132 "next() should still return first element after peeking", "A", peekingIterator.next()); 133 134 assertEquals("Should be able to peek() at middle element", "B", peekingIterator.peek()); 135 assertEquals( 136 "Should be able to peek() middle element multiple times", "B", peekingIterator.peek()); 137 assertEquals( 138 "next() should still return middle element after peeking", "B", peekingIterator.next()); 139 140 assertEquals("Should be able to peek() at last element", "C", peekingIterator.peek()); 141 assertEquals( 142 "Should be able to peek() last element multiple times", "C", peekingIterator.peek()); 143 assertEquals( 144 "next() should still return last element after peeking", "C", peekingIterator.next()); 145 146 try { 147 peekingIterator.peek(); 148 fail("Should throw exception if no next to peek()"); 149 } catch (NoSuchElementException e) { 150 /* expected */ 151 } 152 try { 153 peekingIterator.peek(); 154 fail("Should continue to throw exception if no next to peek()"); 155 } catch (NoSuchElementException e) { 156 /* expected */ 157 } 158 try { 159 peekingIterator.next(); 160 fail("next() should still throw exception after the end of iteration"); 161 } catch (NoSuchElementException e) { 162 /* expected */ 163 } 164 } 165 testCantRemoveAfterPeek()166 public void testCantRemoveAfterPeek() { 167 List<String> list = Lists.newArrayList("A", "B", "C"); 168 Iterator<String> iterator = list.iterator(); 169 PeekingIterator<?> peekingIterator = Iterators.peekingIterator(iterator); 170 171 assertEquals("A", peekingIterator.next()); 172 assertEquals("B", peekingIterator.peek()); 173 174 /* Should complain on attempt to remove() after peek(). */ 175 try { 176 peekingIterator.remove(); 177 fail("remove() should throw IllegalStateException after a peek()"); 178 } catch (IllegalStateException e) { 179 /* expected */ 180 } 181 182 assertEquals( 183 "After remove() throws exception, peek should still be ok", "B", peekingIterator.peek()); 184 185 /* Should recover to be able to remove() after next(). */ 186 assertEquals("B", peekingIterator.next()); 187 peekingIterator.remove(); 188 assertEquals("Should have removed an element", 2, list.size()); 189 assertFalse("Second element should be gone", list.contains("B")); 190 } 191 192 static class ThrowsAtEndException extends RuntimeException { 193 /* nothing */ 194 } 195 196 /** 197 * This Iterator claims to have more elements than the underlying iterable, but when you try to 198 * fetch the extra elements, it throws an unchecked exception. 199 */ 200 static class ThrowsAtEndIterator<E> implements Iterator<E> { 201 Iterator<E> iterator; 202 ThrowsAtEndIterator(Iterable<E> iterable)203 public ThrowsAtEndIterator(Iterable<E> iterable) { 204 this.iterator = iterable.iterator(); 205 } 206 207 @Override hasNext()208 public boolean hasNext() { 209 return true; // pretend that you have more... 210 } 211 212 @Override next()213 public E next() { 214 // ...but throw an unchecked exception when you ask for it. 215 if (!iterator.hasNext()) { 216 throw new ThrowsAtEndException(); 217 } 218 return iterator.next(); 219 } 220 221 @Override remove()222 public void remove() { 223 iterator.remove(); 224 } 225 } 226 testPeekingIteratorDoesntAdvancePrematurely()227 public void testPeekingIteratorDoesntAdvancePrematurely() throws Exception { 228 /* 229 * This test will catch problems where the underlying iterator 230 * throws a RuntimeException when retrieving the nth element. 231 * 232 * If the PeekingIterator is caching elements too aggressively, 233 * it may throw the exception on the (n-1)th element (oops!). 234 */ 235 236 /* Checks the case where the first element throws an exception. */ 237 238 List<Integer> list = emptyList(); 239 Iterator<Integer> iterator = peekingIterator(new ThrowsAtEndIterator<Integer>(list)); 240 assertNextThrows(iterator); 241 242 /* Checks the case where a later element throws an exception. */ 243 244 list = Lists.newArrayList(1, 2); 245 iterator = peekingIterator(new ThrowsAtEndIterator<Integer>(list)); 246 assertTrue(iterator.hasNext()); 247 iterator.next(); 248 assertTrue(iterator.hasNext()); 249 iterator.next(); 250 assertNextThrows(iterator); 251 } 252 assertNextThrows(Iterator<?> iterator)253 private void assertNextThrows(Iterator<?> iterator) { 254 try { 255 iterator.next(); 256 fail(); 257 } catch (ThrowsAtEndException expected) { 258 } 259 } 260 } 261