/** * 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.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import rx.Observable; import rx.Observable.Operator; import rx.Observer; import rx.Scheduler; import rx.Scheduler.Worker; import rx.Subscriber; import rx.functions.Action0; import rx.observers.SerializedObserver; import rx.observers.SerializedSubscriber; /** * Creates windows of values into the source sequence with timed window creation, length and size bounds. * If timespan == timeshift, windows are non-overlapping but may not be continuous if size number of items were already * emitted. If more items arrive after the window has reached its size bound, those items are dropped. * * <p>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.</p> * * @param <T> the value type */ public final class OperatorWindowWithTime<T> implements Operator<Observable<T>, 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; 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<T>> child) { Worker worker = scheduler.createWorker(); child.add(worker); if (timespan == timeshift) { ExactSubscriber s = new ExactSubscriber(child, worker); s.scheduleExact(); return s; } InexactSubscriber s = new InexactSubscriber(child, worker); s.startNewChunk(); s.scheduleChunk(); return s; } /** 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<Object> nl = NotificationLite.instance(); /** The immutable windowing state with one subject. */ static final class State<T> { final Observer<T> consumer; final Observable<T> producer; final int count; static final State<Object> EMPTY = new State<Object>(null, null, 0); public State(Observer<T> consumer, Observable<T> producer, int count) { this.consumer = consumer; this.producer = producer; this.count = count; } public State<T> next() { return new State<T>(consumer, producer, count + 1); } public State<T> create(Observer<T> consumer, Observable<T> producer) { return new State<T>(consumer, producer, 0); } public State<T> clear() { return empty(); } @SuppressWarnings("unchecked") public static <T> State<T> empty() { return (State<T>)EMPTY; } } /** Subscriber with exact, non-overlapping windows. */ final class ExactSubscriber extends Subscriber<T> { final Subscriber<? super Observable<T>> child; final Worker worker; final Object guard; /** Guarded by guard. */ List<Object> queue; /** Guarded by guard. */ boolean emitting; volatile State<T> state; public ExactSubscriber(Subscriber<? super Observable<T>> child, Worker worker) { super(child); this.child = new SerializedSubscriber<Observable<T>>(child); this.worker = worker; this.guard = new Object(); this.state = State.empty(); } @Override public void onStart() { request(Long.MAX_VALUE); } @Override public void onNext(T t) { List<Object> localQueue; synchronized (guard) { if (emitting) { if (queue == null) { queue = new ArrayList<Object>(); } queue.add(t); return; } localQueue = queue; queue = null; emitting = true; } boolean once = true; boolean skipFinal = false; try { do { drain(localQueue); if (once) { once = false; emitValue(t); } synchronized (guard) { localQueue = queue; queue = null; if (localQueue == null) { emitting = false; skipFinal = true; return; } } } while (!child.isUnsubscribed()); } finally { if (!skipFinal) { synchronized (guard) { emitting = false; } } } } void drain(List<Object> queue) { if (queue == null) { return; } for (Object o : queue) { if (o == NEXT_SUBJECT) { replaceSubject(); } else if (nl.isError(o)) { error(nl.getError(o)); break; } else if (nl.isCompleted(o)) { complete(); break; } else { @SuppressWarnings("unchecked") T t = (T)o; emitValue(t); } } } void replaceSubject() { Observer<T> s = state.consumer; if (s != null) { s.onCompleted(); } BufferUntilSubscriber<T> bus = BufferUntilSubscriber.create(); state = state.create(bus, bus); child.onNext(bus); } void emitValue(T t) { State<T> s = state; if (s.consumer != null) { s.consumer.onNext(t); if (s.count == size) { s.consumer.onCompleted(); s = s.clear(); } else { s = s.next(); } } state = s; } @Override public void onError(Throwable e) { synchronized (guard) { if (emitting) { // drop any queued action and terminate asap queue = Collections.<Object>singletonList(nl.error(e)); return; } queue = null; emitting = true; } error(e); } void error(Throwable e) { Observer<T> s = state.consumer; state = state.clear(); if (s != null) { s.onError(e); } child.onError(e); unsubscribe(); } void complete() { Observer<T> s = state.consumer; state = state.clear(); if (s != null) { s.onCompleted(); } child.onCompleted(); unsubscribe(); } @Override public void onCompleted() { List<Object> localQueue; synchronized (guard) { if (emitting) { if (queue == null) { queue = new ArrayList<Object>(); } 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() { List<Object> localQueue; synchronized (guard) { if (emitting) { if (queue == null) { queue = new ArrayList<Object>(); } queue.add(NEXT_SUBJECT); return; } localQueue = queue; queue = null; emitting = true; } boolean once = true; boolean skipFinal = false; try { do { drain(localQueue); if (once) { once = false; replaceSubject(); } synchronized (guard) { localQueue = queue; queue = null; if (localQueue == null) { emitting = false; skipFinal = true; return; } } } while (!child.isUnsubscribed()); } finally { if (!skipFinal) { synchronized (guard) { emitting = false; } } } } } /** * Record to store the subject and the emission count. * @param <T> the subject's in-out type */ static final class CountedSerializedSubject<T> { final Observer<T> consumer; final Observable<T> producer; int count; public CountedSerializedSubject(Observer<T> consumer, Observable<T> producer) { this.consumer = new SerializedObserver<T>(consumer); this.producer = producer; } } /** Subscriber with inexact, potentially overlapping or discontinuous windows. */ final class InexactSubscriber extends Subscriber<T> { final Subscriber<? super Observable<T>> child; final Worker worker; final Object guard; /** Guarded by this. */ final List<CountedSerializedSubject<T>> chunks; /** Guarded by this. */ boolean done; public InexactSubscriber(Subscriber<? super Observable<T>> child, Worker worker) { super(child); this.child = child; this.worker = worker; this.guard = new Object(); this.chunks = new LinkedList<CountedSerializedSubject<T>>(); } @Override public void onStart() { request(Long.MAX_VALUE); } @Override public void onNext(T t) { List<CountedSerializedSubject<T>> list; synchronized (guard) { if (done) { return; } list = new ArrayList<CountedSerializedSubject<T>>(chunks); Iterator<CountedSerializedSubject<T>> it = chunks.iterator(); while (it.hasNext()) { CountedSerializedSubject<T> cs = it.next(); if (++cs.count == size) { it.remove(); } } } for (CountedSerializedSubject<T> cs : list) { cs.consumer.onNext(t); if (cs.count == size) { cs.consumer.onCompleted(); } } } @Override public void onError(Throwable e) { List<CountedSerializedSubject<T>> list; synchronized (guard) { if (done) { return; } done = true; list = new ArrayList<CountedSerializedSubject<T>>(chunks); chunks.clear(); } for (CountedSerializedSubject<T> cs : list) { cs.consumer.onError(e); } child.onError(e); } @Override public void onCompleted() { List<CountedSerializedSubject<T>> list; synchronized (guard) { if (done) { return; } done = true; list = new ArrayList<CountedSerializedSubject<T>>(chunks); chunks.clear(); } for (CountedSerializedSubject<T> 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<T> 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<T> chunk) { boolean terminate = false; synchronized (guard) { if (done) { return; } Iterator<CountedSerializedSubject<T>> it = chunks.iterator(); while (it.hasNext()) { CountedSerializedSubject<T> cs = it.next(); if (cs == chunk) { terminate = true; it.remove(); break; } } } if (terminate) { chunk.consumer.onCompleted(); } } CountedSerializedSubject<T> createCountedSerializedSubject() { BufferUntilSubscriber<T> bus = BufferUntilSubscriber.create(); return new CountedSerializedSubject<T>(bus, bus); } } }