Add command postprocessing

This commit is contained in:
Alexander Söderberg 2020-09-22 20:53:49 +02:00
parent 7501bd4743
commit 77cbf15faa
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
11 changed files with 331 additions and 8 deletions

View file

@ -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<C> {
this.commandRegistrationHandler = commandRegistrationHandler;
this.servicePipeline.registerServiceType(new TypeToken<CommandPreprocessor<C>>() {
}, new AcceptingCommandPreprocessor<>());
this.servicePipeline.registerServiceType(new TypeToken<CommandPostprocessor<C>>() {
}, new AcceptingCommandPostprocessor<>());
}
/**
@ -305,13 +310,26 @@ public abstract class CommandManager<C> {
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<C> processor) {
this.servicePipeline.registerServiceImplementation(new TypeToken<CommandPostprocessor<C>>() {
}, 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<C> context, @Nonnull final LinkedList<String> inputQueue) {
this.servicePipeline.pump(new CommandPreprocessingContext<>(context, inputQueue))
@ -323,6 +341,24 @@ public abstract class CommandManager<C> {
: 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<C> context, @Nonnull final Command<C> command) {
this.servicePipeline.pump(new CommandPostprocessingContext<>(context, command))
.through(new TypeToken<CommandPostprocessor<C>>() {
})
.getResult();
return context.<String>get(AcceptingCommandPostprocessor.PROCESSED_INDICATOR_KEY).orElse("").isEmpty()
? State.REJECTED
: State.ACCEPTED;
}
/**
* Get the command suggestions processor instance currently used in this command manager
*

View file

@ -559,6 +559,16 @@ public final class CommandTree<C> {
return null;
}
/**
* Get the command manager
*
* @return Command manager
*/
@Nonnull
public CommandManager<C> getCommandManager() {
return this.commandManager;
}
/**
* Very simple tree structure
*

View file

@ -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<C> extends CommandExecutionCoordinator<C> {
private final CommandManager<C> commandManager;
private final Executor executor;
private final boolean synchronizeParsing;
@ -52,6 +56,7 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
super(commandTree);
this.executor = executor;
this.synchronizeParsing = synchronizeParsing;
this.commandManager = commandTree.getCommandManager();
}
/**
@ -69,17 +74,21 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
public CompletableFuture<CommandResult<C>> coordinateExecution(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> input) {
final Consumer<Command<C>> commandConsumer = command -> {
if (this.commandManager.postprocessContext(commandContext, command) == State.ACCEPTED) {
command.getCommandExecutionHandler().execute(commandContext);
}
};
final Supplier<CommandResult<C>> supplier;
if (this.synchronizeParsing) {
final Optional<Command<C>> 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);
};
}

View file

@ -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<C> {
@Nonnull final Queue<String> input) {
final CompletableFuture<CommandResult<C>> 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);

View file

@ -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 <C> Command sender type
*/
public final class AcceptingCommandPostprocessor<C> implements CommandPostprocessor<C> {
/**
* 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<C> context) {
context.getCommandContext().store(PROCESSED_INDICATOR_KEY, "true");
}
}

View file

@ -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 <C> Command sender type
*/
public final class CommandPostprocessingContext<C> {
private final CommandContext<C> commandContext;
private final Command<C> command;
/**
* Construct a new command postprocessing context
*
* @param commandContext Command context
* @param command Command instance
*/
public CommandPostprocessingContext(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Command<C> command) {
this.commandContext = commandContext;
this.command = command;
}
/**
* Get the command context
*
* @return Command context
*/
@Nonnull
public CommandContext<C> getCommandContext() {
return this.commandContext;
}
/**
* Get the command instance
*
* @return Command instance
*/
@Nonnull
public Command<C> 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());
}
}

View file

@ -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 <C> Command sender type
*/
public interface CommandPostprocessor<C> extends ConsumerService<CommandPostprocessingContext<C>> {
}

View file

@ -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;

View file

@ -33,7 +33,6 @@ import com.intellectualsites.services.types.ConsumerService;
* {@link ConsumerService#interrupt()}
*
* @param <C> Command sender type
* {@inheritDoc}
*/
public interface CommandPreprocessor<C> extends ConsumerService<CommandPreprocessingContext<C>> {
}

View file

@ -23,6 +23,6 @@
//
/**
* Command preprocessor system
* Command preprocessing system
*/
package com.intellectualsites.commands.execution.preprocessor;

View file

@ -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<TestCommandSender> 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<TestCommandSender> {
@Override
public void accept(@Nonnull final CommandPostprocessingContext<TestCommandSender> context) {
ConsumerService.interrupt();
}
}
}