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

net.pincette.rs.Util 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.time.Duration.between;
import static java.time.Instant.now;
import static java.time.temporal.ChronoUnit.SECONDS;
import static java.util.Collections.emptyList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.locks.LockSupport.park;
import static java.util.concurrent.locks.LockSupport.parkNanos;
import static java.util.logging.Logger.getLogger;
import static net.pincette.rs.Async.mapAsyncSequential;
import static net.pincette.rs.Box.box;
import static net.pincette.rs.Buffer.buffer;
import static net.pincette.rs.Chain.with;
import static net.pincette.rs.Combine.combine;
import static net.pincette.rs.Encode.encode;
import static net.pincette.rs.LambdaSubscriber.lambdaSubscriber;
import static net.pincette.rs.Mapper.map;
import static net.pincette.rs.NotFilter.notFilter;
import static net.pincette.rs.PassThrough.passThrough;
import static net.pincette.rs.Pipe.pipe;
import static net.pincette.rs.Probe.probe;
import static net.pincette.rs.Reducer.reduce;
import static net.pincette.rs.Source.of;
import static net.pincette.util.Collections.list;
import static net.pincette.util.Pair.pair;
import static net.pincette.util.ScheduledCompletionStage.composeAsyncAfter;
import static net.pincette.util.ScheduledCompletionStage.supplyAsyncAfter;
import static net.pincette.util.StreamUtil.rangeExclusive;
import static net.pincette.util.Util.tryToDoRethrow;

import java.nio.ByteBuffer;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Flow.Processor;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.logging.Logger;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import net.pincette.function.ConsumerWithException;
import net.pincette.function.RunnableWithException;
import net.pincette.function.SideEffect;
import net.pincette.rs.encoders.Deflate;
import net.pincette.rs.encoders.DivisibleBy;
import net.pincette.rs.encoders.Gunzip;
import net.pincette.rs.encoders.Gzip;
import net.pincette.rs.encoders.Inflate;
import net.pincette.rs.encoders.Lines;
import net.pincette.util.State;
import net.pincette.util.TimedCache;
import net.pincette.util.Util.GeneralException;

/**
 * Some utilities.
 *
 * @author Werner Donné
 * @since 1.0
 */
public class Util {
  static final Logger LOGGER = getLogger("net.pincette.rs.base");

  private Util() {}

  /**
   * Returns the published values as a list. This function will block when needed.
   *
   * @param publisher the publisher.
   * @param  the value type.
   * @return The generated list.
   * @since 1.6
   */
  public static  List asList(final Publisher publisher) {
    return asListAsync(publisher).toCompletableFuture().join();
  }

  public static  List asList(final Publisher publisher, final int initialCapacity) {
    return asListAsync(publisher, initialCapacity).toCompletableFuture().join();
  }

  /**
   * Returns the published values as a list.
   *
   * @param publisher the publisher.
   * @param  the value type.
   * @return The generated list.
   * @since 1.6
   */
  public static  CompletionStage> asListAsync(final Publisher publisher) {
    return asListAsync(publisher, 1000);
  }

  public static  CompletionStage> asListAsync(
      final Publisher publisher, final int initialCapacity) {
    final CompletableFuture> future = new CompletableFuture<>();

    publisher.subscribe(completerList(future, initialCapacity));

    return future;
  }

  /**
   * Returns the first value. This function will block when needed.
   *
   * @param publisher the publisher.
   * @param  the value type.
   * @return The value.
   * @since 1.7.1
   */
  public static  T asValue(final Publisher publisher) {
    return asValueAsync(publisher).toCompletableFuture().join();
  }

  /**
   * Completes with the first emitted value or null if there was no such value.
   *
   * @param publisher the publisher.
   * @param  the value type.
   * @return The value.
   * @since 1.7.1
   */
  public static  CompletionStage asValueAsync(final Publisher publisher) {
    final CompletableFuture future = new CompletableFuture<>();

    with(publisher).first().get().subscribe(completerFirst(future));

    return future;
  }

  /**
   * This is a debugging aid that throws an exception when more messages are sent than requested.
   *
   * @return The pass through processor.
   * @param  the value type.
   * @since 3.0.2
   */
  public static  Processor backpressureCheck() {
    final State requested = new State<>(0L);
    final State sent = new State<>(0L);

    return probe(
        n -> requested.set(requested.get() + n),
        v -> {
          sent.set(sent.get() + 1);

          if (sent.get() > requested.get()) {
            throw new GeneralException(
                "Sending "
                    + sent.get()
                    + " values, while only "
                    + requested.get()
                    + " have been requested.");
          }
        });
  }

