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

graphql.execution.Async Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.execution;

import graphql.Assert;
import graphql.Internal;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static graphql.Assert.assertTrue;

@Internal
@SuppressWarnings("FutureReturnValueIgnored")
public class Async {

    /**
     * A builder of materialized objects or {@link CompletableFuture}s than can present a promise to the list of them
     * 

* This builder has a strict contract on size whereby if the expectedSize is five, then there MUST be five elements presented to it. * * @param for two */ public interface CombinedBuilder { /** * This adds a {@link CompletableFuture} into the collection of results * * @param completableFuture the CF to add */ void add(CompletableFuture completableFuture); /** * This adds a new value which can be either a materialized value or a {@link CompletableFuture} * * @param object the object to add */ void addObject(Object object); /** * This will return a {@code CompletableFuture>} even if the inputs are all materialized values * * @return a CompletableFuture to a List of values */ CompletableFuture> await(); /** * This will return a {@code CompletableFuture>} if ANY of the input values are async * otherwise it just return a materialised {@code List} * * @return either a CompletableFuture or a materialized list */ /* CompletableFuture> | List */ Object awaitPolymorphic(); } /** * Combines zero or more CFs into one. It is a wrapper around CompletableFuture.allOf. * * @param expectedSize how many we expect * @param for two * * @return a combined builder of CFs */ public static CombinedBuilder ofExpectedSize(int expectedSize) { if (expectedSize == 0) { return new Empty<>(); } else if (expectedSize == 1) { return new Single<>(); } else { return new Many<>(expectedSize); } } private static class Empty implements CombinedBuilder { private int ix; @Override public void add(CompletableFuture completableFuture) { this.ix++; } @Override public void addObject(Object object) { this.ix++; } @Override public CompletableFuture> await() { assertTrue(ix == 0, "expected size was 0 got %d", ix); return typedEmpty(); } @Override public Object awaitPolymorphic() { Assert.assertTrue(ix == 0, () -> "expected size was " + 0 + " got " + ix); return Collections.emptyList(); } // implementation details: infer the type of Completable> from a singleton empty private static final CompletableFuture> EMPTY = CompletableFuture.completedFuture(Collections.emptyList()); @SuppressWarnings("unchecked") private static CompletableFuture typedEmpty() { return (CompletableFuture) EMPTY; } } private static class Single implements CombinedBuilder { // avoiding array allocation as there is only 1 CF private Object value; private int ix; @Override public void add(CompletableFuture completableFuture) { this.value = completableFuture; this.ix++; } @Override public void addObject(Object object) { this.value = object; this.ix++; } @Override public CompletableFuture> await() { commonSizeAssert(); if (value instanceof CompletableFuture) { @SuppressWarnings("unchecked") CompletableFuture cf = (CompletableFuture) value; return cf.thenApply(Collections::singletonList); } //noinspection unchecked return CompletableFuture.completedFuture(Collections.singletonList((T) value)); } @Override public Object awaitPolymorphic() { commonSizeAssert(); if (value instanceof CompletableFuture) { @SuppressWarnings("unchecked") CompletableFuture cf = (CompletableFuture) value; return cf.thenApply(Collections::singletonList); } //noinspection unchecked return Collections.singletonList((T) value); } private void commonSizeAssert() { Assert.assertTrue(ix == 1, () -> "expected size was " + 1 + " got " + ix); } } private static class Many implements CombinedBuilder { private final Object[] array; private int ix; private int cfCount; private Many(int size) { this.array = new Object[size]; this.ix = 0; cfCount = 0; } @Override public void add(CompletableFuture completableFuture) { array[ix++] = completableFuture; cfCount++; } @Override public void addObject(Object object) { array[ix++] = object; if (object instanceof CompletableFuture) { cfCount++; } } @SuppressWarnings("unchecked") @Override public CompletableFuture> await() { commonSizeAssert(); CompletableFuture> overallResult = new CompletableFuture<>(); if (cfCount == 0) { overallResult.complete(materialisedList(array)); } else { CompletableFuture[] cfsArr = copyOnlyCFsToArray(); CompletableFuture.allOf(cfsArr) .whenComplete((ignored, exception) -> { if (exception != null) { overallResult.completeExceptionally(exception); return; } List results = new ArrayList<>(array.length); if (cfsArr.length == array.length) { // they are all CFs for (CompletableFuture cf : cfsArr) { results.add(cf.join()); } } else { // it's a mixed bag of CFs and materialized objects for (Object object : array) { if (object instanceof CompletableFuture) { CompletableFuture cf = (CompletableFuture) object; // join is safe since they are all completed earlier via CompletableFuture.allOf() results.add(cf.join()); } else { results.add((T) object); } } } overallResult.complete(results); }); } return overallResult; } @SuppressWarnings("unchecked") @NotNull private CompletableFuture[] copyOnlyCFsToArray() { if (cfCount == array.length) { // if it's all CFs - make a type safe copy via C code return Arrays.copyOf(array, array.length, CompletableFuture[].class); } else { int i = 0; CompletableFuture[] dest = new CompletableFuture[cfCount]; for (Object o : array) { if (o instanceof CompletableFuture) { dest[i] = (CompletableFuture) o; i++; } } return dest; } } @Override public Object awaitPolymorphic() { if (cfCount == 0) { commonSizeAssert(); return materialisedList(array); } else { return await(); } } @NotNull private List materialisedList(Object[] array) { List results = new ArrayList<>(array.length); for (Object object : array) { //noinspection unchecked results.add((T) object); } return results; } private void commonSizeAssert() { Assert.assertTrue(ix == array.length, () -> "expected size was " + array.length + " got " + ix); } } @SuppressWarnings("unchecked") public static CompletableFuture> each(Collection list, Function cfOrMaterialisedValueFactory) { Object l = eachPolymorphic(list, cfOrMaterialisedValueFactory); if (l instanceof CompletableFuture) { return (CompletableFuture>) l; } else { return CompletableFuture.completedFuture((List) l); } } /** * This will run the value factory for each of the values in the provided list. *

* If any of the values provided is a {@link CompletableFuture} it will return a {@link CompletableFuture} result object * that joins on all values otherwise if none of the values are a {@link CompletableFuture} then it will return a materialized list. * * @param list the list to work over * @param cfOrMaterialisedValueFactory the value factory to call for each iterm in the list * @param for two * * @return a {@link CompletableFuture} to the list of resolved values or the list of values in a materialized fashion */ public static /* CompletableFuture> | List */ Object eachPolymorphic(Collection list, Function cfOrMaterialisedValueFactory) { CombinedBuilder futures = ofExpectedSize(list.size()); for (T t : list) { try { Object value = cfOrMaterialisedValueFactory.apply(t); futures.addObject(value); } catch (Exception e) { CompletableFuture cf = new CompletableFuture<>(); // Async.each makes sure that it is not a CompletionException inside a CompletionException cf.completeExceptionally(new CompletionException(e)); futures.add(cf); } } return futures.awaitPolymorphic(); } public static CompletableFuture> eachSequentially(Iterable list, BiFunction, Object> cfOrMaterialisedValueFactory) { CompletableFuture> result = new CompletableFuture<>(); eachSequentiallyPolymorphicImpl(list.iterator(), cfOrMaterialisedValueFactory, new ArrayList<>(), result); return result; } @SuppressWarnings("unchecked") private static void eachSequentiallyPolymorphicImpl(Iterator iterator, BiFunction, Object> cfOrMaterialisedValueFactory, List tmpResult, CompletableFuture> overallResult) { if (!iterator.hasNext()) { overallResult.complete(tmpResult); return; } Object value; try { value = cfOrMaterialisedValueFactory.apply(iterator.next(), tmpResult); } catch (Exception e) { overallResult.completeExceptionally(new CompletionException(e)); return; } if (value instanceof CompletableFuture) { CompletableFuture cf = (CompletableFuture) value; cf.whenComplete((cfResult, exception) -> { if (exception != null) { overallResult.completeExceptionally(exception); return; } tmpResult.add(cfResult); eachSequentiallyPolymorphicImpl(iterator, cfOrMaterialisedValueFactory, tmpResult, overallResult); }); } else { tmpResult.add((U) value); eachSequentiallyPolymorphicImpl(iterator, cfOrMaterialisedValueFactory, tmpResult, overallResult); } } /** * Turns an object T into a CompletableFuture if it's not already * * @param t - the object to check * @param for two * * @return a CompletableFuture */ @SuppressWarnings("unchecked") public static CompletableFuture toCompletableFuture(Object t) { if (t instanceof CompletionStage) { return ((CompletionStage) t).toCompletableFuture(); } else { return CompletableFuture.completedFuture((T) t); } } /** * Turns a CompletionStage into a CompletableFuture if it's not already, otherwise leaves it alone * as a materialized object. * * @param object - the object to check * * @return a CompletableFuture from a CompletionStage or the materialized object itself */ public static Object toCompletableFutureOrMaterializedObject(Object object) { if (object instanceof CompletionStage) { return ((CompletionStage) object).toCompletableFuture(); } else { return object; } } public static CompletableFuture tryCatch(Supplier> supplier) { try { return supplier.get(); } catch (Exception e) { CompletableFuture result = new CompletableFuture<>(); result.completeExceptionally(e); return result; } } public static CompletableFuture exceptionallyCompletedFuture(Throwable exception) { CompletableFuture result = new CompletableFuture<>(); result.completeExceptionally(exception); return result; } /** * If the passed in CompletableFuture is null, then it creates a CompletableFuture that resolves to null * * @param completableFuture the CF to use * @param for two * * @return the completableFuture if it's not null or one that always resoles to null */ public static @NotNull CompletableFuture orNullCompletedFuture(@Nullable CompletableFuture completableFuture) { return completableFuture != null ? completableFuture : CompletableFuture.completedFuture(null); } }