All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.autonomouslogic.commons.rxjava3.Rx3Util Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
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 zipper,
			boolean delayError,
			int bufferSize,
			@NonNull Publisher... sources) {
		return new ZipAll(zipper, delayError, bufferSize, sources).createFlowable();
	}

	public static <@NonNull T, @NonNull R> Flowable zipAllFlowable(
			@NonNull Function zipper, @NonNull Publisher... 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 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);
			});
		});
	}
}