/* * Copyright 2016 Maxim Tuev. * * 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 com.agna.ferro.rx; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import rx.Observable; import rx.Subscriber; import rx.exceptions.Exceptions; import rx.functions.Func2; import rx.observers.SerializedSubscriber; /** * This operator freezes all rx events (onNext, onError, onComplete) when freeze selector emits true, * and unfreeze it after freeze selector emits false. * If freeze selector does not emit any elements, all events would be frozen * If you want reduce num of elements in freeze buffer, you can define replaceFrozenEventPredicate. * When Observable frozen and source observable emits normal (onNext) event, before it is added to * the end of buffer, it compare with all already buffered events using replaceFrozenEventPredicate, * and if replaceFrozenEventPredicate return true, buffered element would be removed. * * Observable after this operator can emit event in different threads */ public class OperatorFreeze<T> implements Observable.Operator<T, T> { private final Observable<Boolean> freezeSelector; private final Func2<T, T, Boolean> replaceFrozenEventPredicate; public OperatorFreeze(Observable<Boolean> freezeSelector, Func2<T, T, Boolean> replaceFrozenEventPredicate) { this.freezeSelector = freezeSelector; this.replaceFrozenEventPredicate = replaceFrozenEventPredicate; } public OperatorFreeze(Observable<Boolean> freezeSelector) { this(freezeSelector, new Func2<T, T, Boolean>() { @Override public Boolean call(T frozenEvent, T newEvent) { return false; } }); } @Override public Subscriber<? super T> call(Subscriber<? super T> child) { final FreezeSubscriber<T> freezeSubscriber = new FreezeSubscriber<>( new SerializedSubscriber<>(child), replaceFrozenEventPredicate); final Subscriber<Boolean> freezeSelectorSubscriber = new Subscriber<Boolean>() { @Override public void onCompleted() { freezeSubscriber.forceOnComplete(); } @Override public void onError(Throwable e) { freezeSubscriber.forceOnError(e); } @Override public void onNext(Boolean freeze) { freezeSubscriber.setFrozen(freeze); } }; child.add(freezeSubscriber); child.add(freezeSelectorSubscriber); freezeSelector.unsafeSubscribe(freezeSelectorSubscriber); return freezeSubscriber; } private static final class FreezeSubscriber<T> extends Subscriber<T> { private final Subscriber<T> child; private final Func2<T, T, Boolean> replaceFrozenEventPredicate; private final List<T> frozenEventsBuffer = new LinkedList<>(); private boolean frozen = true; private boolean done = false; private Throwable error = null; public FreezeSubscriber(Subscriber<T> child, Func2<T, T, Boolean> replaceFrozenEventPredicate) { this.child = child; this.replaceFrozenEventPredicate = replaceFrozenEventPredicate; } @Override public void onCompleted() { if (done || error != null) { return; } synchronized (this) { if (frozen) { done = true; } else { child.onCompleted(); unsubscribe(); } } } @Override public void onError(Throwable e) { if (done || error != null) { return; } synchronized (this) { if (frozen) { error = e; } else { child.onError(e); unsubscribe(); } } } @Override public void onNext(T event) { if (done || error != null) { return; } synchronized (this) { if (frozen) { bufferEvent(event); } else { child.onNext(event); } } } private void bufferEvent(T event) { for (ListIterator<T> it = frozenEventsBuffer.listIterator(); it.hasNext(); ) { T frozenEvent = it.next(); try { if (replaceFrozenEventPredicate.call(frozenEvent, event)) { it.remove(); } } catch (Throwable ex) { Exceptions.throwIfFatal(ex); unsubscribe(); onError(ex); return; } } frozenEventsBuffer.add(event); } public void forceOnComplete() { child.onCompleted(); unsubscribe(); } public void forceOnError(Throwable e) { child.onError(e); unsubscribe(); } public synchronized void setFrozen(boolean frozen) { this.frozen = frozen; if (!frozen) { emitFrozenEvents(); if (error != null) { forceOnError(error); } if (done) { forceOnComplete(); } } } private void emitFrozenEvents() { for (T event : frozenEventsBuffer) { child.onNext(event); } frozenEventsBuffer.clear(); } } }