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

javascalautils.concurrent.FutureImpl Maven / Gradle / Ivy

There is a newer version: 1.11.2
Show newest version
/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils.concurrent;

import static javascalautils.Option.None;
import static javascalautils.OptionCompanion.Some;
import static javascalautils.TryCompanion.Failure;
import static javascalautils.TryCompanion.Success;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import javascalautils.Option;
import javascalautils.Try;
import javascalautils.Validator;

/**
 * The future implementation.
 * 
 * @author Peter Nerg
 * @since 1.2
 */
final class FutureImpl implements Future {
    /** Will contain the result once {@link #complete(Try)} is invoked. */
    private Option> response = None();

    /** The success/failure/complete handlers set by the user. */
    private final List eventHandlers = new CopyOnWriteArrayList<>();

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#isCompleted()
     */
    @Override
    public boolean isCompleted() {
        return response.isDefined();
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#value()
     */
    @Override
    public Option> value() {
        return response;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#onFailure(java.util.function.Consumer)
     */
    @Override
    public void onFailure(Consumer consumer) {
        Validator.requireNonNull(consumer, "Null is not a valid consumer");
        // register a complete handler and ignore any Success responses
        onComplete(result -> {
            // transform Failure to a Success with the Throwable
            // should it be a Success it will be transformed into a Failure and forEach will do nothing
            result.failed().forEach(consumer);
        });
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#onSuccess(java.util.function.Consumer)
     */
    @Override
    public void onSuccess(Consumer consumer) {
        Validator.requireNonNull(consumer, "Null is not a valid consumer");
        // register a complete handler and ignore any Failure responses
        onComplete(result -> {
            // should it be a Failure the forEach will do nothing
            result.forEach(consumer);
        });
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#onComplete(java.util.function.Consumer)
     */
    @Override
    public void onComplete(Consumer> c) {
        Validator.requireNonNull(c, "Null is not a valid consumer");
        eventHandlers.add(new EventHandler(c));
        // invoke all new handlers in case this Future is already completed
        notifyHandlers();
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#forEach(java.util.function.Consumer)
     */
    @Override
    public void forEach(Consumer c) {
        onSuccess(c);
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#map(java.util.function.Function)
     */
    @Override
    public  Future map(Function function) {
        // the onFailure function just passed the error as-is without transformation
        return transform(function, t -> t);
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#flatMap(java.util.function.Function)
     */
    @Override
    public  Future flatMap(Function> function) {
        Validator.requireNonNull(function, "Null is not a valid function");
        // Create new future expected to hold the value of the mapped type
        FutureImpl future = new FutureImpl<>();

        // install success handler that will map the result before applying it
        onSuccess(value -> {
            // use the provided function to create a mapped future
            Future mapped = function.apply(value);
            // install success/failure handlers on the mapped future to bridge between this instance
            // and the one created a few lines above
            mapped.onComplete(t -> future.complete(t));
        });

        // install failure handler that just passes the result through
        onFailure(t -> future.failure(t));

        return future;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#filter(java.util.function.Predicate)
     */
    @Override
    public Future filter(Predicate predicate) {
        Validator.requireNonNull(predicate, "Null is not a valid predicate");
        // Create new future expected to hold the value of the mapped type
        FutureImpl future = new FutureImpl<>();
        // install success handler that will filter the result before applying it
        onSuccess(value -> {
            if (predicate.test(value)) {
                future.success(value);
            } else {
                future.failure(new NoSuchElementException("The predicate failed on value [" + value + "]"));
            }
        });
        // install failure handler that just passes the result through
        onFailure(t -> future.failure(t));
        return future;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#transform(java.util.function.Function, java.util.function.Function)
     */
    @Override
    public  Future transform(Function onSuccess, Function onFailure) {
        Validator.requireNonNull(onSuccess, "Null is not a valid function");
        Validator.requireNonNull(onFailure, "Null is not a valid function");
        // Create new future expected to hold the value of the mapped type
        FutureImpl future = new FutureImpl<>();
        // install success handler that will map the result before applying it
        onSuccess(value -> future.success(onSuccess.apply(value)));
        // install failure handler that will map the error before applying it
        onFailure(t -> future.failure(onFailure.apply(t)));
        return future;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#recover(java.util.function.Function)
     */
    @Override
    public Future recover(Function recoverFunction) {
        Validator.requireNonNull(recoverFunction, "Null is not a valid function");
        // Create new future expected to hold the value of the mapped type
        FutureImpl future = new FutureImpl<>();

        // installs a handler that will automatically recover the result/Try should it be needed
        onComplete(t -> future.complete(t.recover(recoverFunction)));

        return future;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javascalautils.concurrent.Future#result(long, java.util.concurrent.TimeUnit)
     */
    @Override
    public T result(long duration, TimeUnit timeUnit) throws Throwable, TimeoutException {
        // The response is now set to Some
        // return the value of the response (Try), should it be a Failure the exception is raised
        return ready(duration, timeUnit).response.get().get();
    }

    /* (non-Javadoc)
     * @see javascalautils.concurrent.Future#ready(long, java.util.concurrent.TimeUnit)
     */
    @Override
    public FutureImpl ready(long duration, TimeUnit timeUnit) throws TimeoutException, InterruptedException {
        Validator.requireNonNull(timeUnit, "Null is not a valid time unit");
        CountDownLatch latch = new CountDownLatch(1);

        // install a handler that releases the count down latch when notified with a result.
        // the actual result/response is of no interest as we anyways have access to it internally in this class/instance.
        onComplete(t -> latch.countDown());

        // block for either the time to pass or the Future gets completed
        if (!latch.await(duration, timeUnit)) {
            throw new TimeoutException("Timeout waiting for Future to complete");
        }
        
        //The future is now complete, return ourselves
        return this;
    }
    
    /**
     * Returns a String representation of the instance.
     * 
     * @since 1.2
     */
    @Override
    public String toString() {
        return "Future:" + response;
    }

    /**
     * Completes this Future with a result. 
* Invoked by the Promise owning this instance. * * @param result * @return Returns itself */ Future complete(Try result) { // save the result this.response = Some(result); // notify all potential handlers notifyHandlers(); return this; } /** * Internal report success to this future.
* * @param value * The response value */ private void success(T value) { complete(Success(value)); } /** * Internal report failure to this future.
* * @param throwable * The failure Throwable */ private void failure(Throwable throwable) { complete(Failure(throwable)); } /** * Invoke all handlers with the value of this Future.
* The response may or may not exist at this point.
* A filter is applied to make sure we only notify handlers that have not been notified before. * * @param value * The value/result to notify */ private void notifyHandlers() { // the response may or may not exist at this point response.forEach(t -> eventHandlers.stream().forEach(h -> h.notify(t))); } /** * Internal holder for the success/failure/complete handlers provided by the user.
* Used primarily to keep track on if a particular handler already has been notified.
* This is to make sure the same handler won't be notified more than once. * * @author Peter Nerg * * @param */ private final class EventHandler { private final AtomicBoolean notified = new AtomicBoolean(false); private final Consumer> consumer; private EventHandler(Consumer> consumer) { this.consumer = consumer; } /** * Notifies the response to the handler.
* Invoking this more than once makes no difference. * * @param response */ private void notify(Try response) { if (notified.compareAndSet(false, true)) { consumer.accept(response); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy