/* * Copyright 2016 Jake Wharton * * 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.jakewharton.rx3; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.FlowableTransformer; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.ObservableTransformer; import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.annotations.NonNull; import io.reactivex.rxjava3.annotations.Nullable; import io.reactivex.rxjava3.disposables.Disposable; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; /** * A transformer which combines the {@code replay(1)}, {@code publish()}, and {@code refCount()} * operators. * <p> * Unlike traditional combinations of these operators, `ReplayingShare` caches the last emitted * value from the upstream observable or flowable *only* when one or more downstream subscribers * are connected. This allows expensive upstream sources to be shut down when no one is listening * while also replaying the last value seen by *any* subscriber to new ones. */ public final class ReplayingShare<T> implements ObservableTransformer<T, T>, FlowableTransformer<T, T> { private static final ReplayingShare<Object> INSTANCE = new ReplayingShare<>(null); /** The singleton instance of this transformer. */ @NonNull @SuppressWarnings("unchecked") // Safe because of erasure. public static <T> ReplayingShare<T> instance() { return (ReplayingShare<T>) INSTANCE; } /** * Creates a `ReplayingShare` transformer with a default value which will be emitted downstream * on subscription if there is not any cached value yet. * * @param defaultValue the initial value delivered to new subscribers before any events are * cached. */ @NonNull public static <T> ReplayingShare<T> createWithDefault(@NonNull T defaultValue) { if (defaultValue == null) throw new NullPointerException("defaultValue == null"); return new ReplayingShare<>(defaultValue); } private final @Nullable T defaultValue; private ReplayingShare(@Nullable T defaultValue) { this.defaultValue = defaultValue; } @Override public Observable<T> apply(Observable<T> upstream) { LastSeen<T> lastSeen = new LastSeen<>(defaultValue); return new LastSeenObservable<>(upstream.doOnEach(lastSeen).share(), lastSeen); } @Override public Flowable<T> apply(Flowable<T> upstream) { LastSeen<T> lastSeen = new LastSeen<>(defaultValue); return new LastSeenFlowable<>(upstream.doOnEach(lastSeen).share(), lastSeen); } static final class LastSeen<T> implements Observer<T>, Subscriber<T> { private final @Nullable T defaultValue; volatile @Nullable T value; LastSeen(@Nullable T defaultValue) { this.defaultValue = defaultValue; value = defaultValue; } @Override public void onNext(T value) { this.value = value; } @Override public void onError(Throwable e) { value = defaultValue; } @Override public void onComplete() { value = defaultValue; } @Override public void onSubscribe(Subscription ignored) {} @Override public void onSubscribe(Disposable ignored) {} } static final class LastSeenObservable<T> extends Observable<T> { private final Observable<T> upstream; private final LastSeen<T> lastSeen; LastSeenObservable(Observable<T> upstream, LastSeen<T> lastSeen) { this.upstream = upstream; this.lastSeen = lastSeen; } @Override protected void subscribeActual(Observer<? super T> observer) { upstream.subscribe(new LastSeenObserver<>(observer, lastSeen)); } } static final class LastSeenObserver<T> implements Observer<T> { private final Observer<? super T> downstream; private final LastSeen<T> lastSeen; LastSeenObserver(Observer<? super T> downstream, LastSeen<T> lastSeen) { this.downstream = downstream; this.lastSeen = lastSeen; } @Override public void onSubscribe(Disposable d) { downstream.onSubscribe(d); T value = lastSeen.value; if (value != null && !d.isDisposed()) { downstream.onNext(value); } } @Override public void onNext(T value) { downstream.onNext(value); } @Override public void onComplete() { downstream.onComplete(); } @Override public void onError(Throwable e) { downstream.onError(e); } } static final class LastSeenFlowable<T> extends Flowable<T> { private final Flowable<T> upstream; private final LastSeen<T> lastSeen; LastSeenFlowable(Flowable<T> upstream, LastSeen<T> lastSeen) { this.upstream = upstream; this.lastSeen = lastSeen; } @Override protected void subscribeActual(Subscriber<? super T> subscriber) { upstream.subscribe(new LastSeenSubscriber<>(subscriber, lastSeen)); } } static final class LastSeenSubscriber<T> implements Subscriber<T>, Subscription { private final Subscriber<? super T> downstream; private final LastSeen<T> lastSeen; private @Nullable Subscription subscription; private volatile boolean cancelled; private boolean first = true; LastSeenSubscriber(Subscriber<? super T> downstream, LastSeen<T> lastSeen) { this.downstream = downstream; this.lastSeen = lastSeen; } @Override public void onSubscribe(Subscription subscription) { this.subscription = subscription; downstream.onSubscribe(this); } @Override public void request(long amount) { if (amount == 0) return; if (first) { first = false; T value = lastSeen.value; if (value != null && !cancelled) { downstream.onNext(value); if (amount != Long.MAX_VALUE && --amount == 0) { return; } } } Subscription subscription = this.subscription; assert subscription != null; subscription.request(amount); } @Override public void cancel() { Subscription subscription = this.subscription; assert subscription != null; cancelled = true; subscription.cancel(); } @Override public void onNext(T value) { downstream.onNext(value); } @Override public void onComplete() { downstream.onComplete(); } @Override public void onError(Throwable t) { downstream.onError(t); } } }