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

reactor.core.composable.Stream Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
 *
 * 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.core.composable;

import reactor.core.Environment;
import reactor.core.Observable;
import reactor.core.action.*;
import reactor.core.composable.spec.DeferredStreamSpec;
import reactor.event.dispatch.Dispatcher;
import reactor.event.selector.Selector;
import reactor.function.*;
import reactor.function.support.Tap;
import reactor.timer.Timer;
import reactor.tuple.Tuple2;
import reactor.util.Assert;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * A {@code Stream} is a stateless event processor that provides methods for attaching {@link
 * Consumer Consumers} to consume the values passing through the stream. Some methods like
 * {@link #map(reactor.function.Function)}, {@link #filter(reactor.function.Predicate)},
 * {@link #first()}, {@link #last()}, and {@link #reduce(reactor.function.Function, Object)}
 * return new {@code Stream Streams} whose values are the result of transformation functions,
 * filtering, and the like.
 * 

* Typically, new {@code Stream Streams} aren't created directly. To create a {@code Stream}, * create a {@link DeferredStreamSpec} and configure it with the appropriate {@link Environment}, * {@link Dispatcher}, and other settings, then call {@link Deferred#compose()}, which will * return a {@code Stream} that handles the values passed into the {@link Deferred}. *

* * @param the type of the values in the stream * @author Jon Brisbin * @author Andy Wilkinson * @author Stephane Maldini */ public class Stream extends Composable { protected final int batchSize; /** * Create a new Stream that will use the {@link Observable} to pass its values to registered * handlers. *

* The stream will batch values into batches of the given * {@code batchSize}, affecting the values that are passed to the {@link #first()} and {@link * #last()} substreams. A size of {@code -1} indicates that the stream should not be batched. *

* Once all event handler have been registered, {@link #flush} must be called to pass the initial values * to those handlers. The stream will accept errors from the given {@code parent}. * * @param observable The observable used to drive event handlers * @param batchSize The size of the batches, or {@code -1} for no batching * @param parent The stream's parent. May be {@code null} */ public Stream(@Nullable final Observable observable, int batchSize, @Nullable final Composable parent, @Nullable final Environment environment) { this(observable, batchSize, parent, null, environment); } /** * Create a new Stream that will use the {@link Observable} to pass its values to registered * handlers. *

* The stream will batch values into batches of the given {@code batchSize}, affecting the values that are passed to * the {@link #first()} and {@link #last()} substreams. A size of {@code -1} indicates that the stream should not be * batched. *

* Once all event handler have been registered, {@link #flush} must be called to pass the initial values * to those handlers. The stream will accept errors from the given {@code parent}. * * @param observable The observable used to drive event handlers * @param batchSize The size of the batches, or {@code -1} for no batching * @param parent The stream's parent. May be {@code null} * @param acceptSelector The tuple Selector/Key to accept values on this observable. May be {@code null} */ public Stream(@Nullable final Observable observable, int batchSize, @Nullable final Composable parent, Tuple2 acceptSelector, @Nullable final Environment environment) { super(observable, parent, acceptSelector, environment); this.batchSize = batchSize; } @Override public Stream consume(@Nonnull Consumer consumer) { return (Stream) super.consume(consumer); } @Override public Stream connect(@Nonnull Composable consumer) { return (Stream) super.connect(consumer); } @Override public Stream consume(@Nonnull Object key, @Nonnull Observable observable) { return (Stream) super.consume(key, observable); } @Override public Stream flush() { return (Stream) super.flush(); } @Override public Stream when(@Nonnull Class exceptionType, @Nonnull Consumer onError) { return (Stream) super.when(exceptionType, onError); } @Override public Stream map(@Nonnull Function fn) { return (Stream) super.map(fn); } @Override public > Stream mapMany(@Nonnull Function fn) { return (Stream) super.mapMany(fn); } @Override public Stream filter() { return (Stream) super.filter(); } @Override public Stream filter(@Nonnull Composable elseComposable) { return (Stream) super.filter(elseComposable); } @Override public Stream filter(@Nonnull Function p) { return (Stream) super.filter(p); } @Override public Stream filter(@Nonnull Predicate p) { return (Stream) super.filter(p); } @Override public Stream filter(@Nonnull Predicate p, Composable elseComposable) { return (Stream) super.filter(p, elseComposable); } @Override public Stream connectValues(@Nonnull Composable composable) { return (Stream) super.connectValues(composable); } @Override public Stream timeout(long timeout, Timer timer) { return (Stream) super.timeout(timeout, timer); } @Override public Stream timeout(long timeout) { return (Stream) super.timeout(timeout); } @Override public Stream merge(Composable... composables) { return (Stream) super.merge(composables); } @Override public Stream consumeFlush(@Nonnull Flushable consumer) { return (Stream) super.consumeFlush(consumer); } @Override public Stream connectErrors(Composable composable) { return (Stream) super.connectErrors(composable); } /** * Create a new {@code Stream} whose values will be each iterated item from {@param iterable} * Every time flush is triggered, {@param iterable} is drained. * * @param iterable the iterable to drain * @return a new {@code Stream} whose values are the iterated one on flush * @since 1.1 */ public Stream propagate(Iterable iterable) { final Stream d = newComposable(batchSize); consumeFlush(new ForEachAction(iterable, d.getObservable(), d.getAcceptKey(), d.getError().getObject(), d.getFlush().getObject()) ).connectErrors(d); return d; } @Override public Stream propagate(Supplier supplier) { return (Stream) super.propagate(supplier); } /** * Consume values and trigger flush when {@param predicate} matches. * * @param predicate the test returning true to trigger flush * @return the current Stream * @since 1.1 */ public Stream flushWhen(Predicate predicate) { add(new WhenAction(predicate, getObservable(), getFlush().getObject(), getError().getObject())); return this; } /** * Create a new {@code Stream} whose values will be only the first value of each batch. Requires a {@code batchSize} * 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 {@code Stream} whose values are the first value of each batch */ public Stream first() { return first(batchSize); } /** * Create a new {@code Stream} whose values will be only the first value of each batch. Requires a {@code batchSize} * to * have been set. *

* 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 {@code Stream} whose values are the first value of each batch) */ public Stream first(int batchSize) { Assert.state(batchSize > 0, "Cannot first() an unbounded Stream. Try extracting a batch first."); final Stream d = newComposable(batchSize); add(new BatchAction(batchSize, d.getObservable(), null, d.getError().getObject(), null, d.getAcceptKey())).connectErrors(d); return d; } /** * Create a new {@code Stream} whose values will be only the last value of each batch. Requires a {@code batchSize} * * @return a new {@code Stream} whose values are the last value of each batch */ public Stream last() { return last(batchSize); } /** * Create a new {@code Stream} whose values will be only the last value of each batch. Requires a {@code batchSize} * * @param batchSize the batch size to use * @return a new {@code Stream} whose values are the last value of each batch */ public Stream last(int batchSize) { Assert.state(batchSize > 0, "Cannot last() an unbounded Stream. Try extracting a batch first."); final Stream d = newComposable(batchSize); add(new BatchAction(batchSize, d.getObservable(), null, d.getError().getObject(), d.getAcceptKey(), null)).connectErrors(d); return d; } /** * Create a new {@code Stream} that filters out consecutive equals values. * * @return a new {@code Stream} whose values are the last value of each batch * @since 1.1 */ public Stream distinct() { final Stream d = newComposable(batchSize); add(new DistinctAction( d.getObservable(), d.getAcceptKey(), d.getError().getObject() )).consumeErrorAndFlush(d); return d; } /** * 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 {@code Stream} whose values result from the iterable input * @since 1.1 */ public Stream split() { return split(batchSize); } /** * 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 {@code Stream} whose values result from the iterable input * @since 1.1 */ public Stream split(int batchSize) { final Stream d = newComposable(batchSize); getObservable().on(getAcceptSelector(), new ForEachAction(d.getObservable(), d.getAcceptKey(), d.getError().getObject(), d.getFlush().getObject())); connectErrors(d); return d; } /** * Create a {@link Tap} that maintains a reference to the last value seen by this {@code Stream}. The {@link Tap} is * continually updated when new values pass through the {@code Stream}. * * @return the new {@link Tap} * @see Consumer */ public Tap tap() { final Tap tap = new Tap(); consume(tap); return tap; } /** * Use {@link reactor.core.Observable#batchNotify(Object)} to group dispatching by the current batch size. * When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}. * * @return a new {@code Stream} whose values result from the iterable input * @since 1.1 */ public Stream buffer() { return buffer(batchSize); } /** * Use {@link reactor.core.Observable#batchNotify(Object)} to group dispatch by the passed {@param * batchSize}. * 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 {@code Stream} whose values result from the iterable input * @since 1.1 */ public Stream buffer(int batchSize) { final Stream d = newComposable(batchSize); add(new BufferAction(batchSize, d.getObservable(), d.getAcceptKey(), d.getError().getObject(), d.getFlush().getObject()) ); return d; } /** * Collect incoming values into a {@link List} that will be pushed separately into the returned {@code Stream} * every time {@code * batchSize} has been reached. All errors are also captured until current batchSize or flush is called. * * @return a new {@code Stream} whose values of all values in this batch * @since 1.1 */ public Stream bufferWithErrors() { return bufferWithErrors(batchSize); } /** * Collect incoming values into a {@link List} that will be pushed separately into the returned {@code Stream} every * time {@code * batchSize} has been reached. All errors are also captured until batchSize or flush is called. * * @param batchSize the collected size * @return a new {@code Stream} whose values are all values in this batch * @since 1.1 */ public Stream bufferWithErrors(int batchSize) { final Stream d = newComposable(batchSize); final BufferAction errorBuffer = new BufferAction(batchSize, d.getObservable(), d.getError().getObject(), null, null); getObservable().on(getError(), errorBuffer); consumeFlush(errorBuffer); add(new BufferAction(batchSize, d.getObservable(), d.getAcceptKey(), d.getError().getObject(), d.getFlush().getObject()) ); return d; } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every time {@code * batchSize} or flush is triggered has been reached. * * @return a new {@code Stream} whose values are a {@link List} of all values in this batch */ public Stream> collect() { return collect(batchSize); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every time {@code * batchSize} has been reached. * * @param batchSize the collected size * @return a new {@code Stream} whose values are a {@link List} of all values in this batch */ public Stream> collect(int batchSize) { final Stream> d = newComposable(1); add(new CollectAction(batchSize, d.getObservable(), d.getAcceptKey(), d.getError().getObject())).connectErrors(d); return d; } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified * time from the {@param period} in milliseconds. The window runs on a timer from the stream {@link * this#environment}. * * @param period the time period when each window close and flush the attached consumer * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> window(int period) { return window(period, TimeUnit.MILLISECONDS); } /** * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned * {@code Stream} every specified {@param period} in milliseconds. The window runs on a timer from the stream {@link * this#environment}. After accepting {@param backlog} of items, every old item will be dropped. Resulting {@link * List} will be at most {@param backlog} items long. * * @param period the time period when each window close and flush the attached consumer * @param backlog maximum amount of items to keep * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> movingWindow(int period, int backlog) { return movingWindow(period, TimeUnit.MILLISECONDS, backlog); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified * time from the {@param period} and a {@param timeUnit}. The window * runs on a timer from the stream {@link this#environment}. * * @param period the time period when each window close and flush the attached consumer * @param timeUnit the time unit used for the period * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> window(int period, TimeUnit timeUnit) { return window(period, timeUnit, 0); } /** * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned * {@code Stream} every specified time from the {@param period} and a {@param timeUnit}. * * @param period the time period when each window close and flush the attached consumer * @param timeUnit the time unit used for the period * @param backlog maximum amount of items to keep * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> movingWindow(int period, TimeUnit timeUnit, int backlog) { return movingWindow(period, timeUnit, 0, backlog); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified * time from the {@param period}, {@param timeUnit} after an initial {@param delay} in milliseconds. The window * runs on a timer from the stream {@link this#environment}. * * @param period the time period when each window close and flush the attached consumer * @param timeUnit the time unit used for the period * @param delay the initial delay in milliseconds * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> window(int period, TimeUnit timeUnit, int delay) { Assert.state(getEnvironment() != null, "Cannot use default timer as no environment has been provided to this Stream"); return window(period, timeUnit, delay, getEnvironment().getRootTimer()); } /** * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned * {@code Stream} every specified time from the {@param period} and a {@param timeUnit} after an initial {@param * delay} * in milliseconds. * * @param period the time period when each window close and flush the attached consumer * @param timeUnit the time unit used for the period * @param delay the initial delay in milliseconds * @param backlog maximum amount of items to keep * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> movingWindow(int period, TimeUnit timeUnit, int delay, int backlog) { Assert.state(getEnvironment() != null, "Cannot use default timer as no environment has been provided to this Stream"); return movingWindow(period, timeUnit, delay, backlog, getEnvironment().getRootTimer()); } /** * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified * time from the {@param period}, {@param timeUnit} after an initial {@param delay} in milliseconds. The window * runs on a supplied {@param timer}. * * @param period the time period when each window close and flush the attached consumer * @param timeUnit the time unit used for the period * @param delay the initial delay in milliseconds * @param timer the reactor timer to run the window on * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> window(int period, TimeUnit timeUnit, int delay, Timer timer) { Assert.state(timer != null, "Timer must be supplied"); final Stream> d = newComposable(1); add(new WindowAction(d.getObservable(), d.getAcceptKey(), d.getError().getObject(), timer, period, timeUnit, delay)).connectErrors(d); return d; } /** * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned * {@code Stream} every specified time from the {@param period} and a {@param timeUnit} after an initial {@param * delay} * in milliseconds. * * @param period the time period when each window close and flush the attached consumer * @param timeUnit the time unit used for the period * @param delay the initial delay in milliseconds * @param backlog maximum amount of items to keep * @param timer the reactor timer to run the window on * @return a new {@code Stream} whose values are a {@link List} of all values in this window * @since 1.1 */ public Stream> movingWindow(int period, TimeUnit timeUnit, int delay, int backlog, Timer timer) { Assert.state(timer != null, "Timer must be supplied"); final Stream> d = newComposable(1); add(new MovingWindowAction(d.getObservable(), d.getAcceptKey(), d.getError().getObject(), timer, period, timeUnit, delay, backlog)).connectErrors(d); return d; } /** * Reduce 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. * * @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 {@code Stream} whose values contain only the reduced objects */ public Stream reduce(@Nonnull Function, A> fn, A initial) { return reduce(fn, Functions.supplier(initial), batchSize); } /** * Reduce the values passing through this {@code Stream} into an object {@code A}. The given {@link Supplier} will be * used to produce initial accumulator objects either on the first reduce call, in the case of an unbounded {@code * Stream}, or on the first value of each batch, if a {@code batchSize} is set. *

* In an unbounded {@code Stream}, the accumulated value will be published on the returned {@code Stream} on flush * only. But when a {@code batchSize} has been, the accumulated * value will only be published on the new {@code Stream} at the end of each batch. On the next value (the first of * the next batch), the {@link Supplier} is called again for a new accumulator object and the reduce starts over with * a new accumulator. * * @param fn the reduce function * @param accumulators the {@link Supplier} that will provide accumulators * @param batchSize the batch size to use * @param the type of the reduced object * @return a new {@code Stream} whose values contain only the reduced objects */ public Stream reduce(@Nonnull final Function, A> fn, @Nullable final Supplier accumulators, final int batchSize ) { final Stream stream = newComposable(1); add(new ReduceAction(batchSize, accumulators, fn, stream.getObservable(), stream.getAcceptKey(), stream.getError().getObject())).connectErrors(stream); return stream; } /** * Reduce the values passing through this {@code Stream} into an object {@code A}. * * @param fn the reduce function * @param the type of the reduced object * @return a new {@code Stream} whose values contain only the reduced objects */ public Stream reduce(@Nonnull final Function, A> fn) { return reduce(fn, null, batchSize); } /** * 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 fn the scan function * @param initial the initial argument to pass to the reduce function * @param the type of the reduced object * @return a new {@code Stream} whose values contain only the reduced objects * @since 1.1 */ public Stream scan(@Nonnull Function, A> fn, A initial) { return scan(fn, Functions.supplier(initial)); } /** * Scan the values passing through this {@code Stream} into an object {@code A}. The given {@link Supplier} will be * used to produce initial accumulator objects either on the first reduce call, in the case of an unbounded {@code * Stream}, or on the first value of each batch, if a {@code batchSize} is set. *

* The accumulated value will be published on the returned {@code Stream} every time * a * value is accepted. * * @param fn the scan function * @param accumulators the {@link Supplier} that will provide accumulators * @param the type of the reduced object * @return a new {@code Stream} whose values contain only the reduced objects * @since 1.1 */ public Stream scan(@Nonnull final Function, A> fn, @Nullable final Supplier accumulators) { final Stream stream = newComposable(1); add(new ScanAction(accumulators, fn, stream.getObservable(), stream.getAcceptKey(), stream.getError().getObject())).consumeErrorAndFlush(stream); return stream; } /** * Scan the values passing through this {@code Stream} into an object {@code A}. * * @param fn the reduce function * @param the type of the reduced object * @return a new {@code Stream} whose values contain only the reduced objects * @since 1.1 */ public Stream scan(@Nonnull final Function, A> fn) { return scan(fn, (Supplier) null); } /** * Count accepted events for each batch (every flush) and pass each accumulated long to the {@param stream}. * * @param stream the stream to consume accumulated number of accepted event between 2 flushes * @since 1.1 */ public Stream count(Stream stream) { add(new CountAction(stream.getObservable(), stream.getAcceptKey(), stream.getError().getObject())); return this; } @Override protected Stream newComposable() { return newComposable(batchSize); } protected Stream newComposable(int batchSize) { return new Stream(null, batchSize, this, getEnvironment()); } BufferAction bufferConsumer(int batchSize) { BufferAction bufferAction = new BufferAction(batchSize, getObservable(), getAcceptKey(), getError(), getFlush()); consumeFlush(bufferAction); return bufferAction; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy