package im.actor.runtime.promise; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import im.actor.runtime.Crypto; import im.actor.runtime.function.Function; import im.actor.runtime.function.ListFunction; import im.actor.runtime.function.Predicate; import im.actor.runtime.function.Predicates; /** * Array of Promises. Allows you to invoke map, flatMap and other useful methods * for manipulating data. * * @param <T> type of array */ public class PromisesArray<T> { /** * Create PromisesArray from collection * * @param collection Source collection * @param <T> type of array * @return array */ @SuppressWarnings("unchecked") public static <T> PromisesArray<T> of(Collection<T> collection) { final ArrayList<Promise<T>> res = new ArrayList<>(); for (T t : collection) { res.add(Promise.success(t)); } final Promise[] promises = res.toArray(new Promise[res.size()]); return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> { executor.result(promises); }); } /** * Create PromisesArray from values * * @param items elements * @param <T> type of array * @return array */ @SafeVarargs public static <T> PromisesArray<T> of(T... items) { ArrayList<Promise<T>> res = new ArrayList<>(); for (T t : items) { res.add(Promise.success(t)); } final Promise[] promises = res.toArray(new Promise[res.size()]); return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> { executor.result(promises); }); } /** * Create PromisesArray from multiple Promise * * @param items promises * @param <T> type of array * @return array */ @SafeVarargs public static <T> PromisesArray<T> ofPromises(Promise<T>... items) { ArrayList<Promise<T>> res = new ArrayList<>(); Collections.addAll(res, items); final Promise[] promises = res.toArray(new Promise[res.size()]); return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> { executor.result(promises); }); } public static <T> PromisesArray<T> ofPromises(Collection<Promise<T>> items) { ArrayList<Promise<T>> res = new ArrayList<>(items); // Collections.addAll(res, items); final Promise[] promises = res.toArray(new Promise[res.size()]); return new PromisesArray<>((PromiseFunc<Promise<T>[]>) executor -> { executor.result(promises); }); } // // Constructors and methods // private Promise<Promise<T>[]> promises; private PromisesArray(Promise<Promise<T>[]> promises) { this.promises = promises; } private PromisesArray(PromiseFunc<Promise<T>[]> executor) { this(new Promise<>(executor)); } /** * Map promises results to new promises * * @param fun mapping function * @param <R> type of result promises * @return PromisesArray */ public <R> PromisesArray<R> map(final Function<T, Promise<R>> fun) { return mapSourcePromises(srcPromise -> new Promise<R>(resolver -> { srcPromise.then(t -> { Promise<R> mapped = fun.apply(t); mapped.then(t2 -> resolver.result(t2)); mapped.failure(e -> resolver.error(e)); }); srcPromise.failure(e -> resolver.error(e)); })); } public <R> PromisesArray<R> mapOptional(final Function<T, Promise<R>> fun) { return map(fun) .ignoreFailed() .filterNull(); } public PromisesArray<T> ignoreFailed() { return mapSourcePromises(tPromise -> new Promise<T>(resolver -> { tPromise.then(t -> resolver.result(t)); tPromise.failure(e -> resolver.result(null)); })); } public PromisesArray<T> filterNull() { return filter(Predicates.NOT_NULL); } private <R> PromisesArray<R> mapSourcePromises(final Function<Promise<T>, Promise<R>> fun) { return new PromisesArray<R>(executor -> { // // Handling source results // promises.then(sourcePromises -> { // // Building mapped promises // final Promise<R>[] mappedPromises = new Promise[sourcePromises.length]; for (int i = 0; i < mappedPromises.length; i++) { mappedPromises[i] = fun.apply(sourcePromises[i]); } // // Returning mapped promises // executor.result(mappedPromises); }); // // Handling failure // promises.failure(e -> executor.error(e)); }); } public PromisesArray<T> filter(final Predicate<T> predicate) { return flatMap(t -> { if (predicate.apply(t)) { return (T[]) new Object[]{t}; } return (T[]) new Object[0]; }); } public PromisesArray<T> sort(final Comparator<T> comparator) { return flatMapAll(ts -> { T[] res = (T[]) new Object[ts.length]; System.arraycopy(ts, 0, res, 0, ts.length); Arrays.sort(res, comparator); return res; }); } public PromisesArray<T> first(final int count) { return flatMapAll(ts -> { int len = Math.min(count, ts.length); T[] res = (T[]) new Object[len]; System.arraycopy(ts, 0, res, 0, len); return res; }); } public Promise<T> first() { return first(1) .zip() .map(src -> { if (src.size() == 0) { throw new RuntimeException("Array is empty (first)"); } return src.get(0); }); } public Promise<T> random() { return flatMapAll(ts -> { if (ts.length == 0) { throw new RuntimeException("Array is empty"); } return (T[]) new Object[]{ts[Crypto.randomInt(ts.length)]}; }).first(); } public <R> PromisesArray<R> flatMapAll(final Function<T[], R[]> fuc) { return new PromisesArray<R>(new Promise<>((PromiseFunc<Promise<R>[]>) resolver -> { // // Handling source results // promises.then(sourcePromises -> { final Object[] res = new Object[sourcePromises.length]; final Boolean[] ended = new Boolean[sourcePromises.length]; for (int i = 0; i < sourcePromises.length; i++) { final int finalI = i; sourcePromises[i].then(t -> { res[finalI] = t; ended[finalI] = true; for (int i1 = 0; i1 < sourcePromises.length; i1++) { if (ended[i1] == null || !ended[i1]) { return; } } R[] resMap = fuc.apply((T[]) res); ArrayList<Promise<R>> resultList = new ArrayList<>(); for (R r : resMap) { resultList.add(Promise.success(r)); } resolver.result(resultList.toArray(new Promise[0])); }); sourcePromises[i].failure(e -> resolver.error(e)); } if (sourcePromises.length == 0) { resolver.result(new Promise[0]); } }); // // Handling failure // promises.failure(e -> resolver.error(e)); })); } public <R> PromisesArray<R> flatMap(final Function<T, R[]> map) { return new PromisesArray<R>(new Promise<>((PromiseFunc<Promise<R>[]>) resolver -> { // // Handling source results // promises.then(sourcePromises -> { final Object[][] res = new Object[sourcePromises.length][]; final Boolean[] ended = new Boolean[sourcePromises.length]; for (int i = 0; i < sourcePromises.length; i++) { final int finalI = i; sourcePromises[i].then(t -> { res[finalI] = map.apply(t); ended[finalI] = true; for (int i1 = 0; i1 < sourcePromises.length; i1++) { if (ended[i1] == null || !ended[i1]) { return; } } ArrayList<Promise<R>> resultList = new ArrayList<>(); for (int i2 = 0; i2 < sourcePromises.length; i2++) { for (int j = 0; j < res[i2].length; j++) { resultList.add(Promise.success((R) res[i2][j])); } } resolver.result(resultList.toArray(new Promise[0])); }); sourcePromises[i].failure(e -> resolver.error(e)); } if (sourcePromises.length == 0) { resolver.result(new Promise[0]); } }); // // Handling failure // promises.failure(e -> resolver.error(e)); })); } /** * Zip array to single promise * * @param fuc zipping function * @param <R> type of result * @return promise */ public <R> Promise<R> zipPromise(final ListFunction<T, Promise<R>> fuc) { return new Promise<>(resolver -> { promises.then(promises1 -> { final ArrayList<T> res = new ArrayList<T>(); for (int i = 0; i < promises1.length; i++) { res.add(null); } final Boolean[] ended = new Boolean[promises1.length]; for (int i = 0; i < promises1.length; i++) { final int finalI = i; promises1[i].then(t -> { res.set(finalI, t); ended[finalI] = true; for (int i1 = 0; i1 < promises1.length; i1++) { if (ended[i1] == null || !ended[i1]) { return; } } fuc.apply(res) .pipeTo(resolver); }); promises1[i].failure(e -> resolver.error(e)); } if (promises1.length == 0) { fuc.apply(res) .pipeTo(resolver); } }); promises.failure(e -> resolver.error(e)); }); } /** * Zipping array of promises to single promise of array * * @return promise */ public Promise<List<T>> zip() { return zipPromise(t -> Promise.success(t)); } }