View Javadoc
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  
59      public PeekingIteratorTester(Collection<T> master) {
60        super(master.size() + 3, MODIFIABLE, master,
61            IteratorTester.KnownOrder.KNOWN_ORDER);
62        this.master = master;
63      }
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      }
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  
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  
90    public void testPeekingIteratorBehavesLikeIteratorOnEmptyIterable() {
91      actsLikeIteratorHelper(Collections.emptyList());
92    }
93  
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")
100   public void testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable() {
101     actsLikeIteratorHelper(Lists.newArrayList("A", "B", "C"));
102   }
103 
104   @GwtIncompatible("works but takes 5 minutes to run")
105   public void testPeekingIteratorAcceptsNullElements() {
106     actsLikeIteratorHelper(Lists.newArrayList(null, "A", null));
107   }
108 
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 
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 
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;
194     public ThrowsAtEndIterator(Iterable<E> iterable) {
195       this.iterator = iterable.iterator();
196     }
197     @Override
198     public boolean hasNext() {
199       return true;  // pretend that you have more...
200     }
201     @Override
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
210     public void remove() {
211       iterator.remove();
212     }
213   }
214 
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 
242   private void assertNextThrows(Iterator<?> iterator) {
243     try {
244       iterator.next();
245       fail();
246     } catch (ThrowsAtEndException expected) {
247     }
248   }
249 }