Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.xbib.helianthus.common.stream.DefaultStreamMessage Maven / Gradle / Ivy
package org.xbib.helianthus.common.stream;
import static java.util.Objects.requireNonNull;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.xbib.helianthus.common.util.Exceptions;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Supplier;
import java.util.logging.Logger;
/**
* A {@link StreamMessage} which buffers the elements to be signaled into a {@link Queue}.
*
* This class implements the {@link StreamWriter} interface as well. A written element will be buffered
* into the {@link Queue} until a {@link Subscriber} consumes it. Use {@link StreamWriter#onDemand(Runnable)}
* to control the rate of production so that the {@link Queue} does not grow up infinitely.
*
*
{@code
* void stream(QueueBasedPublished pub, int start, int end) {
* // Write 100 integers at most.
* int actualEnd = (int) Math.min(end, start + 100L);
* int i;
* for (i = start; i < actualEnd; i++) {
* pub.write(i);
* }
*
* if (i == end) {
* // Wrote the last element.
* return;
* }
*
* pub.onDemand(() -> stream(pub, i, end));
* }
*
* final QueueBasedPublisher myPub = new QueueBasedPublisher<>();
* stream(myPub, 0, Integer.MAX_VALUE);
* }
*
* @param the type of element signaled
*/
public class DefaultStreamMessage implements StreamMessage, StreamWriter {
private static final Logger logger = Logger.getLogger(DefaultStreamMessage.class.getName());
private static final CloseEvent SUCCESSFUL_CLOSE = new CloseEvent(null);
private static final CloseEvent CANCELLED_CLOSE = new CloseEvent(
Exceptions.clearTrace(CancelledSubscriptionException.get()));
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater subscriptionUpdater =
AtomicReferenceFieldUpdater.newUpdater(DefaultStreamMessage.class, SubscriptionImpl.class, "subscription");
@SuppressWarnings("rawtypes")
private static final AtomicLongFieldUpdater demandUpdater =
AtomicLongFieldUpdater.newUpdater(DefaultStreamMessage.class, "demand");
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater stateUpdater =
AtomicReferenceFieldUpdater.newUpdater(DefaultStreamMessage.class, State.class, "state");
private final Queue queue;
private final CompletableFuture closeFuture = new CompletableFuture<>();
@SuppressWarnings("unused")
private volatile SubscriptionImpl subscription; // set only via subscriptionUpdater
@SuppressWarnings("unused")
private volatile long demand; // set only via demandUpdater
@SuppressWarnings("FieldMayBeFinal")
private volatile State state = State.OPEN;
private volatile boolean wroteAny;
public DefaultStreamMessage() {
this(new ConcurrentLinkedQueue<>());
}
public DefaultStreamMessage(Queue queue) {
this.queue = requireNonNull(queue, "queue");
}
@Override
public boolean isOpen() {
return state == State.OPEN;
}
@Override
public boolean isEmpty() {
return !isOpen() && !wroteAny;
}
@Override
public void subscribe(Subscriber super T> subscriber) {
requireNonNull(subscriber, "subscriber");
subscribe0(new SubscriptionImpl(this, subscriber, null));
}
@Override
public void subscribe(Subscriber super T> subscriber, Executor executor) {
requireNonNull(subscriber, "subscriber");
requireNonNull(executor, "executor");
subscribe0(new SubscriptionImpl(this, subscriber, executor));
}
private void subscribe0(SubscriptionImpl subscription) {
if (!subscriptionUpdater.compareAndSet(this, null, subscription)) {
throw new IllegalStateException(
"subscribed by other subscriber already: " + this.subscription.subscriber());
}
final Executor executor = subscription.executor();
if (executor != null) {
executor.execute(() -> subscription.subscriber().onSubscribe(subscription));
} else {
subscription.subscriber().onSubscribe(subscription);
}
}
@Override
public void abort() {
final SubscriptionImpl currentSubscription = subscription;
if (currentSubscription != null) {
currentSubscription.cancel();
return;
}
final SubscriptionImpl newSubscription = new SubscriptionImpl(this, AbortingSubscriber.INSTANCE, null);
if (subscriptionUpdater.compareAndSet(this, null, newSubscription)) {
newSubscription.subscriber().onSubscribe(newSubscription);
} else {
subscription.cancel();
}
}
@Override
public boolean write(T obj) {
requireNonNull(obj, "obj");
if (!isOpen()) {
return false;
}
wroteAny = true;
pushObject(obj);
return true;
}
@Override
public boolean write(Supplier extends T> supplier) {
return write(supplier.get());
}
@Override
public CompletableFuture onDemand(Runnable task) {
requireNonNull(task, "task");
final AwaitDemandFuture f = new AwaitDemandFuture();
if (!isOpen()) {
f.completeExceptionally(ClosedPublisherException.get());
} else {
pushObject(f);
}
return f.thenRun(task);
}
private void pushObject(Object obj) {
queue.add(obj);
notifySubscriber();
}
protected void notifySubscriber() {
final SubscriptionImpl subscription = this.subscription;
if (subscription == null) {
return;
}
final Queue queue = this.queue;
if (queue.isEmpty()) {
return;
}
final Executor executor = subscription.executor();
if (executor != null) {
executor.execute(() -> notifySubscribers0(subscription, queue));
} else {
notifySubscribers0(subscription, queue);
}
}
private void notifySubscribers0(SubscriptionImpl subscription, Queue queue) {
if (state == State.CLEANUP) {
cleanup();
return;
}
final Subscriber subscriber = subscription.subscriber();
for (;;) {
final Object o = queue.peek();
if (o == null) {
break;
}
if (o instanceof CloseEvent) {
notifySubscriberWithCloseEvent(subscriber, (CloseEvent) o);
break;
}
if (o instanceof AwaitDemandFuture) {
if (notifyCompletableFuture(queue)) {
// Notified successfully.
continue;
} else {
// Not enough demand.
break;
}
}
if (!notifySubscriber(subscriber, queue)) {
// Not enough demand.
break;
}
}
}
private boolean notifySubscriber(Subscriber subscriber, Queue queue) {
for (;;) {
final long demand = this.demand;
if (demand == 0) {
break;
}
if (demand == Long.MAX_VALUE || demandUpdater.compareAndSet(this, demand, demand - 1)) {
@SuppressWarnings("unchecked")
final T o = (T) queue.remove();
onRemoval(o);
subscriber.onNext(o);
return true;
}
}
return false;
}
private boolean notifyCompletableFuture(Queue queue) {
if (demand == 0) {
return false;
}
@SuppressWarnings("unchecked")
final CompletableFuture f = (CompletableFuture) queue.remove();
f.complete(null);
return true;
}
private void notifySubscriberWithCloseEvent(Subscriber subscriber, CloseEvent o) {
setState(State.CLEANUP);
try {
final Throwable cause = o.cause();
if (cause == null) {
try {
subscriber.onComplete();
} finally {
closeFuture.complete(null);
}
} else {
try {
if (!o.isCancelled()) {
subscriber.onError(cause);
}
} finally {
closeFuture.completeExceptionally(cause);
}
}
} finally {
cleanup();
}
}
protected void onRemoval(T obj) {
}
@Override
public CompletableFuture closeFuture() {
return closeFuture;
}
@Override
public void close() {
if (setState(State.CLOSED)) {
pushObject(SUCCESSFUL_CLOSE);
}
}
@Override
public void close(Throwable cause) {
requireNonNull(cause, "cause");
if (cause instanceof CancelledSubscriptionException) {
throw new IllegalArgumentException("cause: " + cause + " (must use Subscription.cancel())");
}
if (setState(State.CLOSED)) {
pushObject(new CloseEvent(cause));
}
}
private boolean setState(State state) {
if (state == State.OPEN) {
throw new IllegalStateException();
}
return stateUpdater.compareAndSet(this, State.OPEN, state);
}
private void cleanup() {
final Throwable cause = ClosedPublisherException.get();
for (;;) {
final Object e = queue.poll();
if (e == null) {
break;
}
if (e instanceof CloseEvent) {
final Throwable closeCause = ((CloseEvent) e).cause();
if (closeCause != null) {
closeFuture.completeExceptionally(closeCause);
} else {
closeFuture.complete(null);
}
continue;
}
if (e instanceof CompletableFuture) {
((CompletableFuture>) e).completeExceptionally(cause);
}
@SuppressWarnings("unchecked")
T obj = (T) e;
onRemoval(obj);
}
}
private enum State {
/**
* The initial state. Will enter {@link #CLOSED} or {@link #CLEANUP}.
*/
OPEN,
/**
* {@link #close()} or {@link #close(Throwable)} has been called. Will enter {@link #CLEANUP} after
* {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)} is invoked.
*/
CLOSED,
/**
* Anything in the queue must be cleaned up.
* Enters this state when there's no chance of consumption by subscriber.
* i.e. when any of the following methods are invoked:
*
* {@link Subscription#cancel()}
* {@link #abort()} (via {@link AbortingSubscriber})
* {@link Subscriber#onComplete()}
* {@link Subscriber#onError(Throwable)}
*
*/
CLEANUP
}
private static final class SubscriptionImpl implements Subscription {
private final DefaultStreamMessage> publisher;
private final Subscriber subscriber;
private final Executor executor;
@SuppressWarnings("unchecked")
SubscriptionImpl(DefaultStreamMessage> publisher, Subscriber> subscriber, Executor executor) {
this.publisher = publisher;
this.subscriber = (Subscriber) subscriber;
this.executor = executor;
}
Subscriber subscriber() {
return subscriber;
}
Executor executor() {
return executor;
}
@Override
public void request(long n) {
if (n <= 0) {
throw new IllegalArgumentException("n: " + n + " (expected: > 0)");
}
for (;;) {
final long oldDemand = publisher.demand;
final long newDemand;
if (oldDemand >= Long.MAX_VALUE - n) {
newDemand = Long.MAX_VALUE;
} else {
newDemand = oldDemand + n;
}
if (demandUpdater.compareAndSet(publisher, oldDemand, newDemand)) {
if (oldDemand == 0) {
publisher.notifySubscriber();
}
break;
}
}
}
@Override
public void cancel() {
if (publisher.setState(State.CLEANUP)) {
final CloseEvent closeEvent =
Exceptions.isVerbose() ? new CloseEvent(CancelledSubscriptionException.get())
: CANCELLED_CLOSE;
publisher.pushObject(closeEvent);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Subscription:publisher=")
.append(publisher)
.append(",demand=")
.append(publisher.demand)
.append(",executor=")
.append(executor);
return sb.toString();
}
}
private static final class AwaitDemandFuture extends CompletableFuture {
}
private static final class CloseEvent {
private final Throwable cause;
CloseEvent(Throwable cause) {
this.cause = cause;
}
boolean isCancelled() {
return cause instanceof CancelledSubscriptionException;
}
Throwable cause() {
return cause;
}
@Override
public String toString() {
if (cause == null) {
return "CloseEvent";
} else {
return "CloseEvent(" + cause + ')';
}
}
}
}