// // MIT License // // Copyright (c) 2022 Alexander Söderberg & Contributors // // 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 cloud.commandframework.tasks; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import org.checkerframework.checker.nullness.qual.NonNull; /** * A task recipe is a chain of tasks with optional synchronization steps, * that can be used to produce some sort of result from some input */ @SuppressWarnings({"unchecked", "rawtypes", "unused", "overloads"}) public final class TaskRecipe { private final TaskSynchronizer synchronizer; private final LinkedHashMap recipeSteps = new LinkedHashMap<>(); TaskRecipe(final @NonNull TaskSynchronizer synchronizer) { this.synchronizer = synchronizer; } /** * Begin the recipe. This step always runs asynchronously. * * @param input Input * @param Input type * @return Function that maps the input to itself */ public @NonNull TaskRecipeComponentOutputting begin(final @NonNull I input) { this.addAsynchronous(TaskFunction.identity()); return new TaskRecipeComponentOutputting<>(input); } private void addAsynchronous(final TaskRecipeStep taskRecipeStep) { this.recipeSteps.put(taskRecipeStep, false); } private void addSynchronous(final TaskRecipeStep taskRecipeStep) { this.recipeSteps.put(taskRecipeStep, true); } private void execute(final @NonNull Object initialInput, final @NonNull Runnable callback) { final Iterator> iterator = new LinkedHashMap<>(this.recipeSteps).entrySet().iterator(); CompletableFuture completableFuture = CompletableFuture.completedFuture(initialInput); completableFuture.whenComplete(this.execute(iterator, callback)); } private BiConsumer execute( final @NonNull Iterator> iterator, final @NonNull Runnable callback ) { return (o, o2) -> { if (iterator.hasNext()) { final Map.Entry entry = iterator.next(); final boolean synchronous = entry.getValue(); CompletableFuture other; if (entry.getKey() instanceof TaskFunction) { final TaskFunction function = (TaskFunction) entry.getKey(); if (synchronous) { other = this.synchronizer.runSynchronous(o, function); } else { other = this.synchronizer.runAsynchronous(o, function); } } else { final TaskConsumer consumer = (TaskConsumer) entry.getKey(); if (synchronous) { other = this.synchronizer.runSynchronous(o, consumer); } else { other = this.synchronizer.runAsynchronous(o, consumer); } } other.whenComplete(this.execute(iterator, callback)); } else { callback.run(); } }; } /** * Represents a partial recipe * * @param Input type * @param Output type */ @SuppressWarnings("UnusedTypeParameter") // already in public API public final class TaskRecipeComponentOutputting { private final Object initialInput; private TaskRecipeComponentOutputting(final @NonNull Object initialInput) { this.initialInput = initialInput; } /** * Add a new synchronous step, consuming the input of the earlier step * * @param function Function mapping the input to some output * @param Output type * @return New task recipe component */ public TaskRecipeComponentOutputting synchronous(final @NonNull TaskFunction function) { TaskRecipe.this.addSynchronous(function); return new TaskRecipeComponentOutputting<>(this.initialInput); } /** * Add a new asynchronous step, consuming the input of the earlier step * * @param function Function mapping the input to some output * @param Output type * @return New task recipe component */ public TaskRecipeComponentOutputting asynchronous(final @NonNull TaskFunction function) { TaskRecipe.this.addAsynchronous(function); return new TaskRecipeComponentOutputting<>(this.initialInput); } /** * Add a new synchronous step, consuming the input of the earlier step * * @param consumer Consumer that consumes the input * @return New task recipe component */ public TaskRecipeComponentVoid synchronous(final @NonNull TaskConsumer consumer) { TaskRecipe.this.addSynchronous(consumer); return new TaskRecipeComponentVoid<>(this.initialInput); } /** * Add a new asynchronous step, consuming the input of the earlier step * * @param consumer Consumer that consumes the input * @return New task recipe component */ public TaskRecipeComponentVoid asynchronous(final @NonNull TaskConsumer consumer) { TaskRecipe.this.addAsynchronous(consumer); return new TaskRecipeComponentVoid<>(this.initialInput); } /** * Execute the recipe * * @param callback Callback function */ public void execute(final @NonNull Runnable callback) { TaskRecipe.this.execute(this.initialInput, callback); } /** * Execute the recipe */ public void execute() { this.execute(() -> { }); } } /** * Represents a partial recipe * * @param Input type */ public final class TaskRecipeComponentVoid { private final Object initialInput; private TaskRecipeComponentVoid(final @NonNull Object initialInput) { this.initialInput = initialInput; } /** * Add a new synchronous step, consuming the input of the earlier step * * @param consumer Consumer that consumes the input * @return New task recipe component */ public TaskRecipeComponentVoid synchronous(final @NonNull TaskConsumer consumer) { TaskRecipe.this.addSynchronous(consumer); return new TaskRecipeComponentVoid<>(this.initialInput); } /** * Add a new asynchronous step, consuming the input of the earlier step * * @param consumer Consumer that consumes the input * @return New task recipe component */ public TaskRecipeComponentVoid asynchronous(final @NonNull TaskConsumer consumer) { TaskRecipe.this.addSynchronous(consumer); return new TaskRecipeComponentVoid<>(this.initialInput); } /** * Execute the recipe * * @param callback Callback function */ public void execute(final @NonNull Runnable callback) { TaskRecipe.this.execute(this.initialInput, callback); } /** * Execute the recipe */ public void execute() { this.execute(() -> { }); } } }