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

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

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

import static java.lang.Math.min;
import static java.lang.Thread.currentThread;
import static java.util.Optional.empty;
import static java.util.concurrent.locks.LockSupport.unpark;
import static net.pincette.rs.Util.parking;
import static net.pincette.util.Pair.pair;
import static net.pincette.util.Util.rethrow;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;

/**
 * This is an input stream that can be used as a reactive streams subscriber. It complies with the
 * reactive backpressure mechanism.
 *
 * @author Werner Donn\u00e9
 * @since 1.0
 */
public class InputStreamSubscriber extends InputStream implements Subscriber {
  private final long timeout;
  private final Thread thread = currentThread();
  private ByteBuffer buffer;
  private boolean ended;
  private Throwable exception;
  private Subscription subscription;

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

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

  private Optional getBuffer() {
    if (noData()) {
      readBuffer();
    }

    return ended ? empty() : Optional.of(buffer);
  }

  private boolean noData() {
    return subscription == null || buffer == null || !buffer.hasRemaining();
  }

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

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

    ended = true;
    exception = t;
    unpark(thread);
    rethrow(t);
  }

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

    this.buffer = buffer;
    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;
      subscription.request(1);
    }
  }

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

  @Override
  public int read() throws IOException {
    final byte[] b = new byte[1];

    return read(b, 0, b.length) == -1 ? -1 : (255 & b[0]);
  }

  @Override
  public int read(final byte[] bytes, final int offset, final int length) throws IOException {
    if (exception != null) {
      throw new IOException(exception);
    }

    return ended ? -1 : readFromBuffer(bytes, offset, length);
  }

  private void readBuffer() {
    if (subscription != null) {
      subscription.request(1);
    }

    park();
  }

  private int readFromBuffer(final byte[] bytes, final int offset, final int length) {
    return getBuffer()
        .map(b -> pair(b, min(min(length, bytes.length - offset), b.remaining())))
        .map(pair -> pair(pair.first.get(bytes, offset, pair.second), pair.second))
        .map(pair -> pair.second)
        .orElse(-1);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy