Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
rx.internal.operators.OperatorWindowWithTime 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 java.util.concurrent.TimeUnit;
import rx.*;
import rx.Observable;
import rx.Observable.Operator;
import rx.Observer;
import rx.Scheduler.Worker;
import rx.functions.Action0;
import rx.observers.*;
import rx.subjects.UnicastSubject;
import rx.subscriptions.Subscriptions;
/**
* Creates windows of values into the source sequence with timed window creation, length and size bounds.
* If timespan == timeshift, windows are non-overlapping but always continuous, i.e., when the size bound is reached, a new
* window is opened.
*
* 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 OperatorWindowWithTime implements Operator, T> {
/** Length of each window. */
final long timespan;
/** Period of creating new windows. */
final long timeshift;
final TimeUnit unit;
final Scheduler scheduler;
final int size;
/** Indicate the current subject should complete and a new subject be emitted. */
static final Object NEXT_SUBJECT = new Object();
/** For error and completion indication. */
static final NotificationLite NL = NotificationLite.instance();
public OperatorWindowWithTime(long timespan, long timeshift, TimeUnit unit, int size, Scheduler scheduler) {
this.timespan = timespan;
this.timeshift = timeshift;
this.unit = unit;
this.size = size;
this.scheduler = scheduler;
}
@Override
public Subscriber super T> call(Subscriber super Observable> child) {
Worker worker = scheduler.createWorker();
if (timespan == timeshift) {
ExactSubscriber s = new ExactSubscriber(child, worker);
s.add(worker);
s.scheduleExact();
return s;
}
InexactSubscriber s = new InexactSubscriber(child, worker);
s.add(worker);
s.startNewChunk();
s.scheduleChunk();
return s;
}
/** The immutable windowing state with one subject. */
static final class State {
final Observer consumer;
final Observable producer;
final int count;
static final State EMPTY = new State(null, null, 0);
public State(Observer consumer, Observable producer, int count) {
this.consumer = consumer;
this.producer = producer;
this.count = count;
}
public State next() {
return new State(consumer, producer, count + 1);
}
public State create(Observer consumer, Observable producer) {
return new State(consumer, producer, 0);
}
public State clear() {
return empty();
}
@SuppressWarnings("unchecked")
public static State empty() {
return (State)EMPTY;
}
}
/** Subscriber with exact, non-overlapping windows. */
final class ExactSubscriber extends Subscriber {
final Subscriber super Observable> child;
final Worker worker;
final Object guard;
/** Guarded by guard. */
List queue;
/** Guarded by guard. */
boolean emitting;
volatile State state;
public ExactSubscriber(Subscriber super Observable> child, Worker worker) {
this.child = new SerializedSubscriber>(child);
this.worker = worker;
this.guard = new Object();
this.state = State.empty();
child.add(Subscriptions.create(new Action0() {
@Override
public void call() {
// if there is no active window, unsubscribe the upstream
if (state.consumer == null) {
unsubscribe();
}
}
}));
}
@Override
public void onStart() {
request(Long.MAX_VALUE);
}
@Override
public void onNext(T t) {
synchronized (guard) {
if (emitting) {
if (queue == null) {
queue = new ArrayList();
}
queue.add(t);
return;
}
emitting = true;
}
boolean skipFinal = false;
try {
if (!emitValue(t)) {
return;
}
for (;;) {
List localQueue;
synchronized (guard) {
localQueue = queue;
if (localQueue == null) {
emitting = false;
skipFinal = true;
return;
}
queue = null;
}
if (!drain(localQueue)) {
return;
}
}
} finally {
if (!skipFinal) {
synchronized (guard) {
emitting = false;
}
}
}
}
boolean drain(List queue) {
if (queue == null) {
return true;
}
for (Object o : queue) {
if (o == NEXT_SUBJECT) {
if (!replaceSubject()) {
return false;
}
} else
if (NL.isError(o)) {
error(NL.getError(o));
break;
} else
if (NL.isCompleted(o)) {
complete();
break;
} else {
@SuppressWarnings("unchecked")
T t = (T)o;
if (!emitValue(t)) {
return false;
}
}
}
return true;
}
boolean replaceSubject() {
Observer s = state.consumer;
if (s != null) {
s.onCompleted();
}
// if child has unsubscribed, unsubscribe upstream instead of opening a new window
if (child.isUnsubscribed()) {
state = state.clear();
unsubscribe();
return false;
}
UnicastSubject bus = UnicastSubject.create();
state = state.create(bus, bus);
child.onNext(bus);
return true;
}
boolean emitValue(T t) {
State s = state;
if (s.consumer == null) {
if (!replaceSubject()) {
return false;
}
s = state;
}
s.consumer.onNext(t);
if (s.count == size - 1) {
s.consumer.onCompleted();
s = s.clear();
} else {
s = s.next();
}
state = s;
return true;
}
@Override
public void onError(Throwable e) {
synchronized (guard) {
if (emitting) {
// drop any queued action and terminate asap
queue = Collections.singletonList(NL.error(e));
return;
}
queue = null;
emitting = true;
}
error(e);
}
void error(Throwable e) {
Observer s = state.consumer;
state = state.clear();
if (s != null) {
s.onError(e);
}
child.onError(e);
unsubscribe();
}
void complete() {
Observer s = state.consumer;
state = state.clear();
if (s != null) {
s.onCompleted();
}
child.onCompleted();
unsubscribe();
}
@Override
public void onCompleted() {
List localQueue;
synchronized (guard) {
if (emitting) {
if (queue == null) {
queue = new ArrayList();
}
queue.add(NL.completed());
return;
}
localQueue = queue;
queue = null;
emitting = true;
}
try {
drain(localQueue);
} catch (Throwable e) {
error(e);
return;
}
complete();
}
void scheduleExact() {
worker.schedulePeriodically(new Action0() {
@Override
public void call() {
nextWindow();
}
}, 0, timespan, unit);
}
void nextWindow() {
synchronized (guard) {
if (emitting) {
if (queue == null) {
queue = new ArrayList();
}
queue.add(NEXT_SUBJECT);
return;
}
emitting = true;
}
boolean skipFinal = false;
try {
if (!replaceSubject()) {
return;
}
for (;;) {
List localQueue;
synchronized (guard) {
localQueue = queue;
if (localQueue == null) {
emitting = false;
skipFinal = true;
return;
}
queue = null;
}
if (!drain(localQueue)) {
return;
}
}
} finally {
if (!skipFinal) {
synchronized (guard) {
emitting = false;
}
}
}
}
}
/**
* Record to store the subject and the emission count.
* @param the subject's in-out type
*/
static final class CountedSerializedSubject {
final Observer consumer;
final Observable producer;
int count;
public CountedSerializedSubject(Observer consumer, Observable producer) {
this.consumer = new SerializedObserver(consumer);
this.producer = producer;
}
}
/** Subscriber with inexact, potentially overlapping or discontinuous windows. */
final class InexactSubscriber extends Subscriber {
final Subscriber super Observable> child;
final Worker worker;
final Object guard;
/** Guarded by this. */
final List> chunks;
/** Guarded by this. */
boolean done;
public InexactSubscriber(Subscriber super Observable> child, Worker worker) {
super(child);
this.child = child;
this.worker = worker;
this.guard = new Object();
this.chunks = new LinkedList>();
}
@Override
public void onStart() {
request(Long.MAX_VALUE);
}
@Override
public void onNext(T t) {
List> list;
synchronized (guard) {
if (done) {
return;
}
list = new ArrayList>(chunks);
Iterator> it = chunks.iterator();
while (it.hasNext()) {
CountedSerializedSubject cs = it.next();
if (++cs.count == size) {
it.remove();
}
}
}
for (CountedSerializedSubject cs : list) {
cs.consumer.onNext(t);
if (cs.count == size) {
cs.consumer.onCompleted();
}
}
}
@Override
public void onError(Throwable e) {
List> list;
synchronized (guard) {
if (done) {
return;
}
done = true;
list = new ArrayList>(chunks);
chunks.clear();
}
for (CountedSerializedSubject cs : list) {
cs.consumer.onError(e);
}
child.onError(e);
}
@Override
public void onCompleted() {
List> list;
synchronized (guard) {
if (done) {
return;
}
done = true;
list = new ArrayList>(chunks);
chunks.clear();
}
for (CountedSerializedSubject cs : list) {
cs.consumer.onCompleted();
}
child.onCompleted();
}
void scheduleChunk() {
worker.schedulePeriodically(new Action0() {
@Override
public void call() {
startNewChunk();
}
}, timeshift, timeshift, unit);
}
void startNewChunk() {
final CountedSerializedSubject chunk = createCountedSerializedSubject();
synchronized (guard) {
if (done) {
return;
}
chunks.add(chunk);
}
try {
child.onNext(chunk.producer);
} catch (Throwable e) {
onError(e);
return;
}
worker.schedule(new Action0() {
@Override
public void call() {
terminateChunk(chunk);
}
}, timespan, unit);
}
void terminateChunk(CountedSerializedSubject chunk) {
boolean terminate = false;
synchronized (guard) {
if (done) {
return;
}
Iterator> it = chunks.iterator();
while (it.hasNext()) {
CountedSerializedSubject cs = it.next();
if (cs == chunk) {
terminate = true;
it.remove();
break;
}
}
}
if (terminate) {
chunk.consumer.onCompleted();
}
}
CountedSerializedSubject createCountedSerializedSubject() {
UnicastSubject bus = UnicastSubject.create();
return new CountedSerializedSubject(bus, bus);
}
}
}