Home | History | Annotate | Download | only in collect
      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 }
    250