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

functionalj.promise.Promise Maven / Gradle / Ivy

// ============================================================================
// Copyright (c) 2017-2019 Nawapunth Manusitthipol (NawaMan - http://nawaman.net).
// ----------------------------------------------------------------------------
// MIT License
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ============================================================================
package functionalj.promise;

import static functionalj.function.Func.carelessly;
import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import functionalj.function.Func0;
import functionalj.function.Func1;
import functionalj.function.Func2;
import functionalj.function.Func3;
import functionalj.function.Func4;
import functionalj.function.Func5;
import functionalj.function.Func6;
import functionalj.function.FuncUnit1;
import functionalj.function.FuncUnit2;
import functionalj.function.NamedExpression;
import functionalj.pipeable.Pipeable;
import functionalj.ref.Ref;
import functionalj.result.HasResult;
import functionalj.result.Result;
import lombok.val;


// TODO - Find a way to make toString more useful ... like giving this a name.
// TODO - Should extract important stuff to PromiseBase ... so it is not flooded with the less important things.
@SuppressWarnings("javadoc")
public class Promise implements HasPromise, HasResult, Pipeable> {
    
    private static final int INITIAL_CAPACITY = 2;
    
    public static final Ref waitTimeout = Ref.ofValue(-1L);
    
    public static  Promise ofResult(HasResult asResult) {
        if (asResult instanceof HasPromise)
            return ((HasPromise)asResult).getPromise();
        
        return DeferActionBuilder
                .from(()->asResult.getResult().value())
                .build()
                .getPromise();
    }
    public static  Promise of(D value) {
        return DeferAction.of((Class)null)
                .start()
                .complete(value)
                .getPromise();
    }
    
    public static  Promise ofException(Exception exception) {
        return DeferAction.of((Class)null)
                .start()
                .fail(exception)
                .getPromise();
    }
    
    public static  Promise ofAborted() {
        return DeferAction.of((Class)null)
                .start()
                .abort()
                .getPromise();
    }
    
    
    public static  Promise from(
            NamedExpression> promise1,
            NamedExpression> promise2,
            Func2                merger) {
        val action  = DeferAction.from(promise1, promise2, merger);
        val promise = action.getPromise();
        return promise;
    }
    
    public static  Promise from(
            NamedExpression> promise1,
            NamedExpression> promise2,
            NamedExpression> promise3,
            Func3            merger) {
        val action  = DeferAction.from(promise1, promise2, promise3, merger);
        val promise = action.getPromise();
        return promise;
    }
    
    public static  Promise from(
            NamedExpression> promise1,
            NamedExpression> promise2,
            NamedExpression> promise3,
            NamedExpression> promise4,
            Func4        merger) {
        val action  = DeferAction.from(promise1, promise2, promise3, promise4, merger);
        val promise = action.getPromise();
        return promise;
    }
    
    public static  Promise from(
            NamedExpression> promise1,
            NamedExpression> promise2,
            NamedExpression> promise3,
            NamedExpression> promise4,
            NamedExpression> promise5,
            Func5    merger) {
        val action  = DeferAction.from(promise1, promise2, promise3, promise4, promise5, merger);
        val promise = action.getPromise();
        return promise;
    }
    
    public static  Promise from(
            NamedExpression>  promise1,
            NamedExpression>  promise2,
            NamedExpression>  promise3,
            NamedExpression>  promise4,
            NamedExpression>  promise5,
            NamedExpression>  promise6,
            Func6 merger) {
        val action  = DeferAction.from(promise1, promise2, promise3, promise4, promise5, promise6, merger);
        val promise = action.getPromise();
        return promise;
    }
    
    
    // DATA
    //    StartableAction -> NOT START
    //    consumer        -> Pending
    //    result          -> done.
    //      result.cancelled -> aborted
    //      result.completed -> completed
    final Map, FuncUnit1>> consumers     = new ConcurrentHashMap<>(INITIAL_CAPACITY);
    final List>>                          eavesdroppers = new ArrayList<>(INITIAL_CAPACITY);
    
    private static final AtomicInteger ID = new AtomicInteger(0);
    
    final AtomicReference dataRef = new AtomicReference<>();
    private final int id = ID.getAndIncrement();
    
    public int hashCode() {
        return id;
    }
    public String toString() {
        return "Promise#" + id;
    }
    
    Promise(OnStart onStart) {
        dataRef.set(onStart);
    }
    Promise(StartableAction action) {
        dataRef.set(action);
    }
    
    Promise(@SuppressWarnings("rawtypes") Promise parent) {
        this.dataRef.set(parent);
    }
    
    @Override
    public Promise getPromise() {
        return this;
    }
    
    public HasPromise __data() throws Exception {
        return this;
    }
    
    public final PromiseStatus getStatus() {
        val data = dataRef.get();
        if (data instanceof Promise) {
            @SuppressWarnings("unchecked")
            Promise promise = (Promise)data;
            PromiseStatus parentStatus = promise.getStatus();
            // Pending ... as the result is not yet propagated down
            if (parentStatus.isNotDone())
                 return parentStatus;
            else return PromiseStatus.PENDING;
        }
        
        if ((data instanceof StartableAction) || (data instanceof OnStart))
            return PromiseStatus.NOT_STARTED;
        if (consumers == data)
            return PromiseStatus.PENDING;
        if (data instanceof Result) {
            @SuppressWarnings("unchecked")
            val result = (Result)data;
            if (result.isCancelled())
                return PromiseStatus.ABORTED;
            if (result.isReady())
                return PromiseStatus.COMPLETED;
        }
        
        dataRef.set(Result.ofException(new IllegalStateException("Promise is in an unknown state!: " + data)));
        try {
            handleIllegalStatusException(data);
        } catch (Exception e) {
            // Do nothing
        }
        return PromiseStatus.COMPLETED;
    }
    
    //== Internal working ==
    
    @SuppressWarnings("unchecked")
    public final boolean start() {
        val data = dataRef.get();
        if (data instanceof Promise) {
            val parent = (Promise)data;
            return parent.start();
        }
        
        val isStartAction = (data instanceof StartableAction);
        val isOnStart     = (data instanceof OnStart);
        if (!isStartAction && !isOnStart)
            return false;
        
        val isJustStarted = dataRef.compareAndSet(data, consumers);
        if (isJustStarted) {
            if (isStartAction)  ((StartableAction)data).start();
            else if (isOnStart) ((OnStart)data).run();
        }
        return isJustStarted;
    }
    
    OnStart getOnStart() {
        val data = dataRef.get();
        return (data instanceof OnStart) ? (OnStart)data : OnStart.DoNothing;
    }
    
    boolean abort() {
        @SuppressWarnings("unchecked")
        val cancelResult = (Result)Result.ofCancelled();
        return makeDone(cancelResult);
    }
    boolean abort(String message) {
        @SuppressWarnings("unchecked")
        val cancelResult = (Result)Result.ofCancelled(message);
        return makeDone(cancelResult);
    }
    boolean abort(Exception cause) {
        @SuppressWarnings("unchecked")
        val cancelResult = (Result)Result.ofCancelled(null, cause);
        return makeDone(cancelResult);
    }
    boolean abort(String message, Exception cause) {
        @SuppressWarnings("unchecked")
        val cancelResult = (Result)Result.ofCancelled(message, cause);
        return makeDone(cancelResult);
    }
    
    boolean makeComplete(DATA data) { 
        val result = Result.valueOf(data);
        return makeDone(result);
    }
    
    boolean makeFail(Exception exception) {
        @SuppressWarnings("unchecked")
        val result = (Result)Result.ofException(exception);
        return makeDone(result);
    }
    
     T synchronouseOperation(Func0 operation) {
        synchronized (dataRef) {
            return operation.get();
        }
    }
    static  boolean makeDone(Promise promise, Result result) {
        val isDone = promise.synchronouseOperation(()->{
            val data = promise.dataRef.get();
            try {
                if (data instanceof Promise) {
                    @SuppressWarnings("unchecked")
                    val parent = (Promise)data;
                    try {
                        if (!promise.dataRef.compareAndSet(parent, result))
                            return false;
                    } finally {
                        parent.unsubscribe(promise);
                    }
                } else if ((data instanceof StartableAction) || (data instanceof OnStart)) {
                    if (!promise.dataRef.compareAndSet(data, result))
                        return false;
                } else {
                    if (!promise.dataRef.compareAndSet(promise.consumers, result))
                        return false;
                }
                return null;
            } finally {
            }
        });
        
        if (isDone != null)
            return isDone.booleanValue();
        
        val subscribers = new HashMap, FuncUnit1>>(promise.consumers);
        promise.consumers.clear();
        
        val eavesdroppers = new ArrayList>>(promise.eavesdroppers);
        promise.eavesdroppers.clear();
        
        for (val eavesdropper : eavesdroppers) {
            carelessly(()->{
                eavesdropper.accept(result);
            });
        }
        
        subscribers
        .forEach((subscription, consumer) -> {
            try {
                consumer.accept(result);
            } catch (Exception e) {
                try {
                    promise.handleResultConsumptionExcepion(subscription, consumer, result);
                } catch (Exception anotherException) {
                    // Do nothing this time.
                }
            }
        });
        return true;
    }
    
    private boolean makeDone(Result result) {
        return makeDone(this, result);
    }
    
    //== Customizable ==
    
    protected void handleIllegalStatusException(Object data) {
    }
    
    protected void handleResultConsumptionExcepion(
            SubscriptionRecord subscription,
            FuncUnit1>  consumer,
            Result             result) {
    }
    
    private  Promise newSubPromise(FuncUnit2, Promise> resultConsumer) {
        val promise = new Promise(this);
        onComplete(resultConsumer.elevateWith(promise));
        return promise;
    }
    
    //== Basic functionality ==
    
    public final boolean isStarted() {
        return !PromiseStatus.NOT_STARTED.equals(getStatus());
    }
    public final boolean isPending() {
        return PromiseStatus.PENDING.equals(getStatus());
    }
    public final boolean isAborted() {
        return PromiseStatus.ABORTED.equals(getStatus());
    }
    public final boolean isComplete() {
        return PromiseStatus.COMPLETED.equals(getStatus());
    }
    public final boolean isDone() {
        val status = getStatus();
        val isDone = (null != status) && status.isDone();
        return isDone;
    }
    public final boolean isNotDone() {
        return !isDone();
    }
    
    boolean isSubscribed(SubscriptionRecord subscription) {
        return consumers.containsKey(subscription);
    }
    void unsubscribe(SubscriptionRecord subscription) {
        consumers.remove(subscription);
        abortWhenNoSubscription();
    }
    void unsubscribe(Promise promise) {
        val entry = consumers.entrySet().stream().filter(e -> Objects.equals(e.getValue(), promise)).findFirst();
        if (entry.isPresent())
            consumers.remove(entry.get().getKey());
        abortWhenNoSubscription();
    }
    
    private void abortWhenNoSubscription() {
        if (consumers.isEmpty())
            abort("No more listener.");
    }
    
    private SubscriptionRecord listen(boolean isEavesdropping, FuncUnit1> resultConsumer) {
        val subscription = new SubscriptionRecord(this);
        if (isEavesdropping)
             eavesdroppers.add(resultConsumer);
        else consumers    .put(subscription, resultConsumer);
        
        return subscription;
    }
    
    public final Result getResult() {
        long timeout = waitTimeout.whenAbsentUse(-1L).get().longValue();
        return getResult(timeout, null);
    }
    public final Result getResult(long timeout, TimeUnit unit) {
        start();
        if (!isDone()) {
            val latch = new CountDownLatch(1);
            synchronouseOperation(()->{
                onComplete(result -> {
                    latch.countDown();
                });
                return isDone();
            });
            
            if (!isDone()) {
                try {
                    if ((timeout < 0) || (unit == null))
                         latch.await();
                    else latch.await(timeout, unit);
                    
                } catch (InterruptedException exception) {
                    throw new UncheckedInterruptedException(exception);
                }
            }
        }
        
        if (!isDone())
            throw new UncheckedInterruptedException(new InterruptedException());
        
        val currentResult = getCurrentResult();
        return currentResult;
    }
    
    public final Result getCurrentResult() {
        val data = dataRef.get();
        if (data instanceof Result) {
            @SuppressWarnings("unchecked")
            val result = (Result)data;
            return result;
        }
        if (data instanceof Promise) {
            @SuppressWarnings("unchecked")
            val parent = (Promise)data;
            return parent.getCurrentResult();
        }
        return Result.ofNotReady();
    }
    
    public final SubscriptionRecord subscribe(FuncUnit1 resultConsumer) {
        return onComplete(Wait.forever(), r -> r.ifPresent(resultConsumer));
    }
    
    public final SubscriptionRecord subscribe(Wait wait, FuncUnit1 resultConsumer) {
        return doSubscribe(false, wait, r -> r.ifPresent(resultConsumer));
    }
    
    public final SubscriptionHolder onComplete() {
        return new SubscriptionHolder<>(false, Wait.forever(), this);
    }
    
    public final SubscriptionRecord onComplete(FuncUnit1> resultConsumer) {
        return onComplete(Wait.forever(), resultConsumer);
    }
    
    public final SubscriptionHolder onComplete(Wait wait) {
        return new SubscriptionHolder<>(false, wait, this);
    }
    
    public final SubscriptionRecord onComplete(Wait wait, FuncUnit1> resultConsumer) {
        return doSubscribe(false, wait, resultConsumer);
    }
    
    public final SubscriptionHolder eavesdrop() {
        return new SubscriptionHolder<>(true, Wait.forever(), this);
    }
    
    public final SubscriptionHolder eavesdrop(Wait wait) {
        return new SubscriptionHolder<>(true, wait, this);
    }
    
    public final SubscriptionRecord eavesdrop(FuncUnit1> resultConsumer) {
        return doSubscribe(true, Wait.forever(), resultConsumer);
    }
    
    public final SubscriptionRecord eavesdrop(Wait wait, FuncUnit1> resultConsumer) {
        return doSubscribe(true, wait, resultConsumer);
    }
    
    public final Promise abortNoSubscriptionAfter(Wait wait) {
        val subscriptionHolder = onComplete(wait);
        subscriptionHolder.assign(__ -> subscriptionHolder.unsubscribe());
        return this;
    }
    
    @SuppressWarnings("unchecked")
    final SubscriptionRecord doSubscribe(boolean isEavesdropping, Wait wait, FuncUnit1> resultConsumer) {
        val toRunNow           = new AtomicBoolean(false);
        val returnSubscription = (SubscriptionRecord)synchronouseOperation(()->{
            val data = dataRef.get();
            if (data instanceof Result) {
                val subscription = new SubscriptionRecord(this);
                toRunNow.set(true);
                return subscription;
            }
            
            val hasNotified  = new AtomicBoolean(false);
            val waitSession  = wait != null ? wait.newSession() : Wait.forever().newSession();
            val subscription = listen(isEavesdropping, result -> {
                if (hasNotified.compareAndSet(false, true)) {
                    try {
                        resultConsumer.accept(result);
                    } catch (Throwable e) {
                        // TODO - Use null here because don't know what to do.
                        // FIXME
                        handleResultConsumptionExcepion(null, resultConsumer, result);
                    }
                }
            });
            waitSession.onExpired((message, throwable) -> {
                if (!hasNotified.compareAndSet(false, true))
                    return;
                
                subscription.unsubscribe();
                Result result;
                try {
                    if (wait instanceof WaitOrDefault) {
                        val supplier = ((WaitOrDefault)wait).getDefaultSupplier();
                        if (supplier == null)
                             result = Result.ofCancelled(message, throwable);
                        else result = supplier.get();
                    } else {
                        result = Result.ofCancelled(message, throwable);
                    }
                } catch (Exception e) {
                    result = Result.ofCancelled(null, e);
                }
                try {
                    resultConsumer.accept(result);
                } catch (Throwable e) {
                    // TODO - Use null here because don't know what to do.
                    // FIXME
                    handleResultConsumptionExcepion(null, resultConsumer, result);
                }
            });
            return subscription;
        });
        
        if (toRunNow.get()) {
            // The consumer can be heavy so we remove it out of the locked operation.
            val data = dataRef.get();
            val result = (Result)data;
            try {
                resultConsumer.accept(result);
            } catch (Throwable e) {
                handleResultConsumptionExcepion(returnSubscription, resultConsumer, result);
            }
        }
        
        return returnSubscription;
    }
    
    //== Functional ==
    
    public final Promise filter(Predicate predicate) {
        requireNonNull(predicate);
        return (Promise)newSubPromise((Result r, Promise targetPromise) -> {
            val result = r.filter(predicate);
            targetPromise.makeDone((Result) result);
        });
    }
    
    public final Promise peek(FuncUnit1 peeker) {
        requireNonNull(peeker);
        return (Promise)newSubPromise((Result r, Promise targetPromise) -> {
            val result = r.peek(peeker);
            targetPromise.makeDone((Result) result);
        });
    }
    
    @SuppressWarnings("unchecked")
    public final  Promise map(Func1 mapper) {
        requireNonNull(mapper);
        return (Promise)newSubPromise((Result r, Promise targetPromise) -> {
            val result = r.map(mapper);
            targetPromise.makeDone((Result) result);
        });
    }
    
    @SuppressWarnings("unchecked")
    public final  Promise mapResult(Function, Result> mapper) {
        requireNonNull(mapper);
        return (Promise)newSubPromise((Result r, Promise targetPromise) -> {
            val result = mapper.apply(r);
            targetPromise.makeDone((Result) result);
        });
    }
    
    public final  Promise flatMap(Func1> mapper) {
        return chain(mapper);
    }
    public final  Promise chain(Func1> mapper) {
        return (Promise)newSubPromise((Result r, Promise targetPromise) -> {
            val targetResult = r.map(mapper);
            targetResult.ifPresent(hasPromise -> {
                hasPromise.getPromise().onComplete(result -> {
                    targetPromise.makeDone((Result) result);
                });
            });
        });
    }
    
    @SuppressWarnings("unchecked")
    public final  Promise whenAbsentUse(TARGET elseValue) {
        return (Promise)newSubPromise((Result result, Promise targetPromise) -> {
            result
            .ifPresent(value -> {
                targetPromise.makeDone((Result)result);
            })
            .ifAbsent(() -> {
                targetPromise.makeDone(Result.valueOf(elseValue));
            });
        });
    }
    
    @SuppressWarnings("unchecked")
    public final  Promise whenAbsentGet(Supplier elseSupplier) {
        return (Promise)newSubPromise((Result result, Promise targetPromise) -> {
            result
            .ifPresent(value -> {
                targetPromise.makeDone((Result)result);
            })
            .ifAbsent(() -> {
                targetPromise.makeDone(Result.from(elseSupplier));
            });
        });
    }
    
    // TODO - Consider if adding whenPresent, whenNull, whenException  add any value.
    // TODO - Consider if adding ifException, ifCancel .... is any useful ... or just subscribe is good enough.
    
}