com.autonomouslogic.commons.rxjava3.Rx3Util Maven / Gradle / Ivy
package com.autonomouslogic.commons.rxjava3;
import com.autonomouslogic.commons.rxjava3.internal.CheckOrder;
import com.autonomouslogic.commons.rxjava3.internal.ErrorWrapFlowableTransformer;
import com.autonomouslogic.commons.rxjava3.internal.ErrorWrapObservableTransformer;
import com.autonomouslogic.commons.rxjava3.internal.OrderedMerger;
import com.autonomouslogic.commons.rxjava3.internal.ZipAll;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.FlowableTransformer;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.ObservableTransformer;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.functions.Predicate;
import java.time.Duration;
import java.util.Comparator;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.reactivestreams.Publisher;
/**
* Various helper methods for working with RxJava 3.
*/
public class Rx3Util {
private Rx3Util() {}
/**
* Converts a {@link CompletionStage} to a {@link Single}.
*
* Null return values will result in an error from RxJava, as those aren't allowed.
* Use {@link #toMaybe(CompletionStage)} instead to handle null values properly.
*
* {@link Single#fromFuture(Future)} works in a blocking fashion, whereas {@link CompletionStage} can be utilised to avoid blocking calls.
*
* @param future the completion stage
* @return the Single
* @param the return parameter of the future
*/
public static Single toSingle(CompletionStage future) {
return Single.create(subscriber -> {
future.thenAccept(result -> {
subscriber.onSuccess(result);
})
.exceptionally(e -> {
subscriber.onError(e);
return null;
});
});
}
/**
* Converts a {@link CompletionStage} to a {@link Maybe}.
*
* Null return values will result in an empty Maybe.
*
* {@link Maybe#fromFuture(Future)} works in a blocking fashion, whereas {@link CompletionStage} can be utilised to avoid blocking calls.
*
* @param future the completion stage
* @return the Maybe
* @param the return parameter of the future
*/
public static Maybe toMaybe(CompletionStage future) {
return Maybe.create(subscriber -> {
future.thenAccept(result -> {
if (result == null) {
subscriber.onComplete();
} else {
subscriber.onSuccess(result);
}
})
.exceptionally(e -> {
subscriber.onError(e);
return null;
});
});
}
/**
* Converts a {@link CompletionStage} to a {@link Completable}.
*
* {@link Completable#fromFuture(Future)} works in a blocking fashion, whereas {@link CompletionStage} can be utilised to avoid blocking calls.
*
* @param future the completion stage
* @return the Completable
*/
public static Completable toCompletable(CompletionStage future) {
return Completable.create(subscriber -> {
future.thenAccept(ignore -> {
subscriber.onComplete();
})
.exceptionally(e -> {
subscriber.onError(e);
return null;
});
});
}
/**
* Wraps an {@link ObservableTransformer} such that any errors thrown from anything done by the
* transformer
will be wrapped in a {@link RuntimeException} with the provided message.
* Any errors occurring before or after the transformer
is applied are not subject to exception
* wrapping.
* This is useful when debugging multi-threaded asynchronous code where the relevant stack trace can get lost.
*
* @param message message for the exception
* @param transformer transformer used for composition
* @return a new transformer
* @param see ObservableTransformer
* @param see ObservableTransformer
*/
public static ObservableTransformer wrapTransformerErrors(
String message, ObservableTransformer transformer) {
return new ErrorWrapObservableTransformer<>(message, transformer);
}
/**
* Wraps an {@link FlowableTransformer} such that any errors thrown from anything done by the
* transformer
will be wrapped in a {@link RuntimeException} with the provided message.
* Any errors occurring before or after the transformer
is applied are not subject to exception
* wrapping.
* This is useful when debugging multi-threaded asynchronous code where the relevant stack trace can get lost.
*
* @param message message for the exception
* @param transformer transformer used for composition
* @return a new transformer
* @param see FlowableTransformer
* @param see FlowableTransformer
*/
public static FlowableTransformer wrapTransformerErrors(
String message, FlowableTransformer transformer) {
return new ErrorWrapFlowableTransformer<>(message, transformer);
}
/**
* Merges a number of sources together always picking the next item from the source which compares the lowest.
* In order to merge sources in a completely ordered way, it is assumed the sources are already themselves sorted.
* @param comparator
* @param sources
* @return the merged Publisher
* @param the type of the Publisher to merge
*/
public static Publisher orderedMerge(Comparator comparator, Publisher... sources) {
return new OrderedMerger<>(comparator, sources).createPublisher();
}
/**
* Like {@link Flowable#zipArray(Function, boolean, int, Publisher[])}, but keeps going until all the sources
* have ended. It does this by wrapping all the values in {@link Optional}s and replacing the ended sources with empty
* ones.
*/
public static <@NonNull T, @NonNull R> Flowable zipAllFlowable(
@NonNull Function super Object[], ? extends R> zipper,
boolean delayError,
int bufferSize,
@NonNull Publisher extends T>... sources) {
return new ZipAll(zipper, delayError, bufferSize, sources).createFlowable();
}
public static <@NonNull T, @NonNull R> Flowable zipAllFlowable(
@NonNull Function super Object[], ? extends R> zipper, @NonNull Publisher extends T>... sources) {
return zipAllFlowable(zipper, false, Flowable.bufferSize(), sources);
}
/**
* Sorts a stream within a sliding window.
* @param comparator the comparator
* @param minWindowSize the minimum window size. The actual sorting window will be larger.
*/
public static <@NonNull T> WindowSort windowSort(Comparator comparator, int minWindowSize) {
return new WindowSort<>(comparator, minWindowSize);
}
/**
* Creates a transformer which will error if the stream isn't strictly ordered.
* @param comparator the comparator
*/
public static <@NonNull T> CheckOrder checkOrder(Comparator comparator) {
return new CheckOrder(comparator);
}
public static FlowableTransformer retryWithDelayFlowable(int times, Duration delay) {
return retryWithDelayFlowable(times, delay, e -> true);
}
public static FlowableTransformer retryWithDelayFlowable(
int times, Duration delay, Predicate super Throwable> predicate) {
if (times < 0) {
throw new IllegalArgumentException("times >= 0 required but it was " + times);
}
var delayNs = delay.toNanos();
if (delayNs < 0) {
throw new IllegalArgumentException("delay must be zero or more");
}
return upstream -> upstream.retryWhen(e -> {
var t = new AtomicInteger();
return e.flatMap(err -> {
int i = t.incrementAndGet();
if (i <= times && predicate.test(err)) {
return Flowable.timer(delayNs, TimeUnit.NANOSECONDS);
}
return Flowable.error(err);
});
});
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy