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

reactor.rx.Stream Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2014 Pivotal Software, 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 reactor.rx;

import org.reactivestreams.Processor;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.Environment;
import reactor.core.Dispatcher;
import reactor.core.dispatch.SynchronousDispatcher;
import reactor.core.dispatch.TailRecurseDispatcher;
import reactor.core.queue.CompletableBlockingQueue;
import reactor.core.queue.CompletableLinkedQueue;
import reactor.core.queue.CompletableQueue;
import reactor.core.support.Assert;
import reactor.core.support.Exceptions;
import reactor.core.support.NonBlocking;
import reactor.fn.*;
import reactor.fn.support.Tap;
import reactor.fn.timer.Timer;
import reactor.fn.tuple.Tuple2;
import reactor.fn.tuple.TupleN;
import reactor.io.codec.Codec;
import reactor.rx.action.Action;
import reactor.rx.action.CompositeAction;
import reactor.rx.action.Control;
import reactor.rx.action.Signal;
import reactor.rx.action.aggregation.*;
import reactor.rx.action.combination.*;
import reactor.rx.action.conditional.ExistsAction;
import reactor.rx.action.control.*;
import reactor.rx.action.error.*;
import reactor.rx.action.filter.*;
import reactor.rx.action.metrics.CountAction;
import reactor.rx.action.metrics.ElapsedAction;
import reactor.rx.action.metrics.TimestampAction;
import reactor.rx.action.passive.*;
import reactor.rx.action.support.TapAndControls;
import reactor.rx.action.terminal.AdaptiveConsumerAction;
import reactor.rx.action.terminal.ConsumerAction;
import reactor.rx.action.transformation.*;
import reactor.rx.broadcast.Broadcaster;
import reactor.rx.stream.GroupedStream;
import reactor.rx.stream.LiftStream;
import reactor.rx.subscription.PushSubscription;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Base class for components designed to provide a succinct API for working with future values.
 * Provides base functionality and an internal contract for subclasses that make use of
 * the {@link #map(reactor.fn.Function)} and {@link #filter(reactor.fn.Predicate)} methods.
 * 

* A Stream can be implemented to perform specific actions on callbacks (doNext,doComplete,doError,doOnSubscribe). * It is an asynchronous boundary and will run the callbacks using the input {@link Dispatcher}. Stream can * eventually produce a result {@code } and will offer cascading over its own subscribers. *

* * * Typically, new {@code Stream} aren't created directly. To create a {@code Stream}, * create a {@link Streams} and configure it with the appropriate {@link Environment}, * {@link Dispatcher}, and other settings. * * @param The type of the output values * @author Stephane Maldini * @author Jon Brisbin * @since 1.1, 2.0 */ public abstract class Stream implements Publisher, NonBlocking { protected Stream() { } /** * Cast the current Stream flowing data type into a target class type. * * @param the {@link Action} output type * @return the current {link Stream} instance casted * @since 2.0 */ @SuppressWarnings({"unchecked", "unused"}) public final Stream cast(@Nonnull final Class stream) { return (Stream) this; } /** * Defer the subscription of an {@link Action} to the actual pipeline. * Terminal operations such as {@link #consume(reactor.fn.Consumer)} will start the subscription chain. * It will listen for current Stream signals and will be eventually producing signals as well (subscribe,error, * complete,next). *

* The action is returned for functional-style chaining. * * @param the {@link reactor.rx.action.Action} output type * @param action the function to map a provided dispatcher to a fresh Action to subscribe. * @return the passed action * @see {@link org.reactivestreams.Publisher#subscribe(org.reactivestreams.Subscriber)} * @since 2.0 */ public Stream lift(@Nonnull final Supplier> action) { return new LiftStream<>(this, action); } /** * Assign an error handler to exceptions of the given type. Will not stop error propagation, use when(class, * publisher), retry, ignoreError or recover to actively deal with the exception * * @param exceptionType the type of exceptions to handle * @param onError the error handler for each exception * @param type of the exception to handle * @return {@literal new Stream} */ @SuppressWarnings("unchecked") public final Stream when(@Nonnull final Class exceptionType, @Nonnull final Consumer onError) { return lift(new Supplier>() { @Override public Action get() { return new ErrorAction(exceptionType, onError, null); } }); } /** * Assign an error handler that will pass eventual associated values and exceptions of the given type. * Will not stop error propagation, use when(class, * publisher), retry, ignoreError or recover to actively deal with the exception. * * @param exceptionType the type of exceptions to handle * @param onError the error handler for each exception * @param type of the exception to handle * @return {@literal new Stream} */ @SuppressWarnings("unchecked") public final Stream observeError(@Nonnull final Class exceptionType, @Nonnull final BiConsumer onError) { return lift(new Supplier>() { @Override public Action get() { return new ErrorWithValueAction(exceptionType, onError, null); } }); } /** * Subscribe to a fallback publisher when any exception occurs. * * @param fallback the error handler for each exception * @return {@literal new Stream} */ public final Stream onErrorResumeNext(@Nonnull final Publisher fallback) { return onErrorResumeNext(Throwable.class, fallback); } /** * Subscribe to a fallback publisher when exceptions of the given type occur, otherwise propagate the error. * * @param exceptionType the type of exceptions to handle * @param fallback the error handler for each exception * @param type of the exception to handle * @return {@literal new Stream} */ @SuppressWarnings("unchecked") public final Stream onErrorResumeNext(@Nonnull final Class exceptionType, @Nonnull final Publisher fallback) { return lift(new Supplier>() { @Override public Action get() { return new ErrorAction(exceptionType, null, fallback); } }); } /** * Produce a default value if any exception occurs. * * @param fallback the error handler for each exception * @return {@literal new Stream} */ public final Stream onErrorReturn(@Nonnull final Function fallback) { return onErrorReturn(Throwable.class, fallback); } /** * Produce a default value when exceptions of the given type occur, otherwise propagate the error. * * @param exceptionType the type of exceptions to handle * @param fallback the error handler for each exception * @param type of the exception to handle * @return {@literal new Stream} */ @SuppressWarnings("unchecked") public final Stream onErrorReturn(@Nonnull final Class exceptionType, @Nonnull final Function fallback) { return lift(new Supplier>() { @Override public Action get() { return new ErrorReturnAction(exceptionType, fallback); } }); } /** * Only forward onError and onComplete signals into the returned stream. * * @return {@literal new Stream} */ public final Stream after() { return lift(new Supplier>() { @Override public Action get() { return new AfterAction(); } }); } /** * Transform the incoming onSubscribe, onNext, onError and onComplete signals into {@link reactor.rx.action.Signal}. * Since the error is materialized as a {@code Signal}, the propagation will be stopped. * Complete signal will first emit a {@code Signal.complete()} and then effectively complete the stream. * * @return {@literal new Stream} */ public final Stream> materialize() { return lift(new Supplier>>() { @Override public Action> get() { return new MaterializeAction(); } }); } /** * Transform the incoming onSubscribe, onNext, onError and onComplete signals into {@link reactor.rx.action.Signal}. * Since the error is materialized as a {@code Signal}, the propagation will be stopped. * Complete signal will first emit a {@code Signal.complete()} and then effectively complete the stream. * * @return {@literal new Stream} */ @SuppressWarnings("unchecked") public final Stream dematerialize() { Stream> thiz = (Stream>) this; return thiz.lift(new Supplier, X>>() { @Override public Action, X> get() { return new DematerializeAction(); } }); } /** * Subscribe a new {@link Broadcaster} and return it for future subscribers interactions. Effectively it turns any * stream into an Hot Stream where subscribers will only values from the time T when they subscribe to the returned * stream. Complete and Error signals are however retained unless {@link #keepAlive()} has been called before. *

* * @return a new {@literal stream} whose values are broadcasted to all subscribers */ public final Stream broadcast() { return broadcastOn(getDispatcher()); } /** * Subscribe a new {@link Broadcaster} and return it for future subscribers interactions. Effectively it turns any * stream into an Hot Stream where subscribers will only values from the time T when they subscribe to the returned * stream. Complete and Error signals are however retained unless {@link #keepAlive()} has been called before. *

* * @param dispatcher the dispatcher to run the signals * @return a new {@literal stream} whose values are broadcasted to all subscribers */ public final Stream broadcastOn(Dispatcher dispatcher) { Broadcaster broadcaster = Broadcaster.create(getEnvironment(), dispatcher); return broadcastTo(broadcaster); } /** * Subscribe the passed subscriber, only creating once necessary upstream Subscriptions and returning itself. * Mostly used by other broadcast actions which transform any Stream into a publish-subscribe Stream (every * subscribers * see all values). *

* * @param subscriber the subscriber to subscribe to this stream and return * @param the hydrated generic type for the passed argument, allowing for method chaining * @return {@param subscriber} */ public final > E broadcastTo(E subscriber) { subscribe(subscriber); return subscriber; } /** * Create a {@link reactor.fn.support.Tap} that maintains a reference to the last value seen by this {@code * Stream}. The {@link reactor.fn.support.Tap} is * continually updated when new values pass through the {@code Stream}. * * @return the new {@link reactor.fn.support.Tap} * @see Consumer */ public final TapAndControls tap() { final Tap tap = new Tap<>(); return new TapAndControls<>(tap, consume(tap)); } /** * Create a {@link reactor.fn.support.Tap} that maintains a reference to the last value seen by this {@code * Stream}. The {@link reactor.fn.support.Tap} is * continually updated when new values pass through the {@code Stream}. * * @return the new {@link reactor.fn.support.Tap} * @see Consumer */ @SuppressWarnings("unchecked") public final Stream process(final Processor processor) { subscribe(processor); if(Stream.class.isAssignableFrom(processor.getClass())){ return (Stream)processor; } final long capacity = getCapacity(); return new Stream() { @Override public Dispatcher getDispatcher(){ return PROCESSOR_SYNC; } @Override public long getCapacity() { return capacity; } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } @Override public void subscribe(Subscriber s) { try { processor.subscribe(s); } catch (Throwable t) { s.onError(t); } } }; } /** * Defer a Controls operations ready to be requested. * * @return the consuming action */ public Control consumeLater() { return consume(null); } /** * Instruct the stream to request the produced subscription indefinitely. If the dispatcher * is asynchronous (RingBufferDispatcher for instance), it will proceed the request asynchronously as well. * * @return the consuming action */ @SuppressWarnings("unchecked") public Control consume() { return consume(NOOP); } private final static Consumer NOOP = new Consumer() { @Override public void accept(Object o) { } }; /** * Instruct the action to request upstream subscription if any for N elements. * * @return a new {@link Control} interface to operate on the materialized upstream */ public Control consume(final long n) { Control controls = consume(null); if (n > 0) { controls.requestMore(n); } return controls; } /** * Attach a {@link Consumer} to this {@code Stream} that will consume any values accepted by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. * It will also eagerly prefetch upstream publisher. *

* For a passive version that observe and forward incoming data see {@link #observe(reactor.fn.Consumer)} * * @param consumer the consumer to invoke on each value * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control consume(final Consumer consumer) { return consumeOn(getDispatcher(), consumer); } /** * Attach a {@link Consumer} to this {@code Stream} that will consume any values accepted by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. It will also eagerly prefetch upstream * publisher. *

* For a passive version that observe and forward incoming data see {@link #observe(reactor.fn.Consumer)} * * @param dispatcher the dispatcher to run the consumer * @param consumer the consumer to invoke on each value * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control consumeOn(Dispatcher dispatcher, final Consumer consumer) { ConsumerAction consumerAction = new ConsumerAction( SynchronousDispatcher.INSTANCE != dispatcher && PROCESSOR_SYNC != dispatcher && dispatcher == getDispatcher() ? Long.MAX_VALUE : getCapacity(), dispatcher, consumer, null, null ); subscribe(consumerAction); return consumerAction; } /** * Attach 2 {@link Consumer} to this {@code Stream} that will consume any values signaled by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. * Any Error signal will be consumed by the error consumer. * It will also eagerly prefetch upstream publisher. *

* * @param consumer the consumer to invoke on each next signal * @param errorConsumer the consumer to invoke on each error signal * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control consume(final Consumer consumer, Consumer errorConsumer) { return consumeOn(getDispatcher(), consumer, errorConsumer); } /** * Attach 2 {@link Consumer} to this {@code Stream} that will consume any values signaled by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. * Any Error signal will be consumed by the error consumer. * It will also eagerly prefetch upstream publisher. *

* * @param consumer the consumer to invoke on each next signal * @param errorConsumer the consumer to invoke on each error signal * @param dispatcher the dispatcher to run the consumer * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control consumeOn(Dispatcher dispatcher, final Consumer consumer, Consumer errorConsumer) { return consumeOn(dispatcher, consumer, errorConsumer, null); } /** * Attach 3 {@link Consumer} to this {@code Stream} that will consume any values signaled by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. * Any Error signal will be consumed by the error consumer. * The Complete signal will be consumed by the complete consumer. * Only error and complete signal will be signaled downstream. It will also eagerly prefetch upstream publisher. *

* * @param consumer the consumer to invoke on each value * @param errorConsumer the consumer to invoke on each error signal * @param completeConsumer the consumer to invoke on complete signal * @return {@literal new Stream} */ public final Control consume(final Consumer consumer, Consumer errorConsumer, Consumer completeConsumer) { return consumeOn(getDispatcher(), consumer, errorConsumer, completeConsumer); } /** * Attach 3 {@link Consumer} to this {@code Stream} that will consume any values signaled by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. * Any Error signal will be consumed by the error consumer. * The Complete signal will be consumed by the complete consumer. * Only error and complete signal will be signaled downstream. It will also eagerly prefetch upstream publisher. *

* * @param consumer the consumer to invoke on each value * @param errorConsumer the consumer to invoke on each error signal * @param completeConsumer the consumer to invoke on complete signal * @param dispatcher the dispatcher to run the consumer * @return {@literal new Stream} */ public final Control consumeOn(Dispatcher dispatcher, final Consumer consumer, Consumer errorConsumer, Consumer completeConsumer) { ConsumerAction consumerAction = new ConsumerAction( getCapacity(), dispatcher, consumer, errorConsumer, completeConsumer); subscribe(consumerAction); return consumerAction; } /** * Attach a {@link Consumer} to this {@code Stream} that will consume any values accepted by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. It will also eagerly prefetch upstream * publisher. *

* The passed {code requestMapper} function will receive the {@link Stream} of the last N requested elements * -starting with the * capacity defined for the stream- when the N elements have been consumed. It will return a {@link Publisher} of * long signals * S that will instruct the consumer to request S more elements, possibly altering the "batch" size if wished. *

*

* For a passive version that observe and forward incoming data see {@link #observe(reactor.fn.Consumer)} * * @param consumer the consumer to invoke on each value * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control batchConsume(final Consumer consumer, final Function requestMapper) { return batchConsumeOn(getDispatcher(), consumer, requestMapper); } /** * Attach a {@link Consumer} to this {@code Stream} that will consume any values accepted by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. It will also eagerly prefetch upstream * publisher. *

* The passed {code requestMapper} function will receive the {@link Stream} of the last N requested elements * -starting with the * capacity defined for the stream- when the N elements have been consumed. It will return a {@link Publisher} of * long signals * S that will instruct the consumer to request S more elements. *

* For a passive version that observe and forward incoming data see {@link #observe(reactor.fn.Consumer)} * * @param consumer the consumer to invoke on each value * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control adaptiveConsume(final Consumer consumer, final Function, ? extends Publisher> requestMapper) { return adaptiveConsumeOn(getDispatcher(), consumer, requestMapper); } /** * Attach a {@link Consumer} to this {@code Stream} that will consume any values accepted by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. It will also eagerly prefetch upstream * publisher. *

* The passed {code requestMapper} function will receive the {@link Stream} of the last N requested elements * -starting with the * capacity defined for the stream- when the N elements have been consumed. It will return a {@link Publisher} of * long signals * S that will instruct the consumer to request S more elements, possibly altering the "batch" size if wished. *

*

* For a passive version that observe and forward incoming data see {@link #observe(reactor.fn.Consumer)} * * @param consumer the consumer to invoke on each value * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control batchConsumeOn(final Dispatcher dispatcher, final Consumer consumer, final Function requestMapper) { return adaptiveConsumeOn(dispatcher, consumer, new Function, Publisher>() { @Override public Publisher apply(Stream longStream) { return longStream.map(requestMapper); } }); } /** * Attach a {@link Consumer} to this {@code Stream} that will consume any values accepted by this {@code * Stream}. As such this a terminal action to be placed on a stream flow. It will also eagerly prefetch upstream * publisher. *

* The passed {code requestMapper} function will receive the {@link Stream} of the last N requested elements * -starting with the * capacity defined for the stream- when the N elements have been consumed. It will return a {@link Publisher} of * long signals * S that will instruct the consumer to request S more elements. *

* Multiple long signals S can be requested before a given request complete and therefore * an approriate ordering Dispatcher should be used. *

*

* For a passive version that observe and forward incoming data see {@link #observe(reactor.fn.Consumer)} * * @param consumer the consumer to invoke on each value * @return a new {@link Control} interface to operate on the materialized upstream */ public final Control adaptiveConsumeOn(final Dispatcher dispatcher, final Consumer consumer, final Function, ? extends Publisher> requestMapper) { AdaptiveConsumerAction consumerAction = new AdaptiveConsumerAction(dispatcher, getCapacity(), consumer, requestMapper); subscribe(consumerAction); if (consumer != null) { consumerAction.requestMore(consumerAction.getCapacity()); } return consumerAction; } /** * Assign a new Environment and its default Dispatcher to the returned Stream. If the dispatcher is different, * the new action will take * care of buffering incoming data into a StreamSubscription. Otherwise default behavior is picked: * FireHose synchronous subscription is the parent stream != null * * @param environment the environment to get dispatcher from {@link reactor.Environment#getDefaultDispatcher()} * @return a new {@link Stream} running on a different {@link Dispatcher} */ public final Stream dispatchOn(@Nonnull final Environment environment) { return dispatchOn(environment, environment.getDefaultDispatcher()); } /** * Assign a new Dispatcher to handle upstream request to the returned Stream. * * @param environment the environment to get dispatcher from {@link reactor.Environment#getDefaultDispatcher()} * @return a new {@link Stream} whose requests are running on a different {@link Dispatcher} */ public final Stream subscribeOn(@Nonnull final Environment environment) { return subscribeOn(environment.getDefaultDispatcher()); } /** * Assign a new Dispatcher to the returned Stream. If the dispatcher is different, the new action will take * care of buffering incoming data into a StreamSubscription. Otherwise default behavior is picked: * FireHose synchronous subscription is the parent stream != null * * @param dispatcher the new dispatcher * @return a new {@link Stream} running on a different {@link Dispatcher} */ public final Stream dispatchOn(@Nonnull final Dispatcher dispatcher) { return dispatchOn(null, dispatcher); } /** * Assign a new Dispatcher to handle upstream request to the returned Stream. * * @param sub the subscriber to request using the current dispatcher * @param currentDispatcher the new dispatcher */ public final void subscribeOn(@Nonnull final Dispatcher currentDispatcher, Subscriber sub) { subscribeOn(currentDispatcher).subscribe(sub); } /** * Assign a new Dispatcher to handle upstream request to the returned Stream. * * @param currentDispatcher the new dispatcher * @return a new {@link Stream} whose requests are running on a different {@link Dispatcher} */ public final Stream subscribeOn(@Nonnull final Dispatcher currentDispatcher) { return new StreamDispatchedSubscribe<>(this, currentDispatcher); } /** * Assign the a new Dispatcher and an Environment to the returned Stream. If the dispatcher is different, * the new action will take * care of buffering incoming data into a StreamSubscription. Otherwise default behavior is picked: * FireHose synchronous subscription is the parent stream != null * * @param dispatcher the new dispatcher * @param environment the environment * @return a new {@link Stream} running on a different {@link Dispatcher} */ public Stream dispatchOn(final Environment environment, @Nonnull final Dispatcher dispatcher) { if (dispatcher == SynchronousDispatcher.INSTANCE) { if (environment != null && environment != getEnvironment()) { return env(environment); } else { return this; } } Assert.state(dispatcher.supportsOrdering(), "Dispatcher provided doesn't support event ordering. " + " For concurrent signal dispatching, refer to #partition()/groupBy() method and assign individual single " + "dispatchers. "); long _capacity = Action.evaluateCapacity(dispatcher.backlogSize()); long parentCapacity = getCapacity(); final Dispatcher parentDispatcher = getDispatcher(); final long capacity = _capacity > parentCapacity ? parentCapacity : _capacity; return new LiftStream(this, new Supplier>() { @Override public Action get() { return new DispatcherAction(dispatcher, parentDispatcher).capacity(capacity); } }) { @Override public Dispatcher getDispatcher() { return dispatcher; } @Override public Environment getEnvironment() { return environment; } @Override public long getCapacity() { return capacity; } }; } /** * Attach a {@link Consumer} to this {@code Stream} that will observe any values accepted by this {@code * Stream}. * * @param consumer the consumer to invoke on each value * @return {@literal new Stream} * @since 2.0 */ public final Stream observe(@Nonnull final Consumer consumer) { return lift(new Supplier>() { @Override public Action get() { return new CallbackAction(consumer, null); } }); } /** * Cache all signal to this {@code Stream} and release them on request that will observe any values accepted by this * {@code * Stream}. * * @return {@literal new Stream} * @since 2.0 */ public final Stream cache() { Action cacheAction = new CacheAction(); subscribe(cacheAction); return cacheAction; } /** * Attach a {@link java.util.logging.Logger} to this {@code Stream} that will observe any signal emitted. * * @return {@literal new Stream} * @since 2.0 */ public final Stream log() { return log(null); } /** * Attach a {@link java.util.logging.Logger} to this {@code Stream} that will observe any signal emitted. * * @param name The logger name * @return {@literal new Stream} * @since 2.0 */ public final Stream log(final String name) { return lift(new Supplier>() { @Override public Action get() { return new LoggerAction(name); } }); } /** * Attach a {@link Consumer} to this {@code Stream} that will observe any complete signal * * @param consumer the consumer to invoke on complete * @return {@literal a new stream} * @since 2.0 */ public final Stream observeComplete(@Nonnull final Consumer consumer) { return lift(new Supplier>() { @Override public Action get() { return new CallbackAction(null, consumer); } }); } /** * Attach a {@link Consumer} to this {@code Stream} that will observe any subscribe signal * * @param consumer the consumer to invoke ont subscribe * @return {@literal a new stream} * @since 2.0 */ public final Stream observeSubscribe(@Nonnull final Consumer> consumer) { return lift(new Supplier>() { @Override public Action get() { return new StreamStateCallbackAction(consumer, null, null); } }); } /** * Attach a {@link Consumer} to this {@code Stream} that will observe any onSubscribe signal * * @param consumer the consumer to invoke on onSubscribe * @return {@literal a new stream} * @since 2.0 */ public final Stream observeStart(@Nonnull final Consumer consumer) { return lift(new Supplier>() { @Override public Action get() { return new StreamStateCallbackAction(null, null, consumer); } }); } /** * Attach a {@link Consumer} to this {@code Stream} that will observe any cancel signal * * @param consumer the consumer to invoke on cancel * @return {@literal a new stream} * @since 2.0 */ public final Stream observeCancel(@Nonnull final Consumer consumer) { return lift(new Supplier>() { @Override public Action get() { return new StreamStateCallbackAction(null, consumer, null); } }); } /** * Connect an error-proof action that will transform an incoming error signal into a complete signal. * * @return a new fail-proof {@link Stream} */ public Stream ignoreError() { return ignoreError(new Predicate() { @Override public boolean test(Throwable o) { return true; } }); } /** * Connect an error-proof action based on the given predicate matching the current error. * * @param ignorePredicate a predicate to test if an error should be transformed to a complete signal. * @return a new fail-proof {@link Stream} */ public Stream ignoreError(final Predicate ignorePredicate) { return lift(new Supplier>() { @Override public Action get() { return new IgnoreErrorAction(ignorePredicate); } }); } /** * Attach a {@link Consumer} to this {@code Stream} that will observe terminal signal complete|error. * The consumer will listen for the signal and introspect its state. * * @param consumer the consumer to invoke on terminal signal * @return {@literal new Stream} * @since 2.0 */ public final Stream finallyDo(final Consumer> consumer) { return lift(new Supplier>() { @Override public Action get() { return new FinallyAction(consumer); } }); } /** * Create an operation that returns the passed value if the Stream has completed without any emitted signals. * * @param defaultValue the value to forward if the stream is empty * @return {@literal new Stream} * @since 2.0 */ public final Stream defaultIfEmpty(final O defaultValue) { return lift(new Supplier>() { @Override public Action get() { return new DefaultIfEmptyAction(defaultValue); } }); } /** * Assign the given {@link Function} to transform the incoming value {@code T} into a {@code V} and pass it into * another {@code Stream}. * * @param fn the transformation function * @param the type of the return value of the transformation function * @return a new {@link Stream} containing the transformed values */ public final Stream map(@Nonnull final Function fn) { return lift(new Supplier>() { @Override public Action get() { return new MapAction(fn); } }); } /** * Assign the given {@link Function} to transform the incoming value {@code T} into a {@code Stream} and pass * it into another {@code Stream}. * * @param fn the transformation function * @param the type of the return value of the transformation function * @return a new {@link Stream} containing the transformed values * @since 1.1, 2.0 */ public final Stream flatMap(@Nonnull final Function> fn) { return map(fn).merge(); } /** * Assign the given {@link Function} to transform the incoming value {@code T} into a {@code Stream} and pass * it into another {@code Stream}. The produced stream will emit the data from the most recent transformed stream. * * @param fn the transformation function * @param the type of the return value of the transformation function * @return a new {@link Stream} containing the transformed values * @since 1.1, 2.0 */ public final Stream switchMap(@Nonnull final Function> fn) { return map(fn).lift(new Supplier, V>>() { @Override public Action, V> get() { return new SwitchAction(getDispatcher()); } }); } /** * Assign the given {@link Function} to transform the incoming value {@code T} into a {@code Stream} and pass * it into another {@code Stream}. The produced stream will emit the data from all transformed streams in order. * * @param fn the transformation function * @param the type of the return value of the transformation function * @return a new {@link Stream} containing the transformed values * @since 1.1, 2.0 */ public final Stream concatMap(@Nonnull final Function> fn) { return map(fn).lift(new Supplier, V>>() { @Override public Action, V> get() { return new ConcatAction(); } }); } /** * Transform a sequence of codec source elements into codec input elements through {@link Codec#decode(Publisher)} * * @param codec the unmarshalling codec * @param the type of the Input codec type translated from the current "source" type sequence * @return a new {@link Stream} containing the transformed values */ public final Stream decode(final Codec codec) { return new Stream() { @Override public void subscribe(Subscriber s) { codec.decode(Stream.this).subscribe(s); } @Override public long getCapacity() { return Stream.this.getCapacity(); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } }; } /** * Transform a sequence of codec output elements into codec source elements through {@link Codec#encode(Publisher)} * * @param codec the unmarshalling codec * @param the type of the Source codec type translated from the current "output" sequence * @return a new {@link Stream} containing the transformed values */ public final Stream encode(final Codec codec) { return new Stream() { @Override public void subscribe(Subscriber s) { codec.encode(Stream.this).subscribe(s); } @Override public long getCapacity() { return Stream.this.getCapacity(); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } }; } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values to a new {@link Stream}. * Dynamic merge requires use of reactive-pull * offered by default StreamSubscription. If merge hasn't getCapacity() to take new elements because its {@link * #getCapacity()(long)} instructed so, the subscription will buffer * them. * * @param the inner stream flowing data type that will be the produced signal. * @return the merged stream * @since 2.0 */ @SuppressWarnings("unchecked") public final Stream merge() { return fanIn(null); } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values from this current upstream and from the * passed publisher. * * @return the merged stream * @since 2.0 */ public final Stream mergeWith(final Publisher publisher) { return new Stream() { @Override public void subscribe(Subscriber s) { new MergeAction<>(SynchronousDispatcher.INSTANCE, Arrays.asList(Stream.this, publisher)) .subscribe(s); } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } @Override public long getCapacity() { return Stream.this.getCapacity(); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } }; } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values from this current upstream and then on * complete consume from the * passed publisher. * * @return the merged stream * @since 2.0 */ public final Stream concatWith(final Publisher publisher) { return new Stream() { @Override public void subscribe(Subscriber s) { Stream> just = Streams.just(Stream.this, publisher); ConcatAction concatAction = new ConcatAction<>(); concatAction.subscribe(s); just.subscribe(concatAction); } @Override public long getCapacity() { return Stream.this.getCapacity(); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } }; } /** * Start emitting all items from the passed publisher then emits from the current stream. * * @return the merged stream * @since 2.0 */ public final Stream startWith(final Iterable iterable) { return startWith(Streams.from(iterable)); } /** * Start emitting all items from the passed publisher then emits from the current stream. * * @return the merged stream * @since 2.0 */ public final Stream startWith(final O value) { return startWith(Streams.just(value)); } /** * Start emitting all items from the passed publisher then emits from the current stream. * * @return the merged stream * @since 2.0 */ public final Stream startWith(final Publisher publisher) { if(publisher == null) return this; return Streams.concat(publisher, this); } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values to a new {@link Stream} until one of them * complete. * The result will be produced with a list of each upstream most recent emitted data. * * @return the zipped and joined stream * @since 2.0 */ public final Stream> join() { return zip(ZipAction.joinZipper()); } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values to a new {@link Stream} until one of them * complete. * The result will be produced with a list of each upstream most recent emitted data. * * @return the zipped and joined stream * @since 2.0 */ public final Stream> joinWith(Publisher publisher) { return zipWith(publisher, ZipAction., V>joinZipper()); } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values to a new {@link Stream} until one of them * complete. * The result will be produced by the zipper transformation from a tuple of each upstream most recent emitted data. * * @return the merged stream * @since 2.0 */ @SuppressWarnings("unchecked") public final Stream zip(final @Nonnull Function zipper) { final Stream> thiz = (Stream>) this; return thiz.lift(new Supplier, V>>() { @Override public Action, V> get() { return new DynamicMergeAction( new ZipAction(SynchronousDispatcher.INSTANCE, zipper, null)). capacity(getCapacity()); } }); } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values to a new {@link Stream} until one of them * complete. * The result will be produced by the zipper transformation from a tuple of each upstream most recent emitted data. * * @return the zipped stream * @since 2.0 */ @SuppressWarnings("unchecked") public final Stream zipWith(Iterable iterable, @Nonnull Function, V> zipper) { return zipWith(Streams.from(iterable), zipper); } /** * {@link #lift(Supplier)} with the passed {@link Publisher} values to a new {@link Stream} until one of them * complete. * The result will be produced by the zipper transformation from a tuple of each upstream most recent emitted data. * * @return the zipped stream * @since 2.0 */ public final Stream zipWith(final Publisher publisher, final @Nonnull Function, V> zipper) { return new Stream() { @Override public void subscribe(Subscriber s) { new ZipAction<>(SynchronousDispatcher.INSTANCE, zipper, Arrays.asList(Stream.this, publisher)) .subscribe(s); } @Override public long getCapacity() { return Stream.this.getCapacity(); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } }; } /** * {@link #lift(Supplier)} all the nested {@link Publisher} values to a new {@link Stream} calling the logic * inside the provided fanInAction for complex merging strategies. * {@link reactor.rx.action.combination.FanInAction} provides helpers to create subscriber for each source, * a registry of incoming sources and overriding doXXX signals as usual to produce the result via * reactor.rx.action.Action#broadcastXXX. *

* A default fanInAction will act like {@link #merge()}, passing values to doNext. In java8 one can then * implement * stream.fanIn(data -> broadcastNext(data)) or stream.fanIn(System.out::println) *

* Dynamic merge (moving nested data into the top-level returned stream) requires use of reactive-pull offered by * default StreamSubscription. If merge hasn't getCapacity() to * take new elements because its {@link * #getCapacity()(long)} instructed so, the subscription will buffer * them. * * @param the nested type of flowing upstream Stream. * @param the produced output * @return the zipped stream * @since 2.0 */ @SuppressWarnings("unchecked") public Stream fanIn( final FanInAction> fanInAction ) { final Stream> thiz = (Stream>) this; return thiz.lift(new Supplier, V>>() { @Override public Action, V> get() { return new DynamicMergeAction(fanInAction). capacity(getCapacity()); } }); } /** * Bind the stream to a given {@param elements} volume of in-flight data: * - An {@link Action} will request up to the defined volume upstream. * - An {@link Action} will track the pending requests and fire up to {@param elements} when the previous volume has * been processed. * - A {@link reactor.rx.action.aggregation.BatchAction} and any other size-bound action will be limited to the * defined volume. *

*

* A stream capacity can't be superior to the underlying dispatcher capacity: if the {@param elements} overflow the * dispatcher backlog size, the capacity will be aligned automatically to fit it. * RingBufferDispatcher will for instance take to a power of 2 size up to {@literal Integer.MAX_VALUE}, * where a Stream can be sized up to {@literal Long.MAX_VALUE} in flight data. *

*

* When the stream receives more elements than requested, incoming data is eventually staged in a {@link * org.reactivestreams.Subscription}. * The subscription can react differently according to the implementation in-use, * the default strategy is as following: * - The first-level of pair compositions Stream->Action will overflow data in a {@link reactor.core.queue * .CompletableQueue}, * ready to be polled when the action fire the pending requests. * - The following pairs of Action->Action will synchronously pass data * - Any pair of Stream->Subscriber or Action->Subscriber will behave as with the root Stream->Action pair rule. * - {@link #onOverflowBuffer()} force this staging behavior, with a possibilty to pass a {@link reactor.core.queue * .PersistentQueue} * * @param elements maximum number of in-flight data * @return a backpressure capable stream */ public Stream capacity(final long elements) { if(elements == getCapacity()) return this; return new Stream() { @Override public void subscribe(Subscriber s) { Stream.this.subscribe(s); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } @Override public Environment getEnvironment() { return Stream.this.getEnvironment(); } @Override public long getCapacity() { return elements; } }; } /** * Make this Stream subscribers unbounded * * @see #capacity(long) * @return Stream with capacity set to max */ public final Stream unbounded(){ return capacity(Long.MAX_VALUE); } /** * Attach a No-Op Action that only serves the purpose of buffering incoming values if not enough demand is signaled * downstream. A buffering capable stream will prevent underlying dispatcher to be saturated (and sometimes * blocking). * * @return a buffered stream * @since 2.0 */ public final Stream onOverflowBuffer() { return onOverflowBuffer(new Supplier>() { @Override public CompletableQueue get() { return new CompletableLinkedQueue(); } }); } /** * Attach a No-Op Action that only serves the purpose of buffering incoming values if not enough demand is signaled * downstream. A buffering capable stream will prevent underlying dispatcher to be saturated (and sometimes * blocking). * * @param queueSupplier A completable queue {@link reactor.fn.Supplier} to provide support for overflow * @return a buffered stream * @since 2.0 */ public Stream onOverflowBuffer(final Supplier> queueSupplier) { return lift(new Supplier>() { @Override public Action get() { return new FlowControlAction(queueSupplier); } }); } /** * Attach a No-Op Action that only serves the purpose of dropping incoming values if not enough demand is signaled * downstream. A dropping stream will prevent underlying dispatcher to be saturated (and sometimes * blocking). * * @return a dropping stream * @since 2.0 */ public final Stream onOverflowDrop() { return onOverflowBuffer(null); } /** * Evaluate each accepted value against the given {@link Predicate}. If the predicate test succeeds, the value is * passed into the new {@code Stream}. If the predicate test fails, the value is ignored. * * @param p the {@link Predicate} to test values against * @return a new {@link Stream} containing only values that pass the predicate test */ public final Stream filter(final Predicate p) { return lift(new Supplier>() { @Override public Action get() { return new FilterAction(p); } }); } /** * Evaluate each accepted boolean value. If the predicate test succeeds, the value is * passed into the new {@code Stream}. If the predicate test fails, the value is ignored. * * @return a new {@link Stream} containing only values that pass the predicate test * @since 1.1, 2.0 */ @SuppressWarnings("unchecked") public final Stream filter() { return ((Stream) this).filter(FilterAction.simplePredicate); } /** * Create a new {@code Stream} whose only value will be the current instance of the {@link Stream}. * * @return a new {@link Stream} whose only value will be the materialized current {@link Stream} * @since 2.0 */ public final Stream> nest() { return Streams.just(this); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair. The action will start * propagating errors after {@literal Integer.MAX_VALUE}. * * @return a new fault-tolerant {@code Stream} * @since 2.0 */ public final Stream retry() { return retry(-1); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair. The action will start * propagating errors after {@param numRetries}. * This is generally useful for retry strategies and fault-tolerant streams. * * @param numRetries the number of times to tolerate an error * @return a new fault-tolerant {@code Stream} * @since 2.0 */ public final Stream retry(int numRetries) { return retry(numRetries, null); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair. * {@param retryMatcher} will test an incoming {@link Throwable}, if positive the retry will occur. * This is generally useful for retry strategies and fault-tolerant streams. * * @param retryMatcher the predicate to evaluate if retry should occur based on a given error signal * @return a new fault-tolerant {@code Stream} * @since 2.0 */ public final Stream retry(Predicate retryMatcher) { return retry(-1, retryMatcher); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair. The action will start * propagating errors after {@param numRetries}. {@param retryMatcher} will test an incoming {@Throwable}, * if positive * the retry will occur (in conjonction with the {@param numRetries} condition). * This is generally useful for retry strategies and fault-tolerant streams. * * @param numRetries the number of times to tolerate an error * @param retryMatcher the predicate to evaluate if retry should occur based on a given error signal * @return a new fault-tolerant {@code Stream} * @since 2.0 */ public final Stream retry(final int numRetries, final Predicate retryMatcher) { return lift(new Supplier>() { @Override public Action get() { return new RetryAction(getDispatcher(), numRetries, retryMatcher, Stream.this); } }); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair if the exception is of * the given type. * The recoveredValues subscriber will be emitted the associated value if any. If it doesn't match the given * exception type, the error signal will be propagated downstream but not to the recovered values sink. * * @param recoveredValuesSink the subscriber to listen for recovered values * @param exceptionType the type of exceptions to handle * @return a new fault-tolerant {@code Stream} * @since 2.0 */ public final Stream recover(@Nonnull final Class exceptionType, final Subscriber recoveredValuesSink) { return retryWhen(new Function, Publisher>() { @Override public Publisher apply(Stream stream) { stream.map(new Function() { @Override public Object apply(Throwable throwable) { if (exceptionType.isAssignableFrom(throwable.getClass())) { return Exceptions.getFinalValueCause(throwable); } else { return null; } } }).subscribe(recoveredValuesSink); return stream.map(new Function>() { @Override public Signal apply(Throwable throwable) { if (exceptionType.isAssignableFrom(throwable.getClass())) { return Signal.next(throwable); } else { return Signal.error(throwable); } } }).dematerialize(); } }); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair if the backOff stream * produced by the passed mapper emits any next data or complete signal. It will propagate the error if the backOff * stream emits an error signal. * * @param backOffStream the function taking the error stream as an input and returning a new stream that applies * some backoff policy e.g. Streams.timer * @return a new fault-tolerant {@code Stream} * @since 2.0 */ public final Stream retryWhen(final Function, ? extends Publisher> backOffStream) { return lift(new Supplier>() { @Override public Action get() { return new RetryWhenAction(getDispatcher(), backOffStream, Stream.this); } }); } /** * Create a new {@code Stream} which will keep re-subscribing its oldest parent-child stream pair on complete. * * @return a new infinitely repeated {@code Stream} * @since 2.0 */ public final Stream repeat() { return repeat(-1); } /** * Create a new {@code Stream} which will keep re-subscribing its oldest parent-child stream pair on complete. * The action will be propagating complete after {@param numRepeat}. * if positive * * @param numRepeat the number of times to re-subscribe on complete * @return a new repeated {@code Stream} * @since 2.0 */ public final Stream repeat(final int numRepeat) { return lift(new Supplier>() { @Override public Action get() { return new RepeatAction(getDispatcher(), numRepeat, Stream.this); } }); } /** * Create a new {@code Stream} which will re-subscribe its oldest parent-child stream pair if the backOff stream * produced by the passed mapper emits any next signal. It will propagate the complete and error if the backoff * stream emits the relative signals. * * @param backOffStream the function taking a stream of complete timestamp in millis as an input and returning a new * stream that applies some backoff policy, e.g. @{link Streams#timer(long)} * @return a new repeated {@code Stream} * @since 2.0 */ public final Stream repeatWhen(final Function, ? extends Publisher> backOffStream) { return lift(new Supplier>() { @Override public Action get() { return new RepeatWhenAction(getDispatcher(), backOffStream, Stream.this); } }); } /** * Create a new {@code Stream} that will signal the last element observed before complete signal. * * @return a new limited {@code Stream} * @since 2.0 */ public final Stream last() { return lift(new Supplier>() { @Override public Action get() { return new LastAction(); } }); } /** * Create a new {@code Stream} that will signal next elements up to {@param max} times. * * @param max the number of times to broadcast next signals before completing * @return a new limited {@code Stream} * @since 2.0 */ public final Stream take(final long max) { return lift(new Supplier>() { @Override public Action get() { return new TakeAction(max); } }); } /** * Create a new {@code Stream} that will signal next elements up to the specified {@param time}. * * @param time the time window to broadcast next signals before completing * @param unit the time unit to use * @return a new limited {@code Stream} * @since 2.0 */ public final Stream take(long time, TimeUnit unit) { return take(time, unit, getTimer()); } /** * Create a new {@code Stream} that will signal next elements up to the specified {@param time}. * * @param time the time window to broadcast next signals before completing * @param unit the time unit to use * @param timer the Timer to use * @return a new limited {@code Stream} * @since 2.0 */ public final Stream take(final long time, final TimeUnit unit, final Timer timer) { if (time > 0) { Assert.isTrue(timer != null, "Timer can't be found, try assigning an environment to the stream"); return lift(new Supplier>() { @Override public Action get() { return new TakeUntilTimeout(getDispatcher(), time, unit, timer); } }); } else { return Streams.empty(); } } /** * Create a new {@code Stream} that will signal next elements while {@param limitMatcher} is true. * * @param limitMatcher the predicate to evaluate for starting dropping events and completing * @return a new limited {@code Stream} * @since 2.0 */ public final Stream takeWhile(final Predicate limitMatcher) { return lift(new Supplier>() { @Override public Action get() { return new TakeWhileAction(limitMatcher); } }); } /** * Create a new {@code Stream} that will NOT signal next elements up to {@param max} times. * * @param max the number of times to drop next signals before starting * @return a new limited {@code Stream} * @since 2.0 */ public final Stream skip(long max) { return skipWhile(max, null); } /** * Create a new {@code Stream} that will NOT signal next elements up to the specified {@param time}. * * @param time the time window to drop next signals before starting * @param unit the time unit to use * @return a new limited {@code Stream} * @since 2.0 */ public final Stream skip(long time, TimeUnit unit) { return skip(time, unit, getTimer()); } /** * Create a new {@code Stream} that will NOT signal next elements up to the specified {@param time}. * * @param time the time window to drop next signals before starting * @param unit the time unit to use * @param timer the Timer to use * @return a new limited {@code Stream} * @since 2.0 */ public final Stream skip(final long time, final TimeUnit unit, final Timer timer) { if (time > 0) { Assert.isTrue(timer != null, "Timer can't be found, try assigning an environment to the stream"); return lift(new Supplier>() { @Override public Action get() { return new SkipUntilTimeout(time, unit, timer); } }); } else { return this; } } /** * Create a new {@code Stream} that will NOT signal next elements while {@param limitMatcher} is true. * * @param limitMatcher the predicate to evaluate to start broadcasting events * @return a new limited {@code Stream} * @since 2.0 */ public final Stream skipWhile(Predicate limitMatcher) { return skipWhile(Long.MAX_VALUE, limitMatcher); } /** * Create a new {@code Stream} that will NOT signal next elements while {@param limitMatcher} is true or * up to {@param max} times. * * @param max the number of times to drop next signals before starting * @param limitMatcher the predicate to evaluate for starting dropping events and completing * @return a new limited {@code Stream} * @since 2.0 */ public final Stream skipWhile(final long max, final Predicate limitMatcher) { if (max > 0) { return lift(new Supplier>() { @Override public Action get() { return new SkipAction(limitMatcher, max); } }); } else { return this; } } /** * Create a new {@code Stream} that accepts a {@link reactor.fn.tuple.Tuple2} of T1 {@link Long} system time in * millis and T2 {@link } associated data * * @return a new {@link Stream} that emits tuples of millis time and matching data * @since 2.0 */ public final Stream> timestamp() { return lift(new Supplier>>() { @Override public Action> get() { return new TimestampAction(); } }); } /** * Create a new {@code Stream} that accepts a {@link reactor.fn.tuple.Tuple2} of T1 {@link Long} timemillis and T2 * {@link } associated data. The timemillis corresponds to the elapsed time between the subscribe and the first * next signal OR between two next signals. * * @return a new {@link Stream} that emits tuples of time elapsed in milliseconds and matching data * @since 2.0 */ public final Stream> elapsed() { return lift(new Supplier>>() { @Override public Action> get() { return new ElapsedAction(); } }); } /** * Create a new {@code Stream} that emits an item at a specified index from a source {@code Stream} * * @param index index of an item * @return a source item at a specified index */ public final Stream elementAt(final int index) { return lift(new Supplier>() { @Override public Action get() { return new ElementAtAction(index); } }); } /** * Create a new {@code Stream} that emits an item at a specified index from a source {@code Stream} * or default value when index is out of bounds * * @param index index of an item * @return a source item at a specified index or a default value */ public final Stream elementAtOrDefault(final int index, final O defaultValue) { return lift(new Supplier>() { @Override public Action get() { return new ElementAtAction(index, defaultValue); } }); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. Requires a {@code * getCapacity()} to have been set. *

* When a new batch is triggered, the first value of that next batch will be pushed into this {@code Stream}. * * @return a new {@link Stream} whose values are the first value of each batch */ public final Stream sampleFirst() { return sampleFirst((int) Math.min(Integer.MAX_VALUE, getCapacity())); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. *

* When a new batch is triggered, the first value of that next batch will be pushed into this {@code Stream}. * * @param batchSize the batch size to use * @return a new {@link Stream} whose values are the first value of each batch) */ public final Stream sampleFirst(final int batchSize) { return lift(new Supplier>() { @Override public Action get() { return new SampleAction(getDispatcher(), batchSize, true); } }); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are the first value of each batch */ public final Stream sampleFirst(long timespan, TimeUnit unit) { return sampleFirst(timespan, unit, getTimer()); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are the first value of each batch */ public final Stream sampleFirst(long timespan, TimeUnit unit, Timer timer) { return sampleFirst(Integer.MAX_VALUE, timespan, unit, timer); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. * * @param maxSize the max counted size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are the first value of each batch */ public final Stream sampleFirst(int maxSize, long timespan, TimeUnit unit) { return sampleFirst(maxSize, timespan, unit, getTimer()); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. * * @param maxSize the max counted size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are the first value of each batch */ public final Stream sampleFirst(final int maxSize, final long timespan, final TimeUnit unit, final Timer timer) { return lift(new Supplier>() { @Override public Action get() { return new SampleAction(getDispatcher(), true, maxSize, timespan, unit, timer); } }); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. Requires a {@code * getCapacity()} * * @return a new {@link Stream} whose values are the last value of each batch */ public final Stream sample() { return sample((int) Math.min(Integer.MAX_VALUE, getCapacity())); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. Requires a {@code * getCapacity()} * * @param batchSize the batch size to use * @return a new {@link Stream} whose values are the last value of each batch */ public final Stream sample(final int batchSize) { return lift(new Supplier>() { @Override public Action get() { return new SampleAction(getDispatcher(), batchSize); } }); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are the last value of each batch */ public final Stream sample(long timespan, TimeUnit unit) { return sample(timespan, unit, getTimer()); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are the last value of each batch */ public final Stream sample(long timespan, TimeUnit unit, Timer timer) { return sample(Integer.MAX_VALUE, timespan, unit, timer); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. * * @param maxSize the max counted size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are the last value of each batch */ public final Stream sample(int maxSize, long timespan, TimeUnit unit) { return sample(maxSize, timespan, unit, getEnvironment() == null ? Environment.timer() : getEnvironment().getTimer ()); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. * * @param maxSize the max counted size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are the last value of each batch */ public final Stream sample(final int maxSize, final long timespan, final TimeUnit unit, final Timer timer) { return lift(new Supplier>() { @Override public Action get() { return new SampleAction(getDispatcher(), false, maxSize, timespan, unit, timer); } }); } /** * Create a new {@code Stream} that filters out consecutive equals values. * * @return a new {@link Stream} whose values are the last value of each batch * @since 2.0 */ public final Stream distinctUntilChanged() { return lift(new Supplier>() { @Override public Action get() { return new DistinctUntilChangedAction(null); } }); } /** * Create a new {@code Stream} that filters out consecutive values having equal keys computed by function * * @param keySelector function to compute comparison key for each element * @return a new {@link Stream} whose values are the last value of each batch * @since 2.0 */ public final Stream distinctUntilChanged(final Function keySelector) { return lift(new Supplier>() { @Override public Action get() { return new DistinctUntilChangedAction(keySelector); } }); } /** * Create a new {@code Stream} that filters in only unique values. * * @return a new {@link Stream} with unique values */ public final Stream distinct() { return lift(new Supplier>() { @Override public Action get() { return new DistinctAction(null); } }); } /** * Create a new {@code Stream} that filters in only values having distinct keys computed by function * * @param keySelector function to compute comparison key for each element * @return a new {@link Stream} with values having distinct keys */ public final Stream distinct(final Function keySelector) { return lift(new Supplier>() { @Override public Action get() { return new DistinctAction(keySelector); } }); } /** * Create a new {@code Stream} that emits true when any value satisfies a predicate * and false otherwise * * @param predicate predicate tested upon values * @return a new {@link Stream} with true if any value satisfies a predicate * and false otherwise * @since 2.0 */ public final Stream exists(final Predicate predicate) { return lift(new Supplier>() { @Override public Action get() { return new ExistsAction(predicate); } }); } /** * Create a new {@code Stream} whose values will be each element E of any Iterable flowing this Stream * When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}. * * @return a new {@link Stream} whose values result from the iterable input * @since 1.1, 2.0 */ public final Stream split() { return split(Long.MAX_VALUE); } /** * Create a new {@code Stream} whose values will be each element E of any Iterable flowing this Stream *

* When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}. * * @param batchSize the batch size to use * @return a new {@link Stream} whose values result from the iterable input * @since 1.1, 2.0 */ @SuppressWarnings("unchecked") public final Stream split(final long batchSize) { final Stream> iterableStream = (Stream>) this; /*return iterableStream.flatMap(new Function, Publisher>() { @Override public Publisher apply(Iterable vs) { return Streams.from(vs); } });*/ return iterableStream.lift(new Supplier, V>>() { @Override public Action, V> get() { return new SplitAction().capacity(batchSize); } }); } /** * Collect incoming values into a {@link java.util.List} that will be pushed into the returned {@code Stream} every * time {@link #getCapacity()} has been reached, or flush is triggered. * * @return a new {@link Stream} whose values are a {@link java.util.List} of all values in this batch */ public final Stream> buffer() { return buffer((int) Math.min(Integer.MAX_VALUE, getCapacity())); } /** * Collect incoming values into multiple {@link List} buckets that will be pushed into the returned {@code Stream} * every time {@link #getCapacity()} has been reached. * * @param maxSize the collected size * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final int maxSize) { return lift(new Supplier>>() { @Override public Action> get() { return new BufferAction(getDispatcher(), maxSize); } }); } /** * Collect incoming values into a {@link List} that will be moved into the returned {@code Stream} every time the * passed boundary publisher emits an item. * Complete will flush any remaining items. * * @param bucketOpening the publisher to subscribe to on start for creating new buffer on next or complete * signals. * @param boundarySupplier the factory to provide a publisher to subscribe to when a buffer has been started * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final Publisher bucketOpening, final Supplier> boundarySupplier) { return lift(new Supplier>>() { @Override public Action> get() { return new BufferShiftWhenAction(bucketOpening, boundarySupplier); } }); } /** * Collect incoming values into a {@link List} that will be moved into the returned {@code Stream} every time the * passed boundary publisher emits an item. * Complete will flush any remaining items. * * @param boundarySupplier the factory to provide a publisher to subscribe to on start for emiting and starting a * new buffer * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final Supplier> boundarySupplier) { return lift(new Supplier>>() { @Override public Action> get() { return new BufferWhenAction(boundarySupplier); } }); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every time {@code * maxSize} has been reached by any of them. Complete signal will flush any remaining buckets. * * @param skip the number of items to skip before creating a new bucket * @param maxSize the collected size * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final int maxSize, final int skip) { if (maxSize == skip) { return buffer(maxSize); } return lift(new Supplier>>() { @Override public Action> get() { return new BufferShiftAction(getDispatcher(), maxSize, skip); } }); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every * timespan. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(long timespan, TimeUnit unit) { return buffer(timespan, unit, getTimer()); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every * timespan. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(long timespan, TimeUnit unit, Timer timer) { return buffer(Integer.MAX_VALUE, timespan, unit, timer); } /** * Collect incoming values into multiple {@link List} buckets created every {@code timeshift }that will be pushed * into the returned {@code Stream} every * timespan. Complete signal will flush any remaining buckets. * * @param timespan the period in unit to use to release buffered lists * @param timeshift the period in unit to use to create a new bucket * @param unit the time unit * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final long timespan, final long timeshift, final TimeUnit unit) { return buffer(timespan, timeshift, unit, getTimer()); } /** * Collect incoming values into multiple {@link List} buckets created every {@code timeshift }that will be pushed * into the returned {@code Stream} every * timespan. Complete signal will flush any remaining buckets. * * @param timespan the period in unit to use to release buffered lists * @param timeshift the period in unit to use to create a new bucket * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final long timespan, final long timeshift, final TimeUnit unit, final Timer timer) { if (timespan == timeshift) { return buffer(timespan, unit, timer); } return lift(new Supplier>>() { @Override public Action> get() { return new BufferShiftAction(getDispatcher(), Integer.MAX_VALUE, Integer.MAX_VALUE, timeshift, timespan, unit, timer); } }); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every * timespan OR maxSize items. * * @param maxSize the max collected size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(int maxSize, long timespan, TimeUnit unit) { return buffer(maxSize, timespan, unit, getTimer()); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every * timespan OR maxSize items * * @param maxSize the max collected size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are a {@link List} of all values in this batch */ public final Stream> buffer(final int maxSize, final long timespan, final TimeUnit unit, final Timer timer) { return lift(new Supplier>>() { @Override public Action> get() { return new BufferAction(getDispatcher(), maxSize, timespan, unit, timer); } }); } /** * Stage incoming values into a {@link java.util.PriorityQueue} that will be re-ordered and signaled to the * returned fresh {@link Stream}. Possible flush triggers are: {@link #getCapacity()}, * complete signal or request signal. * PriorityQueue will use the {@link Comparable} interface from an incoming data signal. * * @return a new {@link Stream} whose values re-ordered using a PriorityQueue. * @since 2.0 */ public final Stream sort() { return sort(null); } /** * Stage incoming values into a {@link java.util.PriorityQueue} that will be re-ordered and signaled to the * returned fresh {@link Stream}. Possible flush triggers are: {@link #getCapacity()}, * complete signal or request signal. * PriorityQueue will use the {@link Comparable} interface from an incoming data signal. * * @param maxCapacity a fixed maximum number or elements to re-order at once. * @return a new {@link Stream} whose values re-ordered using a PriorityQueue. * @since 2.0 */ public final Stream sort(int maxCapacity) { return sort(maxCapacity, null); } /** * Stage incoming values into a {@link java.util.PriorityQueue} that will be re-ordered and signaled to the * returned fresh {@link Stream}. Possible flush triggers are: {@link #getCapacity()}, * complete signal or request signal. * PriorityQueue will use the {@link Comparable} interface from an incoming data signal. * * @param comparator A {@link Comparator} to evaluate incoming data * @return a new {@link Stream} whose values re-ordered using a PriorityQueue. * @since 2.0 */ public final Stream sort(Comparator comparator) { return sort((int) Math.min(Integer.MAX_VALUE, getCapacity()), comparator); } /** * Stage incoming values into a {@link java.util.PriorityQueue} that will be re-ordered and signaled to the * returned fresh {@link Stream}. Possible flush triggers are: {@link #getCapacity()}, * complete signal or request signal. * PriorityQueue will use the {@link Comparable} interface from an incoming data signal. * * @param maxCapacity a fixed maximum number or elements to re-order at once. * @param comparator A {@link Comparator} to evaluate incoming data * @return a new {@link Stream} whose values re-ordered using a PriorityQueue. * @since 2.0 */ public final Stream sort(final int maxCapacity, final Comparator comparator) { return lift(new Supplier>() { @Override public Action get() { return new SortAction(getDispatcher(), maxCapacity, comparator); } }); } /** * Re-route incoming values into a dynamically created {@link Stream} every pre-defined {@link #getCapacity()} * times. The nested streams will be pushed into the returned {@code Stream}. * * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> window() { return window((int) Math.min(Integer.MAX_VALUE, getCapacity())); } /** * Re-route incoming values into a dynamically created {@link Stream} every pre-defined {@param backlog} times. * The nested streams will be pushed into the returned {@code Stream}. * * @param backlog the time period when each window close and flush the attached consumer * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> window(final int backlog) { return lift(new Supplier>>() { @Override public Action> get() { return new WindowAction(getEnvironment(), getDispatcher(), backlog); } }); } /** * Re-route incoming values into bucket streams that will be pushed into the returned {@code Stream} every {@code * skip} and complete every time {@code * maxSize} has been reached by any of them. Complete signal will flush any remaining buckets. * * @param skip the number of items to skip before creating a new bucket * @param maxSize the collected size * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window */ public final Stream> window(final int maxSize, final int skip) { if (maxSize == skip) { return window(maxSize); } return lift(new Supplier>>() { @Override public Action> get() { return new WindowShiftAction(getEnvironment(), getDispatcher(), maxSize, skip); } }); } /** * Re-route incoming values into bucket streams that will be pushed into the returned {@code Stream} every and * complete every time {@code boundarySupplier} stream emits an item. * * @param boundarySupplier the factory to create the stream to listen to for separating each window * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window */ public final Stream> window(final Supplier> boundarySupplier) { return lift(new Supplier>>() { @Override public Action> get() { return new WindowWhenAction(getEnvironment(), getDispatcher(), boundarySupplier); } }); } /** * Re-route incoming values into bucket streams that will be pushed into the returned {@code Stream} every and * complete every time {@code boundarySupplier} stream emits an item. Window starts forwarding when the * bucketOpening stream emits an item, then subscribe to the boundary supplied to complete. * * @param bucketOpening the publisher to listen for signals to create a new window * @param boundarySupplier the factory to create the stream to listen to for closing an open window * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window */ public final Stream> window(final Publisher bucketOpening, final Supplier> boundarySupplier) { return lift(new Supplier>>() { @Override public Action> get() { return new WindowShiftWhenAction(getEnvironment(), getDispatcher(), bucketOpening, boundarySupplier); } }); } /** * Re-route incoming values into a dynamically created {@link Stream} every pre-defined timespan. * The nested streams will be pushed into the returned {@code Stream}. * * @param timespan the period in unit to use to release a new window as a Stream * @param unit the time unit * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> window(long timespan, TimeUnit unit) { return window(timespan, unit, getTimer()); } /** * Re-route incoming values into a dynamically created {@link Stream} every pre-defined timespan. * The nested streams will be pushed into the returned {@code Stream}. * * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> window(long timespan, TimeUnit unit, Timer timer) { return window(Integer.MAX_VALUE, timespan, unit, timer); } /** * Re-route incoming values into a dynamically created {@link Stream} every pre-defined timespan OR maxSize items. * The nested streams will be pushed into the returned {@code Stream}. * * @param maxSize the max collected size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> window(int maxSize, long timespan, TimeUnit unit) { return window(maxSize, timespan, unit, getEnvironment() == null ? Environment.timer() : getEnvironment().getTimer ()); } /** * Re-route incoming values into a dynamically created {@link Stream} every pre-defined timespan OR maxSize items. * The nested streams will be pushed into the returned {@code Stream}. * * @param maxSize the max collected size * @param timespan the period in unit to use to release a buffered list * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> window(final int maxSize, final long timespan, final TimeUnit unit, final Timer timer) { return lift(new Supplier>>() { @Override public Action> get() { return new WindowAction(getEnvironment(), getDispatcher(), maxSize, timespan, unit, timer); } }); } /** * Re-route incoming values into bucket streams that will be pushed into the returned {@code Stream} every {@code * timeshift} period. These streams will complete every {@code * timespan} period has cycled. Complete signal will flush any remaining buckets. * * @param timespan the period in unit to use to complete a window * @param timeshift the period in unit to use to create a new window * @param unit the time unit * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window */ public final Stream> window(final long timespan, final long timeshift, final TimeUnit unit) { return window(timespan, timeshift, unit, getTimer()); } /** * Re-route incoming values into bucket streams that will be pushed into the returned {@code Stream} every {@code * timeshift} period. These streams will complete every {@code * timespan} period has cycled. Complete signal will flush any remaining buckets. * * @param timespan the period in unit to use to complete a window * @param timeshift the period in unit to use to create a new window * @param unit the time unit * @param timer the Timer to run on * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window */ public final Stream> window(final long timespan, final long timeshift, final TimeUnit unit, final Timer timer) { if (timeshift == timespan) { return window(timespan, unit, timer); } return lift(new Supplier>>() { @Override public Action> get() { return new WindowShiftAction( getEnvironment(), getDispatcher(), Integer.MAX_VALUE, Integer.MAX_VALUE, timespan, timeshift, unit, timer); } }); } /** * Re-route incoming values into a dynamically created {@link Stream} for each unique key evaluated by the * {param keyMapper}. * * @param keyMapper the key mapping function that evaluates an incoming data and returns a key. * @return a new {@link Stream} whose values are a {@link Stream} of all values in this window * @since 2.0 */ public final Stream> groupBy(final Function keyMapper) { return lift(new Supplier>>() { @Override public Action> get() { return new GroupByAction<>(getEnvironment(), keyMapper, getDispatcher()); } }); } /** * Re-route incoming values into a dynamically created {@link Stream} for each unique key evaluated by the * {param keyMapper}. The hashcode of the incoming data will be used for partitioning over {@link * Environment#PROCESSORS} buckets. * That means that at any point of time at most {@link Environment#PROCESSORS} number of streams will be created and * used accordingly * to the current hashcode % n result. * * @return a new {@link Stream} whose values are a {@link Stream} of all values routed to this partition * @since 2.0 */ public final Stream> partition() { return partition(Environment.PROCESSORS); } /** * Re-route incoming values into a dynamically created {@link Stream} for each unique key evaluated by the * {param keyMapper}. The hashcode of the incoming data will be used for partitioning over the buckets number passed. * That means that at any point of time at most {@code buckets} number of streams will be created and used * accordingly to the positive modulo of the current hashcode with respect to the number of buckets specified. * * @param buckets the maximum number of buckets to partition the values across * @return a new {@link Stream} whose values are a {@link Stream} of all values routed to this partition * @since 2.0 */ public final Stream> partition(final int buckets) { return groupBy(new Function() { @Override public Integer apply(O o) { int bucket = o.hashCode() % buckets; return bucket < 0 ? bucket + buckets : bucket; } }); } /** * Reduce the values passing through this {@code Stream} into an object {@code T}. * This is a simple functional way for accumulating values. * The arguments are the N-1 and N next signal in this order. * * @param fn the reduce function * @return a new {@link Stream} whose values contain only the reduced objects */ public final Stream reduce(@Nonnull final BiFunction fn) { return scan(fn).last(); } /** * Reduce the values passing through this {@code Stream} into an object {@code A}. * The arguments are the N-1 and N next signal in this order. * * @param fn the reduce function * @param initial the initial argument to pass to the reduce function * @param the type of the reduced object * @return a new {@link Stream} whose values contain only the reduced objects */ public final Stream reduce(final A initial, @Nonnull BiFunction fn) { return scan(initial, fn).last(); } /** * Scan the values passing through this {@code Stream} into an object {@code A}. * The arguments are the N-1 and N next signal in this order. * * @param fn the reduce function * @return a new {@link Stream} whose values contain only the reduced objects * @since 1.1, 2.0 */ public final Stream scan(@Nonnull final BiFunction fn) { return scan(null, fn); } /** * Scan the values passing through this {@code Stream} into an object {@code A}. The given initial object will be * passed to the function's {@link Tuple2} argument. Behave like Reduce but triggers downstream Stream for every * transformation. * * @param initial the initial argument to pass to the reduce function * @param fn the scan function * @param the type of the reduced object * @return a new {@link Stream} whose values contain only the reduced objects * @since 1.1, 2.0 */ public final Stream scan(final A initial, final @Nonnull BiFunction fn) { return lift(new Supplier>() { @Override public Action get() { return new ScanAction(initial, fn); } }); } /** * Count accepted events for each batch and pass each accumulated long to the {@param stream}. */ public final Stream count() { return count(getCapacity()); } /** * Count accepted events for each batch {@param i} and pass each accumulated long to the {@param stream}. * * @return a new {@link Stream} */ public final Stream count(final long i) { return lift(new Supplier>() { @Override public Action get() { return new CountAction(i); } }); } /** * Request once the parent stream every {@param period} milliseconds. Timeout is run on the environment root timer. * * @param period the period in milliseconds between two notifications on this stream * @return a new {@link Stream} * @since 2.0 */ public final Stream throttle(long period) { Timer timer = getTimer(); Assert.state(timer != null, "Cannot use default timer as no environment has been provided to this " + "Stream"); return throttle(period, timer); } /** * Request once the parent stream every {@param period} milliseconds after an initial {@param delay}. * Timeout is run on the given {@param timer}. * * @param period the timeout in milliseconds between two notifications on this stream * @param timer the reactor timer to run the timeout on * @return a new {@link Stream} * @since 2.0 */ public final Stream throttle(final long period, final Timer timer) { return lift(new Supplier>() { @Override public Action get() { return new ThrottleRequestAction( getDispatcher(), timer, period ); } }); } /** * Request the parent stream every time the passed throttleStream signals a Long request volume. Complete and Error * signals will be propagated. * * @param throttleStream a function that takes a broadcasted stream of request signals and must return a stream of * valid request signal (long). * @return a new {@link Stream} * @since 2.0 */ public final Stream requestWhen(final Function, ? extends Publisher> throttleStream) { return lift(new Supplier>() { @Override public Action get() { return new ThrottleRequestWhenAction( getDispatcher(), throttleStream ); } }); } /** * Signal an error if no data has been emitted for {@param * timeout} milliseconds. Timeout is run on the environment root timer. *

* A Timeout Exception will be signaled if no data or complete signal have been sent within the given period. * * @param timeout the timeout in milliseconds between two notifications on this composable * @return a new {@link Stream} * @since 1.1, 2.0 */ public final Stream timeout(long timeout) { return timeout(timeout, null); } /** * Signal an error if no data has been emitted for {@param * timeout} milliseconds. Timeout is run on the environment root timer. *

* A Timeout Exception will be signaled if no data or complete signal have been sent within the given period. * * @param timeout the timeout in unit between two notifications on this composable * @param unit the time unit * @return a new {@link Stream} * @since 1.1, 2.0 */ public final Stream timeout(long timeout, TimeUnit unit) { return timeout(timeout, unit, null); } /** * Switch to the fallback Publisher if no data has been emitted for {@param * timeout} milliseconds. Timeout is run on the environment root timer. *

* The current subscription will be cancelled and the fallback publisher subscribed. *

* A Timeout Exception will be signaled if no data or complete signal have been sent within the given period. * * @param timeout the timeout in unit between two notifications on this composable * @param unit the time unit * @param fallback the fallback {@link Publisher} to subscribe to once the timeout has occured * @return a new {@link Stream} * @since 2.0 */ public final Stream timeout(long timeout, TimeUnit unit, Publisher fallback) { Timer timer = getTimer(); Assert.state(timer != null, "Cannot use default timer as no environment has been provided to this " + "Stream"); return timeout(timeout, unit, fallback, timer); } /** * Signal an error if no data has been emitted for {@param * timeout} milliseconds. Timeout is run on the environment root timer. *

* A Timeout Exception will be signaled if no data or complete signal have been sent within the given period. * * @param timeout the timeout in milliseconds between two notifications on this composable * @param unit the time unit * @param timer the reactor timer to run the timeout on * @return a new {@link Stream} * @since 1.1, 2.0 */ public final Stream timeout(final long timeout, final TimeUnit unit, final Publisher fallback, final Timer timer) { return lift(new Supplier>() { @Override public Action get() { return new TimeoutAction( getDispatcher(), fallback, timer, unit != null ? TimeUnit.MILLISECONDS.convert(timeout, unit) : timeout ); } }); } /** * Combine the most ancient upstream action to act as the {@link org.reactivestreams.Subscriber} input component and * the current stream to act as the {@link org.reactivestreams.Publisher}. *

* Useful to share and ship a full stream whilst hiding the staging actions in the middle. *

* Default behavior, e.g. a single stream, will raise an {@link java.lang.IllegalStateException} as there would not * be any Subscriber (Input) side to combine. {@link reactor.rx.action.Action#combine()} is the usual reference * implementation used. * * @param the type of the most ancien action input. * @return new Combined Action */ public CompositeAction combine() { throw new IllegalStateException("Cannot combine a single Stream"); } /** * Return the promise of the next triggered signal. * A promise is a container that will capture only once the first arriving error|next|complete signal * to this {@link Stream}. It is useful to coordinate on single data streams or await for any signal. * * @return a new {@link Promise} * @since 2.0 */ public final Promise next() { Promise d = new Promise( getDispatcher(), getEnvironment() ); subscribe(d); return d; } /** * Fetch all values in a List to the returned Promise * * @return the promise of all data from this Stream * @since 2.0 */ public final Promise> toList() { return toList(-1); } /** * Return the promise of N signals collected into an array list. * * @param maximum list size and therefore events signal to listen for * @return the promise of all data from this Stream * @since 2.0 */ public final Promise> toList(long maximum) { if (maximum > 0) return take(maximum).buffer().next(); else { return buffer(Integer.MAX_VALUE).next(); } } /** * Assign an Environment to be provided to this Stream Subscribers * * @param environment the environment * @return a */ public Stream env(final Environment environment) { return new Stream() { @Override public void subscribe(Subscriber s) { Stream.this.subscribe(s); } @Override public long getCapacity() { return Stream.this.getCapacity(); } @Override public Dispatcher getDispatcher() { return Stream.this.getDispatcher(); } @Override public Environment getEnvironment() { return environment; } }; } /** * Blocking call to pass values from this stream to the queue that can be polled from a consumer. * * @return the buffered queue * @since 2.0 */ public final CompletableBlockingQueue toBlockingQueue() { return toBlockingQueue(-1); } /** * Blocking call to eagerly fetch values from this stream * * @param maximum queue getCapacity(), a full queue might block the stream producer. * @return the buffered queue * @since 2.0 */ @SuppressWarnings("unchecked") public final CompletableBlockingQueue toBlockingQueue(int maximum) { final CompletableBlockingQueue blockingQueue; Stream tail = this; if (maximum > 0) { blockingQueue = new CompletableBlockingQueue(maximum); tail = take(maximum); } else { blockingQueue = new CompletableBlockingQueue(1); } Consumer terminalConsumer = new Consumer() { @Override public void accept(Object o) { blockingQueue.complete(); } }; consume( new Consumer() { @Override public void accept(O o) { try { blockingQueue.put(o); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, terminalConsumer, terminalConsumer); return blockingQueue; } /** * Prevent a {@link Stream} to be cancelled. Cancel propagation occurs when last subscriber is cancelled. * * @return a new {@literal Stream} that is never cancelled. */ public Stream keepAlive() { return lift(new Supplier>() { @Override public Action get() { return new Action() { @Override protected void doNext(O ev) { broadcastNext(ev); } @Override protected void doShutdown() { //IGNORE } }; } }); } /** * Subscribe the {@link reactor.rx.action.CompositeAction#input()} to this Stream. Combining action * through {@link * reactor.rx.action.Action#combine()} allows for easy distribution of a full flow. * * @param subscriber the combined actions to subscribe * @since 2.0 */ public final void subscribe(final CompositeAction subscriber) { subscribe(subscriber.input()); } @Override public long getCapacity() { return Long.MAX_VALUE; } @Override public boolean isReactivePull(Dispatcher dispatcher, long producerCapacity) { return (getCapacity() < producerCapacity) && getDispatcher().getClass() != TailRecurseDispatcher.class && dispatcher.getClass() != TailRecurseDispatcher.class; } /** * Get the current timer available if any or try returning the shared Environment one (which may cause an exception * if no Environment has been globally initialized) * * @return any available timer */ public Timer getTimer() { return getEnvironment() == null ? Environment.timer() : getEnvironment().getTimer(); } /** * Get the current action child subscription * * @return current child {@link reactor.rx.subscription.PushSubscription} */ public PushSubscription downstreamSubscription() { return null; } /** * Try cleaning a given subscription from the stream references. Unicast implementation such as IterableStream * (Streams.from(1,2,3)) or SupplierStream (Streams.generate(-> 1)) won't need to perform any job and as such will * return @{code false} upon this call. * Alternatively, Action and HotStream (Streams.from()) will clean any reference to that subscription from their * internal registry and might return {@code true} if successful. * * @return current child {@link reactor.rx.subscription.PushSubscription} */ public boolean cancelSubscription(PushSubscription oPushSubscription) { return false; } /** * Get the assigned {@link reactor.Environment}. * * @return current {@link Environment} */ public Environment getEnvironment() { return null; } /** * Get the dispatcher used to execute signals on this Stream instance. * * @return assigned dispatcher */ public Dispatcher getDispatcher() { return SynchronousDispatcher.INSTANCE; } @Override public String toString() { return getClass().getSimpleName(); } private static final class SubscribeOn implements Subscriber, Consumer, NonBlocking { private final Subscriber subscriber; private final Action action; private final Dispatcher dispatcher; @SuppressWarnings("unchecked") public SubscribeOn(Dispatcher dispatcher, Subscriber subscriber) { this.dispatcher = dispatcher; this.subscriber = subscriber; if(Action.class.isAssignableFrom(subscriber.getClass())){ this.action = (Action)subscriber; }else{ this.action = null; } } @Override public boolean isReactivePull(Dispatcher dispatcher, long producerCapacity) { return action == null || action.isReactivePull(dispatcher, producerCapacity); } @Override public long getCapacity() { return action != null ? action.getCapacity() : Long.MAX_VALUE; } @Override public void accept(Subscription subscription) { subscriber.onSubscribe(subscription); } @Override public void onSubscribe(Subscription s) { if( dispatcher.inContext() ){ accept(s); } else { dispatcher.dispatch(s, this, null); } } @Override public void onNext(O o) { subscriber.onNext(o); } @Override public void onError(Throwable t) { subscriber.onError(t); } @Override public void onComplete() { subscriber.onComplete(); } } private final static class StreamDispatchedSubscribe extends Stream implements Consumer> { private final Dispatcher currentDispatcher; private final Stream stream; public StreamDispatchedSubscribe(Stream stream, Dispatcher currentDispatcher) { this.currentDispatcher = currentDispatcher; this.stream = stream; } @Override public void accept(Subscriber subscriber) { stream.subscribe(new Stream.SubscribeOn<>(currentDispatcher, subscriber)); } @Override public long getCapacity() { return stream.getCapacity(); } @Override public Environment getEnvironment() { return stream.getEnvironment(); } @Override public Dispatcher getDispatcher() { return stream.getDispatcher(); } @Override public void subscribe(final Subscriber subscriber) { currentDispatcher.dispatch(subscriber, this, null); } } private static final SynchronousDispatcher PROCESSOR_SYNC = new SynchronousDispatcher(); }