feat(core): support root command deletion & standardize capabilities (#369)
This commit is contained in:
parent
08a97b2c4f
commit
28ff5d3003
14 changed files with 416 additions and 16 deletions
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue