001//
002// MIT License
003//
004// Copyright (c) 2021 Alexander Söderberg & Contributors
005//
006// Permission is hereby granted, free of charge, to any person obtaining a copy
007// of this software and associated documentation files (the "Software"), to deal
008// in the Software without restriction, including without limitation the rights
009// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010// copies of the Software, and to permit persons to whom the Software is
011// furnished to do so, subject to the following conditions:
012//
013// The above copyright notice and this permission notice shall be included in all
014// copies or substantial portions of the Software.
015//
016// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022// SOFTWARE.
023//
024package cloud.commandframework.execution;
025
026import cloud.commandframework.Command;
027import cloud.commandframework.CommandManager;
028import cloud.commandframework.CommandTree;
029import cloud.commandframework.context.CommandContext;
030import cloud.commandframework.exceptions.CommandExecutionException;
031import cloud.commandframework.services.State;
032import cloud.commandframework.types.tuples.Pair;
033import org.checkerframework.checker.nullness.qual.NonNull;
034import org.checkerframework.checker.nullness.qual.Nullable;
035
036import java.util.Queue;
037import java.util.concurrent.CompletableFuture;
038import java.util.concurrent.Executor;
039import java.util.concurrent.ForkJoinPool;
040import java.util.function.Consumer;
041import java.util.function.Function;
042
043/**
044 * Execution coordinator parses and/or executes commands on a separate thread from the calling thread
045 *
046 * @param <C> Command sender type
047 */
048public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExecutionCoordinator<C> {
049
050    private final CommandManager<C> commandManager;
051    private final Executor executor;
052    private final boolean synchronizeParsing;
053
054    private AsynchronousCommandExecutionCoordinator(
055            final @Nullable Executor executor,
056            final boolean synchronizeParsing,
057            final @NonNull CommandTree<C> commandTree
058    ) {
059        super(commandTree);
060        this.executor = executor == null ? ForkJoinPool.commonPool() : executor;
061        this.synchronizeParsing = synchronizeParsing;
062        this.commandManager = commandTree.getCommandManager();
063    }
064
065    /**
066     * Create a new {@link Builder} instance
067     *
068     * @param <C> Command sender type
069     * @return Builder
070     */
071    public static <C> @NonNull Builder<C> newBuilder() {
072        return new Builder<>();
073    }
074
075    @Override
076    public @NonNull CompletableFuture<CommandResult<C>> coordinateExecution(
077            final @NonNull CommandContext<C> commandContext,
078            final @NonNull Queue<@NonNull String> input
079    ) {
080        final CompletableFuture<CommandResult<C>> resultFuture = new CompletableFuture<>();
081
082        final Consumer<Command<C>> commandConsumer = command -> {
083            if (this.commandManager.postprocessContext(commandContext, command) == State.ACCEPTED) {
084                try {
085                    command.getCommandExecutionHandler().execute(commandContext);
086                } catch (final CommandExecutionException exception) {
087                    resultFuture.completeExceptionally(exception);
088                } catch (final Exception exception) {
089                    resultFuture.completeExceptionally(new CommandExecutionException(exception, commandContext));
090                }
091            }
092        };
093
094        if (this.synchronizeParsing) {
095            final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
096                    this.getCommandTree().parse(commandContext, input);
097            if (pair.getSecond() != null) {
098                final CompletableFuture<CommandResult<C>> future = new CompletableFuture<>();
099                future.completeExceptionally(pair.getSecond());
100                return future;
101            }
102            return CompletableFuture.supplyAsync(() -> {
103                commandConsumer.accept(pair.getFirst());
104                return new CommandResult<>(commandContext);
105            }, this.executor);
106        }
107
108        this.executor.execute(() -> {
109            try {
110                final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
111                        this.getCommandTree().parse(commandContext, input);
112                if (pair.getSecond() != null) {
113                    resultFuture.completeExceptionally(pair.getSecond());
114                } else {
115                    commandConsumer.accept(pair.getFirst());
116                    resultFuture.complete(new CommandResult<>(commandContext));
117                }
118            } catch (final Exception e) {
119                resultFuture.completeExceptionally(e);
120            }
121        });
122
123        return resultFuture;
124    }
125
126
127    /**
128     * Builder for {@link AsynchronousCommandExecutionCoordinator} instances
129     *
130     * @param <C> Command sender type
131     */
132    public static final class Builder<C> {
133
134        private Executor executor = null;
135        private boolean synchronizeParsing = false;
136
137        private Builder() {
138        }
139
140        /**
141         * This forces the command parsing to run on the calling thread,
142         * and only the actual command execution will run using the executor
143         *
144         * @return Builder instance
145         */
146        public @NonNull Builder<C> withSynchronousParsing() {
147            this.synchronizeParsing = true;
148            return this;
149        }
150
151        /**
152         * Both command parsing and execution will run using the executor
153         *
154         * @return Builder instance
155         */
156        public @NonNull Builder<C> withAsynchronousParsing() {
157            this.synchronizeParsing = false;
158            return this;
159        }
160
161        /**
162         * Specify an executor that will be used to coordinate tasks.
163         * By default the executor uses {@link java.util.concurrent.ForkJoinPool#commonPool()}
164         *
165         * @param executor Executor to use
166         * @return Builder instance
167         */
168        public @NonNull Builder<C> withExecutor(final @NonNull Executor executor) {
169            this.executor = executor;
170            return this;
171        }
172
173        /**
174         * Builder a function that generates a command execution coordinator
175         * using the options specified in this builder
176         *
177         * @return Function that builds the coordinator
178         */
179        public @NonNull Function<@NonNull CommandTree<C>, @NonNull CommandExecutionCoordinator<C>> build() {
180            return tree -> new AsynchronousCommandExecutionCoordinator<>(this.executor, this.synchronizeParsing, tree);
181        }
182
183    }
184
185}