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

net.pincette.rs.DequePublisher Maven / Gradle / Ivy

There is a newer version: 3.7.1
Show newest version
package net.pincette.rs;


import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import net.pincette.util.NotifyingDeque;

/**
 * A publisher that is fed by a deque. The elements should be added to the head of the queue.
 *
 * @param  the value type.
 * @author Werner Donné
 * @since 3.1
 */
public class DequePublisher implements Publisher {
  private final Deque deque = new NotifyingDeque<>(this::added, null);
  private boolean closed;
  private boolean completed;
  private long requested;
  private Subscriber subscriber;

  /**
   * Creates a deque publisher.
   *
   * @return The publisher.
   * @param  the value type.
   */
  public static  DequePublisher dequePublisher() {
    return new DequePublisher<>();
  }

  private void added(final Deque deque) {
    if (closed) {
      throw new UnsupportedOperationException("The publisher is already closed.");
    }

    dispatch(this::emit);
  }

  /**
   * After closing, it is not allowed to add more elements to the deque. As soon as the deque is
   * depleted the subscriber will be notified of the end of the stream.
   */
  public void close() {
    dispatch(
        () -> {
          closed = true;

          if (deque.isEmpty()) {
            complete();
          }
        });
  }

  private void complete() {
    completed = true;
    dispatch(subscriber::onComplete);
  }

  private List consume() {
    int i;
    final List result = new ArrayList<>((int) requested);

    for (i = 0; i < requested && !deque.isEmpty(); ++i) {
      result.add(deque.removeLast());
    }

    requested -= i;

    return result;
  }

  private void dispatch(final Runnable action) {
    Serializer.dispatch(action::run, this::onError);
  }

  private void emit() {
    if (subscriber != null && !completed) {
      final List elements = consume();

      dispatch(() -> elements.forEach(e -> subscriber.onNext(e)));

      if (closed && deque.isEmpty()) {
        complete();
      }
    }
  }

  /**
   * Returns the deque to which the elements are fed. You should only add elements to the head of
   * the deque, otherwise the publishing order will be random.
   *
   * @return The deque.
   */
  public Deque getDeque() {
    return deque;
  }

  private void onError(final Throwable t) {
    if (subscriber != null) {
      subscriber.onError(t);
    }
  }

  public void subscribe(final Subscriber subscriber) {
    if (subscriber == null) {
      throw new NullPointerException("A subscriber can't be null.");
    }

    this.subscriber = subscriber;
    subscriber.onSubscribe(new Backpressure());
  }

  private class Backpressure implements Subscription {
    public void cancel() {
      deque.clear();
      close();
    }

    public void request(final long number) {
      if (number <= 0) {
        throw new IllegalArgumentException("A request must be strictly positive.");
      }

      dispatch(
          () -> {
            requested += number;
            emit();
          });
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy