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
17package com.google.common.collect;
18
19import com.google.common.annotations.GwtCompatible;
20import com.google.common.annotations.GwtIncompatible;
21import com.google.common.testing.GcFinalization;
22
23import junit.framework.TestCase;
24
25import java.lang.ref.WeakReference;
26import java.util.Iterator;
27import 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?
37public 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