• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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