From 77cbf15faab7e5ec1f04d887e76248d4297b62cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Tue, 22 Sep 2020 20:53:49 +0200 Subject: [PATCH] Add command postprocessing --- .../commands/CommandManager.java | 38 +++++++- .../commands/CommandTree.java | 10 ++ ...ynchronousCommandExecutionCoordinator.java | 15 ++- .../CommandExecutionCoordinator.java | 8 +- .../AcceptingCommandPostprocessor.java | 46 ++++++++++ .../CommandPostprocessingContext.java | 92 +++++++++++++++++++ .../postprocessor/CommandPostprocessor.java | 34 +++++++ .../execution/postprocessor/package-info.java | 28 ++++++ .../preprocessor/CommandPreprocessor.java | 1 - .../execution/preprocessor/package-info.java | 2 +- .../commands/CommandPostProcessorTest.java | 65 +++++++++++++ 11 files changed, 331 insertions(+), 8 deletions(-) create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/AcceptingCommandPostprocessor.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessingContext.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessor.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/package-info.java create mode 100644 cloud-core/src/test/java/com/intellectualsites/commands/CommandPostProcessorTest.java diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java index f93c2777..f35158ea 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -38,6 +38,9 @@ import com.intellectualsites.commands.execution.CommandExecutionCoordinator; import com.intellectualsites.commands.execution.CommandResult; import com.intellectualsites.commands.execution.CommandSuggestionProcessor; import com.intellectualsites.commands.execution.FilteringCommandSuggestionProcessor; +import com.intellectualsites.commands.execution.postprocessor.AcceptingCommandPostprocessor; +import com.intellectualsites.commands.execution.postprocessor.CommandPostprocessingContext; +import com.intellectualsites.commands.execution.postprocessor.CommandPostprocessor; import com.intellectualsites.commands.execution.preprocessor.AcceptingCommandPreprocessor; import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessor; @@ -94,6 +97,8 @@ public abstract class CommandManager { this.commandRegistrationHandler = commandRegistrationHandler; this.servicePipeline.registerServiceType(new TypeToken>() { }, new AcceptingCommandPreprocessor<>()); + this.servicePipeline.registerServiceType(new TypeToken>() { + }, new AcceptingCommandPostprocessor<>()); } /** @@ -305,13 +310,26 @@ public abstract class CommandManager { Collections.emptyList()); } + /** + * Register a new command postprocessor. The order they are registered in is respected, and they + * are called in LIFO order + * + * @param processor Processor to register + * @see #preprocessContext(CommandContext, LinkedList) Preprocess a context + */ + public void registerCommandPostProcessor(@Nonnull final CommandPostprocessor processor) { + this.servicePipeline.registerServiceImplementation(new TypeToken>() { + }, processor, + Collections.emptyList()); + } + /** * Preprocess a command context instance * * @param context Command context * @param inputQueue Command input as supplied by sender * @return {@link State#ACCEPTED} if the command should be parsed and executed, else {@link State#REJECTED} - * @see #registerExceptionHandler(Class, BiConsumer) Register a command preprocessor + * @see #registerCommandPreProcessor(CommandPreprocessor) Register a command preprocessor */ public State preprocessContext(@Nonnull final CommandContext context, @Nonnull final LinkedList inputQueue) { this.servicePipeline.pump(new CommandPreprocessingContext<>(context, inputQueue)) @@ -323,6 +341,24 @@ public abstract class CommandManager { : State.ACCEPTED; } + /** + * Postprocess a command context instance + * + * @param context Command context + * @param command Command instance + * @return {@link State#ACCEPTED} if the command should be parsed and executed, else {@link State#REJECTED} + * @see #registerCommandPostProcessor(CommandPostprocessor) Register a command postprocessor + */ + public State postprocessContext(@Nonnull final CommandContext context, @Nonnull final Command command) { + this.servicePipeline.pump(new CommandPostprocessingContext<>(context, command)) + .through(new TypeToken>() { + }) + .getResult(); + return context.get(AcceptingCommandPostprocessor.PROCESSED_INDICATOR_KEY).orElse("").isEmpty() + ? State.REJECTED + : State.ACCEPTED; + } + /** * Get the command suggestions processor instance currently used in this command manager * diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java index 8e2fefd0..a7bc89aa 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -559,6 +559,16 @@ public final class CommandTree { return null; } + /** + * Get the command manager + * + * @return Command manager + */ + @Nonnull + public CommandManager getCommandManager() { + return this.commandManager; + } + /** * Very simple tree structure * diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java index 96d05725..3ed00441 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java @@ -24,8 +24,10 @@ package com.intellectualsites.commands.execution; import com.intellectualsites.commands.Command; +import com.intellectualsites.commands.CommandManager; import com.intellectualsites.commands.CommandTree; import com.intellectualsites.commands.context.CommandContext; +import com.intellectualsites.services.State; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -33,6 +35,7 @@ import java.util.Optional; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -43,6 +46,7 @@ import java.util.function.Supplier; */ public final class AsynchronousCommandExecutionCoordinator extends CommandExecutionCoordinator { + private final CommandManager commandManager; private final Executor executor; private final boolean synchronizeParsing; @@ -52,6 +56,7 @@ public final class AsynchronousCommandExecutionCoordinator extends CommandExe super(commandTree); this.executor = executor; this.synchronizeParsing = synchronizeParsing; + this.commandManager = commandTree.getCommandManager(); } /** @@ -69,17 +74,21 @@ public final class AsynchronousCommandExecutionCoordinator extends CommandExe public CompletableFuture> coordinateExecution(@Nonnull final CommandContext commandContext, @Nonnull final Queue input) { + final Consumer> commandConsumer = command -> { + if (this.commandManager.postprocessContext(commandContext, command) == State.ACCEPTED) { + command.getCommandExecutionHandler().execute(commandContext); + } + }; final Supplier> supplier; if (this.synchronizeParsing) { final Optional> commandOptional = this.getCommandTree().parse(commandContext, input); supplier = () -> { - commandOptional.ifPresent(command -> command.getCommandExecutionHandler().execute(commandContext)); + commandOptional.ifPresent(commandConsumer); return new CommandResult<>(commandContext); }; } else { supplier = () -> { - this.getCommandTree().parse(commandContext, input).ifPresent( - command -> command.getCommandExecutionHandler().execute(commandContext)); + this.getCommandTree().parse(commandContext, input).ifPresent(commandConsumer); return new CommandResult<>(commandContext); }; } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java index 024955cb..cf1135e5 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java @@ -25,6 +25,7 @@ package com.intellectualsites.commands.execution; import com.intellectualsites.commands.CommandTree; import com.intellectualsites.commands.context.CommandContext; +import com.intellectualsites.services.State; import javax.annotation.Nonnull; import java.util.Queue; @@ -101,8 +102,11 @@ public abstract class CommandExecutionCoordinator { @Nonnull final Queue input) { final CompletableFuture> completableFuture = new CompletableFuture<>(); try { - this.getCommandTree().parse(commandContext, input).ifPresent( - command -> command.getCommandExecutionHandler().execute(commandContext)); + this.getCommandTree().parse(commandContext, input).ifPresent(command -> { + if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) { + command.getCommandExecutionHandler().execute(commandContext); + } + }); completableFuture.complete(new CommandResult<>(commandContext)); } catch (final Exception e) { completableFuture.completeExceptionally(e); diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/AcceptingCommandPostprocessor.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/AcceptingCommandPostprocessor.java new file mode 100644 index 00000000..17366130 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/AcceptingCommandPostprocessor.java @@ -0,0 +1,46 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// 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 com.intellectualsites.commands.execution.postprocessor; + +import javax.annotation.Nonnull; + +/** + * {@link CommandPostprocessor} that does nothing besides indicating that the context + * has been properly processed + * + * @param Command sender type + */ +public final class AcceptingCommandPostprocessor implements CommandPostprocessor { + + /** + * Key used to access the context meta that indicates that the context has been fully processed + */ + public static final String PROCESSED_INDICATOR_KEY = "__COMMAND_POST_PROCESSED__"; + + @Override + public void accept(@Nonnull final CommandPostprocessingContext context) { + context.getCommandContext().store(PROCESSED_INDICATOR_KEY, "true"); + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessingContext.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessingContext.java new file mode 100644 index 00000000..45821779 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessingContext.java @@ -0,0 +1,92 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// 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 com.intellectualsites.commands.execution.postprocessor; + +import com.intellectualsites.commands.Command; +import com.intellectualsites.commands.context.CommandContext; + +import javax.annotation.Nonnull; +import java.util.Objects; + +/** + * Context for {@link CommandPostprocessor command postprocessors} + * + * @param Command sender type + */ +public final class CommandPostprocessingContext { + + private final CommandContext commandContext; + private final Command command; + + /** + * Construct a new command postprocessing context + * + * @param commandContext Command context + * @param command Command instance + */ + public CommandPostprocessingContext(@Nonnull final CommandContext commandContext, + @Nonnull final Command command) { + this.commandContext = commandContext; + this.command = command; + } + + /** + * Get the command context + * + * @return Command context + */ + @Nonnull + public CommandContext getCommandContext() { + return this.commandContext; + } + + /** + * Get the command instance + * + * @return Command instance + */ + @Nonnull + public Command getCommand() { + return this.command; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final CommandPostprocessingContext that = (CommandPostprocessingContext) o; + return Objects.equals(getCommandContext(), that.getCommandContext()) + && Objects.equals(this.getCommand(), that.getCommand()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getCommandContext(), this.getCommand()); + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessor.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessor.java new file mode 100644 index 00000000..ac825696 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/CommandPostprocessor.java @@ -0,0 +1,34 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// 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 com.intellectualsites.commands.execution.postprocessor; + +import com.intellectualsites.services.types.ConsumerService; + +/** + * Command postprocessor that acts on commands before execution + * + * @param Command sender type + */ +public interface CommandPostprocessor extends ConsumerService> { +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/package-info.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/package-info.java new file mode 100644 index 00000000..888a94d5 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/postprocessor/package-info.java @@ -0,0 +1,28 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// 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. +// + +/** + * Command postprocessing system + */ +package com.intellectualsites.commands.execution.postprocessor; diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessor.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessor.java index 34c4be43..80bffd58 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessor.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessor.java @@ -33,7 +33,6 @@ import com.intellectualsites.services.types.ConsumerService; * {@link ConsumerService#interrupt()} * * @param Command sender type - * {@inheritDoc} */ public interface CommandPreprocessor extends ConsumerService> { } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/package-info.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/package-info.java index 25552016..7bcbd686 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/package-info.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/package-info.java @@ -23,6 +23,6 @@ // /** - * Command preprocessor system + * Command preprocessing system */ package com.intellectualsites.commands.execution.preprocessor; diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandPostProcessorTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandPostProcessorTest.java new file mode 100644 index 00000000..5bd9f3ef --- /dev/null +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandPostProcessorTest.java @@ -0,0 +1,65 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// 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 com.intellectualsites.commands; + +import com.intellectualsites.commands.execution.postprocessor.CommandPostprocessingContext; +import com.intellectualsites.commands.execution.postprocessor.CommandPostprocessor; +import com.intellectualsites.commands.meta.SimpleCommandMeta; +import com.intellectualsites.services.types.ConsumerService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; + +public class CommandPostProcessorTest { + + private static CommandManager manager; + private static final boolean[] state = new boolean[] {false}; + + @BeforeAll + static void newTree() { + manager = new TestCommandManager(); + manager.command(manager.commandBuilder("test", SimpleCommandMeta.empty()) + .handler(c -> state[0] = true) + .build()); + manager.registerCommandPostProcessor(new SamplePostprocessor()); + } + + @Test + void testPreprocessing() { + manager.executeCommand(new TestCommandSender(),"test").join(); + Assertions.assertEquals(false, state[0]); + } + + static final class SamplePostprocessor implements CommandPostprocessor { + + @Override + public void accept(@Nonnull final CommandPostprocessingContext context) { + ConsumerService.interrupt(); + } + + } + +}