package com.github.davidmoten.rx;
import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import com.github.davidmoten.rx.StateMachine.Completion;
import com.github.davidmoten.rx.StateMachine.Transition;
import com.github.davidmoten.rx.buffertofile.DataSerializer;
import com.github.davidmoten.rx.buffertofile.DataSerializers;
import com.github.davidmoten.rx.buffertofile.Options;
import com.github.davidmoten.rx.internal.operators.OnSubscribeDoOnEmpty;
import com.github.davidmoten.rx.internal.operators.OnSubscribeMapLast;
import com.github.davidmoten.rx.internal.operators.OperatorBufferPredicateBoundary;
import com.github.davidmoten.rx.internal.operators.OperatorBufferToFile;
import com.github.davidmoten.rx.internal.operators.OperatorDoOnNth;
import com.github.davidmoten.rx.internal.operators.OperatorFromTransformer;
import com.github.davidmoten.rx.internal.operators.OperatorSampleFirst;
import com.github.davidmoten.rx.internal.operators.OperatorWindowMinMax;
import com.github.davidmoten.rx.internal.operators.OperatorWindowMinMax.Metric;
import com.github.davidmoten.rx.internal.operators.OrderedMerge;
import com.github.davidmoten.rx.internal.operators.TransformerDecode;
import com.github.davidmoten.rx.internal.operators.TransformerDelayFinalUnsubscribe;
import com.github.davidmoten.rx.internal.operators.TransformerLimitSubscribers;
import com.github.davidmoten.rx.internal.operators.TransformerOnBackpressureBufferRequestLimiting;
import com.github.davidmoten.rx.internal.operators.TransformerOnTerminateResume;
import com.github.davidmoten.rx.internal.operators.TransformerStateMachine;
import com.github.davidmoten.rx.internal.operators.TransformerStringSplit;
import com.github.davidmoten.rx.util.BackpressureStrategy;
import com.github.davidmoten.rx.util.MapWithIndex;
import com.github.davidmoten.rx.util.MapWithIndex.Indexed;
import com.github.davidmoten.rx.util.Pair;
import com.github.davidmoten.util.Optional;
import rx.Notification;
import rx.Observable;
import rx.Observable.Operator;
import rx.Observable.Transformer;
import rx.Observer;
import rx.Scheduler;
import rx.Scheduler.Worker;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Action2;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.functions.Func3;
import rx.internal.util.RxRingBuffer;
import rx.observables.GroupedObservable;
import rx.schedulers.Schedulers;
public final class Transformers {
static final int DEFAULT_INITIAL_BATCH = 1;
public static <T, R> Operator<R, T> toOperator(
Func1<? super Observable<T>, ? extends Observable<R>> function) {
return OperatorFromTransformer.toOperator(function);
}
public static <T extends Number> Transformer<T, Statistics> collectStats() {
return new Transformer<T, Statistics>() {
@Override
public Observable<Statistics> call(Observable<T> o) {
return o.scan(Statistics.create(), Functions.collectStats());
}
};
}
public static <T, R extends Number> Transformer<T, Pair<T, Statistics>> collectStats(
final Func1<? super T, ? extends R> function) {
return new Transformer<T, Pair<T, Statistics>>() {
@Override
public Observable<Pair<T, Statistics>> call(Observable<T> source) {
return source.scan(Pair.create((T) null, Statistics.create()),
new Func2<Pair<T, Statistics>, T, Pair<T, Statistics>>() {
@Override
public Pair<T, Statistics> call(Pair<T, Statistics> pair, T t) {
return Pair.create(t, pair.b().add(function.call(t)));
}
}).skip(1);
}
};
}
public static <T extends Comparable<? super T>> Transformer<T, T> sort() {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.toSortedList().flatMapIterable(Functions.<List<T>> identity());
}
};
}
public static <T> Transformer<T, T> sort(final Comparator<? super T> comparator) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.toSortedList(Functions.toFunc2(comparator))
.flatMapIterable(Functions.<List<T>> identity());
}
};
}
public static <T> Transformer<T, Set<T>> toSet() {
return new Transformer<T, Set<T>>() {
@Override
public Observable<Set<T>> call(Observable<T> o) {
return o.collect(new Func0<Set<T>>() {
@Override
public Set<T> call() {
return new HashSet<T>();
}
}, new Action2<Set<T>, T>() {
@Override
public void call(Set<T> set, T t) {
set.add(t);
}
});
}
};
}
/**
* <p>
* Returns a {@link Transformer} that wraps stream emissions with their
* corresponding zero based index numbers (0,1,2,3,..) in instances of
* {@link Indexed}.
* <p>
* Example usage:
*
* <pre>
*
* {@code
* Observable
* .just("a","b","c)
* .mapWithIndex(Transformers.mapWithIndex())
* .map(x -> x.index() + "->" + x.value())
* .forEach(System.out::println);
* }
* </pre>
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/mapWithIndex.png?raw=true"
* alt="marble diagram">
*
* @param <T>
* generic type of the stream being supplemented with an index
* @return transformer that supplements each stream emission with its index
* (zero-based position) in the stream.
*/
public static <T> Transformer<T, Indexed<T>> mapWithIndex() {
return MapWithIndex.instance();
}
/**
* <p>
* Returns a {@link Transformer} that allows processing of the source stream
* to be defined in a state machine where transitions of the state machine
* may also emit items to downstream that are buffered if necessary when
* backpressure is requested. <code>flatMap</code> is part of the processing
* chain so the source may experience requests for more items than are
* strictly required by the endpoint subscriber.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/stateMachine.png?raw=true"
* alt="marble diagram">
*
* @param initialStateFactory
* the factory to create the initial state of the state machine.
* @param transition
* defines state transitions and consequent emissions to
* downstream when an item arrives from upstream. The
* {@link Subscriber} is called with the emissions to downstream.
* You can optionally call {@link Subscriber#isUnsubscribed()} to
* check if you can stop emitting from the transition. If you do
* wish to terminate the Observable then call
* {@link Subscriber#unsubscribe()} and return anything (say
* {@code null} from the transition (as the next state which will
* not be used). You can also complete the Observable by calling
* {@link Subscriber#onCompleted} or {@link Subscriber#onError}
* from within the transition and return anything from the
* transition (will not be used). The transition should run
* synchronously so that completion of a call to the transition
* should also signify all emissions from that transition have
* been made.
* @param completion
* defines activity that should happen based on the final state
* just before downstream <code>onCompleted()</code> is called.
* For example any buffered emissions in state could be emitted
* at this point. Don't call <code>observer.onCompleted()</code>
* as it is called for you after the action completes if and only
* if you return true from this function.
* @param backpressureStrategy
* is applied to the emissions from one call of transition and
* should enforce backpressure.
* @param <State>
* the class representing the state of the state machine
* @param <In>
* the input observable type
* @param <Out>
* the output observable type
* @throws NullPointerException
* if {@code initialStateFactory} or {@code transition},or
* {@code completionAction} is null
* @return a backpressure supporting transformer that implements the state
* machine specified by the parameters
*/
public static <State, In, Out> Transformer<In, Out> stateMachine(
Func0<State> initialStateFactory,
Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition,
Func2<? super State, ? super Subscriber<Out>, Boolean> completion,
BackpressureStrategy backpressureStrategy) {
return TransformerStateMachine.<State, In, Out> create(initialStateFactory, transition,
completion, backpressureStrategy, DEFAULT_INITIAL_BATCH);
}
public static <State, In, Out> Transformer<In, Out> stateMachine(
Func0<State> initialStateFactory,
Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition,
Func2<? super State, ? super Subscriber<Out>, Boolean> completion,
BackpressureStrategy backpressureStrategy, int initialRequest) {
return TransformerStateMachine.<State, In, Out> create(initialStateFactory, transition,
completion, backpressureStrategy, initialRequest);
}
/**
* <p>
* Returns a {@link Transformer} that allows processing of the source stream
* to be defined in a state machine where transitions of the state machine
* may also emit items to downstream that are buffered if necessary when
* backpressure is requested. <code>flatMap</code> is part of the processing
* chain so the source may experience requests for more items than are
* strictly required by the endpoint subscriber. The backpressure strategy
* used for emissions from the transition into the flatMap is
* {@link BackpressureStrategy#BUFFER} which corresponds to
* {@link Observable#onBackpressureBuffer}.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/stateMachine.png?raw=true"
* alt="marble diagram">
*
* @param initialStateFactory
* the factory to create the initial state of the state machine.
* @param transition
* defines state transitions and consequent emissions to
* downstream when an item arrives from upstream. The
* {@link Subscriber} is called with the emissions to downstream.
* You can optionally call {@link Subscriber#isUnsubscribed()} to
* check if you can stop emitting from the transition. If you do
* wish to terminate the Observable then call
* {@link Subscriber#unsubscribe()} and return anything (say
* {@code null} from the transition (as the next state which will
* not be used). You can also complete the Observable by calling
* {@link Subscriber#onCompleted} or {@link Subscriber#onError}
* from within the transition and return anything from the
* transition (will not be used). The transition should run
* synchronously so that completion of a call to the transition
* should also signify all emissions from that transition have
* been made.
* @param completion
* defines activity that should happen based on the final state
* just before downstream <code>onCompleted()</code> is called.
* For example any buffered emissions in state could be emitted
* at this point. Don't call <code>observer.onCompleted()</code>
* as it is called for you after the action completes if and only
* if you return true from this function.
* @param <State>
* the class representing the state of the state machine
* @param <In>
* the input observable type
* @param <Out>
* the output observable type
* @throws NullPointerException
* if {@code initialStateFactory} or {@code transition},or
* {@code completion} is null
* @return a backpressure supporting transformer that implements the state
* machine specified by the parameters
*/
public static <State, In, Out> Transformer<In, Out> stateMachine(
Func0<? extends State> initialStateFactory,
Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition,
Func2<? super State, ? super Subscriber<Out>, Boolean> completion) {
return TransformerStateMachine.<State, In, Out> create(initialStateFactory, transition,
completion, BackpressureStrategy.BUFFER, DEFAULT_INITIAL_BATCH);
}
public static StateMachine.Builder stateMachine() {
return StateMachine.builder();
}
/**
* <p>
* Returns the source {@link Observable} merged with the <code>other</code>
* observable using the given {@link Comparator} for order. A precondition
* is that the source and other are already ordered. This transformer
* supports backpressure and its inputs must also support backpressure.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/orderedMerge.png?raw=true"
* alt="marble diagram">
*
* @param other
* the other already ordered observable
* @param comparator
* the ordering to use
* @param <T>
* the generic type of the objects being compared
* @return merged and ordered observable
*/
public static final <T> Transformer<T, T> orderedMergeWith(final Observable<T> other,
final Comparator<? super T> comparator) {
@SuppressWarnings("unchecked")
Collection<Observable<T>> collection = Arrays.asList(other);
return orderedMergeWith(collection, comparator);
}
/**
* <p>
* Returns the source {@link Observable} merged with all of the other
* observables using the given {@link Comparator} for order. A precondition
* is that the source and other are already ordered. This transformer
* supports backpressure and its inputs must also support backpressure.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/orderedMerge.png?raw=true"
* alt="marble diagram">
*
* @param others
* a collection of already ordered observables to merge with
* @param comparator
* the ordering to use
* @param <T>
* the generic type of the objects being compared
* @return merged and ordered observable
*/
public static final <T> Transformer<T, T> orderedMergeWith(
final Collection<Observable<T>> others, final Comparator<? super T> comparator) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> source) {
List<Observable<T>> collection = new ArrayList<Observable<T>>();
collection.add(source);
collection.addAll(others);
return OrderedMerge.<T> create(collection, comparator, false);
}
};
}
/**
* Returns a {@link Transformer} that returns an {@link Observable} that is
* a buffering of the source Observable into lists of sequential items that
* are equal.
*
* <p>
* For example, the stream
* {@code Observable.just(1, 1, 2, 2, 1).compose(toListUntilChanged())}
* would emit {@code [1,1], [2], [1]}.
*
* @param <T>
* the generic type of the source Observable
* @return transformer as above
*/
public static <T> Transformer<T, List<T>> toListUntilChanged() {
Func2<Collection<T>, T, Boolean> equal = HolderEquals.instance();
return toListWhile(equal);
}
private static class HolderEquals {
private static final Func2<Collection<Object>, Object, Boolean> INSTANCE = new Func2<Collection<Object>, Object, Boolean>() {
@Override
public Boolean call(Collection<Object> list, Object t) {
return list.isEmpty() || list.iterator().next().equals(t);
}
};
@SuppressWarnings("unchecked")
static <T> Func2<Collection<T>, T, Boolean> instance() {
return (Func2<Collection<T>, T, Boolean>) (Func2<?, ?, Boolean>) INSTANCE;
}
}
/**
* <p>
* Returns a {@link Transformer} that returns an {@link Observable} that is
* a buffering of the source Observable into lists of sequential items that
* satisfy the condition {@code condition}.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/toListWhile.png?raw=true"
* alt="marble diagram">
*
* @param condition
* condition function that must return true if an item is to be
* part of the list being prepared for emission
* @param <T>
* the generic type of the source Observable
* @return transformer as above
*/
public static <T> Transformer<T, List<T>> toListWhile(
final Func2<? super List<T>, ? super T, Boolean> condition) {
Func0<List<T>> initialState = new Func0<List<T>>() {
@Override
public List<T> call() {
return new ArrayList<T>();
}
};
Action2<List<T>, T> collect = new Action2<List<T>, T>() {
@Override
public void call(List<T> list, T n) {
list.add(n);
}
};
return collectWhile(initialState, collect, condition);
}
/**
* <p>
* Returns a {@link Transformer} that returns an {@link Observable} that is
* collected into {@code Collection} instances created by {@code factory}
* that are emitted when the collection and latest emission do not satisfy
* {@code condition} or on completion.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/collectWhile.png?raw=true"
* alt="marble diagram">
*
* @param factory
* collection instance creator
* @param collect
* collection action
* @param condition
* returns true if and only if emission should be collected in
* current collection being prepared for emission
* @param isEmpty
* indicates that the collection is empty
* @param <T>
* generic type of source observable
* @param <R>
* collection type emitted by transformed Observable
* @return transformer as above
*/
public static <T, R> Transformer<T, R> collectWhile(final Func0<R> factory,
final Action2<? super R, ? super T> collect,
final Func2<? super R, ? super T, Boolean> condition,
final Func1<? super R, Boolean> isEmpty) {
Func3<R, T, Observer<R>, R> transition = new Func3<R, T, Observer<R>, R>() {
@Override
public R call(R collection, T t, Observer<R> observer) {
if (condition.call(collection, t)) {
collect.call(collection, t);
return collection;
} else {
observer.onNext(collection);
R r = factory.call();
collect.call(r, t);
return r;
}
}
};
Func2<R, Observer<R>, Boolean> completionAction = new Func2<R, Observer<R>, Boolean>() {
@Override
public Boolean call(R collection, Observer<R> observer) {
if (!isEmpty.call(collection)) {
observer.onNext(collection);
}
return true;
}
};
return Transformers.stateMachine(factory, transition, completionAction);
}
/**
* <p>
* Returns a {@link Transformer} that returns an {@link Observable} that is
* collected into {@code Collection} instances created by {@code factory}
* that are emitted when items are not equal or on completion.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/collectWhile.png?raw=true"
* alt="marble diagram">
*
* @param factory
* collection instance creator
* @param collect
* collection action
* @param <T>
* generic type of source observable
* @param <R>
* collection type emitted by transformed Observable
* @return transformer as above
*/
public static <T, R extends Collection<T>> Transformer<T, R> collectWhile(
final Func0<R> factory, final Action2<? super R, ? super T> collect) {
return collectWhile(factory, collect, HolderEquals.<T> instance());
}
public static <T, R extends Iterable<?>> Transformer<T, R> collectWhile(final Func0<R> factory,
final Action2<? super R, ? super T> collect,
final Func2<? super R, ? super T, Boolean> condition) {
Func1<R, Boolean> isEmpty = new Func1<R, Boolean>() {
@Override
public Boolean call(R collection) {
return !collection.iterator().hasNext();
}
};
return collectWhile(factory, collect, condition, isEmpty);
}
/**
* Returns a {@link Transformer} that applied to a source {@link Observable}
* calls the given action on the {@code n}th onNext emission.
*
* @param n
* the 1-based count of onNext to do the action on
* @param action
* is performed on {@code n}th onNext.
* @param <T>
* the generic type of the Observable being transformed
* @return Transformer that applied to a source Observable calls the given
* action on the nth onNext emission.
*/
public static <T> Transformer<T, T> doOnNext(final int n, final Action1<? super T> action) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.lift(OperatorDoOnNth.create(action, n));
}
};
}
/**
* Returns a {@link Transformer} that applied to a source {@link Observable}
* calls the given action on the first onNext emission.
*
* @param action
* is performed on first onNext
* @param <T>
* the generic type of the Observable being transformed
* @return Transformer that applied to a source Observable calls the given
* action on the first onNext emission.
*/
public static <T> Transformer<T, T> doOnFirst(final Action1<? super T> action) {
return doOnNext(1, action);
}
/**
* <p>
* Returns an observable that subscribes to {@code this} and wait for
* completion but doesn't emit any items and once completes emits the
* {@code next} observable.
*
* <p>
* <img src=
* "https://github.com/davidmoten/rxjava-extras/blob/master/src/docs/ignoreElementsThen.png?raw=true"
* alt="marble diagram">
*
* @param <R>
* input observable type
* @param <T>
* output observable type
* @param next
* observable to be emitted after ignoring elements of
* {@code this}
* @return Transformer that applied to a source Observable ignores the
* elements of the source and emits the elements of a second
* observable
*/
public static <R, T> Transformer<T, R> ignoreElementsThen(final Observable<R> next) {
return new Transformer<T, R>() {
@SuppressWarnings("unchecked")
@Override
public Observable<R> call(Observable<T> source) {
return ((Observable<R>) (Observable<?>) source.ignoreElements()).concatWith(next);
}
};
}
public static <T> Transformer<String, String> split(String pattern) {
return TransformerStringSplit.split(pattern, null);
}
public static <T> Transformer<String, String> split(Pattern pattern) {
return TransformerStringSplit.split(null, pattern);
}
/**
* <p>
* Decodes a stream of multibyte chunks into a stream of strings that works
* on infinite streams and handles when a multibyte character spans two
* chunks. This method allows for more control over how malformed and
* unmappable characters are handled.
* <p>
* <img width="640" src=
* "https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/St.decode.png"
* alt="">
*
* @param charsetDecoder
* decodes the bytes into strings
* @return the Observable returning a stream of decoded strings
*/
public static Transformer<byte[], String> decode(final CharsetDecoder charsetDecoder) {
return TransformerDecode.decode(charsetDecoder);
}
public static <T> Transformer<T, T> limitSubscribers(AtomicInteger subscriberCount,
int maxSubscribers) {
return new TransformerLimitSubscribers<T>(subscriberCount, maxSubscribers);
}
public static <T> Transformer<T, T> limitSubscribers(int maxSubscribers) {
return new TransformerLimitSubscribers<T>(new AtomicInteger(), maxSubscribers);
}
public static <T> Transformer<T, T> cache(final long duration, final TimeUnit unit,
final Worker worker) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return Obs.cache(o, duration, unit, worker);
}
};
}
public static <T> Transformer<T, T> sampleFirst(final long duration, final TimeUnit unit) {
return sampleFirst(duration, unit, Schedulers.computation());
}
public static <T> Transformer<T, T> sampleFirst(final long duration, final TimeUnit unit,
final Scheduler scheduler) {
if (duration <= 0) {
throw new IllegalArgumentException("duration must be > 0");
}
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> source) {
return source.lift(new OperatorSampleFirst<T>(duration, unit, scheduler));
}
};
}
public static <T> Transformer<T, T> onBackpressureBufferToFile() {
return onBackpressureBufferToFile(DataSerializers.<T> javaIO(), Schedulers.computation(),
Options.defaultInstance());
}
public static <T> Transformer<T, T> onBackpressureBufferToFile(
final DataSerializer<T> serializer) {
return onBackpressureBufferToFile(serializer, Schedulers.computation(),
Options.defaultInstance());
}
public static <T> Transformer<T, T> onBackpressureBufferToFile(
final DataSerializer<T> serializer, final Scheduler scheduler) {
return onBackpressureBufferToFile(serializer, scheduler, Options.defaultInstance());
}
public static <T> Transformer<T, T> onBackpressureBufferToFile(
final DataSerializer<T> serializer, final Scheduler scheduler, final Options options) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.lift(new OperatorBufferToFile<T>(serializer, scheduler, options));
}
};
}
public static <T> Transformer<T, T> windowMin(final int windowSize,
final Comparator<? super T> comparator) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.lift(new OperatorWindowMinMax<T>(windowSize, comparator, Metric.MIN));
}
};
}
public static <T extends Comparable<T>> Transformer<T, T> windowMax(final int windowSize) {
return windowMax(windowSize, Transformers.<T> naturalComparator());
}
public static <T> Transformer<T, T> windowMax(final int windowSize,
final Comparator<? super T> comparator) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.lift(new OperatorWindowMinMax<T>(windowSize, comparator, Metric.MAX));
}
};
}
public static <T extends Comparable<T>> Transformer<T, T> windowMin(final int windowSize) {
return windowMin(windowSize, Transformers.<T> naturalComparator());
}
private static class NaturalComparatorHolder {
static final Comparator<Comparable<Object>> INSTANCE = new Comparator<Comparable<Object>>() {
@Override
public int compare(Comparable<Object> o1, Comparable<Object> o2) {
return o1.compareTo(o2);
}
};
}
@SuppressWarnings("unchecked")
private static <T extends Comparable<T>> Comparator<T> naturalComparator() {
return (Comparator<T>) (Comparator<?>) NaturalComparatorHolder.INSTANCE;
}
/**
* <p>
* Groups the items emitted by an {@code Observable} according to a
* specified criterion, and emits these grouped items as
* {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows
* only a single {@link Subscriber} during its lifetime and if this
* {@code Subscriber} unsubscribes before the source terminates, the next
* emission by the source having the same key will trigger a new
* {@code GroupedObservable} emission.
* <p>
* <img width="640" height="360" src=
* "https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/groupBy.png"
* alt="">
* <p>
* <em>Note:</em> A {@link GroupedObservable} will cache the items it is to
* emit until such time as it is subscribed to. For this reason, in order to
* avoid memory leaks, you should not simply ignore those
* {@code GroupedObservable}s that do not concern you. Instead, you can
* signal to them that they may discard their buffers by applying an
* operator like {@code .ignoreElements()} to them.
* <dl>
* <dt><b>Scheduler:</b></dt>
* <dd>{@code groupBy} does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param keySelector
* a function that extracts the key for each item
* @param elementSelector
* a function that extracts the return element for each item
* @param evictingMapFactory
* a function that given an eviction action returns a {@link Map}
* instance that will be used to assign items to the appropriate
* {@code GroupedObservable}s. The {@code Map} instance must be
* thread-safe and any eviction must trigger a call to the
* supplied action (synchronously or asynchronously). This can be
* used to limit the size of the map by evicting keys by maximum
* size or access time for instance. If
* {@code evictingMapFactory} is null then no eviction strategy
* will be applied (and a suitable default thread-safe
* implementation of {@code Map} will be supplied). Here's an
* example using Guava's {@code CacheBuilder} from v19.0:
*
* <pre>
* {@code
* Func1<Action1<K>, Map<K, Object>> mapFactory
* = action -> CacheBuilder.newBuilder()
* .maximumSize(1000)
* .expireAfterAccess(12, TimeUnit.HOUR)
* .removalListener(key -> action.call(key))
* .<K, Object> build().asMap();
* }
* </pre>
*
* @param <T>
* the type of the input observable
* @param <K>
* the key type
* @param <R>
* the element type
* @return an {@code Observable} that emits {@link GroupedObservable}s, each
* of which corresponds to a unique key value and each of which
* emits those items from the source Observable that share that key
* value
* @see <a href="http://reactivex.io/documentation/operators/groupby.html">
* ReactiveX operators documentation: GroupBy</a>
*/
public static <T, K, R> Transformer<T, GroupedObservable<K, R>> groupByEvicting(
final Func1<? super T, ? extends K> keySelector,
final Func1<? super T, ? extends R> elementSelector,
final Func1<Action1<K>, Map<K, Object>> evictingMapFactory) {
return new Transformer<T, GroupedObservable<K, R>>() {
@Override
public Observable<GroupedObservable<K, R>> call(Observable<T> o) {
return o.groupBy(keySelector, elementSelector, evictingMapFactory);
}
};
}
/**
* If multiple concurrently open subscriptions happen to a source
* transformed by this method then an additional do-nothing subscription
* will be maintained to the source and will only be closed after the
* specified duration has passed from the final unsubscription of the open
* subscriptions. If another subscription happens during this wait period
* then the scheduled unsubscription will be cancelled.
*
* @param duration
* duration of period to leave at least one source subscription
* open
* @param unit
* units for duration
* @param <T>
* generic type of stream
* @return transformer
*/
public static <T> Transformer<T, T> delayFinalUnsubscribe(long duration, TimeUnit unit) {
return delayFinalUnsubscribe(duration, unit, Schedulers.computation());
}
/**
* If multiple concurrently open subscriptions happen to a source
* transformed by this method then an additional do-nothing subscription
* will be maintained to the source and will only be closed after the
* specified duration has passed from the final unsubscription of the open
* subscriptions. If another subscription happens during this wait period
* then the scheduled unsubscription will be cancelled.
*
* @param duration
* duration of period to leave at least one source subscription
* open
* @param unit
* units for duration
* @param scheduler
* scheduler to use to schedule wait for unsubscribe
* @param <T>
* generic type of stream
* @return transformer
*/
public static <T> Transformer<T, T> delayFinalUnsubscribe(long duration, TimeUnit unit,
Scheduler scheduler) {
return new TransformerDelayFinalUnsubscribe<T>(unit.toMillis(duration), scheduler);
}
/**
* Removes pairs non-recursively from a stream. Uses
* {@code Transformers.stateMachine()} under the covers to ensure items are
* emitted as soon as possible (if an item can't be in a pair then it is
* emitted straight away).
*
* @param isCandidateForFirst
* returns true if item is potentially the first of a pair that
* we want to remove
* @param remove
* returns true if a pair should be removed
* @param <T>
* generic type of stream being transformed
* @return transformed stream
*/
public static <T> Transformer<T, T> removePairs(
final Func1<? super T, Boolean> isCandidateForFirst,
final Func2<? super T, ? super T, Boolean> remove) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.compose(Transformers. //
stateMachine() //
.initialState(Optional.<T> absent()) //
.transition(new Transition<Optional<T>, T, T>() {
@Override
public Optional<T> call(Optional<T> state, T value,
Subscriber<T> subscriber) {
if (!state.isPresent()) {
if (isCandidateForFirst.call(value)) {
return Optional.of(value);
} else {
subscriber.onNext(value);
return Optional.absent();
}
} else {
if (remove.call(state.get(), value)) {
// emit nothing and reset state
return Optional.absent();
} else {
subscriber.onNext(state.get());
if (isCandidateForFirst.call(value)) {
return Optional.of(value);
} else {
subscriber.onNext(value);
return Optional.absent();
}
}
}
}
}).completion(new Completion<Optional<T>, T>() {
@Override
public Boolean call(Optional<T> state, Subscriber<T> subscriber) {
if (state.isPresent())
subscriber.onNext(state.get());
// yes, complete
return true;
}
}).build());
}
};
}
/**
* Rather than requesting {@code Long.MAX_VALUE} of upstream as does
* `Observable.onBackpressureBuffer`, this variant only requests of upstream
* what is requested of it. Thus an operator can be written that
* overproduces.
*
* @param <T>
* the value type
* @return transformer that buffers on backpressure but only requests of
* upstream what is requested of it
*/
public static <T> Transformer<T, T> onBackpressureBufferRequestLimiting() {
return TransformerOnBackpressureBufferRequestLimiting.instance();
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, after being
* buffered, and returns true to indicate a new buffer should start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, after being buffered, and
* should return true to indicate a new buffer has to start.
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> bufferUntil(
Func1<? super T, Boolean> predicate) {
return bufferUntil(predicate, 10);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, after being
* buffered, and returns true to indicate a new buffer should start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, after being buffered, and
* should return true to indicate a new buffer has to start.
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> toListUntil(
Func1<? super T, Boolean> predicate) {
return bufferUntil(predicate);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, after being
* buffered, and returns true to indicate a new buffer should start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, after being buffered, and
* should return true to indicate a new buffer has to start.
* @param capacityHint
* the expected number of items in each buffer
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> bufferUntil(Func1<? super T, Boolean> predicate,
int capacityHint) {
return new OperatorBufferPredicateBoundary<T>(predicate, RxRingBuffer.SIZE, capacityHint,
true);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, after being
* buffered, and returns true to indicate a new buffer should start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, after being buffered, and
* should return true to indicate a new buffer has to start.
* @param capacityHint
* the expected number of items in each buffer
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> toListUntil(Func1<? super T, Boolean> predicate,
int capacityHint) {
return bufferUntil(predicate, capacityHint);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, before or
* after being buffered, and returns true to indicate a new buffer should
* start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, before being buffered, and
* should return true to indicate a new buffer has to start.
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> bufferWhile(
Func1<? super T, Boolean> predicate) {
return bufferWhile(predicate, 10);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, before or
* after being buffered, and returns true to indicate a new buffer should
* start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, before being buffered, and
* should return true to indicate a new buffer has to start.
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> toListWhile(
Func1<? super T, Boolean> predicate) {
return bufferWhile(predicate);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, before being
* buffered, and returns true to indicate a new buffer should start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, before being buffered, and
* should return true to indicate a new buffer has to start.
* @param capacityHint
* the expected number of items in each buffer
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> bufferWhile(Func1<? super T, Boolean> predicate,
int capacityHint) {
return new OperatorBufferPredicateBoundary<T>(predicate, RxRingBuffer.SIZE, capacityHint,
false);
}
/**
* Buffers the elements into continuous, non-overlapping Lists where the
* boundary is determined by a predicate receiving each item, before being
* buffered, and returns true to indicate a new buffer should start.
*
* <p>
* The operator won't return an empty first or last buffer.
*
* <dl>
* <dt><b>Backpressure Support:</b></dt>
* <dd>This operator supports backpressure.</dd>
* <dt><b>Scheduler:</b></dt>
* <dd>This operator does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param <T>
* the input value type
* @param predicate
* the Func1 that receives each item, before being buffered, and
* should return true to indicate a new buffer has to start.
* @param capacityHint
* the expected number of items in each buffer
* @return the new Observable instance
* @see #bufferWhile(Func1)
* @since (if this graduates from Experimental/Beta to supported, replace
* this parenthetical with the release number)
*/
public static final <T> Transformer<T, List<T>> toListWhile(Func1<? super T, Boolean> predicate,
int capacityHint) {
return bufferWhile(predicate, capacityHint);
}
public static final <T> Transformer<T, T> delay(final Func1<? super T, Long> time,
final Func0<Double> playRate, final long startTime, final Scheduler scheduler) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(final Observable<T> o) {
return Observable.defer(new Func0<Observable<T>>() {
long startActual = scheduler.now();
@Override
public Observable<T> call() {
return o.concatMap(new Func1<T, Observable<T>>() {
@Override
public Observable<T> call(T t) {
return Observable.just(t) //
.delay(delay(startActual, startTime, time.call(t), playRate,
scheduler.now()), TimeUnit.MILLISECONDS, scheduler);
}
});
}
});
}
};
}
private static long delay(long startActual, long startTime, long emissionTimestamp,
Func0<Double> playRate, long now) {
long elapsedActual = now - startActual;
return Math.max(0,
Math.round((emissionTimestamp - startTime) / playRate.call() - elapsedActual));
}
/**
* <p>
* Modifies the source Observable so that it invokes an action when it calls
* {@code onCompleted} and no items were emitted.
* <dl>
* <dt><b>Scheduler:</b></dt>
* <dd>{@code doOnEmpty} does not operate by default on a particular
* {@link Scheduler}.</dd>
* </dl>
*
* @param onEmpty
* the action to invoke when the source Observable calls
* {@code onCompleted}, contingent on no items were emitted
* @param <T>
* generic type of observable being transformed
* @return the source Observable with the side-effecting behavior applied
*/
public static final <T> Transformer<T, T> doOnEmpty(final Action0 onEmpty) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return Observable.create(new OnSubscribeDoOnEmpty<T>(o, onEmpty));
}
};
}
public static final <T> Transformer<T, T> onTerminateResume(
final Func1<Throwable, Observable<T>> onError, final Observable<T> onCompleted) {
return new TransformerOnTerminateResume<T>(onError, onCompleted);
}
public static final <T> Transformer<T, T> repeatLast() {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> o) {
return o.materialize().buffer(2, 1)
.flatMap(new Func1<List<Notification<T>>, Observable<T>>() {
@Override
public Observable<T> call(List<Notification<T>> list) {
Notification<T> a = list.get(0);
if (list.size() == 2 && list.get(1).isOnCompleted()) {
return Observable.just(a.getValue()).repeat();
} else if (a.isOnError()) {
return Observable.error(list.get(0).getThrowable());
} else if (a.isOnCompleted()) {
return Observable.empty();
} else {
return Observable.just(a.getValue());
}
}
});
}
};
}
public static <T> Transformer<T, T> mapLast(final Func1<? super T, ? extends T> function) {
return new Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> source) {
return Observable.create(new OnSubscribeMapLast<T>(source, function));
}
};
}
public static <A, B, K, C> Transformer<A, C> matchWith(final Observable<B> obs, final Func1<? super A, ? extends K> key1,
final Func1<? super B,? extends K> key2, final Func2<? super A, ? super B, C> combiner) {
return new Transformer<A, C>() {
@Override
public Observable<C> call(Observable<A> source) {
return Obs.match(source, obs, key1, key2, combiner);
}
};
}
public static <A, B, K, C> Transformer<A, C> matchWith(final Observable<B> obs, final Func1<? super A, ? extends K> key1,
final Func1<? super B,? extends K> key2, final Func2<? super A, ? super B, C> combiner, final long requestSize) {
return new Transformer<A, C>() {
@Override
public Observable<C> call(Observable<A> source) {
return Obs.match(source, obs, key1, key2, combiner, requestSize);
}
};
}
public static <T> Transformer<T,T> reverse() {
return new Transformer<T,T>() {
@Override
public Observable<T> call(Observable<T> source) {
return Obs.reverse(source);
}};
}
}