Improve Bukkit conflict management

Conflicting commands will now obey plugin load order. Brigadier aliases will be created for namespaced aliases. No asynchronous completions will be provided for conflicting commands and will only be provided for the namespaced label. Furthermore, error handling the command tree has been improved and the methods now return a pair, rather than an optional. This means that there's no need to catch and unwrap exceptions and they will be forwarded in the correct form.
This commit is contained in:
Alexander Söderberg 2020-10-04 18:32:34 +02:00
parent 8eaf0ac772
commit 22993a46d7
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
10 changed files with 271 additions and 172 deletions

View file

@ -216,7 +216,7 @@ public abstract class CommandManager<C> {
* *
* @return Command registration handler * @return Command registration handler
*/ */
protected @NonNull CommandRegistrationHandler getCommandRegistrationHandler() { public @NonNull CommandRegistrationHandler getCommandRegistrationHandler() {
return this.commandRegistrationHandler; return this.commandRegistrationHandler;
} }

View file

@ -38,6 +38,7 @@ import cloud.commandframework.exceptions.NoPermissionException;
import cloud.commandframework.exceptions.NoSuchCommandException; import cloud.commandframework.exceptions.NoSuchCommandException;
import cloud.commandframework.permission.CommandPermission; import cloud.commandframework.permission.CommandPermission;
import cloud.commandframework.permission.OrPermission; import cloud.commandframework.permission.OrPermission;
import cloud.commandframework.types.tuples.Pair;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -52,7 +53,6 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -85,6 +85,8 @@ import java.util.stream.Collectors;
*/ */
public final class CommandTree<C> { public final class CommandTree<C> {
private static final @Nullable Exception NULL_EXCEPTION = null;
private final Object commandLock = new Object(); private final Object commandLock = new Object();
private final Node<CommandArgument<C, ?>> internalTree = new Node<>(null); private final Node<CommandArgument<C, ?>> internalTree = new Node<>(null);
@ -111,43 +113,46 @@ public final class CommandTree<C> {
* @param commandContext Command context instance * @param commandContext Command context instance
* @param args Input * @param args Input
* @return Parsed command, if one could be found * @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<Command<C>> parse(final @NonNull CommandContext<C> commandContext, public @NonNull Pair<@Nullable Command<C>, @Nullable Exception> parse(final @NonNull CommandContext<C> commandContext,
final @NonNull Queue<@NonNull String> args) throws final @NonNull Queue<@NonNull String> args) {
NoSuchCommandException, NoPermissionException, InvalidSyntaxException { final Pair<@Nullable Command<C>, @Nullable Exception> pair = this.parseCommand(new ArrayList<>(),
final Optional<Command<C>> commandOptional = parseCommand(new ArrayList<>(),
commandContext, commandContext,
args, args,
this.internalTree); this.internalTree);
commandOptional.flatMap(Command::getSenderType).ifPresent(requiredType -> { if (pair.getFirst() != null) {
if (!requiredType.isAssignableFrom(commandContext.getSender().getClass())) { final Command<C> command = pair.getFirst();
throw new InvalidCommandSenderException(commandContext.getSender(), requiredType, Collections.emptyList()); 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<Command<C>> parseCommand(final @NonNull List<@NonNull CommandArgument<C, ?>> parsedArguments, return pair;
}
private @NonNull Pair<@Nullable Command<C>, @Nullable Exception>
parseCommand(final @NonNull List<@NonNull CommandArgument<C, ?>> parsedArguments,
final @NonNull CommandContext<C> commandContext, final @NonNull CommandContext<C> commandContext,
final @NonNull Queue<@NonNull String> commandQueue, final @NonNull Queue<@NonNull String> commandQueue,
final @NonNull Node<@Nullable CommandArgument<C, ?>> root) { final @NonNull Node<@Nullable CommandArgument<C, ?>> root) {
CommandPermission permission = this.isPermitted(commandContext.getSender(), root); CommandPermission permission = this.isPermitted(commandContext.getSender(), root);
if (permission != null) { if (permission != null) {
throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(root) return Pair.of(null, new NoPermissionException(permission,
commandContext.getSender(),
this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
final Optional<Command<C>> parsedChild = this.attemptParseUnambiguousChild(parsedArguments, final Pair<@Nullable Command<C>, @Nullable Exception> parsedChild = this.attemptParseUnambiguousChild(parsedArguments,
commandContext, commandContext,
root, root,
commandQueue); commandQueue);
// noinspection all if (parsedChild.getFirst() != null || parsedChild.getSecond() != null) {
if (parsedChild != null) {
return parsedChild; return parsedChild;
} }
@ -156,24 +161,26 @@ public final class CommandTree<C> {
/* We are at the bottom. Check if there's a command attached, in which case we're done */ /* 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 (root.getValue() != null && root.getValue().getOwningCommand() != null) {
if (commandQueue.isEmpty()) { if (commandQueue.isEmpty()) {
return Optional.ofNullable(this.cast(root.getValue().getOwningCommand())); return Pair.of(this.cast(root.getValue().getOwningCommand()), null);
} else { } else {
/* Too many arguments. We have a unique path, so we can send the entire context */ /* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() return Pair.of(null, new InvalidSyntaxException(
this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, root), .apply(parsedArguments, root),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
} else { } else {
/* Too many arguments. We have a unique path, so we can send the entire context */ /* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() return Pair.of(null, new InvalidSyntaxException(
this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, root), .apply(parsedArguments, root),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
} else { } else {
final Iterator<Node<CommandArgument<C, ?>>> childIterator = root.getChildren().iterator(); final Iterator<Node<CommandArgument<C, ?>>> childIterator = root.getChildren().iterator();
@ -197,36 +204,39 @@ public final class CommandTree<C> {
} }
/* We could not find a match */ /* We could not find a match */
if (root.equals(this.internalTree)) { if (root.equals(this.internalTree)) {
throw new NoSuchCommandException(commandContext.getSender(), return Pair.of(null, new NoSuchCommandException(
commandContext.getSender(),
getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), getChain(root).stream().map(Node::getValue).collect(Collectors.toList()),
stringOrEmpty(commandQueue.peek())); stringOrEmpty(commandQueue.peek())));
} }
/* If we couldn't match a child, check if there's a command attached and execute it */ /* 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<C> command = root.getValue().getOwningCommand(); final Command<C> command = root.getValue().getOwningCommand();
if (!this.getCommandManager().hasPermission(commandContext.getSender(), if (!this.getCommandManager().hasPermission(commandContext.getSender(),
command.getCommandPermission())) { command.getCommandPermission())) {
throw new NoPermissionException(command.getCommandPermission(), return Pair.of(null, new NoPermissionException(
command.getCommandPermission(),
commandContext.getSender(), commandContext.getSender(),
this.getChain(root) this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .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 */ /* We know that there's no command and we also cannot match any of the children */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() return Pair.of(null, new InvalidSyntaxException(
this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, root), .apply(parsedArguments, root),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
} }
private @Nullable Optional<Command<C>> private @NonNull Pair<@Nullable Command<C>, @Nullable Exception> attemptParseUnambiguousChild(
attemptParseUnambiguousChild(final @NonNull List<@NonNull CommandArgument<C, ?>> parsedArguments, final @NonNull List<@NonNull CommandArgument<C, ?>> parsedArguments,
final @NonNull CommandContext<C> commandContext, final @NonNull CommandContext<C> commandContext,
final @NonNull Node<@Nullable CommandArgument<C, ?>> root, final @NonNull Node<@Nullable CommandArgument<C, ?>> root,
final @NonNull Queue<String> commandQueue) { final @NonNull Queue<String> commandQueue) {
@ -237,63 +247,73 @@ public final class CommandTree<C> {
final Node<CommandArgument<C, ?>> child = children.get(0); final Node<CommandArgument<C, ?>> child = children.get(0);
permission = this.isPermitted(commandContext.getSender(), child); permission = this.isPermitted(commandContext.getSender(), child);
if (permission != null) { if (permission != null) {
throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(child) return Pair.of(null, new NoPermissionException(
permission,
commandContext.getSender(),
this.getChain(child)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(
Collectors.toList())));
} }
if (child.getValue() != null) { if (child.getValue() != null) {
if (commandQueue.isEmpty()) { if (commandQueue.isEmpty()) {
if (child.getValue().hasDefaultValue()) { if (child.getValue().hasDefaultValue()) {
commandQueue.add(child.getValue().getDefaultValue()); commandQueue.add(child.getValue().getDefaultValue());
} else if (!child.getValue().isRequired()) { } 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()) { } else if (child.isLeaf()) {
/* The child is not a leaf, but may have an intermediary executor, attempt to use it */ /* The child is not a leaf, but may have an intermediary executor, attempt to use it */
if (root.getValue() != null && root.getValue().getOwningCommand() != null) { if (root.getValue() != null && root.getValue().getOwningCommand() != null) {
final Command<C> command = root.getValue().getOwningCommand(); final Command<C> command = root.getValue().getOwningCommand();
if (!this.getCommandManager().hasPermission(commandContext.getSender(), if (!this.getCommandManager().hasPermission(commandContext.getSender(),
command.getCommandPermission())) { command.getCommandPermission())) {
throw new NoPermissionException(command.getCommandPermission(), return Pair.of(null, new NoPermissionException(
command.getCommandPermission(),
commandContext.getSender(), commandContext.getSender(),
this.getChain(root) this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
return Optional.of(command); return Pair.of(command, null);
} }
/* Not enough arguments */ /* Not enough arguments */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() return Pair.of(null, new InvalidSyntaxException(
this.commandManager.getCommandSyntaxFormatter()
.apply(Objects.requireNonNull( .apply(Objects.requireNonNull(
child.getValue().getOwningCommand()) child.getValue()
.getOwningCommand())
.getArguments(), child), .getArguments(), child),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(
Collectors.toList())));
} else { } else {
/* The child is not a leaf, but may have an intermediary executor, attempt to use it */ /* The child is not a leaf, but may have an intermediary executor, attempt to use it */
if (root.getValue() != null && root.getValue().getOwningCommand() != null) { if (root.getValue() != null && root.getValue().getOwningCommand() != null) {
final Command<C> command = root.getValue().getOwningCommand(); final Command<C> command = root.getValue().getOwningCommand();
if (!this.getCommandManager().hasPermission(commandContext.getSender(), if (!this.getCommandManager().hasPermission(commandContext.getSender(),
command.getCommandPermission())) { command.getCommandPermission())) {
throw new NoPermissionException(command.getCommandPermission(), return Pair.of(null, new NoPermissionException(
command.getCommandPermission(),
commandContext.getSender(), commandContext.getSender(),
this.getChain(root) this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
return Optional.of(command); return Pair.of(command, null);
} }
/* Child does not have a command and so we cannot proceed */ /* Child does not have a command and so we cannot proceed */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() return Pair.of(null, new InvalidSyntaxException(
this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, root), .apply(parsedArguments, root),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
} }
@ -308,32 +328,33 @@ public final class CommandTree<C> {
commandContext.store(child.getValue().getName(), result.getParsedValue().get()); commandContext.store(child.getValue().getName(), result.getParsedValue().get());
if (child.isLeaf()) { if (child.isLeaf()) {
if (commandQueue.isEmpty()) { if (commandQueue.isEmpty()) {
return Optional.ofNullable(this.cast(child.getValue().getOwningCommand())); return Pair.of(this.cast(child.getValue().getOwningCommand()), null);
} else { } else {
/* Too many arguments. We have a unique path, so we can send the entire context */ /* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() return Pair.of(null, new InvalidSyntaxException(
this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, child), .apply(parsedArguments, child),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect( .collect(
Collectors.toList())); Collectors.toList())));
} }
} else { } else {
parsedArguments.add(child.getValue()); parsedArguments.add(child.getValue());
return this.parseCommand(parsedArguments, commandContext, commandQueue, child); return this.parseCommand(parsedArguments, commandContext, commandQueue, child);
} }
} else if (result.getFailure().isPresent()) { } else if (result.getFailure().isPresent()) {
throw new ArgumentParseException(result.getFailure().get(), commandContext.getSender(), return Pair.of(null, new ArgumentParseException(
result.getFailure().get(), commandContext.getSender(),
this.getChain(child) this.getChain(child)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList())));
} }
} }
} }
// noinspection all return Pair.of(null, null);
return null;
} }
/** /**

View file

@ -28,16 +28,16 @@ import cloud.commandframework.CommandManager;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.services.State; import cloud.commandframework.services.State;
import cloud.commandframework.types.tuples.Pair;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; 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 * Execution coordinator parses and/or executes commands on a separate thread from the calling thread
@ -54,7 +54,7 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
final boolean synchronizeParsing, final boolean synchronizeParsing,
final @NonNull CommandTree<C> commandTree) { final @NonNull CommandTree<C> commandTree) {
super(commandTree); super(commandTree);
this.executor = executor; this.executor = executor == null ? ForkJoinPool.commonPool() : executor;
this.synchronizeParsing = synchronizeParsing; this.synchronizeParsing = synchronizeParsing;
this.commandManager = commandTree.getCommandManager(); this.commandManager = commandTree.getCommandManager();
} }
@ -78,24 +78,39 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
command.getCommandExecutionHandler().execute(commandContext); command.getCommandExecutionHandler().execute(commandContext);
} }
}; };
final Supplier<CommandResult<C>> supplier;
if (this.synchronizeParsing) { if (this.synchronizeParsing) {
final Optional<Command<C>> commandOptional = this.getCommandTree().parse(commandContext, input); final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
supplier = () -> { this.getCommandTree().parse(commandContext, input);
commandOptional.ifPresent(commandConsumer); if (pair.getSecond() != null) {
return new CommandResult<>(commandContext); final CompletableFuture<CommandResult<C>> future = new CompletableFuture<>();
}; future.completeExceptionally(pair.getSecond());
} else { return future;
supplier = () -> {
this.getCommandTree().parse(commandContext, input).ifPresent(commandConsumer);
return new CommandResult<>(commandContext);
};
} }
if (this.executor != null) { return CompletableFuture.supplyAsync(() -> {
return CompletableFuture.supplyAsync(supplier, this.executor); commandConsumer.accept(pair.getFirst());
} else { return new CommandResult<>(commandContext);
return CompletableFuture.supplyAsync(supplier); }, this.executor);
} }
final CompletableFuture<CommandResult<C>> resultFuture = new CompletableFuture<>();
this.executor.execute(() -> {
try {
final @NonNull Pair<@Nullable Command<C>, @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;
} }

View file

@ -23,10 +23,13 @@
// //
package cloud.commandframework.execution; package cloud.commandframework.execution;
import cloud.commandframework.Command;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.services.State; import cloud.commandframework.services.State;
import cloud.commandframework.types.tuples.Pair;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -101,12 +104,17 @@ public abstract class CommandExecutionCoordinator<C> {
final @NonNull Queue<@NonNull String> input) { final @NonNull Queue<@NonNull String> input) {
final CompletableFuture<CommandResult<C>> completableFuture = new CompletableFuture<>(); final CompletableFuture<CommandResult<C>> completableFuture = new CompletableFuture<>();
try { try {
this.getCommandTree().parse(commandContext, input).ifPresent(command -> { final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
this.getCommandTree().parse(commandContext, input);
if (pair.getSecond() != null) {
completableFuture.completeExceptionally(pair.getSecond());
} else {
final Command<C> command = pair.getFirst();
if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) { if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) {
command.getCommandExecutionHandler().execute(commandContext); command.getCommandExecutionHandler().execute(commandContext);
} }
});
completableFuture.complete(new CommandResult<>(commandContext)); completableFuture.complete(new CommandResult<>(commandContext));
}
} catch (final Exception e) { } catch (final Exception e) {
completableFuture.completeExceptionally(e); completableFuture.completeExceptionally(e);
} }

View file

@ -38,8 +38,8 @@ public class Pair<U, V> implements Tuple {
private final U first; private final U first;
private final V second; private final V second;
protected Pair(final @NonNull U first, protected Pair(final U first,
final @NonNull V second) { final V second) {
this.first = first; this.first = first;
this.second = second; this.second = second;
} }
@ -53,8 +53,8 @@ public class Pair<U, V> implements Tuple {
* @param <V> Second type * @param <V> Second type
* @return Created pair * @return Created pair
*/ */
public static <U, V> @NonNull Pair<@NonNull U, @NonNull V> of(final @NonNull U first, public static <U, V> @NonNull Pair<U, V> of(final U first,
final @NonNull V second) { final V second) {
return new Pair<>(first, second); return new Pair<>(first, second);
} }
@ -63,7 +63,7 @@ public class Pair<U, V> implements Tuple {
* *
* @return First value * @return First value
*/ */
public final @NonNull U getFirst() { public final U getFirst() {
return this.first; return this.first;
} }
@ -72,7 +72,7 @@ public class Pair<U, V> implements Tuple {
* *
* @return Second value * @return Second value
*/ */
public final @NonNull V getSecond() { public final V getSecond() {
return this.second; return this.second;
} }

View file

@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Optional;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
class CommandTreeTest { class CommandTreeTest {
@ -68,7 +67,8 @@ class CommandTreeTest {
.argument(StringArgument.of("string")) .argument(StringArgument.of("string"))
.argument(IntegerArgument.of("int")) .argument(IntegerArgument.of("int"))
.literal("anotherliteral") .literal("anotherliteral")
.handler(c -> {}) .handler(c -> {
})
.build(); .build();
manager.command(toProxy); manager.command(toProxy);
manager.command(manager.commandBuilder("proxy").proxies(toProxy).build()); manager.command(manager.commandBuilder("proxy").proxies(toProxy).build());
@ -120,31 +120,32 @@ class CommandTreeTest {
@Test @Test
void parse() { void parse() {
final Optional<Command<TestCommandSender>> command = manager.getCommandTree() final Pair<Command<TestCommandSender>, Exception> command = manager.getCommandTree()
.parse(new CommandContext<>( .parse(new CommandContext<>(
new TestCommandSender()), new TestCommandSender()),
new LinkedList<>( new LinkedList<>(
Arrays.asList("test", Arrays.asList("test",
"one"))); "one")));
Assertions.assertTrue(command.isPresent()); Assertions.assertNotNull(command.getFirst());
Assertions.assertThrows(NoPermissionException.class, () -> manager.getCommandTree() Assertions.assertEquals(NoPermissionException.class, manager.getCommandTree()
.parse(new CommandContext<>( .parse(new CommandContext<>(
new TestCommandSender()), new TestCommandSender()),
new LinkedList<>( new LinkedList<>(
Arrays.asList("test", "two")))); Arrays.asList("test", "two")))
.getSecond().getClass());
manager.getCommandTree() manager.getCommandTree()
.parse(new CommandContext<>(new TestCommandSender()), new LinkedList<>(Arrays.asList("test", "opt"))) .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() manager.getCommandTree()
.parse(new CommandContext<>(new TestCommandSender()), new LinkedList<>(Arrays.asList("test", "opt", "12"))) .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 @Test
void testAlias() { void testAlias() {
manager.getCommandTree() manager.getCommandTree()
.parse(new CommandContext<>(new TestCommandSender()), new LinkedList<>(Arrays.asList("other", "öpt", "12"))) .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 @Test

View file

@ -35,14 +35,18 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHandler { public class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHandler {
private final Map<CommandArgument<?, ?>, org.bukkit.command.Command> registeredCommands = new HashMap<>(); private final Map<CommandArgument<?, ?>, org.bukkit.command.Command> registeredCommands = new HashMap<>();
private final Set<String> recognizedAliases = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
private Map<String, org.bukkit.command.Command> bukkitCommands; private Map<String, org.bukkit.command.Command> bukkitCommands;
private BukkitCommandManager<C> bukkitCommandManager; private BukkitCommandManager<C> bukkitCommandManager;
@ -51,7 +55,7 @@ class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHandler {
BukkitPluginRegistrationHandler() { BukkitPluginRegistrationHandler() {
} }
void initialize(final @NonNull BukkitCommandManager<C> bukkitCommandManager) throws Exception { final void initialize(final @NonNull BukkitCommandManager<C> bukkitCommandManager) throws Exception {
final Method getCommandMap = Bukkit.getServer().getClass().getDeclaredMethod("getCommandMap"); final Method getCommandMap = Bukkit.getServer().getClass().getDeclaredMethod("getCommandMap");
getCommandMap.setAccessible(true); getCommandMap.setAccessible(true);
this.commandMap = (CommandMap) getCommandMap.invoke(Bukkit.getServer()); this.commandMap = (CommandMap) getCommandMap.invoke(Bukkit.getServer());
@ -65,31 +69,41 @@ class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHandler {
} }
@Override @Override
public boolean registerCommand(final @NonNull Command<?> command) { public final boolean registerCommand(final @NonNull Command<?> command) {
/* We only care about the root command argument */ /* We only care about the root command argument */
final CommandArgument<?, ?> commandArgument = command.getArguments().get(0); final CommandArgument<?, ?> commandArgument = command.getArguments().get(0);
if (this.registeredCommands.containsKey(commandArgument)) { if (this.registeredCommands.containsKey(commandArgument)) {
return false; return false;
} }
final String label; final String label;
final String prefixedLabel = String.format("%s:%s", this.bukkitCommandManager.getOwningPlugin().getName(),
commandArgument.getName()).toLowerCase();
if (bukkitCommands.containsKey(commandArgument.getName())) { if (bukkitCommands.containsKey(commandArgument.getName())) {
label = String.format("%s:%s", this.bukkitCommandManager.getOwningPlugin().getName(), commandArgument.getName()); label = prefixedLabel;
} else { } else {
label = commandArgument.getName(); label = commandArgument.getName();
} }
@SuppressWarnings("unchecked") final List<String> aliases = ((StaticArgument<C>) commandArgument).getAlternativeAliases(); @SuppressWarnings("unchecked")
final List<String> aliases = new ArrayList<>(((StaticArgument<C>) commandArgument).getAlternativeAliases());
if (!label.contains(":")) {
aliases.add(prefixedLabel);
}
@SuppressWarnings("unchecked") final BukkitCommand<C> bukkitCommand = new BukkitCommand<>( @SuppressWarnings("unchecked") final BukkitCommand<C> bukkitCommand = new BukkitCommand<>(
commandArgument.getName(), label,
(this.bukkitCommandManager.getSplitAliases() ? Collections.<String>emptyList() : aliases), (this.bukkitCommandManager.getSplitAliases() ? Collections.<String>emptyList() : aliases),
(Command<C>) command, (Command<C>) command,
(CommandArgument<C, ?>) commandArgument, (CommandArgument<C, ?>) commandArgument,
this.bukkitCommandManager); this.bukkitCommandManager);
this.registeredCommands.put(commandArgument, bukkitCommand); 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); bukkitCommand);
this.registerExternal(commandArgument.getName(), command, bukkitCommand); this.registerExternal(label, command, bukkitCommand);
this.recognizedAliases.add(label);
if (this.bukkitCommandManager.getSplitAliases()) { if (this.bukkitCommandManager.getSplitAliases()) {
for (final String alias : aliases) { for (final String alias : aliases) {
@ -104,6 +118,7 @@ class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHandler {
.getName().toLowerCase(), .getName().toLowerCase(),
bukkitCommand); bukkitCommand);
this.registerExternal(alias, command, aliasCommand); this.registerExternal(alias, command, aliasCommand);
this.recognizedAliases.add(alias);
} }
} }
} }
@ -111,6 +126,16 @@ class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHandler {
return true; 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, protected void registerExternal(final @NonNull String label,
final @NonNull Command<?> command, final @NonNull Command<?> command,
final @NonNull BukkitCommand<C> bukkitCommand) { final @NonNull BukkitCommand<C> bukkitCommand) {

View file

@ -128,6 +128,13 @@ public final class BungeeCommand<C> extends Command implements TabExecutor {
); );
} else { } else {
commandSender.sendMessage(new ComponentBuilder(throwable.getMessage()).create()); 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(); throwable.printStackTrace();
} }
} }

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.paper; package cloud.commandframework.paper;
import cloud.commandframework.bukkit.BukkitPluginRegistrationHandler;
import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent; import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -51,6 +52,12 @@ class AsyncCommandSuggestionsListener<C> implements Listener {
if (paperCommandManager.getCommandTree().getNamedNode(arguments[0]) == null) { if (paperCommandManager.getCommandTree().getNamedNode(arguments[0]) == null) {
return; return;
} }
@SuppressWarnings("unchecked")
final BukkitPluginRegistrationHandler<C> bukkitPluginRegistrationHandler =
(BukkitPluginRegistrationHandler<C>) this.paperCommandManager.getCommandRegistrationHandler();
if (!bukkitPluginRegistrationHandler.isRecognized(arguments[0])) {
return;
}
final CommandSender sender = event.getSender(); final CommandSender sender = event.getSender();
final C cloudSender = this.paperCommandManager.getCommandSenderMapper().apply(sender); final C cloudSender = this.paperCommandManager.getCommandSenderMapper().apply(sender);
final List<String> suggestions = new ArrayList<>(this.paperCommandManager.suggest(cloudSender, final List<String> suggestions = new ArrayList<>(this.paperCommandManager.suggest(cloudSender,

View file

@ -37,6 +37,7 @@ import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -49,6 +50,7 @@ import java.util.UUID;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.regex.Pattern;
class PaperBrigadierListener<C> implements Listener { class PaperBrigadierListener<C> implements Listener {
@ -168,8 +170,21 @@ class PaperBrigadierListener<C> implements Listener {
@EventHandler @EventHandler
public void onCommandRegister(@Nonnull final CommandRegisteredEvent<BukkitBrigadierCommandSource> event) { public void onCommandRegister(@Nonnull final CommandRegisteredEvent<BukkitBrigadierCommandSource> event) {
if (!(event.getCommand() instanceof PluginIdentifiableCommand)) {
return;
} else if (!((PluginIdentifiableCommand) event.getCommand())
.getPlugin().equals(this.paperCommandManager.getOwningPlugin())) {
return;
}
final CommandTree<C> commandTree = this.paperCommandManager.getCommandTree(); final CommandTree<C> commandTree = this.paperCommandManager.getCommandTree();
final CommandTree.Node<CommandArgument<C, ?>> node = commandTree.getNamedNode(event.getCommandLabel());
String label = event.getCommandLabel();
if (label.contains(":")) {
label = label.split(Pattern.quote(":"))[1];
}
final CommandTree.Node<CommandArgument<C, ?>> node = commandTree.getNamedNode(label);
if (node == null) { if (node == null) {
return; return;
} }