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

org.teavm.common.Promise Maven / Gradle / Ivy

/*
 *  Copyright 2018 Alexey Andreev.
 *
 *  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 org.teavm.common;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.function.Function;

public class Promise {
    private static ThreadLocal> processing = new ThreadLocal<>();
    public static final Promise VOID = Promise.of(null);

    private T value;
    private Throwable error;
    private State state = State.PENDING;
    private List> thenList;
    private List catchList;

    Promise() {
    }

    public static  Promise of(T value) {
        Promise promise = new Promise<>();
        promise.complete(value);
        return promise;
    }

    public static Promise error(Throwable e) {
        Promise promise = new Promise<>();
        promise.completeWithError(e);
        return promise;
    }

    public static Promise allVoid(Collection> promises) {
        if (promises.isEmpty()) {
            return Promise.VOID;
        }
        AllVoidFunction all = new AllVoidFunction(promises.size());

        for (Promise promise : promises) {
            promise.then(all.thenF).catchError(all.catchF);
        }

        return all.result;
    }


    public static  Promise> all(Collection> promises) {
        if (promises.isEmpty()) {
            return Promise.of(Collections.emptyList());
        }
        AllFunction all = new AllFunction<>(promises.size());

        int i = 0;
        for (Promise promise : promises) {
            promise.then(all.thenF(i++)).catchError(all.catchF);
        }

        return all.result;
    }

    static class AllVoidFunction {
        Promise result = new Promise<>();
        int count;
        boolean error;

        AllVoidFunction(int count) {
            this.count = count;
        }

        Function thenF = v -> {
            if (!error && --count == 0) {
                result.complete(null);
            }
            return null;
        };

        Function catchF = e -> {
            if (!error) {
                error = true;
                result.completeWithError(e);
            }
            return null;
        };
    }


    static class AllFunction {
        Promise> result = new Promise<>();
        List list = new ArrayList<>();
        int count;
        boolean error;

        AllFunction(int count) {
            this.count = count;
            list.addAll(Collections.nCopies(count, null));
        }

        Function thenF(int index) {
            return v -> {
                if (!error) {
                    list.set(index, v);
                    if (--count == 0) {
                        result.complete(list);
                    }
                }
                return null;
            };
        }

        Function catchF = e -> {
            if (!error) {
                error = true;
                result.completeWithError(e);
            }
            return null;
        };
    }

    public  Promise then(Function f) {
        Promise result = new Promise<>();
        if (state == State.PENDING || state == State.WAITING_PROMISE) {
            if (thenList == null) {
                thenList = new ArrayList<>();
            }
            thenList.add(new Then<>(f, result, false));
        } else {
            passValue(f, result);
        }
        return result;
    }

    public Promise thenVoid(Consumer f) {
        return then(r -> {
            f.accept(r);
            return null;
        });
    }

    public  Promise thenAsync(Function> f) {
        Promise result = new Promise<>();
        if (state == State.PENDING || state == State.WAITING_PROMISE) {
            if (thenList == null) {
                thenList = new ArrayList<>();
            }
            thenList.add(new Then<>(f, result, true));
        } else if (state == State.COMPLETED) {
            passValueAsync(f, result);
        }
        return result;
    }

    public  Promise catchError(Function f) {
        Promise result = new Promise<>();
        if (state == State.PENDING || state == State.WAITING_PROMISE) {
            if (catchList == null) {
                catchList = new ArrayList<>();
            }
            catchList.add(new Catch(f, result));
        } else if (state == State.ERRORED) {
            passError(f, result);
        }
        return result;
    }


    public Promise catchVoid(Consumer f) {
        return catchError(e -> {
            f.accept(e);
            return null;
        });
    }

     void passValue(Function f, Promise target) {
        runAction(() -> {
            if (state == State.COMPLETED) {
                S next;
                try {
                    next = f.apply(value);
                } catch (Throwable e) {
                    target.completeWithError(e);
                    return;
                }
                target.complete(next);
            } else {
                target.completeWithError(error);
            }
        });
    }

     void passValueAsync(Function> f, Promise target) {
        runAction(() -> {
            if (state == State.COMPLETED) {
                Promise next;
                try {
                    next = f.apply(value);
                } catch (Throwable e) {
                    target.completeWithError(e);
                    return;
                }
                target.completeAsync(next);
            } else {
                target.completeWithError(error);
            }
        });
    }

     void passError(Function f, Promise target) {
        runAction(() -> {
            S next;
            try {
                next = f.apply(error);
            } catch (Throwable e) {
                target.completeWithError(e);
                return;
            }
            target.complete(next);
        });
    }

    void complete(T value) {
        if (state != State.PENDING) {
            throw new IllegalStateException("Already completed");
        }
        completeImpl(value);
    }

    void completeAsync(Promise value) {
        if (state != State.PENDING) {
            throw new IllegalStateException("Already completed");
        }
        state = State.WAITING_PROMISE;

        value
                .then(result -> {
                    completeImpl(result);
                    return null;
                })
                .catchError(e -> {
                    completeWithErrorImpl(e);
                    return null;
                });
    }

    private void completeImpl(T value) {
        runAction(() -> {
            state = State.COMPLETED;
            this.value = value;

            if (thenList != null) {
                var list = thenList;
                thenList = null;
                for (var then : list) {
                    if (then.promise) {
                        passValueAsync((Function>) then.f, (Promise) then.target);
                    } else {
                        passValue(then.f, (Promise) then.target);
                    }
                }
            }
            catchList = null;
        });
    }

    void completeWithError(Throwable e) {
        if (state != State.PENDING) {
            throw new IllegalStateException("Already completed");
        }
        completeWithErrorImpl(e);
    }

    void completeWithErrorImpl(Throwable e) {
        runAction(() -> {
            state = State.ERRORED;
            this.error = e;

            if (catchList != null) {
                List list = catchList;
                thenList = null;
                for (Catch c : list) {
                    passError(c.f, (Promise) c.target);
                }
            } else {
                e.printStackTrace();
            }
            thenList = null;
        });
    }

    private void runAction(Runnable action) {
        var queue = processing.get();
        if (queue != null) {
            queue.add(action);
        } else {
            queue = new ArrayDeque<>();
            queue.add(action);
            processing.set(queue);
            while (!queue.isEmpty()) {
                queue.remove().run();
            }
            processing.remove();
        }
    }

    public static void runNow(Runnable runnable) {
        var queue = processing.get();
        if (queue == null) {
            runnable.run();
        } else {
            processing.remove();
            runnable.run();
            processing.set(queue);
        }
    }

    enum State {
        PENDING,
        WAITING_PROMISE,
        COMPLETED,
        ERRORED
    }

    static class Then {
        Function f;
        Promise target;
        boolean promise;

        Then(Function f, Promise target, boolean promise) {
            this.f = f;
            this.target = target;
            this.promise = promise;
        }
    }

    static class Catch {
        Function f;
        Promise target;

        Catch(Function f, Promise target) {
            this.f = f;
            this.target = target;
        }
    }
}