/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.strands.channels.reactivestreams;
import co.paralleluniverse.fibers.FiberFactory;
import co.paralleluniverse.strands.SuspendableAction2;
import co.paralleluniverse.strands.channels.Channel;
import co.paralleluniverse.strands.channels.Channels;
import co.paralleluniverse.strands.channels.Channels.OverflowPolicy;
import co.paralleluniverse.strands.channels.ReceivePort;
import co.paralleluniverse.strands.channels.SendPort;
import co.paralleluniverse.strands.channels.Topic;
import org.reactivestreams.Processor;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
/**
* Converts between Quasar channels and reactive streams
* @author pron
*/
public class ReactiveStreams {
/**
* Subscribes to a given {@link Publisher} and return a {@link ReceivePort} to the subscription.
* This creates an internal <b>single consumer</b> channel that will receive the published elements.
*
* @param bufferSize the size of the buffer of the internal channel; may be {@code -1} for unbounded, but may not be {@code 0})
* @param policy the {@link OverflowPolicy} of the internal channel.
* @param publisher the subscriber
* @return A {@link ReceivePort} which emits the elements published by the subscriber
*/
public static <T> ReceivePort<T> subscribe(int bufferSize, OverflowPolicy policy, Publisher<T> publisher) {
final Channel<T> channel = Channels.newChannel(bufferSize, policy, true, true);
final ChannelSubscriber<T> sub = new ChannelSubscriber<>(channel, false);
publisher.subscribe(sub);
return sub;
}
/**
* Turns a {@link ReceivePort channel} to a {@link Publisher}. All items sent to the channel will be published by
* the publisher.
* <p>
* The publisher will allow a single subscription, unless the channel is a {@link Channels#isTickerChannel(ReceivePort) ticker channel}
* in which case, multiple subscribers will be allowed, and a new {@link Channels#newTickerConsumerFor(Channel) ticker consumer}
* will be created for each.
* <p>
* Every subscription to the returned publisher creates an internal fiber, that will receive items from the
* channel and publish them.
*
* @param channel the channel
* @param ff the {@link FiberFactory} to create the internal fiber(s); if {@code null} then a default factory is used.
* @return a new publisher for the channel's items
*/
public static <T> Publisher<T> toPublisher(ReceivePort<T> channel, FiberFactory ff) {
if (Channels.isTickerChannel(channel)) {
return new ChannelPublisher<T>(ff, channel, false) {
@Override
protected ChannelSubscription<T> newChannelSubscription(Subscriber<? super T> s, Object channel) {
return super.newChannelSubscription(s, Channels.newTickerConsumerFor((Channel<T>) channel));
}
};
} else
return new ChannelPublisher<T>(ff, channel, true);
}
/**
* Turns a {@link ReceivePort channel} to a {@link Publisher}. All items sent to the channel will be published by
* the publisher.
* <p>
* The publisher will allow a single subscription, unless the channel is a {@link Channels#isTickerChannel(ReceivePort) ticker channel}
* in which case, multiple subscribers will be allowed, and a new {@link Channels#newTickerConsumerFor(Channel) ticker consumer}
* will be created for each.
* <p>
* Every subscription to the returned publisher creates an internal fiber, that will receive items from the
* channel and publish them.
* <p>
* Calling this method is the same as calling {@link #toPublisher(ReceivePort, FiberFactory) toPublisher(channel, null)
*
* @param channel the channel
* @return a new publisher for the channel's items
*/
public static <T> Publisher<T> toPublisher(ReceivePort<T> channel) {
return toPublisher(channel, null);
}
/**
* Turns a {@link Topic topic} to a {@link Publisher}. All items sent to the topic will be published by
* the publisher.
* <p>
* A new <i>transfer channel</i> (i.e. a blocking channel with a buffer of size 0) subscribed to the topic will be created for every subscriber.
* <p>
* Every subscription to the returned publisher creates an internal fiber, that will receive items from the
* subscription's channel and publish them.
*
* @param topic the topic
* @param ff the {@link FiberFactory} to create the internal fiber(s); if {@code null} then a default factory is used.
* @return a new publisher for the topic's items
*/
public static <T> Publisher<T> toPublisher(Topic<T> topic, final FiberFactory ff) {
return new ChannelPublisher<T>(ff, topic, false) {
@Override
protected ChannelSubscription<T> newChannelSubscription(Subscriber<? super T> s, Object channel) {
final Topic<T> topic = (Topic<T>) channel;
final Channel<T> ch = Channels.newChannel(0);
try {
topic.subscribe(ch);
return new ChannelSubscription<T>(s, ch) {
@Override
public void cancel() {
super.cancel();
topic.unsubscribe(ch);
}
};
} catch (Exception e) {
topic.unsubscribe(ch);
throw e;
}
}
};
}
/**
* Turns a {@link Topic topic} to a {@link Publisher}. All items sent to the topic will be published by
* the publisher.
* <p>
* A new <i>transfer channel</i> (i.e. a blocking channel with a buffer of size 0) subscribed to the topic will be created for every subscriber.
* <p>
* Every subscription to the returned publisher creates an internal fiber, that will receive items from the
* subscription's channel and publish them.
* <p>
* Calling this method is the same as calling {@link #toPublisher(ReceivePort, FiberFactory) toPublisher(channel, null)
*
* @param topic the topic
* @return a new publisher for the topic's items
*/
public static <T> Publisher<T> toPublisher(Topic<T> topic) {
return toPublisher(topic, null);
}
/**
* Turns a {@link Channels#fiberTransform(ReceivePort, SendPort, SuspendableAction2) transformer} into a {@link Publisher}.
* The transformer will run in its own fiber.
*
* @param <T> the type of elements flowing into the transformer
* @param <R> the type of elements flowing out of the transformer
* @param ff the {@link FiberFactory} to create the internal fiber(s); if {@code null} then a default factory is used.
* @param bufferSize the size of the buffer of the internal channel; may be {@code -1} for unbounded, but may not be {@code 0})
* @param policy the {@link OverflowPolicy} of the internal channel.
* @param batch if the channel has a bounded buffer, whether to request further elements from the publisher in batches
* whenever the channel's buffer is depleted, or after consuming each element.
* @param transformer a function that reads from it's input channel and writes to its output channel
* @return a {@code Processor} running the given transformer.
*/
public static <T, R> Processor<T, R> toProcessor(FiberFactory ff, int bufferSize, OverflowPolicy policy, SuspendableAction2<? extends ReceivePort<? super T>, ? extends SendPort<? extends R>> transformer) {
final Channel<T> in = Channels.newChannel(bufferSize, policy, true, true);
final Channel<R> out = Channels.newChannel(bufferSize, policy, true, true);
return new ChannelProcessor<T, R>(ff, false, in, out, transformer);
}
/**
* Turns a {@link Channels#fiberTransform(ReceivePort, SendPort, SuspendableAction2) transformer} into a {@link Publisher}.
* The transformer will run in its own fiber.
* <p>
* Same as calling
* {@link #toProcessor(FiberFactory, int, OverflowPolicy, boolean, SuspendableAction2) toProcessor(null, bufferSize, policy, transformer)
*
* @param <T> the type of elements flowing into the transformer
* @param <R> the type of elements flowing out of the transformer
* @param ff the {@link FiberFactory} to create the internal fiber(s); if {@code null} then a default factory is used.
* @param bufferSize the size of the buffer of the internal channel; may be {@code -1} for unbounded, but may not be {@code 0})
* @param policy the {@link OverflowPolicy} of the internal channel.
* @param batch if the channel has a bounded buffer, whether to request further elements from the publisher in batches
* whenever the channel's buffer is depleted, or after consuming each element.
* @param transformer a function that reads from it's input channel and writes to its output channel
* @return a {@code Processor} running the given transformer.
*/
public static <T, R> Processor<T, R> toProcessor(int bufferSize, OverflowPolicy policy, SuspendableAction2<? extends ReceivePort<? super T>, ? extends SendPort<? extends R>> transformer) {
return toProcessor(null, bufferSize, policy, transformer);
}
}