1 /*
2  * Copyright (C) 2007 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 com.google.common.annotations.GwtCompatible;
20 import com.google.common.annotations.GwtIncompatible;
21 import com.google.common.testing.GcFinalization;
22 
23 import junit.framework.TestCase;
24 
25 import java.lang.ref.WeakReference;
26 import java.util.Iterator;
27 import java.util.NoSuchElementException;
28 
29 /**
30  * Unit test for {@code AbstractIterator}.
31  *
32  * @author Kevin Bourrillion
33  */
34 @SuppressWarnings("serial") // No serialization is used in this test
35 @GwtCompatible(emulated = true)
36 // TODO(cpovirk): why is this slow (>1m/test) under GWT when fully optimized?
37 public class AbstractIteratorTest extends TestCase {
38 
39   public void testDefaultBehaviorOfNextAndHasNext() {
40 
41     // This sample AbstractIterator returns 0 on the first call, 1 on the
42     // second, then signals that it's reached the end of the data
43     Iterator<Integer> iter = new AbstractIterator<Integer>() {
44       private int rep;
45       @Override public Integer computeNext() {
46         switch (rep++) {
47           case 0:
48             return 0;
49           case 1:
50             return 1;
51           case 2:
52             return endOfData();
53           default:
54             fail("Should not have been invoked again");
55             return null;
56         }
57       }
58     };
59 
60     assertTrue(iter.hasNext());
61     assertEquals(0, (int) iter.next());
62 
63     // verify idempotence of hasNext()
64     assertTrue(iter.hasNext());
65     assertTrue(iter.hasNext());
66     assertTrue(iter.hasNext());
67     assertEquals(1, (int) iter.next());
68 
69     assertFalse(iter.hasNext());
70 
71     // Make sure computeNext() doesn't get invoked again
72     assertFalse(iter.hasNext());
73 
74     try {
75       iter.next();
76       fail("no exception thrown");
77     } catch (NoSuchElementException expected) {
78     }
79   }
80 
81   public void testDefaultBehaviorOfPeek() {
82     /*
83      * This sample AbstractIterator returns 0 on the first call, 1 on the
84      * second, then signals that it's reached the end of the data
85      */
86     AbstractIterator<Integer> iter = new AbstractIterator<Integer>() {
87       private int rep;
88       @Override public Integer computeNext() {
89         switch (rep++) {
90           case 0:
91             return 0;
92           case 1:
93             return 1;
94           case 2:
95             return endOfData();
96           default:
97             fail("Should not have been invoked again");
98             return null;
99         }
100       }
101     };
102 
103     assertEquals(0, (int) iter.peek());
104     assertEquals(0, (int) iter.peek());
105     assertTrue(iter.hasNext());
106     assertEquals(0, (int) iter.peek());
107     assertEquals(0, (int) iter.next());
108 
109     assertEquals(1, (int) iter.peek());
110     assertEquals(1, (int) iter.next());
111 
112     try {
113       iter.peek();
114       fail("peek() should throw NoSuchElementException at end");
115     } catch (NoSuchElementException expected) {
116     }
117 
118     try {
119       iter.peek();
120       fail("peek() should continue to throw NoSuchElementException at end");
121     } catch (NoSuchElementException expected) {
122     }
123 
124     try {
125       iter.next();
126       fail("next() should throw NoSuchElementException as usual");
127     } catch (NoSuchElementException expected) {
128     }
129 
130     try {
131       iter.peek();
132       fail("peek() should still throw NoSuchElementException after next()");
133     } catch (NoSuchElementException expected) {
134     }
135   }
136 
137   @GwtIncompatible("weak references")
138   public void testFreesNextReference() {
139     Iterator<Object> itr = new AbstractIterator<Object>() {
140       @Override public Object computeNext() {
141         return new Object();
142       }
143     };
144     WeakReference<Object> ref = new WeakReference<Object>(itr.next());
145     GcFinalization.awaitClear(ref);
146   }
147 
148   public void testDefaultBehaviorOfPeekForEmptyIteration() {
149 
150     AbstractIterator<Integer> empty = new AbstractIterator<Integer>() {
151       private boolean alreadyCalledEndOfData;
152       @Override public Integer computeNext() {
153         if (alreadyCalledEndOfData) {
154           fail("Should not have been invoked again");
155         }
156         alreadyCalledEndOfData = true;
157         return endOfData();
158       }
159     };
160 
161     try {
162       empty.peek();
163       fail("peek() should throw NoSuchElementException at end");
164     } catch (NoSuchElementException expected) {
165     }
166 
167     try {
168       empty.peek();
169       fail("peek() should continue to throw NoSuchElementException at end");
170     } catch (NoSuchElementException expected) {
171     }
172   }
173 
174   public void testSneakyThrow() throws Exception {
175     Iterator<Integer> iter = new AbstractIterator<Integer>() {
176       boolean haveBeenCalled;
177       @Override public Integer computeNext() {
178         if (haveBeenCalled) {
179           fail("Should not have been called again");
180         } else {
181           haveBeenCalled = true;
182           sneakyThrow(new SomeCheckedException());
183         }
184         return null; // never reached
185       }
186     };
187 
188     // The first time, the sneakily-thrown exception comes out
189     try {
190       iter.hasNext();
191       fail("No exception thrown");
192     } catch (Exception e) {
193       if (!(e instanceof SomeCheckedException)) {
194         throw e;
195       }
196     }
197 
198     // But the second time, AbstractIterator itself throws an ISE
199     try {
200       iter.hasNext();
201       fail("No exception thrown");
202     } catch (IllegalStateException expected) {
203     }
204   }
205 
206   public void testException() {
207     final SomeUncheckedException exception = new SomeUncheckedException();
208     Iterator<Integer> iter = new AbstractIterator<Integer>() {
209       @Override public Integer computeNext() {
210         throw exception;
211       }
212     };
213 
214     // It should pass through untouched
215     try {
216       iter.hasNext();
217       fail("No exception thrown");
218     } catch (SomeUncheckedException e) {
219       assertSame(exception, e);
220     }
221   }
222 
223   public void testExceptionAfterEndOfData() {
224     Iterator<Integer> iter = new AbstractIterator<Integer>() {
225       @Override public Integer computeNext() {
226         endOfData();
227         throw new SomeUncheckedException();
228       }
229     };
230     try {
231       iter.hasNext();
232       fail("No exception thrown");
233     } catch (SomeUncheckedException expected) {
234     }
235   }
236 
237   public void testCantRemove() {
238     Iterator<Integer> iter = new AbstractIterator<Integer>() {
239       boolean haveBeenCalled;
240       @Override public Integer computeNext() {
241         if (haveBeenCalled) {
242           endOfData();
243         }
244         haveBeenCalled = true;
245         return 0;
246       }
247     };
248 
249     assertEquals(0, (int) iter.next());
250 
251     try {
252       iter.remove();
253       fail("No exception thrown");
254     } catch (UnsupportedOperationException expected) {
255     }
256   }
257 
258   public void testReentrantHasNext() {
259     Iterator<Integer> iter = new AbstractIterator<Integer>() {
260       @Override protected Integer computeNext() {
261         hasNext();
262         return null;
263       }
264     };
265     try {
266       iter.hasNext();
267       fail();
268     } catch (IllegalStateException expected) {
269     }
270   }
271 
272   // Technically we should test other reentrant scenarios (9 combinations of
273   // hasNext/next/peek), but we'll cop out for now, knowing that peek() and
274   // next() both start by invoking hasNext() anyway.
275 
276   /**
277    * Throws a undeclared checked exception.
278    */
279   private static void sneakyThrow(Throwable t) {
280     class SneakyThrower<T extends Throwable> {
281       @SuppressWarnings("unchecked") // not really safe, but that's the point
282       void throwIt(Throwable t) throws T {
283         throw (T) t;
284       }
285     }
286     new SneakyThrower<Error>().throwIt(t);
287   }
288 
289   private static class SomeCheckedException extends Exception {
290   }
291 
292   private static class SomeUncheckedException extends RuntimeException {
293   }
294 }
295