-
Notifications
You must be signed in to change notification settings - Fork 7.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement Maybe.switchIfEmpty(Single) #5582
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/** | ||
* Copyright (c) 2016-present, RxJava Contributors. | ||
* | ||
* 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.internal.operators.maybe; | ||
|
||
import io.reactivex.*; | ||
import io.reactivex.disposables.Disposable; | ||
import io.reactivex.internal.disposables.DisposableHelper; | ||
import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; | ||
|
||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
/** | ||
* Subscribes to the other source if the main source is empty. | ||
* | ||
* @param <T> the value type | ||
*/ | ||
public final class MaybeSwitchIfEmptySingle<T> extends Single<T> implements HasUpstreamMaybeSource<T> { | ||
|
||
final MaybeSource<T> source; | ||
final SingleSource<? extends T> other; | ||
|
||
public MaybeSwitchIfEmptySingle(MaybeSource<T> source, SingleSource<? extends T> other) { | ||
this.source = source; | ||
this.other = other; | ||
} | ||
|
||
@Override | ||
public MaybeSource<T> source() { | ||
return source; | ||
} | ||
|
||
@Override | ||
protected void subscribeActual(SingleObserver<? super T> observer) { | ||
source.subscribe(new SwitchIfEmptyMaybeObserver<T>(observer, other)); | ||
} | ||
|
||
static final class SwitchIfEmptyMaybeObserver<T> | ||
extends AtomicReference<Disposable> | ||
implements MaybeObserver<T>, Disposable { | ||
|
||
private static final long serialVersionUID = 4603919676453758899L; | ||
|
||
final SingleObserver<? super T> actual; | ||
|
||
final SingleSource<? extends T> other; | ||
|
||
SwitchIfEmptyMaybeObserver(SingleObserver<? super T> actual, SingleSource<? extends T> other) { | ||
this.actual = actual; | ||
this.other = other; | ||
} | ||
|
||
@Override | ||
public void dispose() { | ||
DisposableHelper.dispose(this); | ||
} | ||
|
||
@Override | ||
public boolean isDisposed() { | ||
return DisposableHelper.isDisposed(get()); | ||
} | ||
|
||
@Override | ||
public void onSubscribe(Disposable d) { | ||
if (DisposableHelper.setOnce(this, d)) { | ||
actual.onSubscribe(this); | ||
} | ||
} | ||
|
||
@Override | ||
public void onSuccess(T value) { | ||
actual.onSuccess(value); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable e) { | ||
actual.onError(e); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
Disposable d = get(); | ||
if (d != DisposableHelper.DISPOSED) { | ||
if (compareAndSet(d, null)) { | ||
other.subscribe(new OtherSingleObserver<T>(actual, this)); | ||
} | ||
} | ||
} | ||
|
||
static final class OtherSingleObserver<T> implements SingleObserver<T> { | ||
|
||
final SingleObserver<? super T> actual; | ||
|
||
final AtomicReference<Disposable> parent; | ||
OtherSingleObserver(SingleObserver<? super T> actual, AtomicReference<Disposable> parent) { | ||
this.actual = actual; | ||
this.parent = parent; | ||
} | ||
@Override | ||
public void onSubscribe(Disposable d) { | ||
DisposableHelper.setOnce(parent, d); | ||
} | ||
@Override | ||
public void onSuccess(T value) { | ||
actual.onSuccess(value); | ||
} | ||
@Override | ||
public void onError(Throwable e) { | ||
actual.onError(e); | ||
} | ||
} | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/** | ||
* Copyright (c) 2016-present, RxJava Contributors. | ||
* | ||
* 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.internal.operators.maybe; | ||
|
||
import io.reactivex.Maybe; | ||
import io.reactivex.Single; | ||
import io.reactivex.TestHelper; | ||
import io.reactivex.exceptions.TestException; | ||
import io.reactivex.functions.Function; | ||
import io.reactivex.observers.TestObserver; | ||
import io.reactivex.processors.PublishProcessor; | ||
import io.reactivex.schedulers.Schedulers; | ||
import org.junit.Test; | ||
|
||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
public class MaybeSwitchIfEmptySingleTest { | ||
|
||
@Test | ||
public void nonEmpty() { | ||
Maybe.just(1).switchIfEmpty(Single.just(2)).test().assertResult(1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AssertValuesOnly is mostly for Observable & Flowable |
||
} | ||
|
||
@Test | ||
public void empty() { | ||
Maybe.<Integer>empty().switchIfEmpty(Single.just(2)).test().assertResult(2); | ||
} | ||
|
||
@Test | ||
public void error() { | ||
Maybe.<Integer>error(new TestException()).switchIfEmpty(Single.just(2)) | ||
.test().assertFailure(TestException.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be great to also check that no values were emitted to check that it didn't switch to fallback Single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes assertFailure does the job under the hood |
||
} | ||
|
||
@Test | ||
public void errorOther() { | ||
Maybe.empty().switchIfEmpty(Single.<Integer>error(new TestException())) | ||
.test().assertFailure(TestException.class); | ||
} | ||
|
||
@Test | ||
public void dispose() { | ||
PublishProcessor<Integer> pp = PublishProcessor.create(); | ||
|
||
TestObserver<Integer> ts = pp.singleElement().switchIfEmpty(Single.just(2)).test(); | ||
|
||
assertTrue(pp.hasSubscribers()); | ||
|
||
ts.cancel(); | ||
|
||
assertFalse(pp.hasSubscribers()); | ||
} | ||
|
||
|
||
@Test | ||
public void isDisposed() { | ||
PublishProcessor<Integer> pp = PublishProcessor.create(); | ||
|
||
TestHelper.checkDisposed(pp.singleElement().switchIfEmpty(Single.just(2))); | ||
} | ||
|
||
@Test | ||
public void doubleOnSubscribe() { | ||
TestHelper.checkDoubleOnSubscribeMaybeToSingle(new Function<Maybe<Integer>, Single<Integer>>() { | ||
@Override | ||
public Single<Integer> apply(Maybe<Integer> f) throws Exception { | ||
return f.switchIfEmpty(Single.just(2)); | ||
} | ||
}); | ||
} | ||
|
||
@Test | ||
public void emptyCancelRace() { | ||
for (int i = 0; i < 500; i++) { | ||
final PublishProcessor<Integer> pp = PublishProcessor.create(); | ||
|
||
final TestObserver<Integer> ts = pp.singleElement().switchIfEmpty(Single.just(2)).test(); | ||
|
||
Runnable r1 = new Runnable() { | ||
@Override | ||
public void run() { | ||
pp.onComplete(); | ||
} | ||
}; | ||
|
||
Runnable r2 = new Runnable() { | ||
@Override | ||
public void run() { | ||
ts.cancel(); | ||
} | ||
}; | ||
|
||
TestHelper.race(r1, r2, Schedulers.single()); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OtherSingleObserver
can extendAtomicReference
to save field, should we do that @akarnokd?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at L95, you'll see that
parent
is SwitchIfEmptyMaybeObserver which is declared asAtomicReference<Disposable>
thus this is not a new instace of anAtomicReference
here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I saw that, but wouldn't it save field anyway? Though it's super nit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The downstream can interact with only one replaceable reference to the original or the alternative disposable, so you'll need an extra field either way.