  /**
   * Subscribes to the publisher and cancels it immediately.
   *
   * @param publisher the given publisher.
   * @param  the value type.
   * @since 3.4
   */
  public static  void cancel(final Publisher publisher) {
    publisher.subscribe(lambdaSubscriber(v -> {}, () -> {}, t -> {}, Subscription::cancel));
  }

  public static  Publisher completablePublisher(
      final Supplier>> publisher) {
    final Processor passThrough = passThrough();

    publisher.get().thenAccept(p -> p.subscribe(passThrough));

    return passThrough;
  }

  private static Subscriber completerEmpty(final CompletableFuture future) {
    return lambdaSubscriber(v -> {}, () -> future.complete(null), future::completeExceptionally);
  }

  private static  Subscriber completerFirst(final CompletableFuture future) {
    return lambdaSubscriber(
        future::complete, () -> future.complete(null), future::completeExceptionally);
  }

  private static  Subscriber completerList(
      final CompletableFuture> future, final int initialCapacity) {
    final List result = new ArrayList<>(initialCapacity);

    return lambdaSubscriber(
        result::add, () -> future.complete(result), future::completeExceptionally);
  }

  static  List consume(final Deque deque, final long requested) {
    return rangeExclusive(0, min(deque.size(), requested)).map(i -> deque.pollLast()).toList();
  }

  /**
   * Creates a processor that deflates a ByteBuffer stream.
   *
   * @return The processor.
   * @since 3.0
   */
  public static Processor deflate() {
    return encode(Deflate.deflate());
  }

  /**
   * Creates a processor that deflates a ByteBuffer stream.
   *
   * @return The processor.
   * @since 3.0
   */
  public static Processor deflate(final Deflater deflater) {
    return encode(Deflate.deflate(deflater));
  }

  public static  Subscriber devNull() {
    return lambdaSubscriber(v -> {});
  }

  /**
   * Consumes the publisher without doing anything.
   *
   * @param publisher the given publisher.
   * @param  the value type.
   * @since 3.0
   */
  public static  void discard(final Publisher publisher) {
    publisher.subscribe(devNull());
  }

  /**
   * Returns a processor that produces byte buffers the size of which is divisible by a certain
   * number, where the last one may be smaller than that number.
   *
   * @author Werner Donn\u00e9
   * @since 3.0
   */
  public static Processor divisibleBy(final int n) {
    return encode(DivisibleBy.divisibleBy(n));
  }

  /**
   * Filters the elements leaving out duplicates according to the criterion over the
   * time window.
   *
   * @param criterion the given criterion.
   * @param window the time window to look at.
   * @param  the value type.
   * @param  the criterion type.
   * @return The filtered stream.
   * @since 3.0
   */
  public static  Processor duplicateFilter(
      final Function criterion, final Duration window) {
    final TimedCache cache = new TimedCache<>(window);

    return pipe(map((T v) -> pair(v, criterion.apply(v))))
        .then(notFilter(pair -> cache.get(pair.second).isPresent()))
        .then(
            map(
                pair ->
                    Optional.of(pair.second)
                        .map(
                            c ->
                                SideEffect.run(() -> cache.put(c, c))
                                    .andThenGet(() -> pair.first))
                        .orElse(null)));
  }

  @SuppressWarnings("java:S1905") // For type inference.
  private static  CompletionStage> element(
      final Publisher publisher, final Processor terminator) {
    return reduce(subscribe(publisher, terminator), () -> (T) null, (result, value) -> value)
        .thenApply(Optional::ofNullable);
  }

  /**
   * Returns a publisher that emits no values.
   *
   * @param  the value type.
   * @return The empty publisher.
   * @since 1.0
   */
  public static  Publisher empty() {
    return of(emptyList());
  }

  /**
   * Waits until the empty publisher completes.
   *
   * @param publisher the publisher.
   * @since 1.7.1
   */
  public static void empty(final Publisher publisher) {
    emptyAsync(publisher).toCompletableFuture().join();
  }

  /**
   * Completes when the empty publisher completes.
   *
   * @param publisher the publisher.
   * @return The empty completion stage.
   * @since 1.7.1
   */
  public static CompletionStage emptyAsync(final Publisher publisher) {
    final CompletableFuture future = new CompletableFuture<>();

    publisher.subscribe(completerEmpty(future));

    return future;
  }

  /**
   * Returns the first element publisher emits if there is one.
   *
   * @param publisher the given publisher.
   * @param  the value type.
   * @return The optional first element.
   * @since 1.4
   */
  public static  CompletionStage> first(final Publisher publisher) {
    return element(publisher, new First<>());
  }

  /**
   * Returns a publisher that always emits values from supplier when asked for
   * messages. When the supplier returns null the publisher completes.
   *
   * @param supplier the generator function.
   * @param  the value type.
   * @return The publisher.
   * @since 1.6
   */
  public static  Publisher generate(final Supplier supplier) {
    return subscriber ->
        subscriber.onSubscribe(
            new Subscription() {
              public void cancel() {
                subscriber.onComplete();
              }

              public void request(final long n) {
                boolean completed = false;

                for (long i = 0; !completed && i < n; ++i) {
                  final T value = supplier.get();

                  if (value != null) {
                    subscriber.onNext(value);
                  } else {
                    completed = true;
                    subscriber.onComplete();
                  }
                }
              }
            });
  }

  /**
   * Returns a publisher that always emits values from initial and next
   * when asked for messages. When the supplier returns null the publisher completes.
   *
   * @param initial the generator function for the first value.
   * @param next the generator function for all the subsequent values. The function receives the
   *     last value that was emitted.
   * @param  the value type.
   * @return The publisher.
   * @since 1.6
   */
  public static  Publisher generate(final Supplier initial, final UnaryOperator next) {
    final State state = new State<>();

    return generate(() -> state.set(state.get() == null ? initial.get() : next.apply(state.get())));
  }

  /**
   * Creates a processor that compresses a ByteBuffer stream in GZIP format.
   *
   * @return The processor.
   * @since 3.0
   */
  public static Processor gzip() {
    return encode(Gzip.gzip());
  }

  /**
   * Creates a processor that uncompresses a ByteBuffer stream in GZIP format.
   *
   * @return The processor.
   * @since 3.0
   */
  public static Processor gunzip() {
    return encode(Gunzip.gunzip());
  }

  /**
   * Creates a processor that inflates a ByteBuffer stream.
   *
   * @return The processor.
   * @since 3.0
   */
  public static Processor inflate() {
    return encode(Inflate.inflate());
  }

  /**
   * Creates a processor that inflates a ByteBuffer stream.
   *
   * @return The processor.
   * @since 3.0
   */
  public static Processor inflate(final Inflater inflater) {
    return encode(Inflate.inflate(inflater));
  }

  static  Deque> initialStageDeque() {
    final ArrayDeque> deque = new ArrayDeque<>(1000);

    deque.addFirst(completedFuture(null));

    return deque;
  }

  /**
   * Returns a blocking Iterable with a request size of 100.
   *
   * @param publisher the publisher from which the elements are buffered.
   * @param  the element type.
   * @return The iterable.
   * @since 1.2
   */
  public static  Iterable iterate(final Publisher publisher) {
    return iterate(publisher, 100);
  }

  /**
   * Returns a blocking Iterable.
   *
   * @param publisher the publisher from which the elements are buffered.
   * @param requestSize the size of the requests the subscriber will issue to the publisher.
   * @param  the element type.
   * @return The iterable.
   * @since 1.2
   */
  public static  Iterable iterate(final Publisher publisher, final long requestSize) {
    final BlockingSubscriber subscriber = new BlockingSubscriber<>(requestSize);

    publisher.subscribe(subscriber);

    return subscriber;
  }

  /**
   * Blocks until the publisher is done.
   *
   * @param publisher the given publisher.
   * @param  the value type.
   * @since 1.5
   */
  public static  void join(final Publisher publisher) {
    reduce(publisher, (v1, v2) -> v1).toCompletableFuture().join();
  }

  /**
   * Returns the last element publisher emits if there is one.
   *
   * @param publisher the given publisher.
   * @param  the value type.
   * @return The optional first element.
   * @since 1.4
   */
  public static  CompletionStage> last(final Publisher publisher) {
    return element(publisher, new Last<>());
  }

  /**
   * Returns a processor that receives buffers and interprets the contents as a UTF-8 encoded
   * string. It emits the individual lines in the string without the line separators.
   *
   * @author Werner Donn\u00e9
   * @since 3.0
   */
  public static Processor lines() {
    return encode(Lines.lines());
  }

  static  Optional> nextValues(final Deque deque, final long amount) {
    return Optional.of(deque).filter(b -> !b.isEmpty() && amount > 0).map(b -> consume(b, amount));
  }

  /**
   * Returns a subscriber that reacts to the onComplete event.
   *
   * @param runnable the given function.
   * @param  the value type.
   * @return The subscriber.
   * @since 3.0
   */
  public static  Subscriber onComplete(final RunnableWithException runnable) {
    return lambdaSubscriber(v -> {}, runnable);
  }

  /**
   * Returns a processor that reacts to the onComplete event. It is transparent to all
   * events.
   *
   * @param runnable the given function.
   * @param  the value type.
   * @return The processor.
   * @since 3.2.2
   */
  public static  Processor onCompleteProcessor(final RunnableWithException runnable) {
    return probe(n -> {}, v -> {}, () -> tryToDoRethrow(runnable));
  }

  /**
   * Returns a subscriber that reacts to the onError event.
   *
   * @param consumer the given function.
   * @param  the value type.
   * @return The subscriber.
   * @since 3.0
   */
  public static  Subscriber onError(final ConsumerWithException consumer) {
    return lambdaSubscriber(v -> {}, () -> {}, consumer);
  }

  /**
   * Returns a processor that reacts to the onError event. It is transparent to all
   * events.
   *
   * @param consumer the given function.
   * @param  the value type.
   * @return The processor.
   * @since 3.2.2
   */
  public static  Processor onErrorProcessor(
      final ConsumerWithException consumer) {
    return probe(n -> {}, v -> {}, () -> {}, t -> tryToDoRethrow(() -> consumer.accept(t)));
  }

  /**
   * Returns a subscriber that reacts to the onNext event.
   *
   * @param consumer the given function.
   * @param  the value type.
   * @return The subscriber.
   * @since 3.0
   */
  public static  Subscriber onNext(final ConsumerWithException consumer) {
    return lambdaSubscriber(consumer);
  }

  /**
   * Returns a processor that reacts to the onNext event. It is transparent to all
   * events.
   *
   * @param consumer the given function.
   * @param  the value type.
   * @return The processor.
   * @since 3.2.2
   */
  public static  Processor onNextProcessor(final ConsumerWithException consumer) {
    return probe(n -> {}, v -> tryToDoRethrow(() -> consumer.accept(v)));
  }

  static void parking(final Object blocker, final long timeout) {
    if (timeout != -1) {
      parkNanos(blocker, timeout * 1000);
    } else {
      park(blocker);
    }
  }

  /**
   * Pulls data from a processor without doing anything with it. You can use this to get a pipeline
   * started that ends with some side effect.
   *
   * @param processor the given processor.
   * @param  the incoming value type.
   * @param  theo outgoing value type.
   * @return The given processor as a subscriber.
   * @since 3.0
   */
  public static  Subscriber pull(final Processor processor) {
    processor.subscribe(devNull());

    return processor;
  }

  /**
   * Returns a publisher that will recreate the original publisher when an exception occurs.
   *
   * @param publisher the function to create the original publisher.
   * @param retryInterval the time before a new attempt is made.
   * @param  the value type.
   * @return The retrying publisher.
   * @since 1.6
   */
  public static  Publisher retryPublisher(
      final Supplier> publisher, final Duration retryInterval) {
    return retryPublisher(publisher, retryInterval, null);
  }

  /**
   * Returns a publisher that will recreate the original publisher when an exception occurs.
   *
   * @param publisher the function to create the original publisher.
   * @param retryInterval the time before a new attempt is made.
   * @param onException the handler of the exception. It may be null.
   * @param  the value type.
   * @return The retrying publisher.
   * @since 1.6
   */
  public static  Publisher retryPublisher(
      final Supplier> publisher,
      final Duration retryInterval,
      final Consumer onException) {
    final Retry result = new Retry<>(publisher, retryInterval, onException);

    publisher.get().subscribe(result);

    return box(result, buffer(1));
  }

