no.digipost.DiggStreams Maven / Gradle / Ivy
Show all versions of digg Show documentation
/*
* Copyright (C) Posten Norge AS
*
* 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 no.digipost;
import no.digipost.function.ObjIntFunction;
import no.digipost.function.ObjLongFunction;
import java.util.Collection;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.lang.Integer.toBinaryString;
import static java.util.Spliterator.CONCURRENT;
import static java.util.Spliterator.DISTINCT;
import static java.util.Spliterator.IMMUTABLE;
import static java.util.Spliterator.NONNULL;
import static java.util.Spliterator.ORDERED;
import static java.util.Spliterator.SIZED;
import static java.util.Spliterator.SORTED;
import static java.util.Spliterator.SUBSIZED;
import static java.util.stream.Collectors.joining;
import static no.digipost.DiggBase.friendlyName;
/**
* Utilities for working with {@link Stream}s.
*/
public final class DiggStreams {
/**
* Stream the elements from a container which allows access to the elements by an {@code int} index, but does not itself
* offer any means for traversal/iteration/streaming. This method will stream all elements from index 0 up to,
* but not including, the given {@code endExclusive} index.
*
* @param The type of the resolved elements yielded by the {@link Stream}.
* @param The type of the source object which yields elements of type {@code V} by index.
*
* @param source The source object which yields the elements by an index
* @param endExclusive The end index. This is also effectively the resulting size of the stream
* @param resolveValue How to query the {@code source} object for each index. This can usually be a method
* reference like {@code S::get} or similar.
*
* @return the stream yielding the elements
*/
public static final Stream streamByIntIndex(S source, int endExclusive, ObjIntFunction resolveValue) {
return streamByIndex(source, IntStream.range(0, endExclusive), resolveValue);
}
/**
* Stream the elements from a container which allows access to the by through an {@code int} index, but does not itself
* offer any means for traversal/iteration/streaming. This method will stream the elements with indexes from
* the given {@link IntStream}.
*
*
* @param The type of the resolved elements yielded by the {@link Stream}.
* @param The type of the source object which yields elements of type {@code V} by index.
*
* @param source The source object which yields the elements by an index
* @param indexes The indexes
* @param resolveValue How to query the {@code source} object for each index. This can usually be a method
* reference like {@code S::get} or similar.
*
* @return the stream yielding the elements
*/
public static final Stream streamByIndex(S source, IntStream indexes, ObjIntFunction resolveValue) {
return indexes.mapToObj(index -> resolveValue.apply(source, index));
}
/**
* Stream the elements from a container which allows access to the elements by a {@code long} index, but does not itself
* offer any means for traversal/iteration/streaming. This method will stream all elements from index 0 up to,
* but not including, the given {@code endExclusive} index.
*
* @param The type of the resolved elements yielded by the {@link Stream}.
* @param The type of the source object which yields elements of type {@code V} by index.
*
* @param source The source object which yields the elements by an index
* @param endExclusive The end index. This is also effectively the resulting size of the stream
* @param resolveValue How to query the {@code source} object for each index. This can usually be a method
* reference like {@code S::get} or similar.
*
* @return the stream yielding the elements
*/
public static final Stream streamByLongIndex(S source, long endExclusive, ObjLongFunction resolveValue) {
return streamByIndex(source, LongStream.range(0, endExclusive), resolveValue);
}
/**
* Stream the elements from a container which allows access to the by through a {@code long} index, but does not itself
* offer any means for traversal/iteration/streaming. This method will stream the elements with indexes from
* the given {@link IntStream}.
*
*
* @param The type of the resolved elements yielded by the {@link Stream}.
* @param The type of the source object which yields elements of type {@code V} by index.
*
* @param source The source object which yields the elements by an index
* @param indexes The indexes
* @param resolveValue How to query the {@code source} object for each index. This can usually be a method
* reference like {@code S::get} or similar.
*
* @return the stream yielding the elements
*/
public static final Stream streamByIndex(S source, LongStream indexes, ObjLongFunction resolveValue) {
return indexes.mapToObj(index -> resolveValue.apply(source, index));
}
/**
* Stream the elements from a container which allows access to the by through a key, but does not itself
* offer any means for traversal/iteration/streaming. This method will stream the elements with keys from
* the given {@link Stream Stream<K>}.
*
*
* @param The type of the resolved elements yielded by the {@link Stream}.
* @param The type of the source object which yields elements of type {@code V} by key.
* @param The type of the key used to query elements of type {@code V}.
*
* @param source The source object which yields the elements by a key
* @param keys The keys
* @param resolveValue How to query the {@code source} object for each key. This can usually be a method
* reference like {@code S::get} or similar.
*
* @return the stream yielding the elements
*/
public static final Stream streamByKey(S source, Stream keys, BiFunction resolveValue) {
return keys.map(key -> resolveValue.apply(source, key));
}
/**
* Stream elements retrieved from resolved collections while they are non-empty.
* The first empty collection will end the stream.
*
* @param The type of the elements in the resolved collections, and elements in the resulting stream
*
* @param resolveCollection a function accepting an int indicating the page number and returns a {@link Collection} with
* elements to include in the resulting stream
*
* @return the stream yielding the elements of the resolved collections
*/
public static Stream streamWhileNonEmpty(IntFunction extends Collection> resolveCollection) {
return streamPages(resolveCollection, c -> !c.isEmpty()).flatMap(Collection::stream);
}
/**
* Generate a stream of objects resolved from an incrementing int (page number), while a predicate is accepting
* the resolved objects. The first object not accepted by the predicate will end the stream.
*
* @param resolvePage a function accepting an int indicating the page number and returns a page to include
* in the resulting stream
* @param includeWhile the predicate accepting the objects to include in the stream
*
* @return the stream of the resolved objects
*/
public static Stream
streamPages(IntFunction
resolvePage, Predicate super P> includeWhile) {
return streamPages(0, resolvePage, includeWhile);
}
/**
* Generate a stream of objects resolved from an incrementing int (page number), while a predicate is accepting
* the resolved objects. The first object not accepted by the predicate will end the stream.
*
* @param firstPageNum the initial page number
* @param resolvePage a function accepting an int indicating the page number and returns a page to include
* in the resulting stream
* @param includeWhile the predicate accepting the objects to include in the stream
*
* @return the stream of the resolved objects
*/
public static
Stream
streamPages(int firstPageNum, IntFunction
resolvePage, Predicate super P> includeWhile) {
Spliterator
spliterator = new Spliterators.AbstractSpliterator
(Long.MAX_VALUE, 0) {
final AtomicInteger pageNum = new AtomicInteger(firstPageNum);
@Override
public boolean tryAdvance(Consumer super P> action) {
P nextPage = resolvePage.apply(pageNum.getAndIncrement());
if (includeWhile.test(nextPage)) {
action.accept(nextPage);
return true;
} else {
return false;
}
}
};
return StreamSupport.stream(spliterator, false);
}
/**
* Get a description of {@link Spliterator#characteristics() characteristics} of a
* {@code Spliterator}. The returned text is is solely intended for debugging and
* logging purposes, and contents and format may change at any time.
*
* @param spliterator the Spliterator
* @return the description
*/
public static String describeCharacteristics(Spliterator> spliterator) {
int value = spliterator.characteristics();
if (value == 0) {
return friendlyName(spliterator.getClass()) + " with no enabled characteristics";
} else {
String enabledCharacteristics = Stream.of(
spliterator.hasCharacteristics(SIZED) ? "sized" : null,
spliterator.hasCharacteristics(SUBSIZED) ? "subsized" : null,
spliterator.hasCharacteristics(DISTINCT) ? "distinct" : null,
spliterator.hasCharacteristics(NONNULL) ? "non-null" : null,
spliterator.hasCharacteristics(IMMUTABLE) ? "immutable" : null,
spliterator.hasCharacteristics(ORDERED) ? "ordered" : null,
spliterator.hasCharacteristics(CONCURRENT) ? "concurrent" : null,
spliterator.hasCharacteristics(SORTED) ? "sorted" : null)
.filter(Objects::nonNull)
.collect(joining(", ", "", ""));
return enabledCharacteristics + " " + friendlyName(spliterator.getClass()) + " (" + toBinaryString(value) + ")";
}
}
private DiggStreams() {
}
}