/* * Copyright (c) 2011-2015 Pivotal Software Inc, All Rights Reserved. * * 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.ripc.test.internal; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; /** * Original {@see reactor.core.reactivestreams.PublisherFactory} from {@see http://projectreactor.io}. * Use JDK 8 constructs. * <p> * A Reactive Streams {@link org.reactivestreams.Publisher} factory which callbacks on start, request and shutdown * <p> * The Publisher will directly forward all the signals passed to the subscribers and complete when onComplete is called. * <p> * Create such publisher with the provided factory, E.g.: * <pre> * {@code * PublisherFactory.create((n, sub) -> { * for(int i = 0; i < n; i++){ * sub.onNext(i); * } * } * } * </pre> * * @author Stephane Maldini */ public final class PublisherFactory { /** * Create a {@link Publisher} reacting on requests with the passed {@link BiConsumer} * * @param requestConsumer A {@link BiConsumer} with left argument request and right argument target subscriber * @param <T> The type of the data sequence * @return a fresh Reactive Streams publisher ready to be subscribed */ public static <T> Publisher<T> create(BiConsumer<Long, SubscriberWithContext<T, Void>> requestConsumer) { return create(requestConsumer, null, null); } /** * Create a {@link Publisher} reacting on requests with the passed {@link BiConsumer} * The argument {@code contextFactory} is executed once by new subscriber to generate a context shared by every * request calls. * * @param requestConsumer A {@link BiConsumer} with left argument request and right argument target subscriber * @param contextFactory A {@link Function} called for every new subscriber returning an immutable context (IO * connection...) * @param <T> The type of the data sequence * @param <C> The type of contextual information to be read by the requestConsumer * @return a fresh Reactive Streams publisher ready to be subscribed */ public static <T, C> Publisher<T> create(BiConsumer<Long, SubscriberWithContext<T, C>> requestConsumer, Function<Subscriber<? super T>, C> contextFactory) { return create(requestConsumer, contextFactory, null); } /** * Create a {@link Publisher} reacting on requests with the passed {@link BiConsumer}. * The argument {@code contextFactory} is executed once by new subscriber to generate a context shared by every * request calls. * The argument {@code shutdownConsumer} is executed once by subscriber termination event (cancel, onComplete, * onError). * * @param requestConsumer A {@link BiConsumer} with left argument request and right argument target subscriber * @param contextFactory A {@link Function} called once for every new subscriber returning an immutable context * (IO connection...) * @param shutdownConsumer A {@link Consumer} called once everytime a subscriber terminates: cancel, onComplete(), * onError() * @param <T> The type of the data sequence * @param <C> The type of contextual information to be read by the requestConsumer * @return a fresh Reactive Streams publisher ready to be subscribed */ public static <T, C> Publisher<T> create(BiConsumer<Long, SubscriberWithContext<T, C>> requestConsumer, Function<Subscriber<? super T>, C> contextFactory, Consumer<C> shutdownConsumer) { return new ReactorPublisher<T, C>(requestConsumer, contextFactory, shutdownConsumer); } /** * Create a {@link Publisher} reacting on each available {@link Subscriber} read derived with the passed {@link * Consumer}. If a previous request is still running, avoid recursion and extend the previous request iterations. * * @param requestConsumer A {@link Consumer} invoked when available read with the target subscriber * @param <T> The type of the data sequence * @return a fresh Reactive Streams publisher ready to be subscribed */ public static <T> Publisher<T> forEach(Consumer<SubscriberWithContext<T, Void>> requestConsumer) { return forEach(requestConsumer, null, null); } /** * Create a {@link Publisher} reacting on each available {@link Subscriber} read derived with the passed {@link * Consumer}. If a previous request is still running, avoid recursion and extend the previous request iterations. * The argument {@code contextFactory} is executed once by new subscriber to generate a context shared by every * request calls. * * @param requestConsumer A {@link Consumer} invoked when available read with the target subscriber * @param contextFactory A {@link Function} called for every new subscriber returning an immutable context (IO * connection...) * @param <T> The type of the data sequence * @param <C> The type of contextual information to be read by the requestConsumer * @return a fresh Reactive Streams publisher ready to be subscribed */ public static <T, C> Publisher<T> forEach(Consumer<SubscriberWithContext<T, C>> requestConsumer, Function<Subscriber<? super T>, C> contextFactory) { return forEach(requestConsumer, contextFactory, null); } /** * Create a {@link Publisher} reacting on each available {@link Subscriber} read derived with the passed {@link * Consumer}. If a previous request is still running, avoid recursion and extend the previous request iterations. * The argument {@code contextFactory} is executed once by new subscriber to generate a context shared by every * request calls. * The argument {@code shutdownConsumer} is executed once by subscriber termination event (cancel, onComplete, * onError). * * @param requestConsumer A {@link Consumer} invoked when available read with the target subscriber * @param contextFactory A {@link Function} called once for every new subscriber returning an immutable context * (IO connection...) * @param shutdownConsumer A {@link Consumer} called once everytime a subscriber terminates: cancel, onComplete(), * onError() * @param <T> The type of the data sequence * @param <C> The type of contextual information to be read by the requestConsumer * @return a fresh Reactive Streams publisher ready to be subscribed */ public static <T, C> Publisher<T> forEach(final Consumer<SubscriberWithContext<T, C>> requestConsumer, Function<Subscriber<? super T>, C> contextFactory, Consumer<C> shutdownConsumer) { return new ForEachPublisher<T, C>(requestConsumer, contextFactory, shutdownConsumer); } private static class ReactorPublisher<T, C> implements Publisher<T> { protected final Function<Subscriber<? super T>, C> contextFactory; protected final BiConsumer<Long, SubscriberWithContext<T, C>> requestConsumer; protected final Consumer<C> shutdownConsumer; protected ReactorPublisher(BiConsumer<Long, SubscriberWithContext<T, C>> requestConsumer, Function<Subscriber<? super T>, C> contextFactory, Consumer<C> shutdownConsumer) { this.requestConsumer = requestConsumer; this.contextFactory = contextFactory; this.shutdownConsumer = shutdownConsumer; } @Override final public void subscribe(final Subscriber<? super T> subscriber) { try { final C context = contextFactory != null ? contextFactory.apply(subscriber) : null; subscriber.onSubscribe(createSubscription(subscriber, context)); } catch (PrematureCompleteException pce) { //IGNORE } catch (Throwable throwable) { subscriber.onError(throwable); } } protected Subscription createSubscription(Subscriber<? super T> subscriber, C context) { return new SubscriberProxy<>(subscriber, context, requestConsumer, shutdownConsumer); } } private static final class ForEachPublisher<T, C> extends ReactorPublisher<T, C> { final Consumer<SubscriberWithContext<T, C>> forEachConsumer; public ForEachPublisher(Consumer<SubscriberWithContext<T, C>> forEachConsumer, Function<Subscriber<? super T>, C> contextFactory, Consumer<C> shutdownConsumer) { super(null, contextFactory, shutdownConsumer); this.forEachConsumer = forEachConsumer; } @Override protected Subscription createSubscription(Subscriber<? super T> subscriber, C context) { return new SubscriberProxy<>(subscriber, context, new ForEachBiConsumer<>(forEachConsumer), shutdownConsumer); } } private final static class SubscriberProxy<T, C> extends SubscriberWithContext<T, C> implements Subscription { private final BiConsumer<Long, SubscriberWithContext<T, C>> requestConsumer; private final Consumer<C> shutdownConsumer; public SubscriberProxy(Subscriber<? super T> subscriber, C context, BiConsumer<Long, SubscriberWithContext<T, C>> requestConsumer, Consumer<C> shutdownConsumer ) { super(context, subscriber); this.requestConsumer = requestConsumer; this.shutdownConsumer = shutdownConsumer; } @Override public void request(long n) { if (isCancelled()) { return; } if (n <= 0) { onError(new IllegalArgumentException("Spec. Rule 3.9 - Cannot request a non strictly positive number:" + " " + n)); return; } try { requestConsumer.accept(n, this); } catch (Throwable t) { onError(t); } } @Override public void cancel() { if (TERMINAL_UPDATER.compareAndSet(this, 0, 1)) { doShutdown(); } } @Override public void onError(Throwable t) { if (TERMINAL_UPDATER.compareAndSet(this, 0, 1)) { doShutdown(); subscriber.onError(t); } } @Override public void onComplete() { if (TERMINAL_UPDATER.compareAndSet(this, 0, 1)) { doShutdown(); try { subscriber.onComplete(); } catch (Throwable t) { subscriber.onError(t); } } } private void doShutdown() { if (shutdownConsumer == null) return; try { shutdownConsumer.accept(context); } catch (Throwable t) { subscriber.onError(t); } } @Override public void onSubscribe(Subscription s) { throw new UnsupportedOperationException(" the delegate subscriber is already subscribed"); } @Override public String toString() { return context != null ? context.toString() : ("SubscriberProxy{" + "requestConsumer=" + requestConsumer + ", shutdownConsumer=" + shutdownConsumer + '}'); } } private final static class ForEachBiConsumer<T, C> implements BiConsumer<Long, SubscriberWithContext<T, C>> { private final Consumer<SubscriberWithContext<T, C>> requestConsumer; private volatile long pending = 0L; private final static AtomicLongFieldUpdater<ForEachBiConsumer> PENDING_UPDATER = AtomicLongFieldUpdater.newUpdater(ForEachBiConsumer.class, "pending"); public ForEachBiConsumer(Consumer<SubscriberWithContext<T, C>> requestConsumer) { this.requestConsumer = requestConsumer; } @Override public void accept(Long n, SubscriberWithContext<T, C> sub) { if (pending == Long.MAX_VALUE) { return; } long demand = n; long afterAdd; if (!PENDING_UPDATER.compareAndSet(this, 0L, demand) && (afterAdd = PENDING_UPDATER.addAndGet(this, demand)) != demand) { if (afterAdd < 0L) { if (!PENDING_UPDATER.compareAndSet(this, afterAdd, Long.MAX_VALUE)) { return; } } else { return; } } do { long requestCursor = 0l; while ((requestCursor++ < demand || demand == Long.MAX_VALUE) && !sub.isCancelled()) { requestConsumer.accept(sub); } } while ((demand = PENDING_UPDATER.addAndGet(this, -demand)) > 0L && !sub.isCancelled()); } } public static class PrematureCompleteException extends RuntimeException { static public final PrematureCompleteException INSTANCE = new PrematureCompleteException(); private PrematureCompleteException() { } @Override public synchronized Throwable fillInStackTrace() { return this; } } }