Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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 extends Action>
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 extends O> 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 extends O> 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 super E> 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 super O> 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 super O> 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 super O> consumer,
Consumer super Throwable> 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 super O> consumer,
Consumer super Throwable> 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 super O> consumer,
Consumer super Throwable> 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 super O> consumer,
Consumer super Throwable> 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 super O> 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 super O> consumer,
final Function, ? extends Publisher extends Long>>
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 super O> consumer,
final Function
requestMapper) {
return adaptiveConsumeOn(dispatcher, consumer, new Function, Publisher extends Long>>() {
@Override
public Publisher extends Long> 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 super O> consumer,
final Function, ? extends Publisher extends Long>>
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 super O> 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 super O> 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 super Subscriber super O>> 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 super Subscription> 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 super Throwable> 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 super O, ? extends V> 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 super O,
? extends Publisher extends V>> 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 super O,
Publisher extends V>> 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 super O,
Publisher extends V>> 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 super V> 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 super V> 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 extends O> publisher) {
return new Stream() {
@Override
public void subscribe(Subscriber super O> 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 extends O> publisher) {
return new Stream() {
@Override
public void subscribe(Subscriber super O> 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 extends O> 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 extends V> 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 extends T2> 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 extends T2> publisher,
final @Nonnull Function, V> zipper) {
return new Stream() {
@Override
public void subscribe(Subscriber super V> 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 super O> 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 extends CompletableQueue> 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