/** * Copyright 2016 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 io.reactivex.observers; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; import io.reactivex.internal.disposables.*; import io.reactivex.plugins.RxJavaPlugins; /** * Wraps another Subscriber and ensures all onXXX methods conform the protocol * (except the requirement for serialized access). * * @param the value type */ public final class SafeObserver implements Observer, Disposable { /** The actual Subscriber. */ final Observer actual; /** The subscription. */ Disposable s; /** Indicates a terminal state. */ boolean done; /** * Constructs a SafeObserver by wrapping the given actual Observer * @param actual the actual Observer to wrap, not null (not validated) */ public SafeObserver(Observer actual) { this.actual = actual; } @Override public void onSubscribe(Disposable s) { if (DisposableHelper.validate(this.s, s)) { this.s = s; try { actual.onSubscribe(this); } catch (Throwable e) { Exceptions.throwIfFatal(e); done = true; // can't call onError because the actual's state may be corrupt at this point try { s.dispose(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); RxJavaPlugins.onError(new CompositeException(e, e1)); } RxJavaPlugins.onError(e); } } } @Override public void dispose() { s.dispose(); } @Override public boolean isDisposed() { return s.isDisposed(); } @Override public void onNext(T t) { if (done) { return; } if (s == null) { onNextNoSubscription(); return; } if (t == null) { Throwable ex = new NullPointerException(); try { s.dispose(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); onError(new CompositeException(ex, e1)); return; } onError(ex); return; } try { actual.onNext(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); try { s.dispose(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); onError(new CompositeException(e, e1)); return; } onError(e); } } void onNextNoSubscription() { Throwable ex = new NullPointerException("Subscription not set!"); try { actual.onSubscribe(EmptyDisposable.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point RxJavaPlugins.onError(new CompositeException(ex, e)); return; } try { actual.onError(ex); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins RxJavaPlugins.onError(new CompositeException(ex, e)); } } @Override public void onError(Throwable t) { if (done) { return; } done = true; if (s == null) { CompositeException t2 = new CompositeException(t, new NullPointerException("Subscription not set!")); try { actual.onSubscribe(EmptyDisposable.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point t2.suppress(e); RxJavaPlugins.onError(t2); return; } try { actual.onError(t2); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins t2.suppress(e); RxJavaPlugins.onError(t2); } return; } if (t == null) { t = new NullPointerException(); } try { actual.onError(t); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); RxJavaPlugins.onError(new CompositeException(t, ex)); } } @Override public void onComplete() { if (done) { return; } if (s == null) { onCompleteNoSubscription(); return; } done = true; try { actual.onComplete(); } catch (Throwable e) { Exceptions.throwIfFatal(e); RxJavaPlugins.onError(e); } } void onCompleteNoSubscription() { Throwable ex = new NullPointerException("Subscription not set!"); try { actual.onSubscribe(EmptyDisposable.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point RxJavaPlugins.onError(new CompositeException(ex, e)); return; } try { actual.onError(ex); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins RxJavaPlugins.onError(new CompositeException(ex, e)); } } }