  /**
   * Subscribes processor to publisher and returns the processor as a
   * publisher.
   *
   * @param publisher the given publisher.
   * @param processor the given processor.
   * @param  the type of the publisher.
   * @param  the type of the returned publisher.
   * @return The processor as a publisher.
   * @since 1.0
   */
  public static  Publisher subscribe(
      final Publisher publisher, final Processor processor) {
    publisher.subscribe(processor);

    return processor;
  }

  /**
   * Subscribes subscriber to processor and returns processor
   * as a subscriber.
   *
   * @param processor the given processor.
   * @param subscriber the given subscriber.
   * @param  the input type of the processor.
   * @param  the output type of the processor.
   * @return The processor as a subscriber.
   * @since 1.6
   */
  public static  Subscriber subscribe(
      final Processor processor, final Subscriber subscriber) {
    processor.subscribe(subscriber);

    return processor;
  }

  /**
   * Creates a processor where a subscriber can tap the data that goes through it.
   *
   * @param subscriber the given subscriber.
   * @param  the value type.
   * @return The processor.
   * @since 3.0
   */
  public static  Processor tap(final Subscriber subscriber) {
    final Processor pass1 = passThrough();
    final Processor pass2 = passThrough();

    pass1.subscribe(Fanout.of(list(pass2, subscriber), list(true, false)));

    return combine(pass1, pass2);
  }

  static void trace(final Logger logger, final Supplier message) {
    logger.finest(() -> logger.getName() + ": " + message.get());
  }

  public static  R transform(final Processor processor, final T value) {
    return transformAsync(processor, value).toCompletableFuture().join();
  }

  public static  CompletionStage transformAsync(
      final Processor processor, final T value) {
    return asValueAsync(with(Source.of(value)).map(processor).get());
  }

  public static  Processor throttle(final int maxPerSecond) {
    final Running running = new Running(maxPerSecond);

    return mapAsyncSequential(m -> running.update().thenApply(r -> m));
  }

  static void throwBackpressureViolation(
      final Object victim, final Subscription subscription, final long requested) {
    throw new GeneralException(
        "Backpressure violation in "
            + subscription.getClass().getName()
            + ". Requested "
            + requested
            + " elements in "
            + victim.getClass().getName()
            + ", which have already been received.");
  }

  private static class Retry extends PassThrough {
    private final Consumer onException;
    private final Supplier> publisher;
    private final Duration retryInterval;
    private long requested;

    private Retry(
        final Supplier> publisher,
        final Duration retryInterval,
        final Consumer onException) {
      this.publisher = publisher;
      this.retryInterval = retryInterval;
      this.onException = onException;
    }

    @Override
    protected void more(final long number) {
      dispatch(
          () -> {
            requested += number;
            super.more(number);
          });
    }

    @Override
    public void onComplete() {
      dispatch(super::onComplete);
    }

    @Override
    public void onError(Throwable t) {
      dispatch(
          () -> {
            setError(true);

            if (onException != null) {
              onException.accept(t);
            }

            composeAsyncAfter(() -> completedFuture(publisher.get()), retryInterval)
                .thenAccept(
                    p ->
                        dispatch(
                            () -> {
                              setError(false);
                              p.subscribe(this);
                            }));
          });
    }

    @Override
    public void onNext(final T value) {
      dispatch(
          () -> {
            --requested;
            super.onNext(value);
          });
    }

    @Override
    public void onSubscribe(final Subscription subscription) {
      if (this.subscription == null) {
        super.onSubscribe(subscription);
      } else {
        this.subscription = subscription; // Ignore rule 2.5 because that is the purpose here.

        if (requested > 0) {
          subscription.request(requested);
        }
      }
    }
  }

  private static class Running {
    private final int maxPerSecond;
    private long count = 0;
    private Instant second = now().truncatedTo(SECONDS);

    private Running(final int maxPerSecond) {
      this.maxPerSecond = maxPerSecond;
    }

    private CompletionStage update() {
      final Instant now = now().truncatedTo(SECONDS);

      if (between(second, now).toMillis() > 999) {
        count = 0;
        second = now;
      }

      return ++count >= maxPerSecond
          ? supplyAsyncAfter(() -> true, between(now(), now.plusSeconds(1)))
          : completedFuture(true);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy