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

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

package net.pincette.rs;

import static java.lang.Thread.currentThread;
import static java.nio.ByteBuffer.wrap;
import static java.util.concurrent.locks.LockSupport.unpark;
import static net.pincette.rs.Util.parking;

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;

/**
 * This is an output stream that can be used as a reactive streams publisher. It complies with the
 * reactive backpressure mechanism.
 *
 * @author Werner Donn\u00e9
 * @since 1.0
 */
public class OutputStreamPublisher extends OutputStream implements Publisher {
  private final Thread thread = currentThread();
  private final long timeout;
  private long requested;
  private Subscriber subscriber;

  /** Creates the stream with a timeout of 5 seconds. */
  public OutputStreamPublisher() {
    this(5000);
  }

  /**
   * Creates the stream with a timeout. The value -1 indicates no timeout.
   *
   * @param timeout the timeout in millis.
   */
  public OutputStreamPublisher(final long timeout) {
    this.timeout = timeout;
  }

  @Override
  public void close() {
    if (subscriber != null) {
      subscriber.onComplete();
    }
  }

  private void park() {
    while (!requested()) {
      parking(this, timeout);
    }
  }

  private boolean requested() {
    return subscriber != null && requested > 0;
  }

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

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

  @Override
  public void write(final byte[] bytes) {
    write(bytes, 0, bytes.length);
  }

  @Override
  public void write(final byte[] bytes, final int offset, final int length) {
    if (!requested()) {
      park();
    }

    --requested;
    subscriber.onNext(wrap(bytes, offset, length));
  }

  @Override
  public void write(final int b) {
    write(new byte[] {(byte) b}, 0, 1);
  }

  private class StreamSubscription implements Subscription {
    public void cancel() {
      // Output streams don't cancel.
    }

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

      requested += n;
      unpark(thread);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy