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

com.github.tonivade.purefun.concurrent.Promise Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018-2024, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.concurrent;

import static com.github.tonivade.purefun.core.Precondition.checkNonNull;

import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.core.Applicable;
import com.github.tonivade.purefun.core.Bindable;
import com.github.tonivade.purefun.core.CheckedRunnable;
import com.github.tonivade.purefun.core.Consumer1;
import com.github.tonivade.purefun.core.Function1;
import com.github.tonivade.purefun.core.Function2;
import com.github.tonivade.purefun.core.Function3;
import com.github.tonivade.purefun.core.Function4;
import com.github.tonivade.purefun.core.Function5;
import com.github.tonivade.purefun.core.Unit;
import com.github.tonivade.purefun.type.Option;
import com.github.tonivade.purefun.type.Try;
import com.github.tonivade.purefun.type.TryOf;

@HigherKind
public sealed interface Promise extends PromiseOf, Bindable, T>, Applicable, T> permits PromiseImpl {

  boolean tryComplete(Try value);

  default Promise cancel() {
    return failed(new CancellationException());
  }

  default Promise complete(Try value) {
    if (tryComplete(value)) {
      return this;
    } else throw new IllegalStateException("promise already completed");
  }

  default Promise succeeded(T value) {
    return complete(Try.success(value));
  }

  default Promise failed(Throwable error) {
    return complete(Try.failure(error));
  }

  Promise onComplete(Consumer1> consumer);

  default Promise onSuccess(Consumer1 consumer) {
    return onComplete(value -> value.onSuccess(consumer));
  }

  default Promise onFailure(Consumer1 consumer) {
    return onComplete(value -> value.onFailure(consumer));
  }

  @Override
   Promise map(Function1 mapper);

  @Override
   Promise ap(Kind, ? extends Function1> apply);

  @Override
  default  Promise andThen(Kind, ? extends R> next) {
    return PromiseOf.toPromise(Bindable.super.andThen(next));
  }

  @Override
   Promise flatMap(Function1, ? extends R>> mapper);

  default Promise then(Consumer1 next) {
    return map(next.asFunction());
  }

  default Promise thenRun(CheckedRunnable next) {
    return map(next.asProducer().asFunction());
  }

  Try await();
  Try await(Duration timeout);

  boolean isCompleted();

  static  Promise make() {
    return make(Future.DEFAULT_EXECUTOR);
  }

  static  Promise make(Executor executor) {
    return new PromiseImpl<>(executor);
  }

  static  Promise from(CompletableFuture future) {
    return from(Future.DEFAULT_EXECUTOR, future);
  }

  static  Promise from(Executor executor, CompletableFuture future) {
    Promise promise = make(executor);
    future.whenCompleteAsync(
        (value, error) -> promise.tryComplete(
            value != null ? Try.success(value) : Try.failure(error)), executor);
    return promise;
  }

  static  Promise mapN(Promise fa, Promise fb,
      Function2 mapper) {
    return fb.ap(fa.map(mapper.curried()));
  }

  static  Promise mapN(
      Promise fa,
      Promise fb,
      Promise fc,
      Function3 mapper) {
    return fc.ap(mapN(fa, fb, (a, b) -> mapper.curried().apply(a).apply(b)));
  }

  static  Promise mapN(
      Promise fa,
      Promise fb,
      Promise fc,
      Promise fd,
      Function4 mapper) {
    return fd.ap(mapN(fa, fb, fc, (a, b, c) -> mapper.curried().apply(a).apply(b).apply(c)));
  }

  static  Promise mapN(
      Promise fa,
      Promise fb,
      Promise fc,
      Promise fd,
      Promise fe,
      Function5 mapper) {
    return fe.ap(mapN(fa, fb, fc, fd, (a, b, c, d) -> mapper.curried().apply(a).apply(b).apply(c).apply(d)));
  }
}

final class PromiseImpl implements Promise {

  private final ReentrantLock lock = new ReentrantLock();
  private final Condition condition = lock.newCondition();
  private final Queue>> consumers = new ArrayDeque<>();
  private final AtomicReference> reference = new AtomicReference<>();

  private final Executor executor;

  PromiseImpl(Executor executor) {
    this.executor = checkNonNull(executor);
  }

  @Override
  public boolean tryComplete(Try value) {
    if (isEmpty()) {
      lock.lock();
      try {
        if (isEmpty()) {
          set(value);
          condition.signalAll();
          while (true) {
            var consumer = consumers.poll();
            if (consumer != null)
              submit(value, consumer);
            else break;
          }
          return true;
        }
      } finally {
        lock.unlock();
      }
    }
    return false;
  }

  @Override
  public Try await() {
    if (isEmpty()) {
      lock.lock();
      try {
        while (isEmpty()) {
          condition.await();
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        set(Try.failure(e));
      } finally {
        lock.unlock();
      }
    }
    return safeGet();
  }

  @Override
  public Try await(Duration timeout) {
    if (isEmpty()) {
      lock.lock();
      try {
        if (isEmpty() && !condition.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
          set(Try.failure(new TimeoutException()));
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        set(Try.failure(e));
      } finally {
        lock.unlock();
      }
    }
    return safeGet();
  }

  @Override
  public boolean isCompleted() {
    lock.lock();
    try {
      return !isEmpty();
    } finally {
      lock.unlock();
    }
  }

  @Override
  public Promise onComplete(Consumer1> consumer) {
    current(consumer).ifPresent(consumer);
    return this;
  }

  @Override
  public  Promise ap(Kind, ? extends Function1> apply) {
    Promise result = new PromiseImpl<>(executor);
    onComplete(try1 -> PromiseOf.toPromise(apply).onComplete(
        try2 -> result.tryComplete(Try.map2(try2,  try1, Function1::apply))));
    return result;
  }

  @Override
  public  Promise map(Function1 mapper) {
    Promise other = new PromiseImpl<>(executor);
    onComplete(value -> other.tryComplete(value.map(mapper)));
    return other;
  }

  @Override
  public  Promise flatMap(Function1, ? extends R>> mapper) {
    Promise other = new PromiseImpl<>(executor);
    onComplete(value -> {
      Try> map = value.map(mapper.andThen(PromiseOf::toPromise));
      map.fold(
        error -> other.tryComplete(Try.failure(error)),
        next -> next.onComplete(other::tryComplete));
    });
    return other;
  }

  private Option> current(Consumer1> consumer) {
    if (isEmpty()) {
      lock.lock();
      try {
        if (isEmpty()) {
          consumers.add(consumer);
        }
      } finally {
        lock.unlock();
      }
    }
    return Option.of(safeGet());
  }

  @SuppressWarnings("NullAway")
  private Try safeGet() {
    return TryOf.toTry(reference.get());
  }

  private void set(Try value) {
    reference.set(value);
  }

  private boolean isEmpty() {
    return reference.get() == null;
  }

  private void submit(Try value, Consumer1> consumer) {
    executor.execute(() -> consumer.accept(value));
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy