rx.internal.operators.OperatorWindowWithSize Maven / Gradle / Ivy
/**
* 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.internal.operators;
import java.util.*;
import rx.*;
import rx.Observable.Operator;
import rx.Observable;
import rx.Observer;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
/**
* Creates windows of values into the source sequence with skip frequency and size bounds.
*
* If skip == size then the windows are non-overlapping, otherwise, windows may overlap
* or can be discontinuous. The returned Observable sequence is cold and need to be
* consumed while the window operation is in progress.
*
* Note that this conforms the Rx.NET behavior, but does not match former RxJava
* behavior, which operated as a regular buffer and mapped its lists to Observables.
*
* @param the value type
*/
public final class OperatorWindowWithSize implements Operator, T> {
final int size;
final int skip;
public OperatorWindowWithSize(int size, int skip) {
this.size = size;
this.skip = skip;
}
@Override
public Subscriber super T> call(Subscriber super Observable> child) {
if (skip == size) {
return new ExactSubscriber(child);
}
return new InexactSubscriber(child);
}
/** Subscriber with exact, non-overlapping window bounds. */
final class ExactSubscriber extends Subscriber {
final Subscriber super Observable> child;
int count;
BufferUntilSubscriber window;
volatile boolean noWindow = true;
final Subscription parentSubscription = this;
public ExactSubscriber(Subscriber super Observable> child) {
/**
* See https://github.com/ReactiveX/RxJava/issues/1546
* We cannot compose through a Subscription because unsubscribing
* applies to the outer, not the inner.
*/
this.child = child;
/*
* Add unsubscribe hook to child to get unsubscribe on outer (unsubscribing on next window, not on the inner window itself)
*/
child.add(Subscriptions.create(new Action0() {
@Override
public void call() {
// if no window we unsubscribe up otherwise wait until window ends
if (noWindow) {
parentSubscription.unsubscribe();
}
}
}));
child.setProducer(new Producer() {
@Override
public void request(long n) {
if (n > 0) {
long u = n * size;
if (((u >>> 31) != 0) && (u / n != size)) {
u = Long.MAX_VALUE;
}
requestMore(u);
}
}
});
}
void requestMore(long n) {
request(n);
}
@Override
public void onNext(T t) {
if (window == null) {
noWindow = false;
window = BufferUntilSubscriber.create();
child.onNext(window);
}
window.onNext(t);
if (++count % size == 0) {
window.onCompleted();
window = null;
noWindow = true;
if (child.isUnsubscribed()) {
parentSubscription.unsubscribe();
return;
}
}
}
@Override
public void onError(Throwable e) {
if (window != null) {
window.onError(e);
}
child.onError(e);
}
@Override
public void onCompleted() {
if (window != null) {
window.onCompleted();
}
child.onCompleted();
}
}
/** Subscriber with inexact, possibly overlapping or skipping windows. */
final class InexactSubscriber extends Subscriber {
final Subscriber super Observable> child;
int count;
final List> chunks = new LinkedList>();
final Subscription parentSubscription = this;
public InexactSubscriber(Subscriber super Observable> child) {
/**
* See https://github.com/ReactiveX/RxJava/issues/1546
* We cannot compose through a Subscription because unsubscribing
* applies to the outer, not the inner.
*/
this.child = child;
/*
* Add unsubscribe hook to child to get unsubscribe on outer (unsubscribing on next window, not on the inner window itself)
*/
child.add(Subscriptions.create(new Action0() {
@Override
public void call() {
// if no window we unsubscribe up otherwise wait until window ends
if (chunks == null || chunks.size() == 0) {
parentSubscription.unsubscribe();
}
}
}));
}
@Override
public void onStart() {
// no backpressure as we are controlling data flow by window size
request(Long.MAX_VALUE);
}
@Override
public void onNext(T t) {
if (count++ % skip == 0) {
if (!child.isUnsubscribed()) {
CountedSubject cs = createCountedSubject();
chunks.add(cs);
child.onNext(cs.producer);
}
}
Iterator> it = chunks.iterator();
while (it.hasNext()) {
CountedSubject cs = it.next();
cs.consumer.onNext(t);
if (++cs.count == size) {
it.remove();
cs.consumer.onCompleted();
}
}
if (chunks.size() == 0 && child.isUnsubscribed()) {
parentSubscription.unsubscribe();
return;
}
}
@Override
public void onError(Throwable e) {
List> list = new ArrayList>(chunks);
chunks.clear();
for (CountedSubject cs : list) {
cs.consumer.onError(e);
}
child.onError(e);
}
@Override
public void onCompleted() {
List> list = new ArrayList>(chunks);
chunks.clear();
for (CountedSubject cs : list) {
cs.consumer.onCompleted();
}
child.onCompleted();
}
CountedSubject createCountedSubject() {
final BufferUntilSubscriber bus = BufferUntilSubscriber.create();
return new CountedSubject(bus, bus);
}
}
/**
* Record to store the subject and the emission count.
* @param the subject's in-out type
*/
static final class CountedSubject {
final Observer consumer;
final Observable producer;
int count;
public CountedSubject(Observer consumer, Observable producer) {
this.consumer = consumer;
this.producer = producer;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy