feat(core): support root command deletion & standardize capabilities (#369)

This commit is contained in:
Alexander Söderberg 2022-06-08 13:23:41 +02:00 committed by Jason
parent 08a97b2c4f
commit 28ff5d3003
14 changed files with 416 additions and 16 deletions

View file

@ -0,0 +1,85 @@
//
// MIT License
//
// Copyright (c) 2021 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;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a capability that a cloud implementation may have.
*
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public interface CloudCapability {
/**
* Returns the friendly name of this capability.
*
* @return the name of the capability
*/
@Override @NonNull String toString();
/**
* Standard {@link CloudCapability capabilities}.
*
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
enum StandardCapabilities implements CloudCapability {
/**
* The capability to delete root commands using {@link CommandManager#deleteRootCommand(String)}.
*/
ROOT_COMMAND_DELETION;
@Override
public @NonNull String toString() {
return name();
}
}
/**
* Exception thrown when a {@link CloudCapability} is missing, when a method that requires the presence of that
* capability is invoked.
*
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
@SuppressWarnings("serial")
final class CloudCapabilityMissingException extends RuntimeException {
private static final long serialVersionUID = 8961652857372971486L;
/**
* Create a new cloud capability missing exception instance.
*
* @param capability the missing capability
*/
public CloudCapabilityMissingException(final @NonNull CloudCapability capability) {
super(String.format("Missing capability '%s'", capability));
}
}
}

View file

@ -29,6 +29,7 @@ import cloud.commandframework.arguments.CommandSuggestionEngine;
import cloud.commandframework.arguments.CommandSyntaxFormatter; import cloud.commandframework.arguments.CommandSyntaxFormatter;
import cloud.commandframework.arguments.DelegatingCommandSuggestionEngineFactory; import cloud.commandframework.arguments.DelegatingCommandSuggestionEngineFactory;
import cloud.commandframework.arguments.StandardCommandSyntaxFormatter; import cloud.commandframework.arguments.StandardCommandSyntaxFormatter;
import cloud.commandframework.arguments.StaticArgument;
import cloud.commandframework.arguments.flags.CommandFlag; import cloud.commandframework.arguments.flags.CommandFlag;
import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserParameter; import cloud.commandframework.arguments.parser.ParserParameter;
@ -66,15 +67,18 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apiguardian.api.API; import org.apiguardian.api.API;
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;
@ -100,6 +104,7 @@ public abstract class CommandManager<C> {
private final CommandExecutionCoordinator<C> commandExecutionCoordinator; private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandTree<C> commandTree; private final CommandTree<C> commandTree;
private final CommandSuggestionEngine<C> commandSuggestionEngine; private final CommandSuggestionEngine<C> commandSuggestionEngine;
private final Set<CloudCapability> capabilities = new HashSet<>();
private CaptionVariableReplacementHandler captionVariableReplacementHandler = new SimpleCaptionVariableReplacementHandler(); private CaptionVariableReplacementHandler captionVariableReplacementHandler = new SimpleCaptionVariableReplacementHandler();
private CommandSyntaxFormatter<C> commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>(); private CommandSyntaxFormatter<C> commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>();
@ -297,6 +302,40 @@ public abstract class CommandManager<C> {
return this.commandRegistrationHandler; return this.commandRegistrationHandler;
} }
/**
* Registers the given {@code capability}.
*
* @param capability the capability
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
protected final void registerCapability(final @NonNull CloudCapability capability) {
this.capabilities.add(capability);
}
/**
* Checks whether the cloud implementation has the given {@code capability}.
*
* @param capability the capability
* @return {@code true} if the implementation has the {@code capability}, {@code false} if not
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public boolean hasCapability(final @NonNull CloudCapability capability) {
return this.capabilities.contains(capability);
}
/**
* Returns an unmodifiable snapshot of the currently registered {@link CloudCapability capabilities}.
*
* @return the currently registered capabilities
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull Collection<@NonNull CloudCapability> capabilities() {
return Collections.unmodifiableSet(new HashSet<>(this.capabilities));
}
protected final void setCommandRegistrationHandler(final @NonNull CommandRegistrationHandler commandRegistrationHandler) { protected final void setCommandRegistrationHandler(final @NonNull CommandRegistrationHandler commandRegistrationHandler) {
this.requireState(RegistrationState.BEFORE_REGISTRATION); this.requireState(RegistrationState.BEFORE_REGISTRATION);
this.commandRegistrationHandler = commandRegistrationHandler; this.commandRegistrationHandler = commandRegistrationHandler;
@ -382,6 +421,55 @@ public abstract class CommandManager<C> {
*/ */
public abstract boolean hasPermission(@NonNull C sender, @NonNull String permission); public abstract boolean hasPermission(@NonNull C sender, @NonNull String permission);
/**
* Deletes the given {@code rootCommand}.
* <p>
* This will delete all chains that originate at the root command.
*
* @param rootCommand The root command to delete
* @throws CloudCapability.CloudCapabilityMissingException If {@link CloudCapability.StandardCapabilities#ROOT_COMMAND_DELETION} is missing
* @since 1.7.0
*/
@API(status = API.Status.EXPERIMENTAL, since = "1.7.0")
public void deleteRootCommand(final @NonNull String rootCommand) throws CloudCapability.CloudCapabilityMissingException {
if (!this.hasCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION)) {
throw new CloudCapability.CloudCapabilityMissingException(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
}
// Mark the command for deletion.
final CommandTree.Node<@Nullable CommandArgument<C, ?>> node = this.commandTree.getNamedNode(rootCommand);
if (node == null) {
throw new IllegalArgumentException(String.format("No root command named '%s' exists", rootCommand));
}
// The registration handler gets to act before we destruct the command.
this.commandRegistrationHandler.unregisterRootCommand((StaticArgument<?>) node.getValue());
// We then delete it from the tree.
this.commandTree.deleteRecursively(node);
// And lastly we re-build the entire tree.
this.commandTree.verifyAndRegister();
}
/**
* Returns all root command names.
*
* @return Root command names.
* @since 1.7.0
*/
@SuppressWarnings("unchecked")
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull Collection<@NonNull String> rootCommands() {
return this.commandTree.getRootNodes()
.stream()
.map(CommandTree.Node::getValue)
.filter(arg -> arg instanceof StaticArgument)
.map(arg -> (StaticArgument<C>) arg)
.map(StaticArgument::getName)
.collect(Collectors.toList());
}
/** /**
* Create a new command builder. This will also register the creating manager in the command * Create a new command builder. This will also register the creating manager in the command
* builder using {@link Command.Builder#manager(CommandManager)}, so that the command * builder using {@link Command.Builder#manager(CommandManager)}, so that the command

View file

@ -953,6 +953,29 @@ public final class CommandTree<C> {
return null; return null;
} }
void deleteRecursively(final @NonNull Node<@Nullable CommandArgument<C, ?>> node) {
for (final Node<@Nullable CommandArgument<C, ?>> child : new ArrayList<>(node.children)) {
this.deleteRecursively(child);
}
// We need to remove it from the tree.
this.removeNode(node);
}
private boolean removeNode(final @NonNull Node<@Nullable CommandArgument<C, ?>> node) {
if (node.isLeaf()) {
if (this.getRootNodes().contains(node)) {
this.internalTree.removeChild(node);
} else {
return node.getParent().removeChild(node);
}
} else {
throw new IllegalStateException(String.format("Cannot delete intermediate node '%s'", node));
}
return false;
}
/** /**
* Get the command manager * Get the command manager
* *
@ -971,7 +994,7 @@ public final class CommandTree<C> {
private final Map<String, Object> nodeMeta = new HashMap<>(); private final Map<String, Object> nodeMeta = new HashMap<>();
private final List<Node<T>> children = new LinkedList<>(); private final List<Node<T>> children = new LinkedList<>();
private final T value; private T value;
private Node<T> parent; private Node<T> parent;
private Node(final @Nullable T value) { private Node(final @Nullable T value) {
@ -1002,6 +1025,10 @@ public final class CommandTree<C> {
return null; return null;
} }
private boolean removeChild(final @NonNull Node<T> child) {
return this.children.remove(child);
}
/** /**
* Check if the node is a leaf node * Check if the node is a leaf node
* *

View file

@ -24,6 +24,7 @@
package cloud.commandframework.internal; package cloud.commandframework.internal;
import cloud.commandframework.Command; import cloud.commandframework.Command;
import cloud.commandframework.arguments.StaticArgument;
import org.apiguardian.api.API; import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@ -54,6 +55,15 @@ public interface CommandRegistrationHandler {
*/ */
boolean registerCommand(@NonNull Command<?> command); boolean registerCommand(@NonNull Command<?> command);
/**
* Requests that the given {@code rootCommand} should be unregistered.
*
* @param rootCommand The command to delete
* @since 1.7.0
*/
default void unregisterRootCommand(final @NonNull StaticArgument<?> rootCommand) {
}
@API(status = API.Status.INTERNAL, consumers = "cloud.commandframework.*") @API(status = API.Status.INTERNAL, consumers = "cloud.commandframework.*")
final class NullCommandRegistrationHandler implements CommandRegistrationHandler { final class NullCommandRegistrationHandler implements CommandRegistrationHandler {
@ -66,6 +76,9 @@ public interface CommandRegistrationHandler {
return true; return true;
} }
@Override
public void unregisterRootCommand(final @NonNull StaticArgument<?> rootCommand) {
}
} }
} }

View file

@ -0,0 +1,149 @@
//
// MIT License
//
// Copyright (c) 2021 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;
import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.exceptions.NoSuchCommandException;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionHandler;
import cloud.commandframework.internal.CommandRegistrationHandler;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta;
import java.util.concurrent.CompletionException;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
class CommandDeletionTest {
private CommandManager<TestCommandSender> commandManager;
@BeforeEach
void setup() {
this.commandManager = new CommandManager<TestCommandSender>(
CommandExecutionCoordinator.simpleCoordinator(),
CommandRegistrationHandler.nullCommandRegistrationHandler()
) {
{
this.registerCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
}
@Override
public boolean hasPermission(
final @NonNull TestCommandSender sender,
final @NonNull String permission
) {
return true;
}
@Override
public @NonNull CommandMeta createDefaultCommandMeta() {
return SimpleCommandMeta.empty();
}
};
}
@Test
void deleteSimpleCommand() {
// Arrange
this.commandManager.command(this.commandManager.commandBuilder("test").build());
// Pre-assert.
this.commandManager.executeCommand(new TestCommandSender(), "test").join();
// Act
this.commandManager.deleteRootCommand("test");
// Assert
final CompletionException completionException = assertThrows(
CompletionException.class,
() -> this.commandManager.executeCommand(new TestCommandSender(), "test").join()
);
assertThat(completionException).hasCauseThat().isInstanceOf(NoSuchCommandException.class);
assertThat(this.commandManager.suggest(new TestCommandSender(), "")).isEmpty();
assertThat(this.commandManager.getCommandTree().getRootNodes()).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
void deleteIntermediateCommand() {
// Arrange
final CommandExecutionHandler<TestCommandSender> handler1 = mock(CommandExecutionHandler.class);
final Command<TestCommandSender> command1 = this.commandManager
.commandBuilder("test")
.handler(handler1)
.build();
this.commandManager.command(command1);
final CommandExecutionHandler<TestCommandSender> handler2 = mock(CommandExecutionHandler.class);
final Command<TestCommandSender> command2 = this.commandManager
.commandBuilder("test")
.literal("literal")
.handler(handler2)
.build();
this.commandManager.command(command2);
final CommandExecutionHandler<TestCommandSender> handler3 = mock(CommandExecutionHandler.class);
final Command<TestCommandSender> command3 = this.commandManager
.commandBuilder("test")
.literal("literal")
.argument(StringArgument.of("string"))
.handler(handler3)
.build();
this.commandManager.command(command3);
// Act
this.commandManager.deleteRootCommand("test");
// Assert
final CompletionException completionException = assertThrows(
CompletionException.class,
() -> this.commandManager.executeCommand(new TestCommandSender(), "test").join()
);
assertThat(completionException).hasCauseThat().isInstanceOf(NoSuchCommandException.class);
final CompletionException completionException2 = assertThrows(
CompletionException.class,
() -> this.commandManager.executeCommand(new TestCommandSender(), "test literal").join()
);
assertThat(completionException2).hasCauseThat().isInstanceOf(NoSuchCommandException.class);
final CompletionException completionException3 = assertThrows(
CompletionException.class,
() -> this.commandManager.executeCommand(new TestCommandSender(), "test literal hm").join()
);
assertThat(completionException3).hasCauseThat().isInstanceOf(NoSuchCommandException.class);
verifyNoMoreInteractions(handler1);
verifyNoMoreInteractions(handler2);
verifyNoMoreInteractions(handler3);
assertThat(this.commandManager.getCommandTree().getRootNodes()).isEmpty();
}
}

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.javacord; package cloud.commandframework.javacord;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.execution.CommandExecutionCoordinator;
@ -77,6 +78,8 @@ public class JavacordCommandManager<C> extends CommandManager<C> {
this.commandPrefixMapper = commandPrefixMapper; this.commandPrefixMapper = commandPrefixMapper;
this.commandPermissionMapper = commandPermissionMapper; this.commandPermissionMapper = commandPermissionMapper;
this.registerCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
} }
@Override @Override

View file

@ -25,10 +25,12 @@ package cloud.commandframework.javacord;
import cloud.commandframework.Command; import cloud.commandframework.Command;
import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.StaticArgument;
import cloud.commandframework.internal.CommandRegistrationHandler; import cloud.commandframework.internal.CommandRegistrationHandler;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.javacord.api.listener.message.MessageCreateListener;
final class JavacordRegistrationHandler<C> implements CommandRegistrationHandler { final class JavacordRegistrationHandler<C> implements CommandRegistrationHandler {
@ -59,4 +61,15 @@ final class JavacordRegistrationHandler<C> implements CommandRegistrationHandler
return true; return true;
} }
@Override
public void unregisterRootCommand(
final @NonNull StaticArgument<?> rootCommand
) {
final JavacordCommand<C> command = this.registeredCommands.get(rootCommand);
if (command == null) {
return;
}
this.javacordCommandManager.getDiscordApi().removeListener(MessageCreateListener.class, command);
}
} }

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.jda; package cloud.commandframework.jda;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.execution.CommandExecutionCoordinator;
@ -117,6 +118,9 @@ public class JDACommandManager<C> extends CommandManager<C> {
new RoleArgument.RoleParser<>( new RoleArgument.RoleParser<>(
new HashSet<>(Arrays.asList(RoleArgument.ParserMode.values())) new HashSet<>(Arrays.asList(RoleArgument.ParserMode.values()))
)); ));
// No "native" command system means that we can delete commands just fine.
this.registerCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
} }
/** /**

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.pircbotx; package cloud.commandframework.pircbotx;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.captions.Caption; import cloud.commandframework.captions.Caption;
@ -114,6 +115,9 @@ public class PircBotXCommandManager<C> extends CommandManager<C> {
TypeToken.get(User.class), TypeToken.get(User.class),
parameters -> new UserArgument.UserArgumentParser<>() parameters -> new UserArgument.UserArgumentParser<>()
); );
// No "native" command system means that we can delete commands just fine.
this.registerCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
} }
@Override @Override

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.bukkit; package cloud.commandframework.bukkit;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.brigadier.BrigadierManagerHolder; import cloud.commandframework.brigadier.BrigadierManagerHolder;
@ -55,6 +56,7 @@ import java.lang.reflect.Method;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import org.apiguardian.api.API;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
@ -126,6 +128,9 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
final BukkitSynchronizer bukkitSynchronizer = new BukkitSynchronizer(owningPlugin); final BukkitSynchronizer bukkitSynchronizer = new BukkitSynchronizer(owningPlugin);
this.taskFactory = new TaskFactory(bukkitSynchronizer); this.taskFactory = new TaskFactory(bukkitSynchronizer);
/* Register capabilities */
CloudBukkitCapabilities.CAPABLE.forEach(this::registerCapability);
/* Register Bukkit Preprocessor */ /* Register Bukkit Preprocessor */
this.registerCommandPreProcessor(new BukkitCommandPreprocessor<>(this)); this.registerCommandPreProcessor(new BukkitCommandPreprocessor<>(this));
@ -157,7 +162,7 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
new MultiplePlayerSelectorArgument.MultiplePlayerSelectorParser<>()); new MultiplePlayerSelectorArgument.MultiplePlayerSelectorParser<>());
/* Register MC 1.13+ parsers */ /* Register MC 1.13+ parsers */
if (this.queryCapability(CloudBukkitCapabilities.BRIGADIER)) { if (this.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
this.registerParserSupplierFor(ItemStackPredicateArgument.class); this.registerParserSupplierFor(ItemStackPredicateArgument.class);
this.registerParserSupplierFor(BlockPredicateArgument.class); this.registerParserSupplierFor(BlockPredicateArgument.class);
} }
@ -256,7 +261,7 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
* will contain the reason for this. * will contain the reason for this.
*/ */
protected final void checkBrigadierCompatibility() throws BrigadierFailureException { protected final void checkBrigadierCompatibility() throws BrigadierFailureException {
if (!this.queryCapability(CloudBukkitCapabilities.BRIGADIER)) { if (!this.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
throw new BrigadierFailureException( throw new BrigadierFailureException(
BrigadierFailureReason.VERSION_TOO_LOW, BrigadierFailureReason.VERSION_TOO_LOW,
new IllegalArgumentException( new IllegalArgumentException(
@ -271,7 +276,10 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
* *
* @param capability Capability * @param capability Capability
* @return {@code true} if the manager has the given capability, else {@code false} * @return {@code true} if the manager has the given capability, else {@code false}
* @deprecated for removal since 1.7.0. Use the new standard {@link #hasCapability(CloudCapability)} instead.
*/ */
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final boolean queryCapability(final @NonNull CloudBukkitCapabilities capability) { public final boolean queryCapability(final @NonNull CloudBukkitCapabilities capability) {
return capability.capable(); return capability.capable();
} }
@ -371,7 +379,7 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
} }
final void lockIfBrigadierCapable() { final void lockIfBrigadierCapable() {
if (this.queryCapability(CloudBukkitCapabilities.BRIGADIER)) { if (this.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
this.lockRegistration(); this.lockRegistration();
} }
} }

View file

@ -27,7 +27,6 @@ import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.bukkit.internal.BukkitBackwardsBrigadierSenderMapper; import cloud.commandframework.bukkit.internal.BukkitBackwardsBrigadierSenderMapper;
import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext;
import cloud.commandframework.execution.preprocessor.CommandPreprocessor; import cloud.commandframework.execution.preprocessor.CommandPreprocessor;
import java.util.Set;
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;
@ -40,7 +39,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
final class BukkitCommandPreprocessor<C> implements CommandPreprocessor<C> { final class BukkitCommandPreprocessor<C> implements CommandPreprocessor<C> {
private final BukkitCommandManager<C> commandManager; private final BukkitCommandManager<C> commandManager;
private final Set<CloudBukkitCapabilities> bukkitCapabilities;
private final @Nullable BukkitBackwardsBrigadierSenderMapper<C, ?> mapper; private final @Nullable BukkitBackwardsBrigadierSenderMapper<C, ?> mapper;
/** /**
@ -50,8 +48,8 @@ final class BukkitCommandPreprocessor<C> implements CommandPreprocessor<C> {
*/ */
BukkitCommandPreprocessor(final @NonNull BukkitCommandManager<C> commandManager) { BukkitCommandPreprocessor(final @NonNull BukkitCommandManager<C> commandManager) {
this.commandManager = commandManager; this.commandManager = commandManager;
this.bukkitCapabilities = commandManager.queryCapabilities();
if (this.bukkitCapabilities.contains(CloudBukkitCapabilities.BRIGADIER)) { if (this.commandManager.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
this.mapper = new BukkitBackwardsBrigadierSenderMapper<>(this.commandManager); this.mapper = new BukkitBackwardsBrigadierSenderMapper<>(this.commandManager);
} else { } else {
this.mapper = null; this.mapper = null;
@ -76,7 +74,7 @@ final class BukkitCommandPreprocessor<C> implements CommandPreprocessor<C> {
); );
context.getCommandContext().store( context.getCommandContext().store(
BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES, BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES,
this.bukkitCapabilities this.commandManager.queryCapabilities()
); );
} }

View file

@ -23,15 +23,17 @@
// //
package cloud.commandframework.bukkit; package cloud.commandframework.bukkit;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection; import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
* Capabilities for the Bukkit module * Capabilities for the Bukkit module
*/ */
public enum CloudBukkitCapabilities { public enum CloudBukkitCapabilities implements CloudCapability {
BRIGADIER(CraftBukkitReflection.classExists("com.mojang.brigadier.tree.CommandNode") BRIGADIER(CraftBukkitReflection.classExists("com.mojang.brigadier.tree.CommandNode")
&& CraftBukkitReflection.findOBCClass("command.BukkitCommandWrapper") != null), && CraftBukkitReflection.findOBCClass("command.BukkitCommandWrapper") != null),
@ -56,4 +58,9 @@ public enum CloudBukkitCapabilities {
boolean capable() { boolean capable() {
return this.capable; return this.capable;
} }
@Override
public @NonNull String toString() {
return name();
}
} }

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.paper; package cloud.commandframework.paper;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.CommandTree; import cloud.commandframework.CommandTree;
import cloud.commandframework.brigadier.CloudBrigadierManager; import cloud.commandframework.brigadier.CloudBrigadierManager;
import cloud.commandframework.bukkit.BukkitCommandManager; import cloud.commandframework.bukkit.BukkitCommandManager;
@ -120,7 +121,7 @@ public class PaperCommandManager<C> extends BukkitCommandManager<C> {
public void registerBrigadier() throws BrigadierFailureException { public void registerBrigadier() throws BrigadierFailureException {
this.requireState(RegistrationState.BEFORE_REGISTRATION); this.requireState(RegistrationState.BEFORE_REGISTRATION);
this.checkBrigadierCompatibility(); this.checkBrigadierCompatibility();
if (!this.queryCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) { if (!this.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
super.registerBrigadier(); super.registerBrigadier();
} else { } else {
try { try {
@ -153,11 +154,11 @@ public class PaperCommandManager<C> extends BukkitCommandManager<C> {
* is up to the caller to guarantee that such is the case * is up to the caller to guarantee that such is the case
* *
* @throws IllegalStateException when the server does not support asynchronous completions. * @throws IllegalStateException when the server does not support asynchronous completions.
* @see #queryCapability(CloudBukkitCapabilities) Check if the capability is present * @see #hasCapability(CloudCapability) Check if the capability is present
*/ */
public void registerAsynchronousCompletions() throws IllegalStateException { public void registerAsynchronousCompletions() throws IllegalStateException {
this.requireState(RegistrationState.BEFORE_REGISTRATION); this.requireState(RegistrationState.BEFORE_REGISTRATION);
if (!this.queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { if (!this.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
throw new IllegalStateException("Failed to register asynchronous command completion listener."); throw new IllegalStateException("Failed to register asynchronous command completion listener.");
} }
Bukkit.getServer().getPluginManager().registerEvents( Bukkit.getServer().getPluginManager().registerEvents(

View file

@ -160,13 +160,13 @@ public final class ExamplePlugin extends JavaPlugin {
// //
// Register Brigadier mappings // Register Brigadier mappings
// //
if (this.manager.queryCapability(CloudBukkitCapabilities.BRIGADIER)) { if (this.manager.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
this.manager.registerBrigadier(); this.manager.registerBrigadier();
} }
// //
// Register asynchronous completions // Register asynchronous completions
// //
if (this.manager.queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { if (this.manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
((PaperCommandManager<CommandSender>) this.manager).registerAsynchronousCompletions(); ((PaperCommandManager<CommandSender>) this.manager).registerAsynchronousCompletions();
} }
// //
@ -386,7 +386,7 @@ public final class ExamplePlugin extends JavaPlugin {
); );
// Commands using MC 1.13+ argument types // Commands using MC 1.13+ argument types
if (this.manager.queryCapability(CloudBukkitCapabilities.BRIGADIER)) { if (this.manager.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
new Mc113(this.manager).registerCommands(); new Mc113(this.manager).registerCommands();
} }