@Override
 public boolean tryAdvance(Consumer<? super R> action) {
   if (left != null) {
     if (accept(handleLeft(), action)) {
       return true;
     }
   }
   if (cur == none()) { // start
     if (!source.tryAdvance(this)) {
       return accept(pushRight(none(), none()), action);
     }
   }
   T first = cur;
   R acc = mapper.apply(cur);
   T last = first;
   while (source.tryAdvance(this)) {
     if (!this.mergeable.test(last, cur)) {
       action.accept(acc);
       return true;
     }
     last = cur;
     acc = this.accumulator.apply(acc, last);
   }
   return accept(pushRight(acc, last), action);
 }
  @Test
  public void splitsEventsBackward() {
    final String stream = generateStreamName();

    List<EventData> events = newTestEvents(40);
    eventstore.appendToStream(stream, ExpectedVersion.NO_STREAM, events).join();

    Spliterator<ResolvedEvent> spliterator =
        new StreamEventsSpliterator(
            StreamPosition.END, i -> eventstore.readStreamEventsBackward(stream, i, 8, false));

    List<ResolvedEvent> result = new ArrayList<>();

    range(0, 5)
        .forEach(
            i -> {
              Spliterator<ResolvedEvent> s = spliterator.trySplit();
              assertNotNull(s);
              s.forEachRemaining(result::add);
            });

    assertEquals(40, result.size());

    assertNull(spliterator.trySplit());
    spliterator.forEachRemaining(e -> fail("Should be no more elements remaining"));
    assertFalse(spliterator.tryAdvance(result::add));

    assertEquals(40, result.size());
    assertThat(recordedEventsFrom(result), containsInOrder(reverse(events)));
  }
 @Override
 public void forEachRemaining(Consumer<? super R> action) {
   while (left != null) {
     accept(handleLeft(), action);
   }
   if (cur == none()) {
     if (!source.tryAdvance(this)) {
       accept(pushRight(none(), none()), action);
       return;
     }
   }
   acc = mapper.apply(cur);
   source.forEachRemaining(
       next -> {
         if (!this.mergeable.test(cur, next)) {
           action.accept(acc);
           acc = mapper.apply(next);
         } else {
           acc = accumulator.apply(acc, next);
         }
         cur = next;
       });
   if (accept(pushRight(acc, cur), action)) {
     if (right != null) {
       action.accept(right.acc);
       right = null;
     }
   }
 }
  @Test
  public void iteratesAllRemainingEventsForward() {
    final String stream = generateStreamName();

    List<EventData> events = newTestEvents(82);
    eventstore.appendToStream(stream, ExpectedVersion.NO_STREAM, events).join();

    Spliterator<ResolvedEvent> spliterator =
        new StreamEventsSpliterator(
            StreamPosition.START, i -> eventstore.readStreamEventsForward(stream, i, 17, false));

    List<ResolvedEvent> result = new ArrayList<>();

    assertTrue(spliterator.tryAdvance(result::add));
    spliterator.forEachRemaining(result::add);

    assertFalse(spliterator.tryAdvance(result::add));
    spliterator.forEachRemaining(e -> fail("Should be no more elements remaining"));

    assertNull(spliterator.trySplit());

    assertEquals(82, result.size());
    assertThat(recordedEventsFrom(result), containsInOrder(events));
  }
 private R handleLeft() {
   synchronized (root) {
     Connector<T, R> l = left;
     if (l == null) {
       return none();
     }
     if (l.left == NONE && l.right == NONE && l.acc != NONE) {
       return l.drain();
     }
   }
   if (source.tryAdvance(this)) {
     T first = this.cur;
     T last = first;
     R acc = this.mapper.apply(first);
     while (source.tryAdvance(this)) {
       if (!this.mergeable.test(last, cur)) return pushLeft(first, acc);
       last = cur;
       acc = this.accumulator.apply(acc, last);
     }
     cur = none();
     return connectOne(first, acc, last);
   }
   return connectEmpty();
 }
  @Test
  public void failsWhenActionIsNull() {
    Spliterator<ResolvedEvent> spliterator =
        new StreamEventsSpliterator(
            StreamPosition.START, i -> eventstore.readStreamEventsForward("foo", i, 1, false));

    try {
      spliterator.tryAdvance(null);
      fail("should fail with 'NullPointerException'");
    } catch (Exception e) {
      assertThat(e, instanceOf(NullPointerException.class));
    }

    try {
      spliterator.forEachRemaining(null);
      fail("should fail with 'NullPointerException'");
    } catch (Exception e) {
      assertThat(e, instanceOf(NullPointerException.class));
    }
  }
  @Test
  public void iteratesAndSplitsEventsBackward() {
    final String stream = generateStreamName();

    List<EventData> events = newTestEvents(40);
    eventstore.appendToStream(stream, ExpectedVersion.NO_STREAM, events).join();

    Spliterator<ResolvedEvent> spliterator =
        new StreamEventsSpliterator(
            StreamPosition.END, i -> eventstore.readStreamEventsBackward(stream, i, 8, false));

    List<ResolvedEvent> result = new ArrayList<>();
    Spliterator<ResolvedEvent> s1, s2, s3;

    assertTrue(spliterator.tryAdvance(result::add));

    s1 = spliterator.trySplit();
    assertNotNull(s1);
    assertTrue(s1.tryAdvance(result::add));

    s2 = s1.trySplit();
    assertNotNull(s2);
    assertTrue(s2.tryAdvance(result::add));
    s2.forEachRemaining(result::add);

    assertTrue(s1.tryAdvance(result::add));
    s1.forEachRemaining(result::add);

    assertTrue(spliterator.tryAdvance(result::add));

    s3 = spliterator.trySplit();
    assertNotNull(s3);
    assertTrue(s3.tryAdvance(result::add));
    s3.forEachRemaining(result::add);

    assertTrue(spliterator.tryAdvance(result::add));
    spliterator.forEachRemaining(result::add);

    assertNull(spliterator.trySplit());

    assertEquals(40, result.size());
    assertThat(recordedEventsFrom(result), containsInOrder(reverse(events)));
  }