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

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

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

import static java.lang.Long.MAX_VALUE;
import static java.lang.Thread.currentThread;
import static java.util.concurrent.locks.LockSupport.unpark;
import static net.pincette.rs.Util.parking;
import static net.pincette.util.Util.rethrow;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.function.Consumer;
import net.pincette.function.SideEffect;

/**
 * With this class a publisher can be wrapped in a blocking iterable. It buffers the received
 * elements proportional to the size of the requests towards the publisher.
 *
 * @param  the element type.
 * @since 1.2
 */
public class BlockingSubscriber implements Subscriber, Iterable {
  private final Iterator iterator = new Elements();
  private final Queue queue = new ConcurrentLinkedQueue<>();
  private final long requestSize;
  private final Spliterator spliterator = new SplitElements();
  private final Thread thread = currentThread();
  private boolean complete;
  private Subscription subscription;

  /**
   * Creates a subscriber with a request size of 100.
   *
   * @since 1.2
   */
  public BlockingSubscriber() {
    this(100);
  }

  /**
   * Creates a subscriber with a subscription that requests requestSize elements at the
   * time.
   *
   * @param requestSize the number of elements the subscription asks from the publisher.
   * @since 1.2
   */
  public BlockingSubscriber(final long requestSize) {
    this.requestSize = requestSize;
  }

  public static  BlockingSubscriber blockingSubscriber() {
    return new BlockingSubscriber<>();
  }

  public static  BlockingSubscriber blockingSubscriber(final long requestSize) {
    return new BlockingSubscriber<>(requestSize);
  }

  /**
   * Returns an immutable iterator over the published elements. The iterator will block when there
   * are no elements while the publisher isn't completed yet.
   *
   * @return The iterator.
   * @since 1.2
   */
  public Iterator iterator() {
    return iterator;
  }

  private void more() {
    if (subscription != null) {
      subscription.request(requestSize);
    }
  }

  public void onComplete() {
    complete = true;
    unpark(thread);
  }

  public void onError(final Throwable t) {
    if (t == null) {
      throw new NullPointerException("Can't throw null.");
    }

    complete = true;
    unpark(thread);
    rethrow(t);
  }

  public void onNext(T value) {
    if (value == null) {
      throw new NullPointerException("Can't emit null.");
    }

    queue.add(value);

    if (queue.size() >= requestSize) {
      unpark(thread);
    }
  }

  public void onSubscribe(final Subscription subscription) {
    if (subscription == null) {
      throw new NullPointerException("A subscription can't be null.");
    }

    if (this.subscription != null) {
      subscription.cancel();
    } else {
      this.subscription = subscription;
      more();
    }
  }

  /**
   * Returns an ordered immutable spliterator that will not split.
   *
   * @return The spliterator.
   * @since 1.2
   */
  @Override
  public Spliterator spliterator() {
    return spliterator;
  }

  private class Elements implements Iterator {
    public boolean hasNext() {
      return !queue.isEmpty()
          || (!complete && SideEffect.run(this::park).andThenGet(this::hasNext));
    }

    public T next() {
      if (subscription == null || queue.isEmpty()) {
        throw new NoSuchElementException();
      }

      return queue.poll();
    }

    private boolean noData() {
      return subscription == null || (!complete && queue.isEmpty());
    }

    private void park() {
      more();

      while (noData()) {
        parking(this, requestSize * 10);
      }
    }
  }

  private class SplitElements implements Spliterator {
    public int characteristics() {
      return ORDERED | IMMUTABLE;
    }

    public long estimateSize() {
      return MAX_VALUE;
    }

    public boolean tryAdvance(final Consumer action) {
      return iterator.hasNext()
          && SideEffect.run(() -> action.accept(iterator.next())).andThenGet(() -> true);
    }

    public Spliterator trySplit() {
      return null;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy