rx.internal.operators.OperatorWindowWithSize Maven / Gradle / Ivy
The 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.internal.operators;
import java.util.*;
import java.util.concurrent.atomic.*;
import rx.*;
import rx.Observable;
import rx.Observable.Operator;
import rx.functions.Action0;
import rx.internal.util.atomic.SpscLinkedArrayQueue;
import rx.subjects.*;
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 call(Subscriber> child) {
if (skip == size) {
WindowExact parent = new WindowExact(child, size);
child.add(parent.cancel);
child.setProducer(parent.createProducer());
return parent;
} else
if (skip > size) {
WindowSkip parent = new WindowSkip(child, size, skip);
child.add(parent.cancel);
child.setProducer(parent.createProducer());
return parent;
}
WindowOverlap parent = new WindowOverlap(child, size, skip);
child.add(parent.cancel);
child.setProducer(parent.createProducer());
return parent;
}
static final class WindowExact extends Subscriber implements Action0 {
final Subscriber> actual;
final int size;
final AtomicInteger wip;
final Subscription cancel;
int index;
Subject window;
public WindowExact(Subscriber> actual, int size) {
this.actual = actual;
this.size = size;
this.wip = new AtomicInteger(1);
this.cancel = Subscriptions.create(this);
this.add(cancel);
this.request(0);
}
@Override
public void onNext(T t) {
int i = index;
Subject w = window;
if (i == 0) {
wip.getAndIncrement();
w = UnicastSubject.create(size, this);
window = w;
actual.onNext(w);
}
i++;
w.onNext(t);
if (i == size) {
index = 0;
window = null;
w.onCompleted();
} else {
index = i;
}
}
@Override
public void onError(Throwable e) {
Subject w = window;
if (w != null) {
window = null;
w.onError(e);
}
actual.onError(e);
}
@Override
public void onCompleted() {
Subject w = window;
if (w != null) {
window = null;
w.onCompleted();
}
actual.onCompleted();
}
Producer createProducer() {
return new Producer() {
@Override
public void request(long n) {
if (n < 0L) {
throw new IllegalArgumentException("n >= 0 required but it was " + n);
}
if (n != 0L) {
long u = BackpressureUtils.multiplyCap(size, n);
WindowExact.this.request(u);
}
}
};
}
@Override
public void call() {
if (wip.decrementAndGet() == 0) {
unsubscribe();
}
}
}
static final class WindowSkip extends Subscriber implements Action0 {
final Subscriber> actual;
final int size;
final int skip;
final AtomicInteger wip;
final Subscription cancel;
int index;
Subject window;
public WindowSkip(Subscriber> actual, int size, int skip) {
this.actual = actual;
this.size = size;
this.skip = skip;
this.wip = new AtomicInteger(1);
this.cancel = Subscriptions.create(this);
this.add(cancel);
this.request(0);
}
@Override
public void onNext(T t) {
int i = index;
Subject w = window;
if (i == 0) {
wip.getAndIncrement();
w = UnicastSubject.create(size, this);
window = w;
actual.onNext(w);
}
i++;
if (w != null) {
w.onNext(t);
}
if (i == size) {
index = i;
window = null;
w.onCompleted();
} else
if (i == skip) {
index = 0;
} else {
index = i;
}
}
@Override
public void onError(Throwable e) {
Subject w = window;
if (w != null) {
window = null;
w.onError(e);
}
actual.onError(e);
}
@Override
public void onCompleted() {
Subject w = window;
if (w != null) {
window = null;
w.onCompleted();
}
actual.onCompleted();
}
Producer createProducer() {
return new WindowSkipProducer();
}
@Override
public void call() {
if (wip.decrementAndGet() == 0) {
unsubscribe();
}
}
final class WindowSkipProducer extends AtomicBoolean implements Producer {
/** */
private static final long serialVersionUID = 4625807964358024108L;
@Override
public void request(long n) {
if (n < 0L) {
throw new IllegalArgumentException("n >= 0 required but it was " + n);
}
if (n != 0L) {
WindowSkip parent = WindowSkip.this;
if (!get() && compareAndSet(false, true)) {
long u = BackpressureUtils.multiplyCap(n, parent.size);
long v = BackpressureUtils.multiplyCap(parent.skip - parent.size, n - 1);
long w = BackpressureUtils.addCap(u, v);
parent.request(w);
} else {
long u = BackpressureUtils.multiplyCap(n, parent.skip);
parent.request(u);
}
}
}
}
}
static final class WindowOverlap extends Subscriber implements Action0 {
final Subscriber> actual;
final int size;
final int skip;
final AtomicInteger wip;
final Subscription cancel;
final ArrayDeque> windows;
final AtomicLong requested;
final AtomicInteger drainWip;
final Queue> queue;
Throwable error;
volatile boolean done;
int index;
int produced;
public WindowOverlap(Subscriber> actual, int size, int skip) {
this.actual = actual;
this.size = size;
this.skip = skip;
this.wip = new AtomicInteger(1);
this.windows = new ArrayDeque>();
this.drainWip = new AtomicInteger();
this.requested = new AtomicLong();
this.cancel = Subscriptions.create(this);
this.add(cancel);
this.request(0);
int maxWindows = (size + (skip - 1)) / skip;
this.queue = new SpscLinkedArrayQueue>(maxWindows);
}
@Override
public void onNext(T t) {
int i = index;
ArrayDeque> q = windows;
if (i == 0 && !actual.isUnsubscribed()) {
wip.getAndIncrement();
Subject w = UnicastSubject.create(16, this);
q.offer(w);
queue.offer(w);
drain();
}
for (Subject w : windows) {
w.onNext(t);
}
int p = produced + 1;
if (p == size) {
produced = p - skip;
Subject w = q.poll();
if (w != null) {
w.onCompleted();
}
} else {
produced = p;
}
i++;
if (i == skip) {
index = 0;
} else {
index = i;
}
}
@Override
public void onError(Throwable e) {
for (Subject w : windows) {
w.onError(e);
}
windows.clear();
error = e;
done = true;
drain();
}
@Override
public void onCompleted() {
for (Subject w : windows) {
w.onCompleted();
}
windows.clear();
done = true;
drain();
}
Producer createProducer() {
return new WindowOverlapProducer();
}
@Override
public void call() {
if (wip.decrementAndGet() == 0) {
unsubscribe();
}
}
void drain() {
AtomicInteger dw = drainWip;
if (dw.getAndIncrement() != 0) {
return;
}
final Subscriber> a = actual;
final Queue> q = queue;
int missed = 1;
for (;;) {
long r = requested.get();
long e = 0L;
while (e != r) {
boolean d = done;
Subject v = q.poll();
boolean empty = v == null;
if (checkTerminated(d, empty, a, q)) {
return;
}
if (empty) {
break;
}
a.onNext(v);
e++;
}
if (e == r) {
if (checkTerminated(done, q.isEmpty(), a, q)) {
return;
}
}
if (e != 0 && r != Long.MAX_VALUE) {
requested.addAndGet(-e);
}
missed = dw.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
boolean checkTerminated(boolean d, boolean empty, Subscriber> a, Queue> q) {
if (a.isUnsubscribed()) {
q.clear();
return true;
}
if (d) {
Throwable e = error;
if (e != null) {
q.clear();
a.onError(e);
return true;
} else
if (empty) {
a.onCompleted();
return true;
}
}
return false;
}
final class WindowOverlapProducer extends AtomicBoolean implements Producer {
/** */
private static final long serialVersionUID = 4625807964358024108L;
@Override
public void request(long n) {
if (n < 0L) {
throw new IllegalArgumentException("n >= 0 required but it was " + n);
}
if (n != 0L) {
WindowOverlap parent = WindowOverlap.this;
if (!get() && compareAndSet(false, true)) {
long u = BackpressureUtils.multiplyCap(parent.skip, n - 1);
long v = BackpressureUtils.addCap(u, parent.size);
parent.request(v);
} else {
long u = BackpressureUtils.multiplyCap(parent.skip, n);
WindowOverlap.this.request(u);
}
BackpressureUtils.getAndAddRequest(parent.requested, n);
parent.drain();
}
}
}
}
}