From e01fd6af37a6b47cf26b4c81a6f692587ebde599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Sat, 12 Sep 2020 20:29:58 +0200 Subject: [PATCH] Merge pull request #3 * Start implementing command preprocessors * Call the preprocessing step and also add a command suggestions processor * Merge branch 'master' into preprocessing * Improve command pre-processing --- checkstyle.xml | 3 +- .../commands/CommandManager.java | 94 +++++++++++++++++-- .../commands/components/StaticComponent.java | 8 +- .../exceptions/NoSuchCommandException.java | 15 ++- .../CommandExecutionCoordinator.java | 8 +- .../commands/execution/CommandResult.java | 30 +++++- .../execution/CommandSuggestionProcessor.java | 40 ++++++++ .../FilteringCommandSuggestionProcessor.java | 58 ++++++++++++ .../AcceptingCommandPreprocessor.java | 48 ++++++++++ .../CommandPreprocessingContext.java | 94 +++++++++++++++++++ .../preprocessor/CommandPreprocessor.java | 40 ++++++++ .../execution/preprocessor/package-info.java | 28 ++++++ .../commands/CommandPreProcessorTest.java | 88 +++++++++++++++++ 13 files changed, 534 insertions(+), 20 deletions(-) create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandSuggestionProcessor.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/FilteringCommandSuggestionProcessor.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/AcceptingCommandPreprocessor.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessingContext.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessor.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/package-info.java create mode 100644 cloud-core/src/test/java/com/intellectualsites/commands/CommandPreProcessorTest.java diff --git a/checkstyle.xml b/checkstyle.xml index e6aae603..dede079b 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -32,6 +32,7 @@ --> + - + 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 c9beab87..ed50d2a5 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -23,20 +23,29 @@ // package com.intellectualsites.commands; +import com.google.common.reflect.TypeToken; import com.intellectualsites.commands.components.CommandSyntaxFormatter; import com.intellectualsites.commands.components.StandardCommandSyntaxFormatter; +import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.context.CommandContextFactory; import com.intellectualsites.commands.context.StandardCommandContextFactory; 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.preprocessor.CommandPreprocessingContext; +import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessor; +import com.intellectualsites.commands.execution.preprocessor.AcceptingCommandPreprocessor; import com.intellectualsites.commands.internal.CommandRegistrationHandler; import com.intellectualsites.commands.meta.CommandMeta; import com.intellectualsites.commands.sender.CommandSender; +import com.intellectualsites.services.ServicePipeline; +import com.intellectualsites.services.State; import javax.annotation.Nonnull; +import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Queue; import java.util.StringTokenizer; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -51,12 +60,14 @@ import java.util.function.Function; public abstract class CommandManager { private final CommandContextFactory commandContextFactory = new StandardCommandContextFactory<>(); + private final ServicePipeline servicePipeline = ServicePipeline.builder().build(); private final CommandExecutionCoordinator commandExecutionCoordinator; private final CommandRegistrationHandler commandRegistrationHandler; private final CommandTree commandTree; private CommandSyntaxFormatter commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>(); + private CommandSuggestionProcessor commandSuggestionProcessor = new FilteringCommandSuggestionProcessor<>(); /** * Create a new command manager instance @@ -70,6 +81,8 @@ public abstract class CommandManager>() { + }, new AcceptingCommandPreprocessor<>()); } /** @@ -80,9 +93,20 @@ public abstract class CommandManager executeCommand(@Nonnull final C commandSender, @Nonnull final String input) { - return this.commandExecutionCoordinator.coordinateExecution(this.commandContextFactory.create(commandSender), - tokenize(input)); + public CompletableFuture> executeCommand(@Nonnull final C commandSender, @Nonnull final String input) { + final CommandContext context = this.commandContextFactory.create(commandSender); + final LinkedList inputQueue = this.tokenize(input); + try { + if (this.preprocessContext(context, inputQueue) == State.ACCEPTED) { + return this.commandExecutionCoordinator.coordinateExecution(context, inputQueue); + } + } catch (final Exception e) { + final CompletableFuture> future = new CompletableFuture<>(); + future.completeExceptionally(e); + return future; + } + /* Wasn't allowed to execute the command */ + return CompletableFuture.completedFuture(null); } /** @@ -95,13 +119,21 @@ public abstract class CommandManager suggest(@Nonnull final C commandSender, @Nonnull final String input) { - return this.commandTree.getSuggestions(this.commandContextFactory.create(commandSender), tokenize(input)); + final CommandContext context = this.commandContextFactory.create(commandSender); + final LinkedList inputQueue = this.tokenize(input); + if (this.preprocessContext(context, inputQueue) == State.ACCEPTED) { + return this.commandSuggestionProcessor.apply(new CommandPreprocessingContext<>(context, inputQueue), + this.commandTree.getSuggestions( + context, inputQueue)); + } else { + return Collections.emptyList(); + } } @Nonnull - private Queue tokenize(@Nonnull final String input) { + private LinkedList tokenize(@Nonnull final String input) { final StringTokenizer stringTokenizer = new StringTokenizer(input, " "); - final Queue tokens = new LinkedList<>(); + final LinkedList tokens = new LinkedList<>(); while (stringTokenizer.hasMoreElements()) { tokens.add(stringTokenizer.nextToken()); } @@ -194,4 +226,52 @@ public abstract class CommandManager 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} + */ + public State preprocessContext(@Nonnull final CommandContext context, @Nonnull final LinkedList inputQueue) { + this.servicePipeline.pump(new CommandPreprocessingContext<>(context, inputQueue)) + .through(new TypeToken>() { + }) + .getResult(); + return context.get(AcceptingCommandPreprocessor.PROCESSED_INDICATOR_KEY).orElse("").isEmpty() + ? State.REJECTED + : State.ACCEPTED; + } + + /** + * Get the command suggestions processor instance currently used in this command manager + * + * @return Command suggestions processor + */ + @Nonnull + public CommandSuggestionProcessor getCommandSuggestionProcessor() { + return this.commandSuggestionProcessor; + } + + /** + * Set the command suggestions processor for this command manager + * + * @param commandSuggestionProcessor New command suggestions processor + */ + public void setCommandSuggestionProcessor(@Nonnull final CommandSuggestionProcessor commandSuggestionProcessor) { + this.commandSuggestionProcessor = commandSuggestionProcessor; + } + } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/StaticComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/StaticComponent.java index 8b582ea9..f965fcc6 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/StaticComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/StaticComponent.java @@ -23,9 +23,9 @@ // package com.intellectualsites.commands.components; -import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.components.parser.ComponentParseResult; import com.intellectualsites.commands.components.parser.ComponentParser; +import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.sender.CommandSender; import javax.annotation.Nonnull; @@ -33,7 +33,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Queue; import java.util.Set; @@ -109,10 +108,7 @@ public final class StaticComponent extends CommandCompo @Nonnull @Override public List suggestions(@Nonnull final CommandContext commandContext, @Nonnull final String input) { - if (this.name.toLowerCase(Locale.ENGLISH).startsWith(input)) { - return Collections.singletonList(this.name); - } - return Collections.emptyList(); + return Collections.singletonList(this.name); } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java index ed58b244..79beaec7 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java @@ -34,7 +34,7 @@ import java.util.List; * a command that doesn't exist */ @SuppressWarnings("unused") -public class NoSuchCommandException extends CommandParseException { +public final class NoSuchCommandException extends CommandParseException { private final String suppliedCommand; @@ -52,6 +52,19 @@ public class NoSuchCommandException extends CommandParseException { this.suppliedCommand = command; } + + @Override + public String getMessage() { + final StringBuilder builder = new StringBuilder(); + for (final CommandComponent commandComponent : this.getCurrentChain()) { + if (commandComponent == null) { + continue; + } + builder.append(" ").append(commandComponent.getName()); + } + return String.format("Unrecognized command input '%s' following chain%s", this.suppliedCommand, builder.toString()); + } + /** * Get the supplied command * 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 6f76cb9c..f672f9d9 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 @@ -74,7 +74,7 @@ public abstract class CommandExecutionCoordinator coordinateExecution(@Nonnull CommandContext commandContext, + public abstract CompletableFuture> coordinateExecution(@Nonnull CommandContext commandContext, @Nonnull Queue input); /** @@ -102,13 +102,13 @@ public abstract class CommandExecutionCoordinator coordinateExecution(@Nonnull final CommandContext commandContext, + public CompletableFuture> coordinateExecution(@Nonnull final CommandContext commandContext, @Nonnull final Queue input) { - final CompletableFuture completableFuture = new CompletableFuture<>(); + final CompletableFuture> completableFuture = new CompletableFuture<>(); try { this.getCommandTree().parse(commandContext, input).ifPresent( command -> command.getCommandExecutionHandler().execute(commandContext)); - completableFuture.complete(new CommandResult()); + completableFuture.complete(new CommandResult<>(commandContext)); } catch (final Exception e) { completableFuture.completeExceptionally(e); } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandResult.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandResult.java index f93f528c..5c085bc5 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandResult.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandResult.java @@ -23,8 +23,36 @@ // package com.intellectualsites.commands.execution; +import com.intellectualsites.commands.context.CommandContext; +import com.intellectualsites.commands.sender.CommandSender; + +import javax.annotation.Nonnull; + /** * The result of a command execution + * + * @param Command sender type */ -public class CommandResult { +public class CommandResult { + + private final CommandContext commandContext; + + /** + * Construct a new command result instance + * + * @param context Command context + */ + public CommandResult(@Nonnull final CommandContext context) { + this.commandContext = context; + } + + /** + * Get the command context + * + * @return Command context + */ + @Nonnull public CommandContext getCommandContext() { + return this.commandContext; + } + } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandSuggestionProcessor.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandSuggestionProcessor.java new file mode 100644 index 00000000..79bb374a --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/CommandSuggestionProcessor.java @@ -0,0 +1,40 @@ +// +// 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; + +import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; +import com.intellectualsites.commands.sender.CommandSender; + +import java.util.List; +import java.util.function.BiFunction; + +/** + * Processor that formats command suggestions + * + * @param Command sender type + */ +public interface CommandSuggestionProcessor extends + BiFunction, List, List> { + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/FilteringCommandSuggestionProcessor.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/FilteringCommandSuggestionProcessor.java new file mode 100644 index 00000000..97429140 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/FilteringCommandSuggestionProcessor.java @@ -0,0 +1,58 @@ +// +// 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; + +import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; +import com.intellectualsites.commands.sender.CommandSender; + +import javax.annotation.Nonnull; +import java.util.LinkedList; +import java.util.List; + +/** + * Command suggestions processor that checks the input queue head and filters based on that + * + * @param Command sender type + */ +public final class FilteringCommandSuggestionProcessor implements CommandSuggestionProcessor { + + @Nonnull + @Override + public List apply(@Nonnull final CommandPreprocessingContext context, @Nonnull final List strings) { + final String input; + if (context.getInputQueue().isEmpty()) { + input = ""; + } else { + input = context.getInputQueue().peek(); + } + final List suggestions = new LinkedList<>(); + for (final String suggestion : strings) { + if (suggestion.startsWith(input)) { + suggestions.add(suggestion); + } + } + return suggestions; + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/AcceptingCommandPreprocessor.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/AcceptingCommandPreprocessor.java new file mode 100644 index 00000000..4dbe5b62 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/AcceptingCommandPreprocessor.java @@ -0,0 +1,48 @@ +// +// 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.preprocessor; + +import com.intellectualsites.commands.sender.CommandSender; + +import javax.annotation.Nonnull; + +/** + * {@link CommandPreprocessor} that does nothing besides indicating that the context + * has been properly processed + * + * @param Command sender type + */ +public final class AcceptingCommandPreprocessor implements CommandPreprocessor { + + /** + * Key used to access the context meta that indicates that the context has been fully processed + */ + public static final String PROCESSED_INDICATOR_KEY = "__COMMAND_PRE_PROCESSED__"; + + @Override + public void accept(@Nonnull final CommandPreprocessingContext context) { + context.getCommandContext().store(PROCESSED_INDICATOR_KEY, "true"); + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessingContext.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessingContext.java new file mode 100644 index 00000000..3616f216 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessingContext.java @@ -0,0 +1,94 @@ +// +// 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.preprocessor; + +import com.intellectualsites.commands.context.CommandContext; +import com.intellectualsites.commands.sender.CommandSender; + +import javax.annotation.Nonnull; +import java.util.LinkedList; +import java.util.Objects; + +/** + * Context for {@link CommandPreprocessor command preprocessors} + * + * @param Command sender type + */ +public final class CommandPreprocessingContext { + + private final CommandContext commandContext; + private final LinkedList inputQueue; + + /** + * Construct a new command preprocessing context + * + * @param commandContext Command context + * @param inputQueue Command input as supplied by sender + */ + public CommandPreprocessingContext(@Nonnull final CommandContext commandContext, + @Nonnull final LinkedList inputQueue) { + this.commandContext = commandContext; + this.inputQueue = inputQueue; + } + + /** + * Get the command context + * + * @return Command context + */ + @Nonnull + public CommandContext getCommandContext() { + return this.commandContext; + } + + /** + * Get the original input queue. All changes will persist and will be + * used during parsing + * + * @return Input queue + */ + @Nonnull + public LinkedList getInputQueue() { + return this.inputQueue; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final CommandPreprocessingContext that = (CommandPreprocessingContext) o; + return Objects.equals(getCommandContext(), that.getCommandContext()) + && Objects.equals(getInputQueue(), that.getInputQueue()); + } + + @Override + public int hashCode() { + return Objects.hash(getCommandContext(), getInputQueue()); + } + +} 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 new file mode 100644 index 00000000..a94904ed --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/CommandPreprocessor.java @@ -0,0 +1,40 @@ +// +// 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.preprocessor; + +import com.intellectualsites.commands.sender.CommandSender; +import com.intellectualsites.services.types.ConsumerService; + +/** + * Command preprocessor that gets to act on command input + * before it's sent to the command parser. + *

+ * Command preprocessors may filter out invalid commands by using + * {@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 new file mode 100644 index 00000000..25552016 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/preprocessor/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 preprocessor system + */ +package com.intellectualsites.commands.execution.preprocessor; diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandPreProcessorTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandPreProcessorTest.java new file mode 100644 index 00000000..c58dd686 --- /dev/null +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandPreProcessorTest.java @@ -0,0 +1,88 @@ +// +// 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.components.standard.EnumComponent; +import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; +import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessor; +import com.intellectualsites.commands.meta.SimpleCommandMeta; +import com.intellectualsites.commands.sender.CommandSender; +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 CommandPreProcessorTest { + + private static CommandManager manager; + + @BeforeAll + static void newTree() { + manager = new TestCommandManager(); + manager.registerCommand(manager.commandBuilder("test", SimpleCommandMeta.empty()) + .withComponent(EnumComponent.required(SampleEnum.class, "enum")) + .withHandler( + commandContext -> System.out.printf("enum = %s | integer = %d\n", + commandContext.get( + "enum").orElse( + SampleEnum.VALUE1), + commandContext.get( + "int").orElseThrow( + () -> new NullPointerException( + "int")))) + .build()); + manager.registerCommandPreProcessor(new SamplePreprocessor()); + } + + @Test + void testPreprocessing() { + Assertions.assertEquals(10, manager.executeCommand(new TestCommandSender(), "10 test value1") + .join().getCommandContext().get("int").orElse(0)); + manager.executeCommand(new TestCommandSender(), "aa test value1").join(); + } + + + enum SampleEnum { + VALUE1 + } + + + static final class SamplePreprocessor implements CommandPreprocessor { + + @Override + public void accept(@Nonnull final CommandPreprocessingContext context) { + try { + final int num = Integer.parseInt(context.getInputQueue().removeFirst()); + context.getCommandContext().store("int", num); + } catch (final Exception ignored) { + /* Will prevent execution */ + ConsumerService.interrupt(); + } + } + + } + +}