package it.tdlight.reactiveapi; import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.LongConsumer; import org.jetbrains.annotations.NotNull; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.FluxSink; import reactor.core.publisher.FluxSink.OverflowStrategy; import reactor.core.publisher.Mono; import reactor.core.publisher.Signal; import reactor.util.context.Context; public class ReactorUtils { @SuppressWarnings("rawtypes") private static final WaitingSink WAITING_SINK = new WaitingSink<>(); public static Flux subscribeOnce(Flux f) { AtomicBoolean subscribed = new AtomicBoolean(); return f.doOnSubscribe(s -> { if (!subscribed.compareAndSet(false, true)) { throw new UnsupportedOperationException("Can't subscribe more than once!"); } }); } public static Mono subscribeOnce(Mono f) { AtomicBoolean subscribed = new AtomicBoolean(); return f.doOnSubscribe(s -> { if (!subscribed.compareAndSet(false, true)) { throw new UnsupportedOperationException("Can't subscribe more than once!"); } }); } public static Flux createLastestSubscriptionFlux(Flux upstream, int maxBufferSize) { return upstream.transform(parent -> { AtomicReference subscriptionAtomicReference = new AtomicReference<>(); AtomicReference> prevEmitterRef = new AtomicReference<>(); Deque> queue = new ArrayDeque<>(maxBufferSize); return Flux.create(emitter -> { var prevEmitter = prevEmitterRef.getAndSet(emitter); if (prevEmitter != null) { if (prevEmitter != WAITING_SINK) { prevEmitter.error(new CancellationException()); } synchronized (queue) { Signal next; while (!emitter.isCancelled() && (next = queue.peek()) != null) { if (next.isOnNext()) { queue.poll(); var nextVal = next.get(); assert nextVal != null; emitter.next(nextVal); } else if (next.isOnError()) { var throwable = next.getThrowable(); assert throwable != null; emitter.error(throwable); break; } else if (next.isOnComplete()) { emitter.complete(); break; } else { throw new UnsupportedOperationException(); } } } } else { parent.subscribe(new CoreSubscriber<>() { @Override public void onSubscribe(@NotNull Subscription s) { subscriptionAtomicReference.set(s); } @Override public void onNext(K payload) { FluxSink prevEmitter = prevEmitterRef.get(); if (prevEmitter != WAITING_SINK) { prevEmitter.next(payload); } else { synchronized (queue) { queue.add(Signal.next(payload)); } } } @Override public void onError(Throwable throwable) { FluxSink prevEmitter = prevEmitterRef.get(); synchronized (queue) { queue.add(Signal.error(throwable)); } if (prevEmitter != WAITING_SINK) { prevEmitter.error(throwable); } } @Override public void onComplete() { FluxSink prevEmitter = prevEmitterRef.get(); synchronized (queue) { queue.add(Signal.complete()); } if (prevEmitter != WAITING_SINK) { prevEmitter.complete(); } } }); } var s = subscriptionAtomicReference.get(); emitter.onRequest(n -> { if (n > maxBufferSize) { emitter.error(new UnsupportedOperationException("Requests count is bigger than max buffer size! " + n + " > " + maxBufferSize)); } else { s.request(n); } }); //noinspection unchecked emitter.onCancel(() -> prevEmitterRef.compareAndSet(emitter, WAITING_SINK)); //noinspection unchecked emitter.onDispose(() -> prevEmitterRef.compareAndSet(emitter, WAITING_SINK)); }, OverflowStrategy.BUFFER); }); } private static class WaitingSink implements FluxSink { @Override public @NotNull FluxSink next(@NotNull T t) { throw new UnsupportedOperationException(); } @Override public void complete() { throw new UnsupportedOperationException(); } @Override public void error(@NotNull Throwable e) { throw new UnsupportedOperationException(); } @Override public @NotNull Context currentContext() { throw new UnsupportedOperationException(); } @Override public long requestedFromDownstream() { throw new UnsupportedOperationException(); } @Override public boolean isCancelled() { throw new UnsupportedOperationException(); } @Override public @NotNull FluxSink onRequest(@NotNull LongConsumer consumer) { throw new UnsupportedOperationException(); } @Override public @NotNull FluxSink onCancel(@NotNull Disposable d) { throw new UnsupportedOperationException(); } @Override public @NotNull FluxSink onDispose(@NotNull Disposable d) { throw new UnsupportedOperationException(); } } }