From 7df6917fe4f57b7630296b0df9bf543f8f43d09e Mon Sep 17 00:00:00 2001 From: jmp Date: Thu, 19 Nov 2020 19:50:17 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Added=20`CommandExecutionException`?= =?UTF-8?q?=20which=20wraps=20any=20exception=20thrown=20during=20the=20ex?= =?UTF-8?q?ecution=20of=20command=20handlers.=20Should=20be=20handled=20us?= =?UTF-8?q?ing=20`CommandManager#registerExceptionHandler`,=20similar=20to?= =?UTF-8?q?=20`NoSuchCommandException`,=20`ArgumentParseException`,=20etc.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + .../MethodCommandExecutionHandler.java | 7 ++- .../exceptions/CommandExecutionException.java | 46 +++++++++++++++++++ ...ynchronousCommandExecutionCoordinator.java | 12 +++-- .../CommandExecutionCoordinator.java | 11 ++++- .../javacord/JavacordCommand.java | 17 +++++++ .../jda/JDACommandListener.java | 12 +++++ .../pircbotx/CloudListenerAdapter.java | 10 ++++ .../bukkit/BukkitCommand.java | 25 ++++++++-- .../bungee/BungeeCommand.java | 34 +++++++++----- .../cloudburst/CloudburstCommand.java | 20 +++++++- .../extras/MinecraftExceptionHandler.java | 46 +++++++++++++++++-- .../velocity/VelocityExecutor.java | 29 +++++++++--- .../examples/bukkit/ExamplePlugin.java | 1 + 14 files changed, 240 insertions(+), 32 deletions(-) create mode 100644 cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a8207781..c58396bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added a method to get the failure reason of SelectorParseExceptions - Added some methods to FlagContext to work with flag values as optionals - Allow for use of named suggestion providers with `@Flag`s (cloud-annotations) + - Added `CommandExecutionException` which wraps any exception thrown during the execution of command handlers. Should be + handled using `CommandManager#registerExceptionHandler`, similar to `NoSuchCommandException`, `ArgumentParseException`, etc. ### Changed - Allow for use of `@Completions` annotation with argument types other than String diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java index 184c7174..6caa1e49 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java @@ -28,6 +28,7 @@ import cloud.commandframework.annotations.injection.ParameterInjectorRegistry; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.flags.FlagContext; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.execution.CommandExecutionHandler; import org.checkerframework.checker.nullness.qual.NonNull; @@ -115,8 +116,10 @@ class MethodCommandExecutionHandler implements CommandExecutionHandler { /* Invoke the command method */ try { this.methodHandle.invokeWithArguments(arguments); - } catch (final Throwable e) { - e.printStackTrace(); + } catch (final Error e) { + throw e; + } catch (final Throwable throwable) { + throw new CommandExecutionException(throwable); } } diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java new file mode 100644 index 00000000..4a5e9607 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java @@ -0,0 +1,46 @@ +// +// MIT License +// +// Copyright (c) 2020 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.exceptions; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Exception thrown when there is an exception during execution of a command handler + * + * @since 1.2.0 + */ +public class CommandExecutionException extends IllegalArgumentException { + + private static final long serialVersionUID = -4785446899438294661L; + + /** + * Exception thrown when there is an exception during execution of a command handler + * + * @param cause Exception thrown during the execution of a command handler + */ + public CommandExecutionException(final @NonNull Throwable cause) { + super(cause); + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/execution/AsynchronousCommandExecutionCoordinator.java b/cloud-core/src/main/java/cloud/commandframework/execution/AsynchronousCommandExecutionCoordinator.java index ae30bfff..7d6b76fc 100644 --- a/cloud-core/src/main/java/cloud/commandframework/execution/AsynchronousCommandExecutionCoordinator.java +++ b/cloud-core/src/main/java/cloud/commandframework/execution/AsynchronousCommandExecutionCoordinator.java @@ -27,6 +27,7 @@ import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.CommandTree; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.services.State; import cloud.commandframework.types.tuples.Pair; import org.checkerframework.checker.nullness.qual.NonNull; @@ -76,10 +77,17 @@ public final class AsynchronousCommandExecutionCoordinator extends CommandExe final @NonNull CommandContext commandContext, final @NonNull Queue<@NonNull String> input ) { + final CompletableFuture> resultFuture = new CompletableFuture<>(); final Consumer> commandConsumer = command -> { if (this.commandManager.postprocessContext(commandContext, command) == State.ACCEPTED) { - command.getCommandExecutionHandler().execute(commandContext); + try { + command.getCommandExecutionHandler().execute(commandContext); + } catch (final CommandExecutionException exception) { + resultFuture.completeExceptionally(exception); + } catch (final Exception exception) { + resultFuture.completeExceptionally(new CommandExecutionException(exception)); + } } }; @@ -97,8 +105,6 @@ public final class AsynchronousCommandExecutionCoordinator extends CommandExe }, this.executor); } - final CompletableFuture> resultFuture = new CompletableFuture<>(); - this.executor.execute(() -> { try { final @NonNull Pair<@Nullable Command, @Nullable Exception> pair = diff --git a/cloud-core/src/main/java/cloud/commandframework/execution/CommandExecutionCoordinator.java b/cloud-core/src/main/java/cloud/commandframework/execution/CommandExecutionCoordinator.java index 7cfb7643..b1e97c22 100644 --- a/cloud-core/src/main/java/cloud/commandframework/execution/CommandExecutionCoordinator.java +++ b/cloud-core/src/main/java/cloud/commandframework/execution/CommandExecutionCoordinator.java @@ -26,6 +26,7 @@ package cloud.commandframework.execution; import cloud.commandframework.Command; import cloud.commandframework.CommandTree; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.services.State; import cloud.commandframework.types.tuples.Pair; import org.checkerframework.checker.nullness.qual.NonNull; @@ -103,7 +104,7 @@ public abstract class CommandExecutionCoordinator { } @Override - public CompletableFuture> coordinateExecution( + public @NonNull CompletableFuture> coordinateExecution( final @NonNull CommandContext commandContext, final @NonNull Queue<@NonNull String> input ) { @@ -116,7 +117,13 @@ public abstract class CommandExecutionCoordinator { } else { final Command command = Objects.requireNonNull(pair.getFirst()); if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) { - command.getCommandExecutionHandler().execute(commandContext); + try { + command.getCommandExecutionHandler().execute(commandContext); + } catch (final CommandExecutionException exception) { + completableFuture.completeExceptionally(exception); + } catch (final Exception exception) { + completableFuture.completeExceptionally(new CommandExecutionException(exception)); + } } completableFuture.complete(new CommandResult<>(commandContext)); } diff --git a/cloud-discord/cloud-javacord/src/main/java/cloud/commandframework/javacord/JavacordCommand.java b/cloud-discord/cloud-javacord/src/main/java/cloud/commandframework/javacord/JavacordCommand.java index 3f355fc9..095a2012 100644 --- a/cloud-discord/cloud-javacord/src/main/java/cloud/commandframework/javacord/JavacordCommand.java +++ b/cloud-discord/cloud-javacord/src/main/java/cloud/commandframework/javacord/JavacordCommand.java @@ -26,6 +26,7 @@ package cloud.commandframework.javacord; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.StaticArgument; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -42,6 +43,7 @@ import java.util.concurrent.CompletionException; public class JavacordCommand implements MessageCreateListener { + private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_NO_PERMS = "I'm sorry, but you do not have the permission to do this :/"; private final JavacordCommandManager manager; @@ -95,6 +97,7 @@ public class JavacordCommand implements MessageCreateListener { if (throwable == null) { return; } + final Throwable finalThrowable = throwable; if (throwable instanceof CompletionException) { throwable = throwable.getCause(); @@ -153,6 +156,20 @@ public class JavacordCommand implements MessageCreateListener { return; } + if (throwable instanceof CommandExecutionException) { + manager.handleException( + sender, + CommandExecutionException.class, + (CommandExecutionException) throwable, + (c, e) -> { + commandSender.sendErrorMessage(MESSAGE_INTERNAL_ERROR); + finalThrowable.getCause().printStackTrace(); + } + ); + + return; + } + commandSender.sendErrorMessage(throwable.getMessage()); throwable.printStackTrace(); }); diff --git a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandListener.java b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandListener.java index 0993d5f5..1935203c 100644 --- a/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandListener.java +++ b/cloud-discord/cloud-jda/src/main/java/cloud/commandframework/jda/JDACommandListener.java @@ -24,6 +24,7 @@ package cloud.commandframework.jda; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -41,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; @SuppressWarnings("deprecation") public class JDACommandListener extends ListenerAdapter { + private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_INVALID_SYNTAX = "Invalid Command Syntax. Correct command syntax is: "; private static final String MESSAGE_NO_PERMS = "I'm sorry, but you do not have permission to perform this command. " + "Please contact the server administrators if you believe that this is in error."; @@ -116,6 +118,16 @@ public class JDACommandListener extends ListenerAdapter { .getMessage() ) ); + } else if (throwable instanceof CommandExecutionException) { + this.commandManager.handleException(sender, CommandExecutionException.class, + (CommandExecutionException) throwable, (c, e) -> { + this.sendMessage( + event, + MESSAGE_INTERNAL_ERROR + ); + throwable.getCause().printStackTrace(); + } + ); } else { this.sendMessage(event, throwable.getMessage()); } diff --git a/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/CloudListenerAdapter.java b/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/CloudListenerAdapter.java index b20aa876..82f3fba8 100644 --- a/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/CloudListenerAdapter.java +++ b/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/CloudListenerAdapter.java @@ -24,6 +24,7 @@ package cloud.commandframework.pircbotx; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -34,6 +35,7 @@ import org.pircbotx.hooks.types.GenericMessageEvent; final class CloudListenerAdapter extends ListenerAdapter { + private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_INVALID_SYNTAX = "Invalid Command Syntax. Correct command syntax is: "; private static final String MESSAGE_NO_PERMS = "I'm sorry, but you do not have permission to perform this command. " + "Please contact the server administrators if you believe that this is in error."; @@ -57,6 +59,7 @@ final class CloudListenerAdapter extends ListenerAdapter { if (throwable == null) { return; } + final Throwable finalThrowable = throwable; if (throwable instanceof InvalidSyntaxException) { this.manager.handleException(sender, @@ -90,6 +93,13 @@ final class CloudListenerAdapter extends ListenerAdapter { "Invalid Command Argument: " + throwable.getCause().getMessage() ) ); + } else if (throwable instanceof CommandExecutionException) { + this.manager.handleException(sender, CommandExecutionException.class, + (CommandExecutionException) throwable, (c, e) -> { + event.respondWith(MESSAGE_INTERNAL_ERROR); + finalThrowable.getCause().printStackTrace(); + } + ); } else { event.respondWith(throwable.getMessage()); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java index 737d4e75..e60f8622 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java @@ -26,6 +26,7 @@ package cloud.commandframework.bukkit; import cloud.commandframework.Command; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -38,9 +39,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; import java.util.concurrent.CompletionException; +import java.util.logging.Level; final class BukkitCommand extends org.bukkit.command.Command implements PluginIdentifiableCommand { + private static final String MESSAGE_INTERNAL_ERROR = ChatColor.RED + + "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_NO_PERMS = ChatColor.RED + "I'm sorry, but you do not have permission to perform this command. " + "Please contact the server administrators if you believe that this is in error."; @@ -50,7 +54,6 @@ final class BukkitCommand extends org.bukkit.command.Command implements Plugi private final BukkitCommandManager manager; private final Command cloudCommand; - @SuppressWarnings("unchecked") BukkitCommand( final @NonNull String label, final @NonNull List<@NonNull String> aliases, @@ -133,9 +136,25 @@ final class BukkitCommand extends org.bukkit.command.Command implements Plugi + ChatColor.GRAY + finalThrowable.getCause() .getMessage()) ); + } else if (throwable instanceof CommandExecutionException) { + this.manager.handleException(sender, + CommandExecutionException.class, + (CommandExecutionException) throwable, (c, e) -> { + commandSender.sendMessage(MESSAGE_INTERNAL_ERROR); + this.manager.getOwningPlugin().getLogger().log( + Level.SEVERE, + "Exception executing command handler", + finalThrowable.getCause() + ); + } + ); } else { - commandSender.sendMessage(throwable.getMessage()); - throwable.printStackTrace(); + commandSender.sendMessage(MESSAGE_INTERNAL_ERROR); + this.manager.getOwningPlugin().getLogger().log( + Level.SEVERE, + "An unhandled exception was thrown during command execution", + throwable + ); } } }); diff --git a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/BungeeCommand.java b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/BungeeCommand.java index 2ec0c046..45c80978 100644 --- a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/BungeeCommand.java +++ b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/BungeeCommand.java @@ -26,6 +26,7 @@ package cloud.commandframework.bungee; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.StaticArgument; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -38,9 +39,11 @@ import net.md_5.bungee.api.plugin.TabExecutor; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.concurrent.CompletionException; +import java.util.logging.Level; public final class BungeeCommand extends Command implements TabExecutor { + private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_NO_PERMS = "I'm sorry, but you do not have permission to perform this command. " + "Please contact the server administrators if you believe that this is in error."; @@ -138,18 +141,27 @@ public final class BungeeCommand extends Command implements TabExecutor { .getMessage()) .create()) ); - } else { - commandSender.sendMessage(new ComponentBuilder(throwable.getMessage()).create()); - this.manager.getOwningPlugin().getLogger().warning( - String.format( - "(Cloud) Unknown exception type '%s' with cause '%s'", - throwable.getClass().getCanonicalName(), - throwable.getCause() == null ? "none" - : throwable.getCause() - .getClass().getCanonicalName() - ) + } else if (throwable instanceof CommandExecutionException) { + this.manager.handleException(sender, + CommandExecutionException.class, + (CommandExecutionException) throwable, (c, e) -> { + commandSender.sendMessage(new ComponentBuilder(MESSAGE_INTERNAL_ERROR) + .color(ChatColor.RED) + .create()); + this.manager.getOwningPlugin().getLogger().log( + Level.SEVERE, + "Exception executing command handler", + finalThrowable.getCause() + ); + } + ); + } else { + commandSender.sendMessage(new ComponentBuilder(MESSAGE_INTERNAL_ERROR).color(ChatColor.RED).create()); + this.manager.getOwningPlugin().getLogger().log( + Level.SEVERE, + "An unhandled exception was thrown during command execution", + throwable ); - throwable.printStackTrace(); } } }); diff --git a/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java b/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java index bb2b7be1..4bf7ef6e 100644 --- a/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java +++ b/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java @@ -26,6 +26,7 @@ package cloud.commandframework.cloudburst; import cloud.commandframework.Command; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -41,6 +42,7 @@ import java.util.concurrent.CompletionException; final class CloudburstCommand extends PluginCommand { + private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_NO_PERMS = "I'm sorry, but you do not have permission to perform this command. " + "Please contact the server administrators if you believe that this is in error."; @@ -124,9 +126,23 @@ final class CloudburstCommand extends PluginCommand { "Invalid Command Argument: " + finalThrowable.getCause().getMessage()) ); + } else if (throwable instanceof CommandExecutionException) { + this.manager.handleException(sender, + CommandExecutionException.class, + (CommandExecutionException) throwable, (c, e) -> { + commandSender.sendMessage(MESSAGE_INTERNAL_ERROR); + manager.getOwningPlugin().getLogger().error( + "Exception executing command handler", + finalThrowable.getCause() + ); + } + ); } else { - commandSender.sendMessage(throwable.getMessage()); - throwable.printStackTrace(); + commandSender.sendMessage(MESSAGE_INTERNAL_ERROR); + manager.getOwningPlugin().getLogger().error( + "An unhandled exception was thrown during command execution", + throwable + ); } } }); diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java index 6f428ab3..6f210731 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java @@ -25,6 +25,7 @@ package cloud.commandframework.minecraft.extras; import cloud.commandframework.CommandManager; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -89,6 +90,19 @@ public final class MinecraftExceptionHandler { .append(Component.text("Invalid command argument: ", NamedTextColor.RED)) .append(Component.text(e.getCause().getMessage(), NamedTextColor.GRAY)) .build(); + /** + * Default component builder for {@link CommandExecutionException} + * + * @since 1.2.0 + */ + public static final Function DEFAULT_COMMAND_EXECUTION_FUNCTION = + e -> { + e.getCause().printStackTrace(); + return Component.text() + .append(Component.text("An internal error occurred while attempting to perform this command.", + NamedTextColor.RED)) + .build(); + }; private final Map> componentBuilders = new HashMap<>(); private Function decorator = Function.identity(); @@ -130,7 +144,17 @@ public final class MinecraftExceptionHandler { } /** - * Use all four of the default exception handlers + * Use the default {@link CommandExecutionException} handler + * + * @return {@code this} + * @since 1.2.0 + */ + public @NonNull MinecraftExceptionHandler withCommandExecutionHandler() { + return this.withHandler(ExceptionType.COMMAND_EXECUTION, DEFAULT_COMMAND_EXECUTION_FUNCTION); + } + + /** + * Use all of the default exception handlers * * @return {@code this} */ @@ -139,7 +163,8 @@ public final class MinecraftExceptionHandler { .withArgumentParsingHandler() .withInvalidSenderHandler() .withInvalidSyntaxHandler() - .withNoPermissionHandler(); + .withNoPermissionHandler() + .withCommandExecutionHandler(); } /** @@ -237,6 +262,15 @@ public final class MinecraftExceptionHandler { ) ); } + if (componentBuilders.containsKey(ExceptionType.COMMAND_EXECUTION)) { + manager.registerExceptionHandler( + CommandExecutionException.class, + (c, e) -> audienceMapper.apply(c).sendMessage( + Identity.nil(), + this.decorator.apply(this.componentBuilders.get(ExceptionType.COMMAND_EXECUTION).apply(c, e)) + ) + ); + } } @@ -259,7 +293,13 @@ public final class MinecraftExceptionHandler { /** * An argument failed to parse ({@link ArgumentParseException}) */ - ARGUMENT_PARSING + ARGUMENT_PARSING, + /** + * A command handler had an exception ({@link CommandExecutionException}) + * + * @since 1.2.0 + */ + COMMAND_EXECUTION } } diff --git a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityExecutor.java b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityExecutor.java index c045de03..66f65443 100644 --- a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityExecutor.java +++ b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/VelocityExecutor.java @@ -24,6 +24,7 @@ package cloud.commandframework.velocity; import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; @@ -42,6 +43,7 @@ import java.util.function.BiConsumer; final class VelocityExecutor implements Command { + private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command."; private static final String MESSAGE_NO_PERMS = "I'm sorry, but you do not have permission to perform this command. " + "Please contact the server administrators if you believe that this is in error."; @@ -128,10 +130,9 @@ final class VelocityExecutor implements Command { sender, ArgumentParseException.class, (ArgumentParseException) throwable, - (c, e) -> - source.sendMessage( - Identity.nil(), - Component.text() + (c, e) -> source.sendMessage( + Identity.nil(), + Component.text() .append(Component.text( "Invalid Command Argument: ", NamedTextColor.RED @@ -140,12 +141,28 @@ final class VelocityExecutor implements Command { finalThrowable.getCause().getMessage(), NamedTextColor.GRAY )) - ) + ) + ); + } else if (throwable instanceof CommandExecutionException) { + this.manager.handleException( + sender, + CommandExecutionException.class, + (CommandExecutionException) throwable, + (c, e) -> { + source.sendMessage( + Identity.nil(), + Component.text( + MESSAGE_INTERNAL_ERROR, + NamedTextColor.RED + ) + ); + finalThrowable.getCause().printStackTrace(); + } ); } else { source.sendMessage( Identity.nil(), - Component.text(throwable.getMessage(), NamedTextColor.RED) + Component.text(MESSAGE_INTERNAL_ERROR, NamedTextColor.RED) ); throwable.printStackTrace(); } diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java index d7178171..2ed2ecfc 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java @@ -195,6 +195,7 @@ public final class ExamplePlugin extends JavaPlugin { .withInvalidSenderHandler() .withNoPermissionHandler() .withArgumentParsingHandler() + .withCommandExecutionHandler() .withDecorator( component -> Component.text() .append(Component.text("[", NamedTextColor.DARK_GRAY))