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

rx.operators.OperationWindow Maven / Gradle / Ivy

There is a newer version: 0.20.7
Show newest version
/**
 * Copyright 2014 Netflix, 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 rx.operators;

import java.util.concurrent.TimeUnit;

import rx.Observable;
import rx.Observable.OnSubscribeFunc;
import rx.Observer;
import rx.Scheduler;
import rx.Subscription;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.Subscriptions;

public final class OperationWindow extends ChunkedOperation {

    public static  Func0> windowMaker() {
        return new Func0>() {
            @Override
            public Window call() {
                return new Window();
            }
        };
    }

    /**
     * This method creates a {@link rx.functions.Func1} object which represents the window operation. This
     * operation takes values from the specified {@link rx.Observable} source and stores them in a window until
     * the {@link rx.Observable} constructed using the {@link rx.functions.Func0} argument, produces a value.
     * The window is then emitted, and a new window is created to replace it. A new {@link rx.Observable} will
     * be constructed using the provided {@link rx.functions.Func0} object, which will determine when this new
     * window is emitted. When the source {@link rx.Observable} completes or produces an error, the current
     * window is emitted, and the event is propagated to all subscribed {@link rx.Observer}s.
     * 

* Note that this operation only produces non-overlapping windows. At all times there is * exactly one window actively storing values. *

* * @param source * the {@link rx.Observable} which produces values * @param windowClosingSelector * a {@link rx.functions.Func0} object that produces {@link rx.Observable}s. These * {@link rx.Observable}s determine when a window is emitted and replaced by simply * producing an object. * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(final Observable source, final Func0> windowClosingSelector) { return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { NonOverlappingChunks> windows = new NonOverlappingChunks>(observer, OperationWindow. windowMaker()); ChunkCreator creator = new ObservableBasedSingleChunkCreator, TClosing>(windows, windowClosingSelector); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } }; } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in the currently * active window. Initially there are no windows active. *

* Windows can be created by pushing a {@link rx.util.TOpening} value to the {@code windowOpenings} * {@link rx.Observable}. This creates a new window which will then start recording values which are * produced by the {@code source} {@link rx.Observable}. Additionally the {@code windowClosingSelector} * will be used to construct an {@link rx.Observable} which can produce values. When it does so it will * close this (and only this) newly created window. When the source {@link rx.Observable} completes or * produces an error, all windows are emitted, and the event is propagated to all subscribed * {@link rx.Observer}s. *

* Note that when using this operation multiple overlapping windows could be active at any * one point. *

* * @param source * the {@link rx.Observable} which produces values * @param windowOpenings * an {@link rx.Observable} which when it produces a {@link rx.util.TOpening} value will create a * new window which instantly starts recording the {@code source} {@link rx.Observable} * @param windowClosingSelector * a {@link rx.functions.Func0} object that produces {@link rx.Observable}s. These * {@link rx.Observable}s determine when a window is emitted and replaced by simply producing an * object. * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(final Observable source, final Observable windowOpenings, final Func1> windowClosingSelector) { return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { OverlappingChunks> windows = new OverlappingChunks>(observer, OperationWindow. windowMaker()); ChunkCreator creator = new ObservableBasedMultiChunkCreator, TOpening, TClosing>(windows, windowOpenings, windowClosingSelector); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } }; } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window until * the window contains a specified number of elements. The window is then emitted, and a new window is * created to replace it. When the source {@link rx.Observable} completes or produces an error, the current * window is emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation only produces non-overlapping windows. At all times there is * exactly one window actively storing values. *

* * @param source * the {@link rx.Observable} which produces values * @param count * the number of elements a window should have before being emitted and replaced * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(Observable source, int count) { return window(source, count, count); } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in all active * windows until the window contains a specified number of elements. The window is then emitted. Windows are * created after a certain amount of values have been received. When the source {@link rx.Observable} * completes or produces an error, the currently active windows are emitted, and the event is propagated to * all subscribed {@link rx.Observer}s. *

* Note that this operation can produce non-connected, connected non-overlapping, or overlapping * windows depending on the input parameters. *

* * @param source * the {@link rx.Observable} which produces values * @param count * the number of elements a window should have before being emitted * @param skip * the interval with which windows have to be created. Note that when {@code skip == count} that * this is the same as calling {@link rx.operators.OperationWindow#window(rx.Observable, int)}. * If {@code skip < count}, this window operation will produce overlapping windows and if * {@code skip > count} non-overlapping windows will be created and some values will not be * pushed into a window at all! * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(final Observable source, final int count, final int skip) { return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { Chunks> chunks = new SizeBasedChunks>(observer, OperationWindow. windowMaker(), count); ChunkCreator creator = new SkippingChunkCreator>(chunks, skip); return source.subscribe(new ChunkObserver>(chunks, observer, creator)); } }; } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window. * Periodically the window is emitted and replaced with a new window. How often this is done depends on the * specified timespan. When the source {@link rx.Observable} completes or produces an error, the current * window is emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation only produces non-overlapping windows. At all times there is * exactly one window actively storing values. *

* * @param source * the {@link rx.Observable} which produces values * @param timespan * the amount of time all windows must be actively collect values before being emitted * @param unit * the {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(Observable source, long timespan, TimeUnit unit) { return window(source, timespan, unit, Schedulers.threadPoolForComputation()); } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window. * Periodically the window is emitted and replaced with a new window. How often this is done depends on the * specified timespan. When the source {@link rx.Observable} completes or produces an error, the current * window is emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation only produces non-overlapping windows. At all times there is * exactly one window actively storing values. *

* * @param source * the {@link rx.Observable} which produces values * @param timespan * the amount of time all windows must be actively collect values before being emitted * @param unit * the {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan * @param scheduler * the {@link rx.Scheduler} to use for timing windows * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(final Observable source, final long timespan, final TimeUnit unit, final Scheduler scheduler) { return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { NonOverlappingChunks> windows = new NonOverlappingChunks>(observer, OperationWindow. windowMaker()); ChunkCreator creator = new TimeBasedChunkCreator>(windows, timespan, unit, scheduler); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } }; } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window. * Periodically the window is emitted and replaced with a new window. How often this is done depends on the * specified timespan. Additionally the window is automatically emitted once it reaches a specified number * of elements. When the source {@link rx.Observable} completes or produces an error, the current window is * emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation only produces non-overlapping windows. At all times there is * exactly one window actively storing values. *

* * @param source * the {@link rx.Observable} which produces values * @param timespan * the amount of time all windows must be actively collect values before being emitted * @param unit * the {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan * @param count * the maximum size of the window. Once a window reaches this size, it is emitted * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(Observable source, long timespan, TimeUnit unit, int count) { return window(source, timespan, unit, count, Schedulers.threadPoolForComputation()); } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window. * Periodically the window is emitted and replaced with a new window. How often this is done depends on the * specified timespan. Additionally the window is automatically emitted once it reaches a specified number * of elements. When the source {@link rx.Observable} completes or produces an error, the current window is * emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation only produces non-overlapping windows. At all times there is * exactly one window actively storing values. *

* * @param source * the {@link rx.Observable} which produces values * @param timespan * the amount of time all windows must be actively collect values before being emitted * @param unit * the {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan * @param count * the maximum size of the window. Once a window reaches this size, it is emitted * @param scheduler * the {@link rx.Scheduler} to use for timing windows * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(final Observable source, final long timespan, final TimeUnit unit, final int count, final Scheduler scheduler) { return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { Chunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationWindow. windowMaker(), count, timespan, unit, scheduler); ChunkCreator creator = new SingleChunkCreator>(chunks); return source.subscribe(new ChunkObserver>(chunks, observer, creator)); } }; } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window. * Periodically the window is emitted and replaced with a new window. How often this is done depends on the * specified timespan. The creation of windows is also periodical. How often this is done depends on the * specified timeshift. When the source {@link rx.Observable} completes or produces an error, the current * window is emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation can produce non-connected, or overlapping windows depending on * the input parameters. *

* * @param source * the {@link rx.Observable} which produces values * @param timespan * the amount of time all windows must be actively collect values before being emitted * @param timeshift * the amount of time between creating windows * @param unit * the {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(Observable source, long timespan, long timeshift, TimeUnit unit) { return window(source, timespan, timeshift, unit, Schedulers.threadPoolForComputation()); } /** * This method creates a {@link rx.functions.Func1} object which represents the window operation. This * operation takes values from the specified {@link rx.Observable} source and stores them in a window. * Periodically the window is emitted and replaced with a new window. How often this is done depends on the * specified timespan. The creation of windows is also periodical. How often this is done depends on the * specified timeshift. When the source {@link rx.Observable} completes or produces an error, the current * window is emitted, and the event is propagated to all subscribed {@link rx.Observer}s. *

* Note that this operation can produce non-connected, or overlapping windows depending on * the input parameters. *

* * @param source * the {@link rx.Observable} which produces values * @param timespan * the amount of time all windows must be actively collect values before being emitted * @param timeshift * the amount of time between creating windows * @param unit * the {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan * @param scheduler * the {@link rx.Scheduler} to use for timing windows * @return * the {@link rx.functions.Func1} object representing the specified window operation */ public static OnSubscribeFunc> window(final Observable source, final long timespan, final long timeshift, final TimeUnit unit, final Scheduler scheduler) { return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { OverlappingChunks> windows = new TimeBasedChunks>(observer, OperationWindow. windowMaker(), timespan, unit, scheduler); ChunkCreator creator = new TimeBasedChunkCreator>(windows, timeshift, unit, scheduler); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } }; } /** * This class represents a single window: A sequence of recorded values. * * @param * the type of objects which this {@link Window} can hold */ protected static class Window extends Chunk> { /** * @return * the mutable underlying {@link Observable} which contains all the recorded values in this * {@link Window} object */ @Override public Observable getContents() { return Observable.from(contents); } } /** * Emits windows of values of the source Observable where the window boundary is determined by the items of * the boundary Observable. * * @param source * @param boundary * @return */ public static OnSubscribeFunc> window(Observable source, Observable boundary) { return new WindowViaObservable(source, boundary); } /** * Create non-overlapping windows from the source values by using another observable's values as to when to * replace a window. */ private static final class WindowViaObservable implements OnSubscribeFunc> { final Observable source; final Observable boundary; public WindowViaObservable(Observable source, Observable boundary) { this.source = source; this.boundary = boundary; } @Override public Subscription onSubscribe(Observer> t1) { CompositeSubscription csub = new CompositeSubscription(); final SourceObserver so = new SourceObserver(t1, csub); try { t1.onNext(so.subject); } catch (Throwable t) { t1.onError(t); return Subscriptions.empty(); } csub.add(source.subscribe(so)); if (!csub.isUnsubscribed()) { csub.add(boundary.subscribe(new BoundaryObserver(so))); } return csub; } /** * Observe the source and emit the values into the current window. */ private static final class SourceObserver implements Observer { final Observer> observer; final Subscription cancel; final Object guard; Subject subject; public SourceObserver(Observer> observer, Subscription cancel) { this.observer = observer; this.cancel = cancel; this.guard = new Object(); this.subject = create(); } Subject create() { return PublishSubject.create(); } @Override public void onNext(T args) { synchronized (guard) { if (subject == null) { return; } subject.onNext(args); } } @Override public void onError(Throwable e) { synchronized (guard) { if (subject == null) { return; } Subject s = subject; subject = null; s.onError(e); observer.onError(e); } cancel.unsubscribe(); } @Override public void onCompleted() { synchronized (guard) { if (subject == null) { return; } Subject s = subject; subject = null; s.onCompleted(); observer.onCompleted(); } cancel.unsubscribe(); } public void replace() { try { synchronized (guard) { if (subject == null) { return; } Subject s = subject; s.onCompleted(); subject = create(); observer.onNext(subject); } } catch (Throwable t) { onError(t); } } } /** * Observe the boundary and replace the window on each item. */ private static final class BoundaryObserver implements Observer { final SourceObserver so; public BoundaryObserver(SourceObserver so) { this.so = so; } @Override public void onNext(U args) { so.replace(); } @Override public void onError(Throwable e) { so.onError(e); } @Override public void onCompleted() { so.onCompleted(); } } } }