rx.operators.OperationBuffer Maven / Gradle / Ivy
Show all versions of rxjava-core Show documentation
/**
* 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.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
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.subscriptions.CompositeSubscription;
public final class OperationBuffer extends ChunkedOperation {
private static Func0> bufferMaker() {
return new Func0>() {
@Override
public Buffer call() {
return new Buffer();
}
};
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer until the
* {@link Observable} constructed using the {@link Func0} argument, produces a value. The buffer is then
* emitted, and a new buffer is created to replace it. A new {@link Observable} will be constructed using
* the provided {@link Func0} object, which will determine when this new buffer is emitted. When the source
* {@link Observable} completes or produces an error, the current buffer is emitted, and the event is
* propagated to all subscribed {@link Observer}s.
*
* Note that this operation only produces non-overlapping chunks. At all times there is
* exactly one buffer actively storing values.
*
*
* @param source
* the {@link Observable} which produces values
* @param bufferClosingSelector
* a {@link Func0} object which produces {@link Observable}s. These {@link Observable}s determine
* when a buffer is emitted and replaced by simply producing an object.
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(final Observable source, final Func0 extends Observable extends TClosing>> bufferClosingSelector) {
return new OnSubscribeFunc>() {
@Override
public Subscription onSubscribe(Observer super List> observer) {
NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer. bufferMaker());
ChunkCreator creator = new ObservableBasedSingleChunkCreator, TClosing>(buffers, bufferClosingSelector);
return new CompositeSubscription(
new ChunkToSubscription(creator),
source.subscribe(new ChunkObserver>(buffers, observer, creator)));
}
};
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in the currently active chunks.
* Initially there are no chunks active.
*
* Chunks can be created by pushing a {@link rx.util.TOpening} value to the "bufferOpenings"
* {@link Observable}. This creates a new buffer which will then start recording values which are produced
* by the "source" {@link Observable}. Additionally the "bufferClosingSelector" will be used to construct an
* {@link Observable} which can produce values. When it does so it will close this (and only this) newly
* created buffer. When the source {@link Observable} completes or produces an error, all chunks are
* emitted, and the event is propagated to all subscribed {@link Observer}s.
*
* Note that when using this operation multiple overlapping chunks could be active at any
* one point.
*
*
* @param source
* the {@link Observable} which produces values
* @param bufferOpenings
* an {@link Observable} which when it produces a {@link rx.util.TOpening} value will create a
* new buffer which instantly starts recording the "source" {@link Observable}
* @param bufferClosingSelector
* a {@link Func0} object which produces {@link Observable}s. These {@link Observable}s determine
* when a buffer is emitted and replaced by simply producing an object.
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(final Observable source, final Observable extends TOpening> bufferOpenings, final Func1 super TOpening, ? extends Observable extends TClosing>> bufferClosingSelector) {
return new OnSubscribeFunc>() {
@Override
public Subscription onSubscribe(final Observer super List> observer) {
OverlappingChunks> buffers = new OverlappingChunks>(observer, OperationBuffer. bufferMaker());
ChunkCreator creator = new ObservableBasedMultiChunkCreator, TOpening, TClosing>(buffers, bufferOpenings, bufferClosingSelector);
return new CompositeSubscription(
new ChunkToSubscription(creator),
source.subscribe(new ChunkObserver>(buffers, observer, creator)));
}
};
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer until the buffer contains
* a specified number of elements. The buffer is then emitted, and a new buffer is created to replace it.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation only produces non-overlapping chunks. At all times there is
* exactly one buffer actively storing values.
*
*
* @param source
* the {@link Observable} which produces values
* @param count
* the number of elements a buffer should have before being emitted and replaced
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(Observable source, int count) {
return buffer(source, count, count);
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in all active chunks until the buffer
* contains a specified number of elements. The buffer is then emitted. Chunks are created after a certain
* amount of values have been received. When the source {@link Observable} completes or produces an error,
* the currently active chunks are emitted, and the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation can produce non-connected, connected non-overlapping, or overlapping
* chunks depending on the input parameters.
*
*
* @param source
* the {@link Observable} which produces values
* @param count
* the number of elements a buffer should have before being emitted
* @param skip
* the interval with which chunks have to be created. Note that when {@code skip == count} that
* this is the same as calling {@link OperationBuffer#buffer(Observable, int)}. If
* {@code skip < count}, this buffer operation will produce overlapping chunks and if
* {@code skip > count} non-overlapping chunks will be created and some values will not be pushed
* into a buffer at all!
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(final Observable source, final int count, final int skip) {
return new OnSubscribeFunc>() {
@Override
public Subscription onSubscribe(final Observer super List> observer) {
Chunks> chunks = new SizeBasedChunks>(observer, OperationBuffer. bufferMaker(), count);
ChunkCreator creator = new SkippingChunkCreator>(chunks, skip);
return new CompositeSubscription(
new ChunkToSubscription(creator),
source.subscribe(new ChunkObserver>(chunks, observer, creator)));
}
};
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer. Periodically the buffer
* is emitted and replaced with a new buffer. How often this is done depends on the specified timespan.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation only produces non-overlapping chunks. At all times there is
* exactly one buffer actively storing values.
*
*
* @param source
* the {@link Observable} which produces values
* @param timespan
* the amount of time all chunks must be actively collect values before being emitted
* @param unit
* the {@link TimeUnit} defining the unit of time for the timespan
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(Observable source, long timespan, TimeUnit unit) {
return buffer(source, timespan, unit, Schedulers.threadPoolForComputation());
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer. Periodically the buffer
* is emitted and replaced with a new buffer. How often this is done depends on the specified timespan.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation only produces non-overlapping chunks. At all times there is
* exactly one buffer actively storing values.
*
*
* @param source
* the {@link Observable} which produces values
* @param timespan
* the amount of time all chunks must be actively collect values before being emitted
* @param unit
* the {@link TimeUnit} defining the unit of time for the timespan
* @param scheduler
* the {@link Scheduler} to use for timing chunks
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(final Observable source, final long timespan, final TimeUnit unit, final Scheduler scheduler) {
return new OnSubscribeFunc>() {
@Override
public Subscription onSubscribe(final Observer super List> observer) {
NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer. bufferMaker());
ChunkCreator creator = new TimeBasedChunkCreator>(buffers, timespan, unit, scheduler);
return new CompositeSubscription(
new ChunkToSubscription(creator),
source.subscribe(new ChunkObserver>(buffers, observer, creator)));
}
};
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer. Periodically the buffer
* is emitted and replaced with a new buffer. How often this is done depends on the specified timespan.
* Additionally the buffer is automatically emitted once it reaches a specified number of elements.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation only produces non-overlapping chunks. At all times there is
* exactly one buffer actively storing values.
*
*
* @param source
* the {@link Observable} which produces values
* @param timespan
* the amount of time all chunks must be actively collect values before being emitted
* @param unit
* the {@link TimeUnit} defining the unit of time for the timespan
* @param count
* the maximum size of the buffer. Once a buffer reaches this size, it is emitted
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(Observable source, long timespan, TimeUnit unit, int count) {
return buffer(source, timespan, unit, count, Schedulers.threadPoolForComputation());
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer. Periodically the buffer
* is emitted and replaced with a new buffer. How often this is done depends on the specified timespan.
* Additionally the buffer is automatically emitted once it reaches a specified number of elements.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation only produces non-overlapping chunks. At all times there is
* exactly one buffer actively storing values.
*
*
* @param source
* the {@link Observable} which produces values
* @param timespan
* the amount of time all chunks must be actively collect values before being emitted
* @param unit
* the {@link TimeUnit} defining the unit of time for the timespan
* @param count
* the maximum size of the buffer. Once a buffer reaches this size, it is emitted
* @param scheduler
* the {@link Scheduler} to use for timing chunks
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(final Observable source, final long timespan, final TimeUnit unit, final int count, final Scheduler scheduler) {
return new OnSubscribeFunc>() {
@Override
public Subscription onSubscribe(final Observer super List> observer) {
TimeAndSizeBasedChunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationBuffer. bufferMaker(), count, timespan, unit, scheduler);
ChunkCreator creator = new SingleChunkCreator>(chunks);
return new CompositeSubscription(
chunks,
new ChunkToSubscription(creator),
source.subscribe(new ChunkObserver>(chunks, observer, creator)));
}
};
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer. Periodically the buffer
* is emitted and replaced with a new buffer. How often this is done depends on the specified timespan.
* The creation of chunks is also periodical. How often this is done depends on the specified timeshift.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation can produce non-connected, or overlapping chunks depending
* on the input parameters.
*
*
* @param source
* the {@link Observable} which produces values
* @param timespan
* the amount of time all chunks must be actively collect values before being emitted
* @param timeshift
* the amount of time between creating chunks
* @param unit
* the {@link TimeUnit} defining the unit of time for the timespan
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(Observable source, long timespan, long timeshift, TimeUnit unit) {
return buffer(source, timespan, timeshift, unit, Schedulers.threadPoolForComputation());
}
/**
* This method creates a {@link Func1} object which represents the buffer operation. This operation takes
* values from the specified {@link Observable} source and stores them in a buffer. Periodically the buffer
* is emitted and replaced with a new buffer. How often this is done depends on the specified timespan.
* The creation of chunks is also periodical. How often this is done depends on the specified timeshift.
* When the source {@link Observable} completes or produces an error, the current buffer is emitted, and
* the event is propagated to all subscribed {@link Observer}s.
*
* Note that this operation can produce non-connected, or overlapping chunks depending
* on the input parameters.
*
*
* @param source
* the {@link Observable} which produces values
* @param timespan
* the amount of time all chunks must be actively collect values before being emitted
* @param timeshift
* the amount of time between creating chunks
* @param unit
* the {@link TimeUnit} defining the unit of time for the timespan
* @param scheduler
* the {@link Scheduler} to use for timing chunks
* @return
* the {@link Func1} object representing the specified buffer operation
*/
public static OnSubscribeFunc> buffer(final Observable source, final long timespan, final long timeshift, final TimeUnit unit, final Scheduler scheduler) {
return new OnSubscribeFunc>() {
@Override
public Subscription onSubscribe(final Observer super List> observer) {
TimeBasedChunks> buffers = new TimeBasedChunks>(observer, OperationBuffer. bufferMaker(), timespan, unit, scheduler);
ChunkCreator creator = new TimeBasedChunkCreator>(buffers, timeshift, unit, scheduler);
return new CompositeSubscription(
buffers,
new ChunkToSubscription(creator),
source.subscribe(new ChunkObserver>(buffers, observer, creator)));
}
};
}
/**
* This class represents a single buffer: A sequence of recorded values.
*
* @param
* the type of objects which this {@link Buffer} can hold
*/
protected static class Buffer extends Chunk> {
/**
* @return
* The mutable underlying {@link List} which contains all the
* recorded values in this {@link Buffer} object.
*/
@Override
public List getContents() {
return contents;
}
}
/**
* Converts a chunk creator into a subscription which stops the chunk.
*/
private static class ChunkToSubscription implements Subscription {
private ChunkCreator cc;
private final AtomicBoolean done;
public ChunkToSubscription(ChunkCreator cc) {
this.cc = cc;
this.done = new AtomicBoolean();
}
@Override
public void unsubscribe() {
if (done.compareAndSet(false, true)) {
ChunkCreator cc0 = cc;
cc = null;
cc0.stop();
}
}
@Override
public boolean isUnsubscribed() {
return done.get();
}
}
/**
* Create a buffer operator with the given observable sequence as the buffer boundary.
*
* @param source
* @param boundary
* @return
*/
public static OnSubscribeFunc> bufferWithBoundaryObservable(Observable extends T> source, Observable boundary) {
return new BufferWithObservableBoundary(source, boundary, 16);
}
/**
* Create a buffer operator with the given observable sequence as the buffer boundary and
* with the given initial capacity for buffers.
*
* @param source
* @param boundary
* @param initialCapacity
* @return
*/
public static OnSubscribeFunc> bufferWithBoundaryObservable(Observable extends T> source, Observable boundary, int initialCapacity) {
if (initialCapacity <= 0) {
throw new IllegalArgumentException("initialCapacity > 0 required");
}
return new BufferWithObservableBoundary(source, boundary, initialCapacity);
}
/**
* Buffer until an element is emitted from a helper observable.
*
* @param
* the buffered value type
*/
private static final class BufferWithObservableBoundary implements OnSubscribeFunc> {
final Observable extends T> source;
final Observable boundary;
final int initialCapacity;
public BufferWithObservableBoundary(Observable extends T> source, Observable boundary, int initialCapacity) {
this.source = source;
this.boundary = boundary;
this.initialCapacity = initialCapacity;
}
@Override
public Subscription onSubscribe(Observer super List> t1) {
CompositeSubscription csub = new CompositeSubscription();
SourceObserver so = new SourceObserver(t1, initialCapacity, csub);
csub.add(source.subscribe(so));
csub.add(boundary.subscribe(new BoundaryObserver(so)));
return csub;
}
/**
* Observes the source.
*/
private static final class SourceObserver implements Observer {
final Observer super List> observer;
/** The buffer, if null, that indicates a terminal state. */
List buffer;
final int initialCapacity;
final Object guard;
final Subscription cancel;
public SourceObserver(Observer super List> observer, int initialCapacity, Subscription cancel) {
this.observer = observer;
this.initialCapacity = initialCapacity;
this.guard = new Object();
this.cancel = cancel;
buffer = new ArrayList(initialCapacity);
}
@Override
public void onNext(T args) {
synchronized (guard) {
buffer.add(args);
}
}
@Override
public void onError(Throwable e) {
synchronized (guard) {
if (buffer == null) {
return;
}
buffer = null;
}
observer.onError(e);
cancel.unsubscribe();
}
@Override
public void onCompleted() {
emitAndComplete();
cancel.unsubscribe();
}
void emitAndReplace() {
List buf;
synchronized (guard) {
if (buffer == null) {
return;
}
buf = buffer;
buffer = new ArrayList(initialCapacity);
}
observer.onNext(buf);
}
void emitAndComplete() {
List buf;
synchronized (guard) {
if (buffer == null) {
return;
}
buf = buffer;
buffer = null;
}
observer.onNext(buf);
observer.onCompleted();
}
}
/**
* Observes the boundary.
*/
private static final class BoundaryObserver implements Observer {
final SourceObserver so;
public BoundaryObserver(SourceObserver so) {
this.so = so;
}
@Override
public void onNext(T args) {
so.emitAndReplace();
}
@Override
public void onError(Throwable e) {
so.onError(e);
}
@Override
public void onCompleted() {
so.onCompleted();
}
}
}
}