/*
* Copyright 2014 the original author or authors.
*
* 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 ratpack.stream;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import ratpack.exec.ExecSpec;
import ratpack.exec.Promise;
import ratpack.func.Action;
import ratpack.func.BiFunction;
import ratpack.func.Function;
import ratpack.func.Predicate;
import java.util.List;
/**
* A wrapper over a {@link Publisher} that makes it more convenient to chain transformations of different kinds.
* <p>
* Note that this type implements the publisher interface,
* so behaves just like the publisher that it is wrapping with respect to the
* {@link Publisher#subscribe(Subscriber)} method.
*
* @param <T> the type of item emitted by this publisher
*/
public interface TransformablePublisher<T> extends Publisher<T> {
/**
* See {@link Streams#map(Publisher, Function)}.
*
* @param function the transformation
* @param <O> the type of transformed item
* @return the transformed publisher
*/
default <O> TransformablePublisher<O> map(Function<? super T, ? extends O> function) {
return Streams.map(this, function);
}
/**
* See {@link Streams#flatMap(Publisher, Function)}.
*
* @param function the transformation
* @param <O> the type of transformed item
* @return the transformed publisher
*/
default <O> TransformablePublisher<O> flatMap(Function<? super T, ? extends Promise<? extends O>> function) {
return Streams.flatMap(this, function);
}
/**
* See {@link Streams#buffer(Publisher)}.
*
* @return a buffering publisher
*/
default TransformablePublisher<T> buffer() {
return Streams.buffer(this);
}
/**
* See {@link Streams#gate(Publisher, Action)}.
*
* @param valveReceiver an action that receives a runnable “valve” that when run allows request to start flowing upstream
* @return a publisher that is logically equivalent to the given publisher as far as subscribers are concerned
*/
default TransformablePublisher<T> gate(Action<? super Runnable> valveReceiver) {
return Streams.gate(this, valveReceiver);
}
/**
* See {@link Streams#wiretap(Publisher, Action)}.
*
* @param listener the listener for emitted items
* @return a publisher that is logically equivalent to the given publisher as far as subscribers are concerned
*/
default TransformablePublisher<T> wiretap(Action<? super StreamEvent<T>> listener) {
return Streams.wiretap(this, listener);
}
/**
* See {@link Streams#multicast(Publisher)}.
*
* @return a publisher that respects back pressure for each of its subscribers
*/
default TransformablePublisher<T> multicast() {
return Streams.multicast(this);
}
/**
* See {@link Streams#toPromise(Publisher)}.
*
* @return a promise for this publisher's single item
*/
default Promise<T> toPromise() {
return Streams.toPromise(this);
}
/**
* Consumes the given publisher's items to a list.
* <p>
* This method can be useful when testing, but should be uses with care in production code as it will exhaust memory if the stream is very large.
* <pre class="java">{@code
* import org.reactivestreams.Publisher;
* import ratpack.stream.Streams;
* import ratpack.test.exec.ExecHarness;
*
* import java.util.Arrays;
* import java.util.List;
*
* import static org.junit.Assert.*;
*
* public class Example {
* public static void main(String... args) throws Exception {
* List<String> expected = Arrays.asList("a", "b", "c");
* List<String> result = ExecHarness.yieldSingle(execControl ->
* Streams.publish(expected).toList()
* ).getValue();
*
* assertEquals(Arrays.asList("a", "b", "c"), result);
* }
* }
* }</pre>
* <p>
* If the publisher emits an error, the promise will fail and the collected items will be discarded.
* <pre class="java">{@code
* import org.reactivestreams.Publisher;
* import ratpack.stream.Streams;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.*;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Throwable error = ExecHarness.yieldSingle(execControl ->
* Streams.yield(r -> {
* if (r.getRequestNum() < 1) {
* return "a";
* } else {
* throw new RuntimeException("bang!");
* }
* }).toList()
* ).getThrowable();
*
* assertEquals("bang!", error.getMessage());
* }
* }
* }</pre>
*
* @return a promise for the stream's contents as a list
*/
default Promise<List<T>> toList() {
return Streams.toList(this);
}
/**
* Convenience method to allow a non Ratpack publisher transform method to be hooked in.
* <p>
* This transformable publisher will be given to the function, that should return a new publisher.
* The returned publisher will then be wrapped in a transformable wrapper which will be returned by this method.
*
* @param transformer a publisher transformer
* @param <O> the type of transformed item
* @return a publisher that respects back pressure for each of its subscribers
*/
default <O> TransformablePublisher<O> transform(java.util.function.Function<? super TransformablePublisher<? extends T>, ? extends Publisher<O>> transformer) {
return Streams.transformable(transformer.apply(this));
}
/**
* See {@link Streams#streamMap(Publisher, StreamMapper)}.
*
* @param mapper the transformation
* @param <O> the type of transformed item
* @return the transformed publisher
* @since 1.4
*/
default <O> TransformablePublisher<O> streamMap(StreamMapper<? super T, O> mapper) {
return Streams.streamMap(this, mapper);
}
/**
* @deprecated since 1.4, use {@link #streamMap(StreamMapper)}
*/
@Deprecated
default <O> TransformablePublisher<O> streamMap(Function<? super WriteStream<O>, ? extends WriteStream<? super T>> function) {
return Streams.streamMap(this, function);
}
/**
* See {@link Streams#filter(Publisher, Predicate)}.
*
* @param filter the filter
* @return the filtered publisher
*/
default TransformablePublisher<T> filter(Predicate<? super T> filter) {
return Streams.filter(this, filter);
}
/**
* See {@link Streams#bindExec(Publisher)}
*
* @return a publisher bound to the current execution
* @since 1.4
*/
default TransformablePublisher<T> bindExec() {
return Streams.bindExec(this);
}
/**
* See {@link Streams#bindExec(Publisher, Action)}
*
* @param disposer the disposer of unhandled items
* @return a publisher bound to the current execution
* @since 1.5
*/
default TransformablePublisher<T> bindExec(Action<? super T> disposer) {
return Streams.bindExec(this, disposer);
}
/**
* Reduces the stream to a single value, by applying the given function successively.
*
* @param seed the initial value
* @param reducer the reducing function
* @param <R> the type of result
* @return a promise for the reduced value
* @since 1.4
*/
default <R> Promise<R> reduce(R seed, BiFunction<? super R, ? super T, ? extends R> reducer) {
return Streams.reduce(this, seed, reducer);
}
/**
* Consumes the given publisher eagerly in a forked execution, buffering results until ready to be consumed by this execution.
*
* @param execConfig the configuration for the forked execution
* @param disposer the disposer for any buffered items when the stream errors or is cancelled
* @return an execution bound publisher that propagates the items of the given publisher
* @see Streams#fork(Publisher, Action, Action)
* @since 1.5
*/
default TransformablePublisher<T> fork(Action<? super ExecSpec> execConfig, Action<? super T> disposer) {
return Streams.fork(this, execConfig, disposer);
}
/**
* Consumes the given publisher eagerly in a forked execution, buffering results until ready to be consumed by this execution.
* <p>
* This method is identical to {@link #fork(Action, Action)}, but uses {@link Action#noop()} for both arguments.
*
* @return an execution bound publisher that propagates the items of the given publisher
* @see Streams#fork(Publisher, Action, Action)
* @since 1.5
*/
default TransformablePublisher<T> fork() {
return Streams.fork(this, Action.noop(), Action.noop());
}
/**
* See {@link Streams#take(long, Publisher)}.
*
* @return a publisher that will emit a max of {@code n} elements
* @since 1.5
*/
default TransformablePublisher<T> take(long count) {
return Streams.take(count, this);
}
/**
* See {@link Streams#batch(int, Publisher, Action)}.
*
* @return a publisher that batches upstream requests
* @since 1.5
*/
default TransformablePublisher<T> batch(int batchSize, Action<? super T> disposer) {
return Streams.batch(batchSize, this, disposer);
}
}