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

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

/*
 * Copyright (c) 2018-2021, 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.Precondition.checkNonNull;
import java.time.Duration;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import com.github.tonivade.purefun.Bindable;
import com.github.tonivade.purefun.CheckedRunnable;
import com.github.tonivade.purefun.Consumer1;
import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.Unit;
import com.github.tonivade.purefun.type.Option;
import com.github.tonivade.purefun.type.Try;
import com.github.tonivade.purefun.type.TryOf;

@HigherKind(sealed = true)
public interface Promise extends PromiseOf, Bindable {

  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
  default  Promise andThen(Kind next) {
    return PromiseOf.narrowK(Bindable.super.andThen(next));
  }
  
  @Override
   Promise flatMap(Function1> 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;
  }
}

final class PromiseImpl implements SealedPromise {

  private final State state = new State();
  private final Queue>> consumers = new LinkedList<>();
  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()) {
      synchronized (state) {
        if (isEmpty()) {
          state.completed = true;
          setValue(value);
          state.notifyAll();
          return true;
        }
      }
    }
    return false;
  }

  @Override
  public Try await() {
    if (isEmpty()) {
      try {
        synchronized (state) {
          while (!state.completed) {
            state.wait();
          }
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return Try.failure(e);
      }
    }
    return TryOf.narrowK(checkNonNull(reference.get()));
  }

  @Override
  public Try await(Duration timeout) {
    if (isEmpty()) {
      try {
        synchronized (state) {
          if (!state.completed) {
            state.wait(timeout.toMillis());
          }
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return Try.failure(e);
      }
    }
    Option> option = Option.of(reference::get).map(TryOf::narrowK);
    return option.getOrElse(Try.failure(new TimeoutException()));
  }

  @Override
  public boolean isCompleted() {
    synchronized (state) {
      return state.completed;
    }
  }

  @Override
  public Promise onComplete(Consumer1> consumer) {
    current(consumer).ifPresent(consumer);
    return this;
  }
  
  @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> mapper) {
    Promise other = new PromiseImpl<>(executor);
    onComplete(value -> {
      Try> map = value.map(mapper.andThen(PromiseOf::narrowK));
      map.fold(error -> other.tryComplete(Try.failure(error)), next -> next.onComplete(other::tryComplete));
    });
    return other;
  }

  private Option> current(Consumer1> consumer) {
    Try current = reference.get();
    if (current == null) {
      synchronized (state) {
        current = reference.get();
        if (current == null) {
          consumers.add(consumer);
        }
      }
    }
    return Option.of(TryOf.narrowK(current));
  }

  private void setValue(Try value) {
    reference.set(value);
    consumers.forEach(consumer -> submit(value, consumer));
  }

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

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

  private static final class State {
    private boolean completed = false;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy