@Test
 public void MultiReaderUnifiedSet_hasNext() {
   MultiReaderUnifiedSet<Integer> iterable = this.newWith(3, 2, 1);
   iterable.withReadLockAndDelegate(delegate -> assertTrue(delegate.iterator().hasNext()));
   MultiReaderUnifiedSet<?> emptyIterable = this.newWith();
   emptyIterable.withReadLockAndDelegate(delegate -> assertFalse(delegate.iterator().hasNext()));
 }
 @Test
 public void MultiReaderUnifiedSet_next_throws_on_empty() {
   MultiReaderUnifiedSet<Object> iterable = this.newWith();
   assertThrows(
       NoSuchElementException.class,
       () -> iterable.withReadLockAndDelegate(delegate -> delegate.iterator().next()));
 }
  private static void assertUnifiedSetClear(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      Assert.assertTrue(set.add(new CollidingInt(i, shift)));
    }
    set.clear();
    Verify.assertEmpty(set);
    for (int i = 0; i < size; i++) {
      Verify.assertNotContains(new CollidingInt(i, shift), set);
    }
  }
 // TODO Is it possible to pull with withReadLockAndDelegate to MultiReaderMutableCollection?
 // TODO Is it possible to pull with withWriteLockAndDelegate to MultiReaderMutableCollection?
 @Override
 @Test
 public void RichIterable_iterator_iterationOrder() {
   MutableCollection<Integer> iterationOrder = this.newMutableForFilter();
   MultiReaderUnifiedSet<Integer> instanceUnderTest = this.newWith(4, 3, 2, 1);
   instanceUnderTest.withReadLockAndDelegate(
       delegate -> {
         Iterator<Integer> iterator = delegate.iterator();
         while (iterator.hasNext()) {
           iterationOrder.add(iterator.next());
         }
       });
   assertEquals(this.expectedIterationOrder(), iterationOrder);
 }
  private static void assertUnifiedSetReplace(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      set.add(new CollidingInt(i, shift));
    }
    for (int i = 0; i < size; i++) {
      set.add(new CollidingInt(i, shift));
    }
    Verify.assertSize(size, set);
    for (int i = 0; i < size; i++) {
      Verify.assertContains(new CollidingInt(i, shift), set);
    }
  }
 @SafeVarargs
 @Override
 public final <T> MultiReaderUnifiedSet<T> newWith(T... elements) {
   MultiReaderUnifiedSet<T> result = MultiReaderUnifiedSet.newSet();
   IterableTestCase.addAllTo(elements, result);
   return result;
 }
 @Test
 public void MultiReaderUnifiedSet_next_throws_at_end() {
   MultiReaderUnifiedSet<Integer> iterable = this.newWith(3, 2, 1);
   iterable.withReadLockAndDelegate(
       delegate -> {
         Iterator<Integer> iterator = delegate.iterator();
         assertTrue(iterator.hasNext());
         iterator.next();
         assertTrue(iterator.hasNext());
         iterator.next();
         assertTrue(iterator.hasNext());
         iterator.next();
         assertFalse(iterator.hasNext());
         assertThrows(NoSuchElementException.class, (Runnable) iterator::next);
       });
 }
  private static void assertUnifiedSetForEach(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      Assert.assertTrue(set.add(new CollidingInt(i, shift)));
    }
    MutableList<CollidingInt> keys = FastList.newList(size);
    set.forEach(Procedures.cast(keys::add));
    Verify.assertSize(size, keys);
    Collections.sort(keys);

    for (int i = 0; i < size; i++) {
      Verify.assertItemAtIndex(new CollidingInt(i, shift), i, keys);
    }
  }
  private static void runUnifiedSetSerialize(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      set.add(new CollidingInt(i, shift));
    }
    set.add(null);
    set = SerializeTestHelper.serializeDeserialize(set);

    Verify.assertSize(size + 1, set);
    for (int i = 0; i < size; i++) {
      Verify.assertContains(new CollidingInt(i, shift), set);
    }
    Verify.assertContains(null, set);
  }
  private static void runUnifiedSetToArray(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      set.add(new CollidingInt(i, shift));
    }
    Verify.assertSize(size, set);

    Object[] keys = set.toArray();
    Assert.assertEquals(size, keys.length);
    Arrays.sort(keys);

    for (int i = 0; i < size; i++) {
      Verify.assertItemAtIndex(new CollidingInt(i, shift), i, keys);
    }
  }
  private static void runUnifiedSetRetainAllFromSet(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    Set<CollidingInt> toRetain = new HashSet<CollidingInt>();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      set.add(new CollidingInt(i, shift));
      if (i % 2 == 0) {
        toRetain.add(new CollidingInt(i, shift));
      }
    }
    Verify.assertSize(size, set);
    Assert.assertTrue(set.containsAll(toRetain));

    Assert.assertTrue(set.retainAll(toRetain));
    Assert.assertTrue(set.containsAll(toRetain));

    Assert.assertFalse(set.retainAll(toRetain)); // a second call should not modify the set

    Verify.assertSize(size / 2, set);

    for (int i = 0; i < size; i += 2) {
      Verify.assertContains(new CollidingInt(i, shift), set);
    }
  }
  @Test
  public void MultiReaderUnifiedSet_next() {
    MultiReaderUnifiedSet<Integer> iterable = this.newWith(3, 2, 1);

    MutableCollection<Integer> mutableCollection = this.newMutableForFilter();

    iterable.withReadLockAndDelegate(
        delegate -> {
          Iterator<Integer> iterator = delegate.iterator();
          while (iterator.hasNext()) {
            Integer integer = iterator.next();
            mutableCollection.add(integer);
          }

          assertEquals(this.getExpectedFiltered(3, 2, 1), mutableCollection);
          assertFalse(iterator.hasNext());
        });
  }
 @Test
 public void testUnifiedSetKeySetToArrayDest() {
   MutableSet<Integer> set = MultiReaderUnifiedSet.newSetWith(1, 2, 3, 4);
   // deliberately to small to force the method to allocate one of the correct size
   Integer[] dest = new Integer[2];
   Integer[] result = set.toArray(dest);
   Verify.assertSize(4, result);
   Arrays.sort(result);
   Assert.assertArrayEquals(new Integer[] {1, 2, 3, 4}, result);
 }
  @Test
  public void testUnifiedSet() {
    MultiReaderUnifiedSet<Integer> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      Assert.assertTrue(set.add(i));
    }
    Verify.assertSize(size, set);
    for (int i = 0; i < size; i++) {
      Verify.assertContains(i, set);
    }

    for (int i = 0; i < size; i += 2) {
      Assert.assertTrue(set.remove(i));
    }
    Verify.assertSize(size / 2, set);
    for (int i = 1; i < size; i += 2) {
      Verify.assertContains(i, set);
    }
  }
  private static void assertUnifiedSetForEachWith(int shift) {
    MultiReaderUnifiedSet<CollidingInt> set = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      Assert.assertTrue(set.add(new CollidingInt(i, shift)));
    }
    MutableList<CollidingInt> keys = FastList.newList(size);
    set.forEachWith(
        (key, s) -> {
          Assert.assertEquals("foo", s);
          keys.add(key);
        },
        "foo");
    Verify.assertSize(size, keys);
    Collections.sort(keys);

    for (int i = 0; i < size; i++) {
      Verify.assertItemAtIndex(new CollidingInt(i, shift), i, keys);
    }
  }
  private static void assertUnifiedSetEqualsAndHashCode(int shift) {
    MutableSet<CollidingInt> set1 = MultiReaderUnifiedSet.newSet();
    Set<CollidingInt> set2 = new HashSet<CollidingInt>();
    MutableSet<CollidingInt> set3 = MultiReaderUnifiedSet.newSet();
    MutableSet<CollidingInt> set4 = MultiReaderUnifiedSet.newSet();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      set1.add(new CollidingInt(i, shift));
      set2.add(new CollidingInt(i, shift));
      set3.add(new CollidingInt(i, shift));
      set4.add(new CollidingInt(size - i - 1, shift));
    }

    Assert.assertEquals(set1, set2);
    Assert.assertEquals(set1.hashCode(), set2.hashCode());
    Verify.assertSetsEqual(set1, set3);
    Verify.assertEqualsAndHashCode(set1, set3);
    Verify.assertSetsEqual(set2, set4);
    Assert.assertEquals(set4, set2);
    Assert.assertEquals(set2.hashCode(), set4.hashCode());
  }
  private void assertUnifiedSetPutDoesNotReplace(int shift) {
    MultiReaderUnifiedSet<CollidingIntWithFlag> set = MultiReaderUnifiedSet.newSet();

    for (int i = 0; i < 1000; i++) {
      Assert.assertTrue(set.add(new CollidingIntWithFlag(i, shift, false)));
    }
    Assert.assertEquals(1000, set.size());

    for (int i = 0; i < 1000; i++) {
      Assert.assertFalse(set.add(new CollidingIntWithFlag(i, shift, true)));
    }
    Assert.assertEquals(1000, set.size());
    set.withReadLockAndDelegate(
        delegate -> {
          for (CollidingIntWithFlag ciwf : delegate) {
            Assert.assertFalse(ciwf.isFlag());
          }
        });
  }
  private static void assertUnifiedSetAddAllWithHashSet(int shift) {
    Set<CollidingInt> set = new HashSet<CollidingInt>();

    int size = 100000;
    for (int i = 0; i < size; i++) {
      set.add(new CollidingInt(i, shift));
    }
    MultiReaderUnifiedSet<CollidingInt> newSet = MultiReaderUnifiedSet.newSet(size);
    newSet.addAll(set);

    Verify.assertSize(size, newSet);
    for (int i = 0; i < size; i++) {
      Verify.assertContains(new CollidingInt(i, shift), newSet);
    }

    MultiReaderUnifiedSet<CollidingInt> newSet2 = MultiReaderUnifiedSet.newSet();
    newSet2.addAll(set);

    Verify.assertSize(size, newSet2);
    for (int i = 0; i < size; i++) {
      Verify.assertContains(new CollidingInt(i, shift), newSet2);
    }
  }
public class ParallelIterateAcceptanceTest {
  private static final Procedure<Integer> EXCEPTION_PROCEDURE =
      value -> {
        throw new RuntimeException("Thread death on its way!");
      };
  private static final ObjectIntProcedure<Integer> EXCEPTION_OBJECT_INT_PROCEDURE =
      (object, index) -> {
        throw new RuntimeException("Thread death on its way!");
      };

  private static final Function<Integer, Collection<String>> INT_TO_TWO_STRINGS =
      integer -> Lists.fixedSize.of(integer.toString(), integer.toString());

  private static final Function0<AtomicInteger> ATOMIC_INTEGER_NEW = AtomicInteger::new;

  private static final Function<Integer, String> EVEN_OR_ODD =
      value -> value % 2 == 0 ? "Even" : "Odd";
  private int count;
  private final MutableSet<String> threadNames = MultiReaderUnifiedSet.newSet();

  private ImmutableList<RichIterable<Integer>> iterables;
  private final ExecutorService executor = Executors.newFixedThreadPool(2);

  @Before
  public void setUp() {
    Interval interval = Interval.oneTo(20000);
    this.iterables =
        Lists.immutable.of(
            interval.toList(),
            interval.toList().asUnmodifiable(),
            interval.toList().asSynchronized(),
            interval.toList().toImmutable(),
            interval.toSet(),
            interval.toSet().asUnmodifiable(),
            interval.toSet().asSynchronized(),
            interval.toSet().toImmutable(),
            interval.toBag(),
            interval.toBag().asUnmodifiable(),
            interval.toBag().asSynchronized(),
            interval.toBag().toImmutable(),
            interval.toSortedSet(),
            interval.toSortedSet().asUnmodifiable(),
            interval.toSortedSet().asSynchronized(),
            interval.toSortedSet().toImmutable(),
            interval.toMap(Functions.<Integer>getPassThru(), Functions.<Integer>getPassThru()),
            interval
                .toMap(Functions.<Integer>getPassThru(), Functions.<Integer>getPassThru())
                .asUnmodifiable(),
            interval
                .toMap(Functions.<Integer>getPassThru(), Functions.<Integer>getPassThru())
                .asSynchronized(),
            interval
                .toMap(Functions.<Integer>getPassThru(), Functions.<Integer>getPassThru())
                .toImmutable(),
            ArrayListAdapter.<Integer>newList().withAll(interval),
            ArrayListAdapter.<Integer>newList().withAll(interval).asUnmodifiable(),
            ArrayListAdapter.<Integer>newList().withAll(interval).asSynchronized(),
            new CompositeFastList<Integer>().withAll(interval.toList()),
            new CompositeFastList<Integer>().withAll(interval.toList()).asUnmodifiable(),
            new CompositeFastList<Integer>().withAll(interval.toList()).asSynchronized(),
            new CompositeFastList<Integer>().withAll(interval.toList()).toImmutable(),
            ListAdapter.adapt(new LinkedList<Integer>()).withAll(interval),
            ListAdapter.adapt(new LinkedList<Integer>()).withAll(interval).asUnmodifiable(),
            ListAdapter.adapt(new LinkedList<Integer>()).withAll(interval).asSynchronized(),
            UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy())
                .withAll(interval),
            UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy())
                .withAll(interval)
                .asUnmodifiable(),
            UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy())
                .withAll(interval)
                .asSynchronized(),
            UnifiedSetWithHashingStrategy.<Integer>newSet(HashingStrategies.defaultStrategy())
                .withAll(interval)
                .toImmutable());
  }

  @After
  public void tearDown() {
    this.executor.shutdown();
  }

  @Test
  public void testOneLevelCall() {
    new RecursiveProcedure().value(1);

    synchronized (this) {
      Assert.assertEquals("all iterations completed", 20000, this.count);
    }
  }

  @Test
  public void testNestedCall() {
    new RecursiveProcedure().value(2);

    synchronized (this) {
      Assert.assertEquals("all iterations completed", 419980, this.count);
    }
    Assert.assertTrue("uses multiple threads", this.threadNames.size() > 1);
  }

  @Test
  public void testForEachUsingSet() {
    // Tests the default batch size calculations
    IntegerSum sum = new IntegerSum(0);
    MutableSet<Integer> set = Interval.toSet(1, 10000);
    ParallelIterate.forEach(set, new SumProcedure(sum), new SumCombiner(sum));
    Assert.assertEquals(50005000, sum.getSum());

    // Testing batch size 1
    IntegerSum sum2 = new IntegerSum(0);
    UnifiedSet<Integer> set2 = UnifiedSet.newSet(Interval.oneTo(100));
    ParallelIterate.forEach(
        set2, new SumProcedure(sum2), new SumCombiner(sum2), 1, set2.getBatchCount(set2.size()));
    Assert.assertEquals(5050, sum2.getSum());

    // Testing an uneven batch size
    IntegerSum sum3 = new IntegerSum(0);
    UnifiedSet<Integer> set3 = UnifiedSet.newSet(Interval.oneTo(100));
    ParallelIterate.forEach(
        set3, new SumProcedure(sum3), new SumCombiner(sum3), 1, set3.getBatchCount(13));
    Assert.assertEquals(5050, sum3.getSum());

    // Testing divideByZero exception by passing 1 as batchSize
    IntegerSum sum4 = new IntegerSum(0);
    UnifiedSet<Integer> set4 = UnifiedSet.newSet(Interval.oneTo(100));
    ParallelIterate.forEach(set4, new SumProcedure(sum4), new SumCombiner(sum4), 1);
    Assert.assertEquals(5050, sum4.getSum());
  }

  @Test
  public void testForEachUsingMap() {
    // Test the default batch size calculations
    IntegerSum sum1 = new IntegerSum(0);
    MutableMap<String, Integer> map1 =
        Interval.fromTo(1, 10000).toMap(String::valueOf, Functions.getIntegerPassThru());
    ParallelIterate.forEach(map1, new SumProcedure(sum1), new SumCombiner(sum1));
    Assert.assertEquals(50005000, sum1.getSum());

    // Testing batch size 1
    IntegerSum sum2 = new IntegerSum(0);
    UnifiedMap<String, Integer> map2 =
        (UnifiedMap<String, Integer>)
            Interval.fromTo(1, 100).toMap(String::valueOf, Functions.getIntegerPassThru());
    ParallelIterate.forEach(
        map2, new SumProcedure(sum2), new SumCombiner(sum2), 1, map2.getBatchCount(map2.size()));
    Assert.assertEquals(5050, sum2.getSum());

    // Testing an uneven batch size
    IntegerSum sum3 = new IntegerSum(0);
    UnifiedMap<String, Integer> set3 =
        (UnifiedMap<String, Integer>)
            Interval.fromTo(1, 100).toMap(String::valueOf, Functions.getIntegerPassThru());
    ParallelIterate.forEach(
        set3, new SumProcedure(sum3), new SumCombiner(sum3), 1, set3.getBatchCount(13));
    Assert.assertEquals(5050, sum3.getSum());
  }

  @Test
  public void testForEach() {
    IntegerSum sum1 = new IntegerSum(0);
    List<Integer> list1 = ParallelIterateAcceptanceTest.createIntegerList(16);
    ParallelIterate.forEach(
        list1, new SumProcedure(sum1), new SumCombiner(sum1), 1, list1.size() / 2);
    Assert.assertEquals(16, sum1.getSum());

    IntegerSum sum2 = new IntegerSum(0);
    List<Integer> list2 = ParallelIterateAcceptanceTest.createIntegerList(7);
    ParallelIterate.forEach(list2, new SumProcedure(sum2), new SumCombiner(sum2));
    Assert.assertEquals(7, sum2.getSum());

    IntegerSum sum3 = new IntegerSum(0);
    List<Integer> list3 = ParallelIterateAcceptanceTest.createIntegerList(15);
    ParallelIterate.forEach(
        list3, new SumProcedure(sum3), new SumCombiner(sum3), 1, list3.size() / 2);
    Assert.assertEquals(15, sum3.getSum());

    IntegerSum sum4 = new IntegerSum(0);
    List<Integer> list4 = ParallelIterateAcceptanceTest.createIntegerList(35);
    ParallelIterate.forEach(list4, new SumProcedure(sum4), new SumCombiner(sum4));
    Assert.assertEquals(35, sum4.getSum());

    IntegerSum sum5 = new IntegerSum(0);
    MutableList<Integer> list5 = FastList.newList(list4);
    ParallelIterate.forEach(list5, new SumProcedure(sum5), new SumCombiner(sum5));
    Assert.assertEquals(35, sum5.getSum());

    IntegerSum sum6 = new IntegerSum(0);
    List<Integer> list6 = ParallelIterateAcceptanceTest.createIntegerList(40);
    ParallelIterate.forEach(
        list6, new SumProcedure(sum6), new SumCombiner(sum6), 1, list6.size() / 2);
    Assert.assertEquals(40, sum6.getSum());

    IntegerSum sum7 = new IntegerSum(0);
    MutableList<Integer> list7 = FastList.newList(list6);
    ParallelIterate.forEach(
        list7, new SumProcedure(sum7), new SumCombiner(sum7), 1, list6.size() / 2);
    Assert.assertEquals(40, sum7.getSum());
  }

  @Test
  public void testForEachImmutableList() {
    IntegerSum sum1 = new IntegerSum(0);
    ImmutableList<Integer> list1 =
        Lists.immutable.ofAll(ParallelIterateAcceptanceTest.createIntegerList(16));
    ParallelIterate.forEach(
        list1, new SumProcedure(sum1), new SumCombiner(sum1), 1, list1.size() / 2);
    Assert.assertEquals(16, sum1.getSum());

    IntegerSum sum2 = new IntegerSum(0);
    ImmutableList<Integer> list2 =
        Lists.immutable.ofAll(ParallelIterateAcceptanceTest.createIntegerList(7));
    ParallelIterate.forEach(list2, new SumProcedure(sum2), new SumCombiner(sum2));
    Assert.assertEquals(7, sum2.getSum());

    IntegerSum sum3 = new IntegerSum(0);
    ImmutableList<Integer> list3 =
        Lists.immutable.ofAll(ParallelIterateAcceptanceTest.createIntegerList(15));
    ParallelIterate.forEach(
        list3, new SumProcedure(sum3), new SumCombiner(sum3), 1, list3.size() / 2);
    Assert.assertEquals(15, sum3.getSum());

    IntegerSum sum4 = new IntegerSum(0);
    ImmutableList<Integer> list4 =
        Lists.immutable.ofAll(ParallelIterateAcceptanceTest.createIntegerList(35));
    ParallelIterate.forEach(list4, new SumProcedure(sum4), new SumCombiner(sum4));
    Assert.assertEquals(35, sum4.getSum());

    IntegerSum sum5 = new IntegerSum(0);
    ImmutableList<Integer> list5 = FastList.newList(list4).toImmutable();
    ParallelIterate.forEach(list5, new SumProcedure(sum5), new SumCombiner(sum5));
    Assert.assertEquals(35, sum5.getSum());

    IntegerSum sum6 = new IntegerSum(0);
    ImmutableList<Integer> list6 =
        Lists.immutable.ofAll(ParallelIterateAcceptanceTest.createIntegerList(40));
    ParallelIterate.forEach(
        list6, new SumProcedure(sum6), new SumCombiner(sum6), 1, list6.size() / 2);
    Assert.assertEquals(40, sum6.getSum());

    IntegerSum sum7 = new IntegerSum(0);
    ImmutableList<Integer> list7 = FastList.newList(list6).toImmutable();
    ParallelIterate.forEach(
        list7, new SumProcedure(sum7), new SumCombiner(sum7), 1, list6.size() / 2);
    Assert.assertEquals(40, sum7.getSum());
  }

  @Test
  public void testForEachWithException() {
    Verify.assertThrows(
        RuntimeException.class,
        () ->
            ParallelIterate.forEach(
                ParallelIterateAcceptanceTest.createIntegerList(5),
                new PassThruProcedureFactory<>(EXCEPTION_PROCEDURE),
                new PassThruCombiner<>(),
                1,
                5));
  }

  @Test
  public void testForEachWithIndexToArrayUsingFastListSerialPath() {
    Integer[] array = new Integer[200];
    FastList<Integer> list = (FastList<Integer>) Interval.oneTo(200).toList();
    Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
    ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each);
    Assert.assertArrayEquals(array, list.toArray(new Integer[] {}));
  }

  @Test
  public void testForEachWithIndexToArrayUsingFastList() {
    Integer[] array = new Integer[200];
    FastList<Integer> list = (FastList<Integer>) Interval.oneTo(200).toList();
    Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
    ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 10, 10);
    Assert.assertArrayEquals(array, list.toArray(new Integer[] {}));
  }

  @Test
  public void testForEachWithIndexToArrayUsingImmutableList() {
    Integer[] array = new Integer[200];
    ImmutableList<Integer> list = Interval.oneTo(200).toList().toImmutable();
    Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
    ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 10, 10);
    Assert.assertArrayEquals(array, list.toArray(new Integer[] {}));
  }

  @Test
  public void testForEachWithIndexToArrayUsingArrayList() {
    Integer[] array = new Integer[200];
    List<Integer> list = new ArrayList<>(Interval.oneTo(200));
    Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
    ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 10, 10);
    Assert.assertArrayEquals(array, list.toArray(new Integer[] {}));
  }

  @Test
  public void testForEachWithIndexToArrayUsingFixedArrayList() {
    Integer[] array = new Integer[10];
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Assert.assertTrue(ArrayIterate.allSatisfy(array, Predicates.isNull()));
    ParallelIterate.forEachWithIndex(list, (each, index) -> array[index] = each, 1, 2);
    Assert.assertArrayEquals(array, list.toArray(new Integer[list.size()]));
  }

  @Test
  public void testForEachWithIndexException() {
    Verify.assertThrows(
        RuntimeException.class,
        () ->
            ParallelIterate.forEachWithIndex(
                ParallelIterateAcceptanceTest.createIntegerList(5),
                new PassThruObjectIntProcedureFactory<>(EXCEPTION_OBJECT_INT_PROCEDURE),
                new PassThruCombiner<>(),
                1,
                5));
  }

  @Test
  public void select() {
    this.iterables.forEach(Procedures.cast(this::basicSelect));
  }

  private void basicSelect(RichIterable<Integer> iterable) {
    Collection<Integer> actual1 = ParallelIterate.select(iterable, Predicates.greaterThan(10000));
    Collection<Integer> actual2 =
        ParallelIterate.select(
            iterable,
            Predicates.greaterThan(10000),
            HashBag.<Integer>newBag(),
            3,
            this.executor,
            true);
    Collection<Integer> actual3 =
        ParallelIterate.select(iterable, Predicates.greaterThan(10000), true);
    RichIterable<Integer> expected = iterable.select(Predicates.greaterThan(10000));
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(),
        expected,
        actual1);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(),
        expected.toBag(),
        actual2);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(),
        expected.toBag(),
        HashBag.newBag(actual3));
  }

  @Test
  public void selectSortedSet() {
    RichIterable<Integer> iterable = Interval.oneTo(20000).toSortedSet();
    Collection<Integer> actual1 = ParallelIterate.select(iterable, Predicates.greaterThan(10000));
    Collection<Integer> actual2 =
        ParallelIterate.select(iterable, Predicates.greaterThan(10000), true);
    RichIterable<Integer> expected = iterable.select(Predicates.greaterThan(10000));
    Assert.assertSame(expected.getClass(), actual1.getClass());
    Assert.assertSame(expected.getClass(), actual2.getClass());
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(),
        expected,
        actual1);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(),
        expected,
        actual2);
  }

  @Test
  public void count() {
    this.iterables.forEach(Procedures.cast(this::basicCount));
  }

  private void basicCount(RichIterable<Integer> listIterable) {
    int actual1 = ParallelIterate.count(listIterable, Predicates.greaterThan(10000));
    int actual2 =
        ParallelIterate.count(listIterable, Predicates.greaterThan(10000), 11, this.executor);
    Assert.assertEquals(10000, actual1);
    Assert.assertEquals(10000, actual2);
  }

  @Test
  public void reject() {
    this.iterables.forEach(Procedures.cast(this::basicReject));
  }

  private void basicReject(RichIterable<Integer> iterable) {
    Collection<Integer> actual1 = ParallelIterate.reject(iterable, Predicates.greaterThan(10000));
    Collection<Integer> actual2 =
        ParallelIterate.reject(
            iterable,
            Predicates.greaterThan(10000),
            HashBag.<Integer>newBag(),
            3,
            this.executor,
            true);
    Collection<Integer> actual3 =
        ParallelIterate.reject(iterable, Predicates.greaterThan(10000), true);
    RichIterable<Integer> expected = iterable.reject(Predicates.greaterThan(10000));
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(),
        expected,
        actual1);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(),
        expected.toBag(),
        actual2);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(),
        expected.toBag(),
        HashBag.newBag(actual3));
  }

  @Test
  public void collect() {
    this.iterables.forEach(Procedures.cast(this::basicCollect));
  }

  private void basicCollect(RichIterable<Integer> iterable) {
    Collection<String> actual1 = ParallelIterate.collect(iterable, String::valueOf);
    Collection<String> actual2 =
        ParallelIterate.collect(
            iterable, String::valueOf, HashBag.<String>newBag(), 3, this.executor, false);
    Collection<String> actual3 = ParallelIterate.collect(iterable, String::valueOf, true);
    RichIterable<String> expected = iterable.collect(String::valueOf);
    Verify.assertSize(20000, actual1);
    Verify.assertContains(String.valueOf(20000), actual1);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(),
        expected,
        actual1);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(),
        expected.toBag(),
        actual2);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(),
        expected.toBag(),
        HashBag.newBag(actual3));
  }

  @Test
  public void collectIf() {
    this.iterables.forEach(Procedures.cast(this::basicCollectIf));
  }

  private void basicCollectIf(RichIterable<Integer> collection) {
    Predicate<Integer> greaterThan = Predicates.greaterThan(10000);
    Collection<String> actual1 =
        ParallelIterate.collectIf(collection, greaterThan, String::valueOf);
    Collection<String> actual2 =
        ParallelIterate.collectIf(
            collection,
            greaterThan,
            String::valueOf,
            HashBag.<String>newBag(),
            3,
            this.executor,
            true);
    Collection<String> actual3 =
        ParallelIterate.collectIf(
            collection,
            greaterThan,
            String::valueOf,
            HashBag.<String>newBag(),
            3,
            this.executor,
            true);
    Bag<String> expected = collection.collectIf(greaterThan, String::valueOf).toBag();
    Verify.assertSize(10000, actual1);
    Verify.assertNotContains(String.valueOf(9000), actual1);
    Verify.assertNotContains(String.valueOf(21000), actual1);
    Verify.assertContains(String.valueOf(15976), actual1);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(),
        expected,
        HashBag.newBag(actual1));
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(),
        expected,
        actual2);
    Assert.assertEquals(
        expected.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(),
        expected,
        actual3);
  }

  @Test
  public void flatCollect() {
    this.iterables.forEach(Procedures.cast(this::basicFlatCollect));
  }

  private void basicFlatCollect(RichIterable<Integer> iterable) {
    Collection<String> actual1 = ParallelIterate.flatCollect(iterable, INT_TO_TWO_STRINGS);
    Collection<String> actual2 =
        ParallelIterate.flatCollect(
            iterable, INT_TO_TWO_STRINGS, HashBag.<String>newBag(), 3, this.executor, false);
    Collection<String> actual3 = ParallelIterate.flatCollect(iterable, INT_TO_TWO_STRINGS, true);
    RichIterable<String> expected1 = iterable.flatCollect(INT_TO_TWO_STRINGS);
    RichIterable<String> expected2 =
        iterable.flatCollect(INT_TO_TWO_STRINGS, HashBag.<String>newBag());
    Verify.assertContains(String.valueOf(20000), actual1);
    Assert.assertEquals(
        expected1.getClass().getSimpleName() + '/' + actual1.getClass().getSimpleName(),
        expected1,
        actual1);
    Assert.assertEquals(
        expected2.getClass().getSimpleName() + '/' + actual2.getClass().getSimpleName(),
        expected2,
        actual2);
    Assert.assertEquals(
        expected1.getClass().getSimpleName() + '/' + actual3.getClass().getSimpleName(),
        expected1.toBag(),
        HashBag.newBag(actual3));
  }

  @Test
  public void groupBy() {
    FastList<Integer> iterable =
        FastList.newWithNValues(
            10000000,
            new Function0<Integer>() {
              private int current;

              public Integer value() {
                if (this.current < 4) {
                  return Integer.valueOf(this.current++);
                }
                this.current = 0;
                return Integer.valueOf(4);
              }
            });
    iterable.shuffleThis();
    Multimap<String, Integer> expected = iterable.toBag().groupBy(String::valueOf);
    Multimap<String, Integer> expectedAsSet = iterable.toSet().groupBy(String::valueOf);
    Multimap<String, Integer> result1 =
        ParallelIterate.groupBy(iterable.toList(), String::valueOf, 100);
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result1));
    Multimap<String, Integer> result2 = ParallelIterate.groupBy(iterable.toList(), String::valueOf);
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result2));
    Multimap<String, Integer> result3 =
        ParallelIterate.groupBy(
            iterable.toSet(),
            String::valueOf,
            SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap(),
            100);
    Assert.assertEquals(expectedAsSet, result3);
    Multimap<String, Integer> result4 =
        ParallelIterate.groupBy(
            iterable.toSet(),
            String::valueOf,
            SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap());
    Assert.assertEquals(expectedAsSet, result4);
    Multimap<String, Integer> result5 =
        ParallelIterate.groupBy(
            iterable.toSortedSet(),
            String::valueOf,
            SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap(),
            100);
    Assert.assertEquals(expectedAsSet, result5);
    Multimap<String, Integer> result6 =
        ParallelIterate.groupBy(
            iterable.toSortedSet(),
            String::valueOf,
            SynchronizedPutUnifiedSetMultimap.<String, Integer>newMultimap());
    Assert.assertEquals(expectedAsSet, result6);
    Multimap<String, Integer> result7 =
        ParallelIterate.groupBy(
            iterable.toBag(),
            String::valueOf,
            SynchronizedPutHashBagMultimap.<String, Integer>newMultimap(),
            100);
    Assert.assertEquals(expected, result7);
    Multimap<String, Integer> result8 =
        ParallelIterate.groupBy(
            iterable.toBag(),
            String::valueOf,
            SynchronizedPutHashBagMultimap.<String, Integer>newMultimap());
    Assert.assertEquals(expected, result8);
    Multimap<String, Integer> result9 =
        ParallelIterate.groupBy(iterable.toList().toImmutable(), String::valueOf);
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result9));
    Multimap<String, Integer> result10 =
        ParallelIterate.groupBy(iterable.toSortedList(), String::valueOf, 100);
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result10));
    Multimap<String, Integer> result11 =
        ParallelIterate.groupBy(iterable.toSortedList(), String::valueOf);
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result11));

    Multimap<String, Integer> result12 =
        ParallelIterate.groupBy(
            iterable,
            String::valueOf,
            MultiReaderFastListMultimap.<String, Integer>newMultimap(),
            100);
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result12));
    Multimap<String, Integer> result13 =
        ParallelIterate.groupBy(
            iterable, String::valueOf, MultiReaderFastListMultimap.<String, Integer>newMultimap());
    Assert.assertEquals(expected, HashBagMultimap.newMultimap(result13));

    Multimap<String, Integer> result14 =
        ParallelIterate.groupBy(
            iterable,
            String::valueOf,
            MultiReaderHashBagMultimap.<String, Integer>newMultimap(),
            100);
    Assert.assertEquals(expected, result14);
    Multimap<String, Integer> result15 =
        ParallelIterate.groupBy(
            iterable, String::valueOf, MultiReaderHashBagMultimap.<String, Integer>newMultimap());
    Assert.assertEquals(expected, result15);

    Multimap<String, Integer> result16 =
        ParallelIterate.groupBy(
            iterable,
            String::valueOf,
            MultiReaderUnifiedSetMultimap.<String, Integer>newMultimap(),
            100);
    Assert.assertEquals(expectedAsSet, result16);
    Multimap<String, Integer> result17 =
        ParallelIterate.groupBy(
            iterable,
            String::valueOf,
            MultiReaderUnifiedSetMultimap.<String, Integer>newMultimap());
    Assert.assertEquals(expectedAsSet, result17);
  }

  @Test
  public void aggregateInPlaceBy() {
    Procedure2<AtomicInteger, Integer> countAggregator =
        (aggregate, value) -> aggregate.incrementAndGet();
    List<Integer> list = Interval.oneTo(20000);
    MutableMap<String, AtomicInteger> aggregation =
        ParallelIterate.aggregateInPlaceBy(list, EVEN_OR_ODD, ATOMIC_INTEGER_NEW, countAggregator);
    Assert.assertEquals(10000, aggregation.get("Even").intValue());
    Assert.assertEquals(10000, aggregation.get("Odd").intValue());
    ParallelIterate.aggregateInPlaceBy(
        list, EVEN_OR_ODD, ATOMIC_INTEGER_NEW, countAggregator, aggregation);
    Assert.assertEquals(20000, aggregation.get("Even").intValue());
    Assert.assertEquals(20000, aggregation.get("Odd").intValue());
  }

  @Test
  public void aggregateInPlaceByWithBatchSize() {
    MutableList<Integer> list =
        LazyIterate.adapt(Collections.nCopies(1000, 1))
            .concatenate(Collections.nCopies(2000, 2))
            .concatenate(Collections.nCopies(3000, 3))
            .toList()
            .shuffleThis();
    MapIterable<String, AtomicInteger> aggregation =
        ParallelIterate.aggregateInPlaceBy(
            list, String::valueOf, ATOMIC_INTEGER_NEW, AtomicInteger::addAndGet, 100);
    Assert.assertEquals(1000, aggregation.get("1").intValue());
    Assert.assertEquals(4000, aggregation.get("2").intValue());
    Assert.assertEquals(9000, aggregation.get("3").intValue());
  }

  private static List<Integer> createIntegerList(int size) {
    return Collections.nCopies(size, Integer.valueOf(1));
  }

  private class RecursiveProcedure implements Procedure<Integer> {
    private static final long serialVersionUID = 1L;
    private final ExecutorService executorService =
        ParallelIterate.newPooledExecutor("ParallelIterateTest", false);

    @Override
    public void value(Integer level) {
      if (level > 0) {
        ParallelIterateAcceptanceTest.this.threadNames.add(Thread.currentThread().getName());
        this.executeParallelIterate(level - 1, this.executorService);
      } else {
        this.simulateWork();
      }
    }

    private void simulateWork() {
      synchronized (ParallelIterateAcceptanceTest.this) {
        ParallelIterateAcceptanceTest.this.count++;
      }
    }

    private void executeParallelIterate(int level, ExecutorService executorService) {
      MutableList<Integer> items = Lists.mutable.of();
      for (int i = 0; i < 20000; i++) {
        items.add(i % 1000 == 0 ? level : 0);
      }
      ParallelIterate.forEach(items, new RecursiveProcedure(), executorService);
    }
  }

  public static final class IntegerSum {
    private int sum;

    public IntegerSum(int newSum) {
      this.sum = newSum;
    }

    public IntegerSum add(int value) {
      this.sum += value;
      return this;
    }

    public int getSum() {
      return this.sum;
    }
  }

  public static final class SumProcedure
      implements Procedure<Integer>,
          Function2<IntegerSum, Integer, IntegerSum>,
          ProcedureFactory<SumProcedure> {
    private static final long serialVersionUID = 1L;

    private final IntegerSum sum;

    public SumProcedure(IntegerSum newSum) {
      this.sum = newSum;
    }

    @Override
    public SumProcedure create() {
      return new SumProcedure(new IntegerSum(0));
    }

    @Override
    public IntegerSum value(IntegerSum s1, Integer s2) {
      return s1.add(s2);
    }

    @Override
    public void value(Integer object) {
      this.sum.add(object);
    }

    public int getSum() {
      return this.sum.getSum();
    }
  }

  public static final class SumCombiner extends AbstractProcedureCombiner<SumProcedure> {
    private static final long serialVersionUID = 1L;
    private final IntegerSum sum;

    public SumCombiner(IntegerSum initialSum) {
      super(true);
      this.sum = initialSum;
    }

    @Override
    public void combineOne(SumProcedure sumProcedure) {
      this.sum.add(sumProcedure.getSum());
    }
  }
}