All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.emul.java.util.stream.Stream Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2016 Google Inc.
 *
 * 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 java.util.stream;

import static javaemul.internal.InternalPreconditions.checkNotNull;
import static javaemul.internal.InternalPreconditions.checkState;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.Spliterators.AbstractDoubleSpliterator;
import java.util.Spliterators.AbstractIntSpliterator;
import java.util.Spliterators.AbstractLongSpliterator;
import java.util.Spliterators.AbstractSpliterator;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.LongConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.UnaryOperator;

/**
 * See 
 * the official Java API doc for details.
 *
 * @param  the type of data being streamed
 */
public interface Stream extends BaseStream> {
  /**
   * Value holder for various stream operations.
   */
  static final class ValueConsumer implements Consumer {
    T value;

    @Override
    public void accept(T value) {
      this.value = value;
    }
  }

  static  Stream.Builder builder() {
    return new Builder() {
      private Object[] items = new Object[0];

      @Override
      public void accept(T t) {
        checkState(items != null, "Builder already built");
        items[items.length] = t;
      }

      @Override
      @SuppressWarnings("unchecked")
      public Stream build() {
        checkState(items != null, "Builder already built");
        Stream stream = (Stream) Arrays.stream(items);
        items = null;
        return stream;
      }
    };
  }

  static  Stream concat(Stream a, Stream b) {
    // This is nearly the same as flatMap, but inlined, wrapped around a single spliterator of
    // these two objects, and without close() called as the stream progresses. Instead, close is
    // invoked as part of the resulting stream's own onClose, so that either can fail without
    // affecting the other, and correctly collecting suppressed exceptions.

    // TODO replace this flatMap-ish spliterator with one that directly combines the two root
    // streams
    Spliterator> spliteratorOfStreams =
        Arrays.asList(a, b).spliterator();

    AbstractSpliterator spliterator =
        new Spliterators.AbstractSpliterator(Long.MAX_VALUE, 0) {
          Spliterator next;

          @Override
          public boolean tryAdvance(Consumer action) {
            // look for a new spliterator
            while (advanceToNextSpliterator()) {
              // if we have one, try to read and use it
              if (next.tryAdvance(action)) {
                return true;
              } else {
                // failed, null it out so we can find another
                next = null;
              }
            }
            return false;
          }

          private boolean advanceToNextSpliterator() {
            while (next == null) {
              if (!spliteratorOfStreams.tryAdvance(
                  n -> {
                    if (n != null) {
                      next = n.spliterator();
                    }
                  })) {
                return false;
              }
            }
            return true;
          }
        };

    Stream result = new StreamSource(null, spliterator);

    result.onClose(a::close);
    result.onClose(b::close);

    return result;
  }

  static  Stream empty() {
    return new EmptyStreamSource(null);
  }

  static  Stream generate(Supplier s) {
    AbstractSpliterator spliterator =
        new Spliterators.AbstractSpliterator(
            Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.ORDERED) {
          @Override
          public boolean tryAdvance(Consumer action) {
            action.accept(s.get());
            return true;
          }
        };
    return StreamSupport.stream(spliterator, false);
  }

  static  Stream iterate(T seed, UnaryOperator f) {
    AbstractSpliterator spliterator =
        new Spliterators.AbstractSpliterator(
            Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.ORDERED) {
          private T next = seed;

          @Override
          public boolean tryAdvance(Consumer action) {
            action.accept(next);
            next = f.apply(next);
            return true;
          }
        };
    return StreamSupport.stream(spliterator, false);
  }

  static  Stream of(T t) {
    // TODO consider a splittable that returns only a single value, either for use here or in the
    //      singleton collection types
    return Collections.singleton(t).stream();
  }

  static  Stream of(T... values) {
    return Arrays.stream(values);
  }

  /**
   * See 
   * the official Java API doc for details.
   */
  public interface Builder extends Consumer {
    @Override
    void accept(T t);

    default Stream.Builder add(T t) {
      accept(t);
      return this;
    }

    Stream build();
  }

  boolean allMatch(Predicate predicate);

  boolean anyMatch(Predicate predicate);

   R collect(Collector collector);

   R collect(
      Supplier supplier, BiConsumer accumulator, BiConsumer combiner);

  long count();

  Stream distinct();

  Stream filter(Predicate predicate);

  Optional findAny();

  Optional findFirst();

   Stream flatMap(Function> mapper);

  DoubleStream flatMapToDouble(Function mapper);

  IntStream flatMapToInt(Function mapper);

  LongStream flatMapToLong(Function mapper);

  void forEach(Consumer action);

  void forEachOrdered(Consumer action);

  Stream limit(long maxSize);

   Stream map(Function mapper);

  DoubleStream mapToDouble(ToDoubleFunction mapper);

  IntStream mapToInt(ToIntFunction mapper);

  LongStream mapToLong(ToLongFunction mapper);

  Optional max(Comparator comparator);

  Optional min(Comparator comparator);

  boolean noneMatch(Predicate predicate);

  Stream peek(Consumer action);

  Optional reduce(BinaryOperator accumulator);

  T reduce(T identity, BinaryOperator accumulator);

   U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);

  Stream skip(long n);

  Stream sorted();

  Stream sorted(Comparator comparator);

  Object[] toArray();

   A[] toArray(IntFunction generator);

  /**
   * Represents an empty stream, doing nothing for all methods.
   */
  static class EmptyStreamSource extends TerminatableStream>
      implements Stream {

    public EmptyStreamSource(TerminatableStream previous) {
      super(previous);
    }

    @Override
    public Stream filter(Predicate predicate) {
      throwIfTerminated();
      return this;
    }

    @Override
    public  Stream map(Function mapper) {
      throwIfTerminated();
      return (Stream) this;
    }

    @Override
    public IntStream mapToInt(ToIntFunction mapper) {
      throwIfTerminated();
      return new IntStream.EmptyIntStreamSource(this);
    }

    @Override
    public LongStream mapToLong(ToLongFunction mapper) {
      throwIfTerminated();
      return new LongStream.EmptyLongStreamSource(this);
    }

    @Override
    public DoubleStream mapToDouble(ToDoubleFunction mapper) {
      throwIfTerminated();
      return new DoubleStream.EmptyDoubleStreamSource(this);
    }

    @Override
    public  Stream flatMap(Function> mapper) {
      throwIfTerminated();
      return (Stream) this;
    }

    @Override
    public IntStream flatMapToInt(Function mapper) {
      throwIfTerminated();
      return new IntStream.EmptyIntStreamSource(this);
    }

    @Override
    public LongStream flatMapToLong(Function mapper) {
      throwIfTerminated();
      return new LongStream.EmptyLongStreamSource(this);
    }

    @Override
    public DoubleStream flatMapToDouble(Function mapper) {
      throwIfTerminated();
      return new DoubleStream.EmptyDoubleStreamSource(this);
    }

    @Override
    public Stream distinct() {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream sorted() {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream sorted(Comparator comparator) {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream peek(Consumer action) {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream limit(long maxSize) {
      throwIfTerminated();
      checkState(maxSize >= 0, "maxSize may not be negative");
      return this;
    }

    @Override
    public Stream skip(long n) {
      throwIfTerminated();
      checkState(n >= 0, "n may not be negative");
      return this;
    }

    @Override
    public void forEach(Consumer action) {
      terminate();
      // nothing to do
    }

    @Override
    public void forEachOrdered(Consumer action) {
      terminate();
      // nothing to do
    }

    @Override
    public Object[] toArray() {
      terminate();
      return new Object[0];
    }

    @Override
    public  A[] toArray(IntFunction generator) {
      terminate();
      return generator.apply(0);
    }

    @Override
    public T reduce(T identity, BinaryOperator accumulator) {
      terminate();
      return identity;
    }

    @Override
    public Optional reduce(BinaryOperator accumulator) {
      terminate();
      return Optional.empty();
    }

    @Override
    public  U reduce(
        U identity, BiFunction accumulator, BinaryOperator combiner) {
      terminate();
      return identity;
    }

    @Override
    public  R collect(
        Supplier supplier, BiConsumer accumulator, BiConsumer combiner) {
      terminate();
      return supplier.get();
    }

    @Override
    public  R collect(Collector collector) {
      terminate();
      return collector.finisher().apply(collector.supplier().get());
    }

    @Override
    public Optional min(Comparator comparator) {
      terminate();
      return Optional.empty();
    }

    @Override
    public Optional max(Comparator comparator) {
      terminate();
      return Optional.empty();
    }

    @Override
    public long count() {
      terminate();
      return 0;
    }

    @Override
    public boolean anyMatch(Predicate predicate) {
      terminate();
      return false;
    }

    @Override
    public boolean allMatch(Predicate predicate) {
      terminate();
      return true;
    }

    @Override
    public boolean noneMatch(Predicate predicate) {
      terminate();
      return true;
    }

    @Override
    public Optional findFirst() {
      terminate();
      return Optional.empty();
    }

    @Override
    public Optional findAny() {
      terminate();
      return Optional.empty();
    }

    @Override
    public Iterator iterator() {
      terminate();
      return Collections.emptyIterator();
    }

    @Override
    public Spliterator spliterator() {
      terminate();
      return Spliterators.emptySpliterator();
    }

    @Override
    public boolean isParallel() {
      throwIfTerminated();
      return false;
    }

    @Override
    public Stream sequential() {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream parallel() {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream unordered() {
      throwIfTerminated();
      return this;
    }
  }

  /**
   * Object to Object map spliterator.
   * @param  the input type
   * @param  the output type
   */
  static final class MapToObjSpliterator extends Spliterators.AbstractSpliterator {
    private final Function map;
    private final Spliterator original;

    public MapToObjSpliterator(Function map, Spliterator original) {
      super(
          original.estimateSize(),
          original.characteristics() & ~(Spliterator.SORTED | Spliterator.DISTINCT));
      checkNotNull(map);
      this.map = map;
      this.original = original;
    }

    @Override
    public boolean tryAdvance(final Consumer action) {
      return original.tryAdvance(u -> action.accept(map.apply(u)));
    }
  }

  /**
   * Object to Int map spliterator.
   * @param  the input type
   */
  static final class MapToIntSpliterator extends Spliterators.AbstractIntSpliterator {
    private final ToIntFunction map;
    private final Spliterator original;

    public MapToIntSpliterator(ToIntFunction map, Spliterator original) {
      super(
          original.estimateSize(),
          original.characteristics() & ~(Spliterator.SORTED | Spliterator.DISTINCT));
      checkNotNull(map);
      this.map = map;
      this.original = original;
    }

    @Override
    public boolean tryAdvance(final IntConsumer action) {
      return original.tryAdvance(u -> action.accept(map.applyAsInt(u)));
    }
  }

  /**
   * Object to Long map spliterator.
   * @param  the input type
   */
  static final class MapToLongSpliterator extends Spliterators.AbstractLongSpliterator {
    private final ToLongFunction map;
    private final Spliterator original;

    public MapToLongSpliterator(ToLongFunction map, Spliterator original) {
      super(
          original.estimateSize(),
          original.characteristics() & ~(Spliterator.SORTED | Spliterator.DISTINCT));
      checkNotNull(map);
      this.map = map;
      this.original = original;
    }

    @Override
    public boolean tryAdvance(final LongConsumer action) {
      return original.tryAdvance(u -> action.accept(map.applyAsLong(u)));
    }
  }

  /**
   * Object to Double map spliterator.
   * @param  the input type
   */
  static final class MapToDoubleSpliterator extends Spliterators.AbstractDoubleSpliterator {
    private final ToDoubleFunction map;
    private final Spliterator original;

    public MapToDoubleSpliterator(ToDoubleFunction map, Spliterator original) {
      super(
          original.estimateSize(),
          original.characteristics() & ~(Spliterator.SORTED | Spliterator.DISTINCT));
      checkNotNull(map);
      this.map = map;
      this.original = original;
    }

    @Override
    public boolean tryAdvance(final DoubleConsumer action) {
      return original.tryAdvance(u -> action.accept(map.applyAsDouble(u)));
    }
  }

  /**
   * Object filter spliterator.
   * @param  the type of data to iterate over
   */
  static final class FilterSpliterator extends Spliterators.AbstractSpliterator {
    private final Predicate filter;
    private final Spliterator original;

    private boolean found;

    public FilterSpliterator(Predicate filter, Spliterator original) {
      super(original.estimateSize(), original.characteristics() & ~Spliterator.SIZED);
      checkNotNull(filter);
      this.filter = filter;
      this.original = original;
    }

    @Override
    public Comparator getComparator() {
      return original.getComparator();
    }

    @Override
    public boolean tryAdvance(final Consumer action) {
      found = false;
      while (!found
          && original.tryAdvance(
              item -> {
                if (filter.test(item)) {
                  found = true;
                  action.accept(item);
                }
              })) {
        // do nothing, work is done in tryAdvance
      }

      return found;
    }
  }

  /**
   * Object skip spliterator.
   * @param  the type of data to iterate over
   */
  static final class SkipSpliterator extends Spliterators.AbstractSpliterator {
    private long skip;
    private final Spliterator original;

    public SkipSpliterator(long skip, Spliterator original) {
      super(
          original.hasCharacteristics(Spliterator.SIZED)
              ? Math.max(0, original.estimateSize() - skip)
              : Long.MAX_VALUE,
          original.characteristics());
      this.skip = skip;
      this.original = original;
    }

    @Override
    public Comparator getComparator() {
      return original.getComparator();
    }

    @Override
    public boolean tryAdvance(Consumer action) {
      while (skip > 0) {
        if (!original.tryAdvance(ignore -> { })) {
          return false;
        }
        skip--;
      }
      return original.tryAdvance(action);
    }
  }

  /**
   * Object limit spliterator.
   * @param  the type of data to iterate over
   */
  static final class LimitSpliterator extends Spliterators.AbstractSpliterator {
    private final long limit;
    private final Spliterator original;
    private int position = 0;

    public LimitSpliterator(long limit, Spliterator original) {
      super(
          original.hasCharacteristics(Spliterator.SIZED)
              ? Math.min(original.estimateSize(), limit)
              : Long.MAX_VALUE,
          original.characteristics());
      this.limit = limit;
      this.original = original;
    }

    @Override
    public Comparator getComparator() {
      return original.getComparator();
    }

    @Override
    public boolean tryAdvance(Consumer action) {
      if (position >= limit) {
        return false;
      }
      boolean result = original.tryAdvance(action);
      position++;
      return result;
    }
  }

  /**
   * Main implementation of Stream, wrapping a single spliterator and an optional parent stream.
   * @param 
   */
  static class StreamSource extends TerminatableStream> implements Stream {
    private final Spliterator spliterator;

    public StreamSource(TerminatableStream prev, Spliterator spliterator) {
      super(prev);
      this.spliterator = spliterator;
    }

    // terminal
    @Override
    public Spliterator spliterator() {
      terminate();
      return spliterator;
    }

    @Override
    public Iterator iterator() {
      return Spliterators.iterator(spliterator());
    }

    @Override
    public long count() {
      terminate();
      long count = 0;
      while (spliterator.tryAdvance(a -> { })) {
        count++;
      }
      return count;
    }

    @Override
    public void forEach(Consumer action) {
      forEachOrdered(action);
    }

    @Override
    public void forEachOrdered(Consumer action) {
      terminate();
      spliterator.forEachRemaining(action);
    }

    @Override
    public Object[] toArray() {
      return toArray(Object[]::new);
    }

    @Override
    public  A[] toArray(IntFunction generator) {
      List collected = collect(Collectors.toList());
      return collected.toArray(generator.apply(collected.size()));
    }

    @Override
    public  R collect(
        Supplier supplier, BiConsumer accumulator, BiConsumer combiner) {
      return collect(
          Collector.of(
              supplier,
              accumulator,
              (a, b) -> {
                combiner.accept(a, b);
                return a;
              }));
    }

    @Override
    public  R collect(final Collector collector) {
      return collector
          .finisher()
          .apply(
              reduce(
                  collector.supplier().get(),
                  (a, t) -> {
                    collector.accumulator().accept(a, t);
                    return a;
                  },
                  collector.combiner()));
    }

    @Override
    public Optional findFirst() {
      terminate();
      ValueConsumer holder = new ValueConsumer();
      if (spliterator.tryAdvance(holder)) {
        return Optional.of(holder.value);
      }
      return Optional.empty();
    }

    @Override
    public Optional findAny() {
      return findFirst();
    }

    @Override
    public boolean anyMatch(Predicate predicate) {
      return filter(predicate).findFirst().isPresent();
    }

    @Override
    public boolean allMatch(final Predicate predicate) {
      return !anyMatch(predicate.negate());
    }

    @Override
    public boolean noneMatch(final Predicate predicate) {
      return !anyMatch(predicate);
    }

    @Override
    public Optional min(final Comparator comparator) {
      return reduce(BinaryOperator.minBy(comparator));
    }

    @Override
    public Optional max(final Comparator comparator) {
      return reduce(BinaryOperator.maxBy(comparator));
    }

    @Override
    public T reduce(T identity, BinaryOperator accumulator) {
      return reduce(identity, accumulator, accumulator);
    }

    @Override
    public Optional reduce(BinaryOperator accumulator) {
      ValueConsumer consumer = new ValueConsumer();
      if (!spliterator.tryAdvance(consumer)) {
        terminate();
        return Optional.empty();
      }
      return Optional.of(reduce(consumer.value, accumulator));
    }

    // combiner is ignored, since we don't parallelize
    @Override
    public  U reduce(
        U identity, BiFunction accumulator, BinaryOperator combiner) {
      terminate();
      final ValueConsumer consumer = new ValueConsumer();
      consumer.value = identity;
      spliterator.forEachRemaining(
          item -> {
            consumer.accept(accumulator.apply(consumer.value, item));
          });
      return consumer.value;
    }
    // end terminal

    // intermediate
    @Override
    public Stream filter(Predicate predicate) {
      throwIfTerminated();
      return new StreamSource<>(this, new FilterSpliterator<>(predicate, spliterator));
    }

    @Override
    public  Stream map(Function mapper) {
      throwIfTerminated();
      return new StreamSource<>(this, new MapToObjSpliterator<>(mapper, spliterator));
    }

    @Override
    public IntStream mapToInt(ToIntFunction mapper) {
      throwIfTerminated();
      return new IntStream.IntStreamSource(this, new MapToIntSpliterator<>(mapper, spliterator));
    }

    @Override
    public LongStream mapToLong(ToLongFunction mapper) {
      throwIfTerminated();
      return new LongStream.LongStreamSource(this, new MapToLongSpliterator<>(mapper, spliterator));
    }

    @Override
    public DoubleStream mapToDouble(ToDoubleFunction mapper) {
      throwIfTerminated();
      return new DoubleStream.DoubleStreamSource(
          this, new MapToDoubleSpliterator<>(mapper, spliterator));
    }

    @Override
    public  Stream flatMap(final Function> mapper) {
      throwIfTerminated();
      final Spliterator> spliteratorOfStreams =
          new MapToObjSpliterator<>(mapper, spliterator);

      AbstractSpliterator flatMapSpliterator =
          new Spliterators.AbstractSpliterator(Long.MAX_VALUE, 0) {
            Stream nextStream;
            Spliterator next;

            @Override
            public boolean tryAdvance(Consumer action) {
              // look for a new spliterator
              while (advanceToNextSpliterator()) {
                // if we have one, try to read and use it
                if (next.tryAdvance(action)) {
                  return true;
                } else {
                  nextStream.close();
                  nextStream = null;
                  // failed, null it out so we can find another
                  next = null;
                }
              }
              return false;
            }

            private boolean advanceToNextSpliterator() {
              while (next == null) {
                if (!spliteratorOfStreams.tryAdvance(
                    n -> {
                      if (n != null) {
                        nextStream = n;
                        next = n.spliterator();
                      }
                    })) {
                  return false;
                }
              }
              return true;
            }
          };

      return new StreamSource(this, flatMapSpliterator);
    }

    @Override
    public IntStream flatMapToInt(Function mapper) {
      throwIfTerminated();
      final Spliterator spliteratorOfStreams =
          new MapToObjSpliterator<>(mapper, spliterator);

      AbstractIntSpliterator flatMapSpliterator =
          new Spliterators.AbstractIntSpliterator(Long.MAX_VALUE, 0) {
            IntStream nextStream;
            Spliterator.OfInt next;

            @Override
            public boolean tryAdvance(IntConsumer action) {
              // look for a new spliterator
              while (advanceToNextSpliterator()) {
                // if we have one, try to read and use it
                if (next.tryAdvance(action)) {
                  return true;
                } else {
                  nextStream.close();
                  nextStream = null;
                  // failed, null it out so we can find another
                  next = null;
                }
              }
              return false;
            }

            private boolean advanceToNextSpliterator() {
              while (next == null) {
                if (!spliteratorOfStreams.tryAdvance(
                    n -> {
                      if (n != null) {
                        nextStream = n;
                        next = n.spliterator();
                      }
                    })) {
                  return false;
                }
              }
              return true;
            }
          };

      return new IntStream.IntStreamSource(this, flatMapSpliterator);
    }

    @Override
    public LongStream flatMapToLong(Function mapper) {
      throwIfTerminated();
      final Spliterator spliteratorOfStreams =
          new MapToObjSpliterator<>(mapper, spliterator);

      AbstractLongSpliterator flatMapSpliterator =
          new Spliterators.AbstractLongSpliterator(Long.MAX_VALUE, 0) {
            LongStream nextStream;
            Spliterator.OfLong next;

            @Override
            public boolean tryAdvance(LongConsumer action) {
              // look for a new spliterator
              while (advanceToNextSpliterator()) {
                // if we have one, try to read and use it
                if (next.tryAdvance(action)) {
                  return true;
                } else {
                  nextStream.close();
                  nextStream = null;
                  // failed, null it out so we can find another
                  next = null;
                }
              }
              return false;
            }

            private boolean advanceToNextSpliterator() {
              while (next == null) {
                if (!spliteratorOfStreams.tryAdvance(
                    n -> {
                      if (n != null) {
                        nextStream = n;
                        next = n.spliterator();
                      }
                    })) {
                  return false;
                }
              }
              return true;
            }
          };

      return new LongStream.LongStreamSource(this, flatMapSpliterator);
    }

    @Override
    public DoubleStream flatMapToDouble(Function mapper) {
      throwIfTerminated();
      final Spliterator spliteratorOfStreams =
          new MapToObjSpliterator<>(mapper, spliterator);

      AbstractDoubleSpliterator flatMapSpliterator =
          new Spliterators.AbstractDoubleSpliterator(Long.MAX_VALUE, 0) {
            DoubleStream nextStream;
            Spliterator.OfDouble next;

            @Override
            public boolean tryAdvance(DoubleConsumer action) {
              // look for a new spliterator
              while (advanceToNextSpliterator()) {
                // if we have one, try to read and use it
                if (next.tryAdvance(action)) {
                  return true;
                } else {
                  nextStream.close();
                  nextStream = null;
                  // failed, null it out so we can find another
                  next = null;
                }
              }
              return false;
            }

            private boolean advanceToNextSpliterator() {
              while (next == null) {
                if (!spliteratorOfStreams.tryAdvance(
                    n -> {
                      if (n != null) {
                        nextStream = n;
                        next = n.spliterator();
                      }
                    })) {
                  return false;
                }
              }
              return true;
            }
          };

      return new DoubleStream.DoubleStreamSource(this, flatMapSpliterator);
    }

    @Override
    public Stream distinct() {
      throwIfTerminated();
      HashSet seen = new HashSet<>();
      return filter(seen::add);
    }

    @Override
    public Stream sorted() {
      throwIfTerminated();
      Comparator c = (Comparator) Comparator.naturalOrder();
      return sorted(c);
    }

    @Override
    public Stream sorted(final Comparator comparator) {
      throwIfTerminated();

      AbstractSpliterator sortedSpliterator =
          new Spliterators.AbstractSpliterator(
              spliterator.estimateSize(), spliterator.characteristics() | Spliterator.SORTED) {
            Spliterator ordered = null;

            @Override
            public Comparator getComparator() {
              return comparator == Comparator.naturalOrder() ? null : comparator;
            }

            @Override
            public boolean tryAdvance(Consumer action) {
              if (ordered == null) {
                List list = new ArrayList<>();
                spliterator.forEachRemaining(list::add);
                Collections.sort(list, comparator);
                ordered = list.spliterator();
              }
              return ordered.tryAdvance(action);
            }
          };

      return new StreamSource<>(this, sortedSpliterator);
    }

    @Override
    public Stream peek(final Consumer action) {
      checkNotNull(action);
      throwIfTerminated();

      AbstractSpliterator peekSpliterator =
          new Spliterators.AbstractSpliterator(
              spliterator.estimateSize(), spliterator.characteristics()) {
            @Override
            public boolean tryAdvance(final Consumer innerAction) {
              return spliterator.tryAdvance(
                  item -> {
                    action.accept(item);
                    innerAction.accept(item);
                  });
            }
          };

      return new StreamSource<>(this, peekSpliterator);
    }

    @Override
    public Stream limit(long maxSize) {
      throwIfTerminated();
      checkState(maxSize >= 0, "maxSize may not be negative");
      return new StreamSource<>(this, new LimitSpliterator<>(maxSize, spliterator));
    }

    @Override
    public Stream skip(long n) {
      throwIfTerminated();
      checkState(n >= 0, "n may not be negative");
      if (n == 0) {
        return this;
      }
      return new StreamSource<>(this, new SkipSpliterator<>(n, spliterator));
    }

    @Override
    public boolean isParallel() {
      throwIfTerminated();
      return false;
    }

    @Override
    public Stream sequential() {
      throwIfTerminated();
      return this;
    }

    @Override
    public Stream parallel() {
      throwIfTerminated();
      // do nothing, no such thing as gwt+parallel
      return this;
    }

    @Override
    public Stream unordered() {
      throwIfTerminated();
      return this;
    }
  }
}