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

com.github.rutledgepaulv.pagingstreams.PagingStreams Maven / Gradle / Ivy

The newest version!
package com.github.rutledgepaulv.pagingstreams;

import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.util.Spliterator.*;

/**
 * Provides transforms between paged sources to spliterators and streams.
 */
public final class PagingStreams {

    private static final long DEFAULT_PAGE_SIZE = 100;
    private static final int CHARACTERISTICS = IMMUTABLE | ORDERED | SIZED | SUBSIZED;

    /**
     * Gets a builder for constructing a stream from a paged source.
     *
     * @param source The paged source.
     * @param     The type contained within each page / the resulting stream.
     * @return The stream builder.
     */
    public static  StreamBuilder streamBuilder(PageSource source) {
        return new StreamBuilder<>(source);
    }

    /**
     * Gets a builder for constructing a spliterator from a paged source.
     *
     * @param source The paged source.
     * @param     The type contained within each page / the resulting spliterator.
     * @return The spliterator builder.
     */
    public static  SpliteratorBuilder spliteratorBuilder(PageSource source) {
        return new SpliteratorBuilder<>(source);
    }


    public static final class StreamBuilder {
        private StreamBuilder(PageSource source) {
            this.source = Objects.requireNonNull(source);
        }

        private long pageSize = DEFAULT_PAGE_SIZE;
        private PageSource source;
        private boolean parallel;

        public StreamBuilder pageSize(long pageSize) {
            this.pageSize = pageSize;
            return this;
        }

        public StreamBuilder parallel(boolean parallel) {
            this.parallel = parallel;
            return this;
        }

        public Stream build() {
            return StreamSupport.stream(spliteratorBuilder(source)
                    .pageSize(pageSize)::build, CHARACTERISTICS, parallel);
        }

    }

    public static final class SpliteratorBuilder {
        private SpliteratorBuilder(PageSource source) {
            this.source = Objects.requireNonNull(source);
        }

        private long pageSize = DEFAULT_PAGE_SIZE;
        private PageSource source;

        public SpliteratorBuilder pageSize(long pageSize) {
            this.pageSize = pageSize;
            return this;
        }

        public Spliterator build() {
            if (this.pageSize == 0) {
                return Spliterators.emptySpliterator();
            }
            PagingSpliterator pgSp = new PagingSpliterator<>(this.source, 0, 0, this.pageSize);
            pgSp.danglingFirstPage = spliterator(this.source.fetch(0, this.pageSize, l -> pgSp.end = l));
            return pgSp;
        }

    }


    private static final class PagingSpliterator implements Spliterator {

        private long start, end, pageSize;
        private Spliterator currentPage;
        private final PageSource supplier;
        private Spliterator danglingFirstPage;


        private PagingSpliterator(PageSource supplier, long start, long end, long pageSize) {
            this.supplier = supplier;
            this.start = start;
            this.end = end;
            this.pageSize = pageSize;
        }

        @Override
        public final boolean tryAdvance(Consumer action) {
            for (; ; ) {
                if (ensurePage().tryAdvance(action)) {
                    return true;
                }
                if (start >= end) {
                    return false;
                }
                currentPage = null;
            }
        }

        @Override
        public final void forEachRemaining(Consumer action) {
            do {
                ensurePage().forEachRemaining(action);
                currentPage = null;
            } while (start < end);
        }

        @Override
        public final Spliterator trySplit() {
            if (danglingFirstPage != null) {
                Spliterator fp = danglingFirstPage;
                danglingFirstPage = null;
                start = fp.getExactSizeIfKnown();
                return fp;
            }
            if (currentPage != null) {
                return currentPage.trySplit();
            }
            if (end - start > pageSize) {
                long mid = (start + end) >>> 1;
                mid = mid / pageSize * pageSize;
                if (mid == start) {
                    mid += pageSize;
                }
                return new PagingSpliterator<>(supplier, start, start = mid, pageSize);
            }
            return ensurePage().trySplit();
        }

        @Override
        public final long estimateSize() {
            if (currentPage != null) {
                return currentPage.estimateSize();
            }
            return end - start;
        }

        @Override
        public final int characteristics() {
            return CHARACTERISTICS;
        }

        /**
         * Fetch data immediately before traversing or sub-page splitting.
         */
        private Spliterator ensurePage() {
            if (danglingFirstPage != null) {
                Spliterator fp = danglingFirstPage;
                danglingFirstPage = null;
                currentPage = fp;
                start = fp.getExactSizeIfKnown();
                return fp;
            }
            Spliterator sp = currentPage;
            if(sp==null) {
                if(start>=end) return Spliterators.emptySpliterator();
                sp = spliterator(supplier.fetch(
                        start, Math.min(end - start, pageSize), l-> end = Math.min(l, end)));
                start += sp.getExactSizeIfKnown();
                currentPage=sp;
            }
            return sp;
        }

    }


    /**
     * Ensure that the sub-spliterator provided by the List is compatible with
     * ours, i.e. is {@code SIZED | SUBSIZED}. For standard List implementations,
     * the spliterators are, so the costs of dumping into an intermediate array
     * in the other case is irrelevant.
     */
    private static  Spliterator spliterator(List list) {
        Spliterator sp = list.spliterator();

        if ((sp.characteristics() & (SIZED | SUBSIZED)) != (SIZED | SUBSIZED)) {
            sp = Spliterators.spliterator(StreamSupport.stream(sp, false).toArray(), IMMUTABLE | ORDERED);
        }

        return sp;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy