content: /*
   * Copyright (C) 2007 The Guava Authors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.cassandra.utils;
  
  import org.junit.Assert;
  import org.junit.Test;
  
  import java.lang.ref.WeakReference;
  import java.util.Iterator;
  import java.util.NoSuchElementException;
  
  /**
   * Unit test for {@code AbstractIterator}.
   *
   * @author Kevin Bourrillion
   */
  @SuppressWarnings("serial") // No serialization is used in this test
  // TODO(cpovirk): why is this slow (>1m/test) under GWT when fully optimized?
  public class AbstractIteratorTest
  {
      @Test
      public void testDefaultBehaviorOfNextAndHasNext()
      {
  
          // This sample AbstractIterator returns 0 on the first call, 1 on the
          // second, then signals that it's reached the end of the data
          Iterator<Integer> iter = new AbstractIterator<Integer>()
          {
              private int rep;
  
              @Override
              public Integer computeNext()
              {
                  switch (rep++)
                  {
                      case 0:
                          return 0;
                      case 1:
                          return 1;
                      case 2:
                          return endOfData();
                      default:
                          Assert.fail("Should not have been invoked again");
                          return null;
                  }
              }
          };
  
          Assert.assertTrue(iter.hasNext());
          Assert.assertEquals(0, (int) iter.next());
  
          // verify idempotence of hasNext()
          Assert.assertTrue(iter.hasNext());
          Assert.assertTrue(iter.hasNext());
          Assert.assertTrue(iter.hasNext());
          Assert.assertEquals(1, (int) iter.next());
  
          Assert.assertFalse(iter.hasNext());
  
          // Make sure computeNext() doesn't get invoked again
          Assert.assertFalse(iter.hasNext());
  
          try
          {
              iter.next();
              Assert.fail("no exception thrown");
          }
          catch (NoSuchElementException expected)
          {
          }
      }
  
      @Test
      public void testDefaultBehaviorOfPeek()
      {
      /*
       * This sample AbstractIterator returns 0 on the first call, 1 on the
       * second, then signals that it's reached the end of the data
       */
          AbstractIterator<Integer> iter = new AbstractIterator<Integer>()
          {
              private int rep;
  
              @Override
              public Integer computeNext()
              {
                  switch (rep++)
                  {
                      case 0:
                          return 0;
                      case 1:
                          return 1;
                      case 2:
                          return endOfData();
                      default:
                          Assert.fail("Should not have been invoked again");
                          return null;
                  }
              }
          };
  
          Assert.assertEquals(0, (int) iter.peek());
          Assert.assertEquals(0, (int) iter.peek());
          Assert.assertTrue(iter.hasNext());
          Assert.assertEquals(0, (int) iter.peek());
          Assert.assertEquals(0, (int) iter.next());
  
          Assert.assertEquals(1, (int) iter.peek());
          Assert.assertEquals(1, (int) iter.next());
  
          try
          {
              iter.peek();
              Assert.fail("peek() should throw NoSuchElementException at end");
          }
          catch (NoSuchElementException expected)
          {
          }
  
          try
          {
              iter.peek();
              Assert.fail("peek() should continue to throw NoSuchElementException at end");
          }
          catch (NoSuchElementException expected)
          {
          }
  
          try
          {
              iter.next();
              Assert.fail("next() should throw NoSuchElementException as usual");
          }
          catch (NoSuchElementException expected)
          {
          }
  
          try
          {
              iter.peek();
              Assert.fail("peek() should still throw NoSuchElementException after next()");
          }
          catch (NoSuchElementException expected)
          {
          }
      }
  
      @Test
      public void testFreesNextReference() throws InterruptedException
      {
          Iterator<Object> itr = new AbstractIterator<Object>()
          {
              @Override
              public Object computeNext()
              {
                  return new Object();
              }
          };
          WeakReference<Object> ref = new WeakReference<Object>(itr.next());
          while (ref.get() != null)
          {
              System.gc();
              Thread.sleep(1);
          }
      }
  
      @Test
      public void testDefaultBehaviorOfPeekForEmptyIteration()
      {
  
          AbstractIterator<Integer> empty = new AbstractIterator<Integer>()
          {
              private boolean alreadyCalledEndOfData;
  
              @Override
              public Integer computeNext()
              {
                  if (alreadyCalledEndOfData)
                  {
                      Assert.fail("Should not have been invoked again");
                  }
                  alreadyCalledEndOfData = true;
                  return endOfData();
              }
          };
  
          try
          {
              empty.peek();
              Assert.fail("peek() should throw NoSuchElementException at end");
          }
          catch (NoSuchElementException expected)
          {
          }
  
          try
          {
              empty.peek();
              Assert.fail("peek() should continue to throw NoSuchElementException at end");
          }
          catch (NoSuchElementException expected)
          {
          }
      }
  
      @Test
      public void testException()
      {
          final SomeUncheckedException exception = new SomeUncheckedException();
          Iterator<Integer> iter = new AbstractIterator<Integer>()
          {
              @Override
              public Integer computeNext()
              {
                  throw exception;
              }
          };
  
          // It should pass through untouched
          try
          {
              iter.hasNext();
              Assert.fail("No exception thrown");
          }
          catch (SomeUncheckedException e)
          {
              Assert.assertSame(exception, e);
          }
      }
  
      @Test
      public void testExceptionAfterEndOfData()
      {
          Iterator<Integer> iter = new AbstractIterator<Integer>()
          {
              @Override
              public Integer computeNext()
              {
                  endOfData();
                  throw new SomeUncheckedException();
              }
          };
          try
          {
              iter.hasNext();
              Assert.fail("No exception thrown");
          }
          catch (SomeUncheckedException expected)
          {
          }
      }
  
      @Test
      public void testCantRemove()
      {
          Iterator<Integer> iter = new AbstractIterator<Integer>()
          {
              boolean haveBeenCalled;
  
              @Override
              public Integer computeNext()
              {
                  if (haveBeenCalled)
                  {
                      endOfData();
                  }
                  haveBeenCalled = true;
                  return 0;
              }
          };
  
          Assert.assertEquals(0, (int) iter.next());
  
          try
          {
              iter.remove();
              Assert.fail("No exception thrown");
          }
          catch (UnsupportedOperationException expected)
          {
          }
      }
  
      @Test
      public void testSneakyThrow() throws Exception
      {
          Iterator<Integer> iter = new AbstractIterator<Integer>()
          {
              boolean haveBeenCalled;
  
              @Override
              public Integer computeNext()
              {
                  if (haveBeenCalled)
                  {
                      Assert.fail("Should not have been called again");
                  }
                  else
                  {
                      haveBeenCalled = true;
                      sneakyThrow(new SomeCheckedException());
                  }
                  return null; // never reached
              }
          };
  
          // The first time, the sneakily-thrown exception comes out
          try
          {
              iter.hasNext();
              Assert.fail("No exception thrown");
          }
          catch (Exception e)
          {
              if (!(e instanceof SomeCheckedException))
              {
                  throw e;
              }
          }
  
          // But the second time, AbstractIterator itself throws an ISE
          try
          {
              iter.hasNext();
              Assert.fail("No exception thrown");
          }
          catch (IllegalStateException expected)
          {
          }
      }
  
      @Test
      public void testReentrantHasNext()
      {
          Iterator<Integer> iter = new AbstractIterator<Integer>()
          {
              @Override
              protected Integer computeNext()
              {
                  hasNext();
                  return null;
              }
          };
          try
          {
              iter.hasNext();
              Assert.fail();
          }
          catch (IllegalStateException expected)
          {
          }
      }
  
      /**
       * Throws a undeclared checked exception.
       */
      private static void sneakyThrow(Throwable t)
      {
          class SneakyThrower<T extends Throwable>
          {
              @SuppressWarnings("unchecked")
                  // not really safe, but that's the point
              void throwIt(Throwable t) throws T
              {
                  throw (T) t;
              }
          }
          new SneakyThrower<Error>().throwIt(t);
      }
  
      private static class SomeCheckedException extends Exception
      {
      }
  
      private static class SomeUncheckedException extends RuntimeException
      {
      }
  }
  