diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java index c7f7a409..1c2841e8 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java @@ -216,7 +216,7 @@ public abstract class CommandManager { * * @return Command registration handler */ - protected @NonNull CommandRegistrationHandler getCommandRegistrationHandler() { + public @NonNull CommandRegistrationHandler getCommandRegistrationHandler() { return this.commandRegistrationHandler; } diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java index a2486248..5f35c4ae 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java @@ -38,6 +38,7 @@ import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.exceptions.NoSuchCommandException; import cloud.commandframework.permission.CommandPermission; import cloud.commandframework.permission.OrPermission; +import cloud.commandframework.types.tuples.Pair; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -52,7 +53,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Queue; import java.util.stream.Collectors; @@ -85,6 +85,8 @@ import java.util.stream.Collectors; */ public final class CommandTree { + private static final @Nullable Exception NULL_EXCEPTION = null; + private final Object commandLock = new Object(); private final Node> internalTree = new Node<>(null); @@ -111,43 +113,46 @@ public final class CommandTree { * @param commandContext Command context instance * @param args Input * @return Parsed command, if one could be found - * @throws NoSuchCommandException If there is no command matching the input - * @throws NoPermissionException If the sender lacks permission to execute the command - * @throws InvalidSyntaxException If the command syntax is invalid */ - public @NonNull Optional> parse(final @NonNull CommandContext commandContext, - final @NonNull Queue<@NonNull String> args) throws - NoSuchCommandException, NoPermissionException, InvalidSyntaxException { - final Optional> commandOptional = parseCommand(new ArrayList<>(), - commandContext, - args, - this.internalTree); - commandOptional.flatMap(Command::getSenderType).ifPresent(requiredType -> { - if (!requiredType.isAssignableFrom(commandContext.getSender().getClass())) { - throw new InvalidCommandSenderException(commandContext.getSender(), requiredType, Collections.emptyList()); + public @NonNull Pair<@Nullable Command, @Nullable Exception> parse(final @NonNull CommandContext commandContext, + final @NonNull Queue<@NonNull String> args) { + final Pair<@Nullable Command, @Nullable Exception> pair = this.parseCommand(new ArrayList<>(), + commandContext, + args, + this.internalTree); + if (pair.getFirst() != null) { + final Command command = pair.getFirst(); + if (command.getSenderType().isPresent() && !command.getSenderType().get() + .isAssignableFrom(commandContext.getSender().getClass())) { + return Pair.of(null, new InvalidCommandSenderException(commandContext.getSender(), + command.getSenderType().get(), + Collections.emptyList())); } - }); - return commandOptional; - } - - private Optional> parseCommand(final @NonNull List<@NonNull CommandArgument> parsedArguments, - final @NonNull CommandContext commandContext, - final @NonNull Queue<@NonNull String> commandQueue, - final @NonNull Node<@Nullable CommandArgument> root) { - CommandPermission permission = this.isPermitted(commandContext.getSender(), root); - if (permission != null) { - throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); } - final Optional> parsedChild = this.attemptParseUnambiguousChild(parsedArguments, - commandContext, - root, - commandQueue); - // noinspection all - if (parsedChild != null) { + return pair; + } + + private @NonNull Pair<@Nullable Command, @Nullable Exception> + parseCommand(final @NonNull List<@NonNull CommandArgument> parsedArguments, + final @NonNull CommandContext commandContext, + final @NonNull Queue<@NonNull String> commandQueue, + final @NonNull Node<@Nullable CommandArgument> root) { + CommandPermission permission = this.isPermitted(commandContext.getSender(), root); + if (permission != null) { + return Pair.of(null, new NoPermissionException(permission, + commandContext.getSender(), + this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); + } + + final Pair<@Nullable Command, @Nullable Exception> parsedChild = this.attemptParseUnambiguousChild(parsedArguments, + commandContext, + root, + commandQueue); + if (parsedChild.getFirst() != null || parsedChild.getSecond() != null) { return parsedChild; } @@ -156,24 +161,26 @@ public final class CommandTree { /* We are at the bottom. Check if there's a command attached, in which case we're done */ if (root.getValue() != null && root.getValue().getOwningCommand() != null) { if (commandQueue.isEmpty()) { - return Optional.ofNullable(this.cast(root.getValue().getOwningCommand())); + return Pair.of(this.cast(root.getValue().getOwningCommand()), null); } else { /* Too many arguments. We have a unique path, so we can send the entire context */ - throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() - .apply(parsedArguments, root), - commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new InvalidSyntaxException( + this.commandManager.getCommandSyntaxFormatter() + .apply(parsedArguments, root), + commandContext.getSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } } else { /* Too many arguments. We have a unique path, so we can send the entire context */ - throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() - .apply(parsedArguments, root), - commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new InvalidSyntaxException( + this.commandManager.getCommandSyntaxFormatter() + .apply(parsedArguments, root), + commandContext.getSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } } else { final Iterator>> childIterator = root.getChildren().iterator(); @@ -197,39 +204,42 @@ public final class CommandTree { } /* We could not find a match */ if (root.equals(this.internalTree)) { - throw new NoSuchCommandException(commandContext.getSender(), - getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), - stringOrEmpty(commandQueue.peek())); + return Pair.of(null, new NoSuchCommandException( + commandContext.getSender(), + getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), + stringOrEmpty(commandQueue.peek()))); } /* If we couldn't match a child, check if there's a command attached and execute it */ - if (root.getValue() != null && root.getValue().getOwningCommand() != null) { + if (root.getValue() != null && root.getValue().getOwningCommand() != null && commandQueue.isEmpty()) { final Command command = root.getValue().getOwningCommand(); if (!this.getCommandManager().hasPermission(commandContext.getSender(), command.getCommandPermission())) { - throw new NoPermissionException(command.getCommandPermission(), - commandContext.getSender(), - this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new NoPermissionException( + command.getCommandPermission(), + commandContext.getSender(), + this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } - return Optional.of(root.getValue().getOwningCommand()); + return Pair.of(root.getValue().getOwningCommand(), null); } /* We know that there's no command and we also cannot match any of the children */ - throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() - .apply(parsedArguments, root), - commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new InvalidSyntaxException( + this.commandManager.getCommandSyntaxFormatter() + .apply(parsedArguments, root), + commandContext.getSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } } - private @Nullable Optional> - attemptParseUnambiguousChild(final @NonNull List<@NonNull CommandArgument> parsedArguments, - final @NonNull CommandContext commandContext, - final @NonNull Node<@Nullable CommandArgument> root, - final @NonNull Queue commandQueue) { + private @NonNull Pair<@Nullable Command, @Nullable Exception> attemptParseUnambiguousChild( + final @NonNull List<@NonNull CommandArgument> parsedArguments, + final @NonNull CommandContext commandContext, + final @NonNull Node<@Nullable CommandArgument> root, + final @NonNull Queue commandQueue) { CommandPermission permission; final List>> children = root.getChildren(); if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticArgument)) { @@ -237,63 +247,73 @@ public final class CommandTree { final Node> child = children.get(0); permission = this.isPermitted(commandContext.getSender(), child); if (permission != null) { - throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(child) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new NoPermissionException( + permission, + commandContext.getSender(), + this.getChain(child) + .stream() + .map(Node::getValue) + .collect( + Collectors.toList()))); } if (child.getValue() != null) { if (commandQueue.isEmpty()) { if (child.getValue().hasDefaultValue()) { commandQueue.add(child.getValue().getDefaultValue()); } else if (!child.getValue().isRequired()) { - return Optional.ofNullable(this.cast(child.getValue().getOwningCommand())); + return Pair.of(this.cast(child.getValue().getOwningCommand()), null); } else if (child.isLeaf()) { /* The child is not a leaf, but may have an intermediary executor, attempt to use it */ if (root.getValue() != null && root.getValue().getOwningCommand() != null) { final Command command = root.getValue().getOwningCommand(); if (!this.getCommandManager().hasPermission(commandContext.getSender(), command.getCommandPermission())) { - throw new NoPermissionException(command.getCommandPermission(), - commandContext.getSender(), - this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new NoPermissionException( + command.getCommandPermission(), + commandContext.getSender(), + this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } - return Optional.of(command); + return Pair.of(command, null); } /* Not enough arguments */ - throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() - .apply(Objects.requireNonNull( - child.getValue().getOwningCommand()) - .getArguments(), child), - commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new InvalidSyntaxException( + this.commandManager.getCommandSyntaxFormatter() + .apply(Objects.requireNonNull( + child.getValue() + .getOwningCommand()) + .getArguments(), child), + commandContext.getSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect( + Collectors.toList()))); } else { /* The child is not a leaf, but may have an intermediary executor, attempt to use it */ if (root.getValue() != null && root.getValue().getOwningCommand() != null) { final Command command = root.getValue().getOwningCommand(); if (!this.getCommandManager().hasPermission(commandContext.getSender(), command.getCommandPermission())) { - throw new NoPermissionException(command.getCommandPermission(), - commandContext.getSender(), - this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new NoPermissionException( + command.getCommandPermission(), + commandContext.getSender(), + this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } - return Optional.of(command); + return Pair.of(command, null); } /* Child does not have a command and so we cannot proceed */ - throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() - .apply(parsedArguments, root), - commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new InvalidSyntaxException( + this.commandManager.getCommandSyntaxFormatter() + .apply(parsedArguments, root), + commandContext.getSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } } @@ -308,32 +328,33 @@ public final class CommandTree { commandContext.store(child.getValue().getName(), result.getParsedValue().get()); if (child.isLeaf()) { if (commandQueue.isEmpty()) { - return Optional.ofNullable(this.cast(child.getValue().getOwningCommand())); + return Pair.of(this.cast(child.getValue().getOwningCommand()), null); } else { /* Too many arguments. We have a unique path, so we can send the entire context */ - throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() - .apply(parsedArguments, child), - commandContext.getSender(), this.getChain(root) - .stream() - .map(Node::getValue) - .collect( - Collectors.toList())); + return Pair.of(null, new InvalidSyntaxException( + this.commandManager.getCommandSyntaxFormatter() + .apply(parsedArguments, child), + commandContext.getSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect( + Collectors.toList()))); } } else { parsedArguments.add(child.getValue()); return this.parseCommand(parsedArguments, commandContext, commandQueue, child); } } else if (result.getFailure().isPresent()) { - throw new ArgumentParseException(result.getFailure().get(), commandContext.getSender(), - this.getChain(child) - .stream() - .map(Node::getValue) - .collect(Collectors.toList())); + return Pair.of(null, new ArgumentParseException( + result.getFailure().get(), commandContext.getSender(), + this.getChain(child) + .stream() + .map(Node::getValue) + .collect(Collectors.toList()))); } } } - // noinspection all - return null; + return Pair.of(null, null); } /** 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 08b19db6..b78ef70a 100644 --- a/cloud-core/src/main/java/cloud/commandframework/execution/AsynchronousCommandExecutionCoordinator.java +++ b/cloud-core/src/main/java/cloud/commandframework/execution/AsynchronousCommandExecutionCoordinator.java @@ -28,16 +28,16 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.CommandTree; import cloud.commandframework.context.CommandContext; import cloud.commandframework.services.State; +import cloud.commandframework.types.tuples.Pair; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; /** * Execution coordinator parses and/or executes commands on a separate thread from the calling thread @@ -54,7 +54,7 @@ public final class AsynchronousCommandExecutionCoordinator extends CommandExe final boolean synchronizeParsing, final @NonNull CommandTree commandTree) { super(commandTree); - this.executor = executor; + this.executor = executor == null ? ForkJoinPool.commonPool() : executor; this.synchronizeParsing = synchronizeParsing; this.commandManager = commandTree.getCommandManager(); } @@ -78,24 +78,39 @@ public final class AsynchronousCommandExecutionCoordinator extends CommandExe command.getCommandExecutionHandler().execute(commandContext); } }; - final Supplier> supplier; + if (this.synchronizeParsing) { - final Optional> commandOptional = this.getCommandTree().parse(commandContext, input); - supplier = () -> { - commandOptional.ifPresent(commandConsumer); + final @NonNull Pair<@Nullable Command, @Nullable Exception> pair = + this.getCommandTree().parse(commandContext, input); + if (pair.getSecond() != null) { + final CompletableFuture> future = new CompletableFuture<>(); + future.completeExceptionally(pair.getSecond()); + return future; + } + return CompletableFuture.supplyAsync(() -> { + commandConsumer.accept(pair.getFirst()); return new CommandResult<>(commandContext); - }; - } else { - supplier = () -> { - this.getCommandTree().parse(commandContext, input).ifPresent(commandConsumer); - return new CommandResult<>(commandContext); - }; - } - if (this.executor != null) { - return CompletableFuture.supplyAsync(supplier, this.executor); - } else { - return CompletableFuture.supplyAsync(supplier); + }, this.executor); } + + final CompletableFuture> resultFuture = new CompletableFuture<>(); + + this.executor.execute(() -> { + try { + final @NonNull Pair<@Nullable Command, @Nullable Exception> pair = + this.getCommandTree().parse(commandContext, input); + if (pair.getSecond() != null) { + resultFuture.completeExceptionally(pair.getSecond()); + } else { + commandConsumer.accept(pair.getFirst()); + resultFuture.complete(new CommandResult<>(commandContext)); + } + } catch (final Exception e) { + resultFuture.completeExceptionally(e); + } + }); + + return resultFuture; } 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 64888d43..8eddcfa7 100644 --- a/cloud-core/src/main/java/cloud/commandframework/execution/CommandExecutionCoordinator.java +++ b/cloud-core/src/main/java/cloud/commandframework/execution/CommandExecutionCoordinator.java @@ -23,10 +23,13 @@ // package cloud.commandframework.execution; +import cloud.commandframework.Command; import cloud.commandframework.CommandTree; import cloud.commandframework.context.CommandContext; import cloud.commandframework.services.State; +import cloud.commandframework.types.tuples.Pair; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Queue; import java.util.concurrent.CompletableFuture; @@ -101,12 +104,17 @@ public abstract class CommandExecutionCoordinator { final @NonNull Queue<@NonNull String> input) { final CompletableFuture> completableFuture = new CompletableFuture<>(); try { - this.getCommandTree().parse(commandContext, input).ifPresent(command -> { + final @NonNull Pair<@Nullable Command, @Nullable Exception> pair = + this.getCommandTree().parse(commandContext, input); + if (pair.getSecond() != null) { + completableFuture.completeExceptionally(pair.getSecond()); + } else { + final Command command = pair.getFirst(); if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) { command.getCommandExecutionHandler().execute(commandContext); } - }); - completableFuture.complete(new CommandResult<>(commandContext)); + completableFuture.complete(new CommandResult<>(commandContext)); + } } catch (final Exception e) { completableFuture.completeExceptionally(e); } diff --git a/cloud-core/src/main/java/cloud/commandframework/types/tuples/Pair.java b/cloud-core/src/main/java/cloud/commandframework/types/tuples/Pair.java index 920332e0..a490dc5a 100644 --- a/cloud-core/src/main/java/cloud/commandframework/types/tuples/Pair.java +++ b/cloud-core/src/main/java/cloud/commandframework/types/tuples/Pair.java @@ -38,8 +38,8 @@ public class Pair implements Tuple { private final U first; private final V second; - protected Pair(final @NonNull U first, - final @NonNull V second) { + protected Pair(final U first, + final V second) { this.first = first; this.second = second; } @@ -53,8 +53,8 @@ public class Pair implements Tuple { * @param Second type * @return Created pair */ - public static @NonNull Pair<@NonNull U, @NonNull V> of(final @NonNull U first, - final @NonNull V second) { + public static @NonNull Pair of(final U first, + final V second) { return new Pair<>(first, second); } @@ -63,7 +63,7 @@ public class Pair implements Tuple { * * @return First value */ - public final @NonNull U getFirst() { + public final U getFirst() { return this.first; } @@ -72,7 +72,7 @@ public class Pair implements Tuple { * * @return Second value */ - public final @NonNull V getSecond() { + public final V getSecond() { return this.second; } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java index 1386ca7b..9b21e7eb 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; -import java.util.Optional; import java.util.concurrent.CompletionException; class CommandTreeTest { @@ -68,7 +67,8 @@ class CommandTreeTest { .argument(StringArgument.of("string")) .argument(IntegerArgument.of("int")) .literal("anotherliteral") - .handler(c -> {}) + .handler(c -> { + }) .build(); manager.command(toProxy); manager.command(manager.commandBuilder("proxy").proxies(toProxy).build()); @@ -85,7 +85,7 @@ class CommandTreeTest { /* Build command for testing compound types */ manager.command(manager.commandBuilder("pos") .argument(ArgumentPair.of(manager, "pos", Pair.of("x", "y"), - Pair.of(Integer.class, Integer.class)) + Pair.of(Integer.class, Integer.class)) .simple()) .handler(c -> { final Pair pair = c.get("pos"); @@ -93,7 +93,7 @@ class CommandTreeTest { })); manager.command(manager.commandBuilder("vec") .argument(ArgumentPair.of(manager, "vec", Pair.of("x", "y"), - Pair.of(Double.class, Double.class)) + Pair.of(Double.class, Double.class)) .withMapper(Vector2.class, pair -> new Vector2(pair.getFirst(), pair.getSecond())) ) @@ -115,36 +115,37 @@ class CommandTreeTest { System.out.println("Flag present? " + c.flags().isPresent("test")); System.out.println("Numerical flag: " + c.flags().getValue("num", -10)); }) - .build()); + .build()); } @Test void parse() { - final Optional> command = manager.getCommandTree() + final Pair, Exception> command = manager.getCommandTree() + .parse(new CommandContext<>( + new TestCommandSender()), + new LinkedList<>( + Arrays.asList("test", + "one"))); + Assertions.assertNotNull(command.getFirst()); + Assertions.assertEquals(NoPermissionException.class, manager.getCommandTree() .parse(new CommandContext<>( new TestCommandSender()), new LinkedList<>( - Arrays.asList("test", - "one"))); - Assertions.assertTrue(command.isPresent()); - Assertions.assertThrows(NoPermissionException.class, () -> manager.getCommandTree() - .parse(new CommandContext<>( - new TestCommandSender()), - new LinkedList<>( - Arrays.asList("test", "two")))); + Arrays.asList("test", "two"))) + .getSecond().getClass()); manager.getCommandTree() .parse(new CommandContext<>(new TestCommandSender()), new LinkedList<>(Arrays.asList("test", "opt"))) - .ifPresent(c -> c.getCommandExecutionHandler().execute(new CommandContext<>(new TestCommandSender()))); + .getFirst().getCommandExecutionHandler().execute(new CommandContext<>(new TestCommandSender())); manager.getCommandTree() .parse(new CommandContext<>(new TestCommandSender()), new LinkedList<>(Arrays.asList("test", "opt", "12"))) - .ifPresent(c -> c.getCommandExecutionHandler().execute(new CommandContext<>(new TestCommandSender()))); + .getFirst().getCommandExecutionHandler().execute(new CommandContext<>(new TestCommandSender())); } @Test void testAlias() { manager.getCommandTree() .parse(new CommandContext<>(new TestCommandSender()), new LinkedList<>(Arrays.asList("other", "öpt", "12"))) - .ifPresent(c -> c.getCommandExecutionHandler().execute(new CommandContext<>(new TestCommandSender()))); + .getFirst().getCommandExecutionHandler().execute(new CommandContext<>(new TestCommandSender())); } @Test @@ -181,7 +182,7 @@ class CommandTreeTest { @Test void testProxy() { - manager.executeCommand(new TestCommandSender(),"test unproxied foo 10 anotherliteral").join(); + manager.executeCommand(new TestCommandSender(), "test unproxied foo 10 anotherliteral").join(); manager.executeCommand(new TestCommandSender(), "proxy foo 10").join(); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitPluginRegistrationHandler.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitPluginRegistrationHandler.java index 4aa47913..09dae7e8 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitPluginRegistrationHandler.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitPluginRegistrationHandler.java @@ -35,14 +35,18 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; -class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { +public class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { private final Map, org.bukkit.command.Command> registeredCommands = new HashMap<>(); + private final Set recognizedAliases = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); private Map bukkitCommands; private BukkitCommandManager bukkitCommandManager; @@ -51,7 +55,7 @@ class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { BukkitPluginRegistrationHandler() { } - void initialize(final @NonNull BukkitCommandManager bukkitCommandManager) throws Exception { + final void initialize(final @NonNull BukkitCommandManager bukkitCommandManager) throws Exception { final Method getCommandMap = Bukkit.getServer().getClass().getDeclaredMethod("getCommandMap"); getCommandMap.setAccessible(true); this.commandMap = (CommandMap) getCommandMap.invoke(Bukkit.getServer()); @@ -65,31 +69,41 @@ class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { } @Override - public boolean registerCommand(final @NonNull Command command) { + public final boolean registerCommand(final @NonNull Command command) { /* We only care about the root command argument */ final CommandArgument commandArgument = command.getArguments().get(0); if (this.registeredCommands.containsKey(commandArgument)) { return false; } + final String label; + final String prefixedLabel = String.format("%s:%s", this.bukkitCommandManager.getOwningPlugin().getName(), + commandArgument.getName()).toLowerCase(); if (bukkitCommands.containsKey(commandArgument.getName())) { - label = String.format("%s:%s", this.bukkitCommandManager.getOwningPlugin().getName(), commandArgument.getName()); + label = prefixedLabel; } else { label = commandArgument.getName(); } - @SuppressWarnings("unchecked") final List aliases = ((StaticArgument) commandArgument).getAlternativeAliases(); + @SuppressWarnings("unchecked") + final List aliases = new ArrayList<>(((StaticArgument) commandArgument).getAlternativeAliases()); + + if (!label.contains(":")) { + aliases.add(prefixedLabel); + } @SuppressWarnings("unchecked") final BukkitCommand bukkitCommand = new BukkitCommand<>( - commandArgument.getName(), + label, (this.bukkitCommandManager.getSplitAliases() ? Collections.emptyList() : aliases), (Command) command, (CommandArgument) commandArgument, this.bukkitCommandManager); this.registeredCommands.put(commandArgument, bukkitCommand); - this.commandMap.register(commandArgument.getName(), this.bukkitCommandManager.getOwningPlugin().getName().toLowerCase(), + this.commandMap.register(label, + this.bukkitCommandManager.getOwningPlugin().getName().toLowerCase(), bukkitCommand); - this.registerExternal(commandArgument.getName(), command, bukkitCommand); + this.registerExternal(label, command, bukkitCommand); + this.recognizedAliases.add(label); if (this.bukkitCommandManager.getSplitAliases()) { for (final String alias : aliases) { @@ -104,6 +118,7 @@ class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { .getName().toLowerCase(), bukkitCommand); this.registerExternal(alias, command, aliasCommand); + this.recognizedAliases.add(alias); } } } @@ -111,6 +126,16 @@ class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { return true; } + /** + * Check if the given alias is recognizable by this registration handler + * + * @param alias Alias + * @return {@code true} if the alias is recognized, else {@code false} + */ + public boolean isRecognized(@NonNull final String alias) { + return this.recognizedAliases.contains(alias); + } + protected void registerExternal(final @NonNull String label, final @NonNull Command command, final @NonNull BukkitCommand bukkitCommand) { 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 c7040021..de6112db 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 @@ -128,6 +128,13 @@ public final class BungeeCommand extends Command implements TabExecutor { ); } 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()) + ); throwable.printStackTrace(); } } diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/AsyncCommandSuggestionsListener.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/AsyncCommandSuggestionsListener.java index e2c6c8cf..3831ace7 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/AsyncCommandSuggestionsListener.java +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/AsyncCommandSuggestionsListener.java @@ -23,6 +23,7 @@ // package cloud.commandframework.paper; +import cloud.commandframework.bukkit.BukkitPluginRegistrationHandler; import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent; import org.bukkit.command.CommandSender; import org.bukkit.event.EventHandler; @@ -51,6 +52,12 @@ class AsyncCommandSuggestionsListener implements Listener { if (paperCommandManager.getCommandTree().getNamedNode(arguments[0]) == null) { return; } + @SuppressWarnings("unchecked") + final BukkitPluginRegistrationHandler bukkitPluginRegistrationHandler = + (BukkitPluginRegistrationHandler) this.paperCommandManager.getCommandRegistrationHandler(); + if (!bukkitPluginRegistrationHandler.isRecognized(arguments[0])) { + return; + } final CommandSender sender = event.getSender(); final C cloudSender = this.paperCommandManager.getCommandSenderMapper().apply(sender); final List suggestions = new ArrayList<>(this.paperCommandManager.suggest(cloudSender, diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java index cc6181ef..43912ecf 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java @@ -37,6 +37,7 @@ import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent; import com.mojang.brigadier.arguments.ArgumentType; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.event.EventHandler; @@ -49,6 +50,7 @@ import java.util.UUID; import java.util.function.BiPredicate; import java.util.function.Supplier; import java.util.logging.Level; +import java.util.regex.Pattern; class PaperBrigadierListener implements Listener { @@ -168,8 +170,21 @@ class PaperBrigadierListener implements Listener { @EventHandler public void onCommandRegister(@Nonnull final CommandRegisteredEvent event) { + if (!(event.getCommand() instanceof PluginIdentifiableCommand)) { + return; + } else if (!((PluginIdentifiableCommand) event.getCommand()) + .getPlugin().equals(this.paperCommandManager.getOwningPlugin())) { + return; + } + final CommandTree commandTree = this.paperCommandManager.getCommandTree(); - final CommandTree.Node> node = commandTree.getNamedNode(event.getCommandLabel()); + + String label = event.getCommandLabel(); + if (label.contains(":")) { + label = label.split(Pattern.quote(":"))[1]; + } + + final CommandTree.Node> node = commandTree.getNamedNode(label); if (node == null) { return; }