Skip to content

Instantly share code, notes, and snippets.

@Ahmed-Adel-Ismail
Last active February 8, 2024 14:44
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Ahmed-Adel-Ismail/93b3e6f2385d2c7fa1c1a07b6244fc60 to your computer and use it in GitHub Desktop.
Save Ahmed-Adel-Ismail/93b3e6f2385d2c7fa1c1a07b6244fc60 to your computer and use it in GitHub Desktop.
Observable Operators that enables switch-case and if-else in RxJava stream, you can use them with lift() operator, and pass to them a Map of key/function , if the key is matched with the emitted item, it's function will be executed and it's value will be returned in the stream
/**
* an {@link ObservableOperator} that simulates an if-else in an RxJava Stream, it takes a {@link Map}
* of {@link Predicate} as key and a {@link Function} as value ... the if any emitted item passes
* the {@link Predicate}, this emitted item will be passed to the {@link Function} mapped to it,
* and this item will be invoked and it's result will continue down the stream
* <p>
* sample code :
* <p>
* {@code List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);}<br>
* {@code Map<Predicate<Integer>, Function<Integer, String>> blocks = new LinkedHashMap<>(2)}<br>
* {@code blocks.put(i -> i % 2 == 0, i -> "Even number : " + i);}<br>
* {@code blocks.put(i -> i % 2 != 0, i -> "Odd number : " + i);}<br>
* <p>
* {@code Observable.fromIterable(list)}<br>
* {@code .lift(new IfElse<>(blocks))}<br>
* {@code .subscribe(System.out::println);}<br>
* <p>
* // result :<br>
* Odd number : 1<br>
* Even number : 2<br>
* Odd number : 3<br>
* Even number : 4<br>
* Odd number : 5<br>
* Even number : 6<br>
* Odd number : 7<br>
* Even number : 8
*/
public class IfElse<T, R> implements ObservableOperator<R, T> {
private final Map<Predicate<T>, Function<T, R>> blocks = new LinkedHashMap<>();
/**
* create a {@link IfElse} operator, if any {@link Function} returned {@code null}, the
* whole operation will crash
*
* @param blocks the map that holds {@link Function} that are the if-else blocks
*/
public IfElse(@NonNull Map<Predicate<T>, Function<T, R>> blocks) {
if (blocks != null) {
this.blocks.putAll(blocks);
}
}
@Override
public Observer<? super T> apply(final Observer<? super R> observer) throws Exception {
return createResultObserver(observer);
}
private Observer<T> createResultObserver(final Observer<? super R> observer) {
return new Observer<T>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull T emittedItem) {
List<Function<T, R>> validBlocks = Observable.fromIterable(blocks.keySet())
.filter(key -> key.test(emittedItem))
.map(blocks::get)
.toList()
.flatMapMaybe(Maybe::just)
.blockingGet();
if (validBlocks == null) {
return;
}
try {
for (Function<T, R> block : validBlocks) {
invokeOnNext(observer, block.apply(emittedItem));
}
} catch (Throwable e) {
onError(e);
}
}
@Override
public void onError(@NonNull Throwable e) {
observer.onError(e);
}
@Override
public void onComplete() {
observer.onComplete();
}
};
}
private void invokeOnNext(Observer<? super R> observer, R onNextValue) {
observer.onNext(onNextValue);
}
}
/**
* An {@link ObservableOperator} that enables the switch-case behavior in Rx-Java, it uses
* a table look up technique to simulate a switch-case behavior
* <p>
* you must supply a {@link Map} that holds functions that will be executed if the emitted
* item matches the key they are mapped to
* <p>
* if multiple emitted items matches several keys, all there functions will be executed and
* will emit there results, if you want the first match to disable the rest of the checks,
* you can use {@link SwitchCaseBreak} instead
* <p>
* sample code :
* <p>
* {@code List<Integer> list = Arrays.asList(1, 2, 3, 4);}<br>
* {@code Map<Integer, Function<Integer, String>> caseBlocks = new HashMap<>(2);}<br>
* {@code caseBlocks.put(2, (i) -> "TWO SELECTED");}<br>
* {@code caseBlocks.put(3, (i) -> "THREE SELECTED");}<br>
* <p>
* {@code Observable.fromIterable(list)}<br>
* {@code .lift(new SwitchCase<>(caseBlocks))}<br>
* {@code .subscribe(System.out::println);}<br>
* <p>
* // result :<br>
* TWO SELECTED<br>
* THREE SELECTED<br>
*
* @param <T> the type of the items that will be passed to the {@code switch} statement
* @param <R> the type of the items that will be returned from the {@code case} block execution
*/
public class SwitchCase<T, R> implements ObservableOperator<R, T> {
final Map<T, Function<T, R>> caseBlocks = new LinkedHashMap<>();
/**
* create a {@link SwitchCase} operator, if any {@link Function} returned {@code null}, the
* whole operation will crash
*
* @param caseBlocks the map that holds {@link Function} that are the case-blocks in this
* switch-case
*/
public SwitchCase(@NonNull Map<T, Function<T, R>> caseBlocks) {
if (caseBlocks != null) {
this.caseBlocks.putAll(caseBlocks);
}
}
@Override
public Observer<? super T> apply(final Observer<? super R> observer) throws Exception {
return createResultObserver(observer);
}
private Observer<T> createResultObserver(final Observer<? super R> observer) {
return new Observer<T>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull T switchOn) {
Function<T, R> block = Maybe.just(caseBlocks)
.filter(map -> map.containsKey(switchOn))
.map(map -> map.get(switchOn))
.blockingGet();
if (block == null) {
return;
}
try {
invokeOnNext(observer, block.apply(switchOn));
} catch (Throwable e) {
onError(e);
}
}
@Override
public void onError(@NonNull Throwable e) {
observer.onError(e);
}
@Override
public void onComplete() {
observer.onComplete();
}
};
}
void invokeOnNext(Observer<? super R> observer, R onNextValue) {
observer.onNext(onNextValue);
}
}
/**
* same as {@link SwitchCase} but it emits only one item which is the result of the
* {@link Function} mapped to the key matching the first emitted value
* <p>
* sample code :
* <p>
* {@code List<Integer> list = Arrays.asList(1, 2, 3, 4);}<br>
* {@code Map<Integer, Function<Integer, String>> caseBlocks = new HashMap<>(2);}<br>
* {@code caseBlocks.put(2, (i) -> "TWO SELECTED");}<br>
* {@code caseBlocks.put(3, (i) -> "THREE SELECTED");}<br>
* <p>
* {@code Observable.fromIterable(list)}<br>
* {@code .lift(new SwitchCaseBreak<>(caseBlocks))}<br>
* {@code .subscribe(System.out::println);}<br>
* <p>
* // result :<br>
* TWO SELECTED<br>
*/
public class SwitchCaseBreak<T, R> extends SwitchCase<T, R> {
public SwitchCaseBreak(Map<T, Function<T, R>> caseBlocks) {
super(caseBlocks);
}
@Override
void invokeOnNext(Observer<? super R> observer, R onNextValue) {
super.invokeOnNext(observer, onNextValue);
caseBlocks.clear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment