/** * 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.Iterator; import java.util.LinkedList; import java.util.List; import rx.Observable; import rx.Observable.Operator; import rx.Observer; import rx.Subscriber; /** * 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. * * <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 OperatorWindowWithSize<T> implements Operator<Observable<T>, 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<T>> child) { if (skip == size) { return new ExactSubscriber(child); } return new InexactSubscriber(child); } /** Subscriber with exact, non-overlapping window bounds. */ final class ExactSubscriber extends Subscriber<T> { final Subscriber<? super Observable<T>> child; int count; Observer<T> consumer; Observable<T> producer; public ExactSubscriber(Subscriber<? super Observable<T>> child) { super(child); this.child = child; } @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++ % size == 0) { if (consumer != null) { consumer.onCompleted(); } createNewWindow(); child.onNext(producer); } consumer.onNext(t); } @Override public void onError(Throwable e) { if (consumer != null) { consumer.onError(e); } child.onError(e); } @Override public void onCompleted() { if (consumer != null) { consumer.onCompleted(); } child.onCompleted(); } void createNewWindow() { final BufferUntilSubscriber<T> bus = BufferUntilSubscriber.create(); consumer = bus; producer = bus; } } /** Subscriber with inexact, possibly overlapping or skipping windows. */ final class InexactSubscriber extends Subscriber<T> { final Subscriber<? super Observable<T>> child; int count; final List<CountedSubject<T>> chunks; public InexactSubscriber(Subscriber<? super Observable<T>> child) { this.child = child; this.chunks = new LinkedList<CountedSubject<T>>(); } @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) { CountedSubject<T> cs = createCountedSubject(); chunks.add(cs); child.onNext(cs.producer); } Iterator<CountedSubject<T>> it = chunks.iterator(); while (it.hasNext()) { CountedSubject<T> cs = it.next(); cs.consumer.onNext(t); if (++cs.count == size) { it.remove(); cs.consumer.onCompleted(); } } } @Override public void onError(Throwable e) { List<CountedSubject<T>> list = new ArrayList<CountedSubject<T>>(chunks); chunks.clear(); for (CountedSubject<T> cs : list) { cs.consumer.onError(e); } child.onError(e); } @Override public void onCompleted() { List<CountedSubject<T>> list = new ArrayList<CountedSubject<T>>(chunks); chunks.clear(); for (CountedSubject<T> cs : list) { cs.consumer.onCompleted(); } child.onCompleted(); } CountedSubject<T> createCountedSubject() { final BufferUntilSubscriber<T> bus = BufferUntilSubscriber.create(); return new CountedSubject<T>(bus, bus); } } /** * Record to store the subject and the emission count. * @param <T> the subject's in-out type */ static final class CountedSubject<T> { final Observer<T> consumer; final Observable<T> producer; int count; public CountedSubject(Observer<T> consumer, Observable<T> producer) { this.consumer = consumer; this.producer = producer; } } }