cloud.commandframework.CommandManager Maven / Gradle / Ivy
Show all versions of cloud-core Show documentation
//
// MIT License
//
// Copyright (c) 2022 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.annotations.injection.ParameterInjectorRegistry;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.CommandSuggestionEngine;
import cloud.commandframework.arguments.CommandSyntaxFormatter;
import cloud.commandframework.arguments.DelegatingCommandSuggestionEngineFactory;
import cloud.commandframework.arguments.StandardCommandSyntaxFormatter;
import cloud.commandframework.arguments.StaticArgument;
import cloud.commandframework.arguments.flags.CommandFlag;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserParameter;
import cloud.commandframework.arguments.parser.ParserRegistry;
import cloud.commandframework.arguments.parser.StandardParserRegistry;
import cloud.commandframework.captions.CaptionRegistry;
import cloud.commandframework.captions.CaptionVariableReplacementHandler;
import cloud.commandframework.captions.SimpleCaptionRegistryFactory;
import cloud.commandframework.captions.SimpleCaptionVariableReplacementHandler;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandContextFactory;
import cloud.commandframework.context.StandardCommandContextFactory;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.execution.CommandResult;
import cloud.commandframework.execution.CommandSuggestionProcessor;
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
import cloud.commandframework.execution.postprocessor.AcceptingCommandPostprocessor;
import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext;
import cloud.commandframework.execution.postprocessor.CommandPostprocessor;
import cloud.commandframework.execution.preprocessor.AcceptingCommandPreprocessor;
import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext;
import cloud.commandframework.execution.preprocessor.CommandPreprocessor;
import cloud.commandframework.internal.CommandInputTokenizer;
import cloud.commandframework.internal.CommandRegistrationHandler;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.permission.AndPermission;
import cloud.commandframework.permission.CommandPermission;
import cloud.commandframework.permission.OrPermission;
import cloud.commandframework.permission.Permission;
import cloud.commandframework.permission.PredicatePermission;
import cloud.commandframework.services.ServicePipeline;
import cloud.commandframework.services.State;
import io.leangen.geantyref.TypeToken;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.returnsreceiver.qual.This;
/**
* The manager is responsible for command registration, parsing delegation, etc.
*
* @param Command sender type
*/
@API(status = API.Status.STABLE)
public abstract class CommandManager {
private final Map, BiConsumer> exceptionHandlers = new HashMap<>();
private final EnumSet managerSettings = EnumSet.of(
ManagerSettings.ENFORCE_INTERMEDIARY_PERMISSIONS);
private final CommandContextFactory commandContextFactory = new StandardCommandContextFactory<>();
private final ServicePipeline servicePipeline = ServicePipeline.builder().build();
private final ParserRegistry parserRegistry = new StandardParserRegistry<>();
private final Collection> commands = new LinkedList<>();
private final ParameterInjectorRegistry parameterInjectorRegistry = new ParameterInjectorRegistry<>();
private final CommandExecutionCoordinator commandExecutionCoordinator;
private final CommandTree commandTree;
private final CommandSuggestionEngine commandSuggestionEngine;
private final Set capabilities = new HashSet<>();
private CaptionVariableReplacementHandler captionVariableReplacementHandler = new SimpleCaptionVariableReplacementHandler();
private CommandSyntaxFormatter commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>();
private CommandSuggestionProcessor commandSuggestionProcessor =
new FilteringCommandSuggestionProcessor<>(FilteringCommandSuggestionProcessor.Filter.startsWith(true));
private CommandRegistrationHandler commandRegistrationHandler;
private CaptionRegistry captionRegistry;
private final AtomicReference state = new AtomicReference<>(RegistrationState.BEFORE_REGISTRATION);
/**
* Create a new command manager instance
*
* @param commandExecutionCoordinator Execution coordinator instance. The coordinator is in charge of executing incoming
* commands. Some considerations must be made when picking a suitable execution coordinator
* for your platform. For example, an entirely asynchronous coordinator is not suitable
* when the parsers used in that particular platform are not thread safe. If you have
* commands that perform blocking operations, however, it might not be a good idea to
* use a synchronous execution coordinator. In most cases you will want to pick between
* {@link CommandExecutionCoordinator#simpleCoordinator()} and
* {@link cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator}
* @param commandRegistrationHandler Command registration handler. This will get called every time a new command is
* registered to the command manager. This may be used to forward command registration
* to the platform.
*/
protected CommandManager(
final @NonNull Function<@NonNull CommandTree, @NonNull CommandExecutionCoordinator> commandExecutionCoordinator,
final @NonNull CommandRegistrationHandler commandRegistrationHandler
) {
this.commandTree = CommandTree.newTree(this);
this.commandExecutionCoordinator = commandExecutionCoordinator.apply(this.commandTree);
this.commandRegistrationHandler = commandRegistrationHandler;
this.commandSuggestionEngine = new DelegatingCommandSuggestionEngineFactory<>(this).create();
/* Register service types */
this.servicePipeline.registerServiceType(new TypeToken>() {
}, new AcceptingCommandPreprocessor<>());
this.servicePipeline.registerServiceType(new TypeToken>() {
}, new AcceptingCommandPostprocessor<>());
/* Create the caption registry */
this.captionRegistry = new SimpleCaptionRegistryFactory().create();
/* Register default injectors */
this.parameterInjectorRegistry().registerInjector(
CommandContext.class,
(context, annotationAccessor) -> context
);
}
/**
* Execute a command and get a future that completes with the result. The command may be executed immediately
* or at some point in the future, depending on the {@link CommandExecutionCoordinator} used in the command manager.
*
* The command may also be filtered out by preprocessors (see {@link CommandPreprocessor}) before they are parsed,
* or by the {@link CommandArgument} command arguments during parsing. The execution may also be filtered out
* after parsing by a {@link CommandPostprocessor}. In the case that a command was filtered out at any of the
* execution stages, the future will complete with {@code null}.
*
* The future may also complete exceptionally. The command manager contains some utilities that allow users to
* register exception handlers ({@link #registerExceptionHandler(Class, BiConsumer)} and these can be retrieved using
* {@link #getExceptionHandler(Class)}, or used with {@link #handleException(Object, Class, Exception, BiConsumer)}. It
* is highly recommended that these methods are used in the command manager, as it allows users of the command manager
* to override the exception handling as they wish.
*
* @param commandSender Sender of the command
* @param input Input provided by the sender. Prefixes should be removed before the method is being called, and
* the input here will be passed directly to the command parsing pipeline, after having been tokenized.
* @return future that completes with the command result, or {@code null} if the execution was cancelled at any of the
* processing stages.
*/
public @NonNull CompletableFuture> executeCommand(
final @NonNull C commandSender,
final @NonNull String input
) {
final CommandContext context = this.commandContextFactory.create(
false,
commandSender,
this
);
final LinkedList inputQueue = new CommandInputTokenizer(input).tokenize();
/* Store a copy of the input queue in the context */
context.store("__raw_input__", new LinkedList<>(inputQueue));
try {
if (this.preprocessContext(context, inputQueue) == State.ACCEPTED) {
return this.commandExecutionCoordinator.coordinateExecution(context, inputQueue);
}
} catch (final Exception e) {
final CompletableFuture> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
/* Wasn't allowed to execute the command */
return CompletableFuture.completedFuture(null);
}
/**
* Get command suggestions for the "next" argument that would yield a correctly parsing command input. The command
* suggestions provided by the command argument parsers will be filtered using the {@link CommandSuggestionProcessor}
* before being returned.
*
* @param commandSender Sender of the command
* @param input Input provided by the sender. Prefixes should be removed before the method is being called, and
* the input here will be passed directly to the command parsing pipeline, after having been tokenized.
* @return List of suggestions
*/
public @NonNull List<@NonNull String> suggest(
final @NonNull C commandSender,
final @NonNull String input
) {
final CommandContext context = this.commandContextFactory.create(
true,
commandSender,
this
);
return this.commandSuggestionEngine.getSuggestions(context, input);
}
/**
* Register a new command to the command manager and insert it into the underlying command tree. The command will be
* forwarded to the {@link CommandRegistrationHandler} and will, depending on the platform, be forwarded to the platform.
*
* Different command manager implementations have different requirements for the command registration. It is possible
* that a command manager may only allow registration during certain stages of the application lifetime. Read the platform
* command manager documentation to find out more about your particular platform
*
* @param command Command to register
* @return The command manager instance. This is returned so that these method calls may be chained. This will always
* return {@code this}.
*/
public @NonNull @This CommandManager command(final @NonNull Command command) {
if (!(this.transitionIfPossible(RegistrationState.BEFORE_REGISTRATION, RegistrationState.REGISTERING)
|| this.isCommandRegistrationAllowed())) {
throw new IllegalStateException("Unable to register commands because the manager is no longer in a registration "
+ "state. Your platform may allow unsafe registrations by enabling the appropriate manager setting.");
}
this.commandTree.insertCommand(command);
this.commands.add(command);
return this;
}
/**
* Register a new command
*
* @param command Command to register. {@link Command.Builder#build()}} will be invoked.
* @return The command manager instance
*/
public @NonNull CommandManager command(final Command.@NonNull Builder command) {
return this.command(command.manager(this).build());
}
/**
* Get the caption variable replacement handler.
*
* @return the caption variable replacement handler
* @since 1.7.0
* @see #captionVariableReplacementHandler(CaptionVariableReplacementHandler)
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull CaptionVariableReplacementHandler captionVariableReplacementHandler() {
return this.captionVariableReplacementHandler;
}
/**
* Sets the caption variable replacement handler.
*
* @param captionVariableReplacementHandler new replacement handler
* @since 1.7.0
* @see #captionVariableReplacementHandler()
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public void captionVariableReplacementHandler(
final @NonNull CaptionVariableReplacementHandler captionVariableReplacementHandler
) {
this.captionVariableReplacementHandler = captionVariableReplacementHandler;
}
/**
* Get the command syntax formatter
*
* @return Command syntax formatter
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #commandSyntaxFormatter()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public @NonNull CommandSyntaxFormatter getCommandSyntaxFormatter() {
return this.commandSyntaxFormatter();
}
/**
* Returns the command syntax formatter.
*
* @return the syntax formatter
* @since 1.7.0
* @see #commandSyntaxFormatter(CommandSyntaxFormatter)
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull CommandSyntaxFormatter commandSyntaxFormatter() {
return this.commandSyntaxFormatter;
}
/**
* Set the command syntax formatter
*
* @param commandSyntaxFormatter New formatter
* @deprecated for removal since 1.7.0. Use the non-prefixed setter {@link #commandSyntaxFormatter(CommandSyntaxFormatter)}
* instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public void setCommandSyntaxFormatter(final @NonNull CommandSyntaxFormatter commandSyntaxFormatter) {
this.commandSyntaxFormatter(commandSyntaxFormatter);
}
/**
* Sets the command syntax formatter.
*
* The command syntax formatter is used to format the command syntax hints that are used in help and error messages.
*
* @param commandSyntaxFormatter new formatter
* @since 1.7.0
* @see #commandSyntaxFormatter()
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public void commandSyntaxFormatter(final @NonNull CommandSyntaxFormatter commandSyntaxFormatter) {
this.commandSyntaxFormatter = commandSyntaxFormatter;
}
/**
* Get the command registration handler
*
* @return Command registration handler
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #commandRegistrationHandler()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public @NonNull CommandRegistrationHandler getCommandRegistrationHandler() {
return this.commandRegistrationHandler();
}
/**
* Returns the command registration handler.
*
* The command registration handler is able to intercept newly created/deleted commands, in order to propagate
* these changes to the native command handler of the platform.
*
* In platforms without a native command concept, this is likely to return
* {@link CommandRegistrationHandler#nullCommandRegistrationHandler()}.
*
* @return the command registration handler
* @since 1.7.0
*/
public @NonNull CommandRegistrationHandler commandRegistrationHandler() {
return this.commandRegistrationHandler;
}
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
protected final void setCommandRegistrationHandler(final @NonNull CommandRegistrationHandler commandRegistrationHandler) {
this.commandRegistrationHandler(commandRegistrationHandler);
}
@API(status = API.Status.STABLE, since = "1.7.0")
protected final void commandRegistrationHandler(final @NonNull CommandRegistrationHandler commandRegistrationHandler) {
this.requireState(RegistrationState.BEFORE_REGISTRATION);
this.commandRegistrationHandler = commandRegistrationHandler;
}
/**
* Registers the given {@code capability}.
*
* @param capability the capability
* @since 1.7.0
* @see #hasCapability(CloudCapability)
* @see #capabilities()
*/
@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
* @see #capabilities()
*/
@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
* @see #hasCapability(CloudCapability)
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull Collection<@NonNull CloudCapability> capabilities() {
return Collections.unmodifiableSet(new HashSet<>(this.capabilities));
}
/**
* Check if the command sender has the required permission. If the permission node is
* empty, this should return {@code true}
*
* @param sender Command sender
* @param permission Permission node
* @return {@code true} if the sender has the permission, else {@code false}
*/
@SuppressWarnings("unchecked")
public boolean hasPermission(
final @NonNull C sender,
final @NonNull CommandPermission permission
) {
if (permission instanceof Permission) {
if (permission.toString().isEmpty()) {
return true;
}
return this.hasPermission(sender, permission.toString());
} else if (permission instanceof PredicatePermission) {
return ((PredicatePermission) permission).hasPermission(sender);
} else if (permission instanceof OrPermission) {
for (final CommandPermission innerPermission : permission.getPermissions()) {
if (this.hasPermission(sender, innerPermission)) {
return true;
}
}
return false;
} else if (permission instanceof AndPermission) {
for (final CommandPermission innerPermission : permission.getPermissions()) {
if (!this.hasPermission(sender, innerPermission)) {
return false;
}
}
return true;
}
throw new IllegalArgumentException("Unknown permission type " + permission.getClass());
}
/**
* Get the caption registry
*
* @return Caption registry
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #captionRegistry()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final @NonNull CaptionRegistry getCaptionRegistry() {
return this.captionRegistry();
}
/**
* Returns the caption registry.
*
* @return the caption registry
* @since 1.7.0
* @see #captionRegistry(CaptionRegistry)
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public final @NonNull CaptionRegistry captionRegistry() {
return this.captionRegistry;
}
/**
* Replace the caption registry. Some platforms may inject their own captions into the default registry,
* and so you may need to insert these captions yourself if you do decide to replace the caption registry.
*
* @param captionRegistry New caption registry
* @deprecated for removal since 1.7.0. Use the non-prefixed setter {@link #captionRegistry(CaptionRegistry)} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final void setCaptionRegistry(final @NonNull CaptionRegistry captionRegistry) {
this.captionRegistry(captionRegistry);
}
/**
* Replaces the caption registry.
*
* Some platforms may inject their own captions into the default caption registry,
* and so you may need to insert these captions yourself, if you do decide to replace the caption registry.
*
* @param captionRegistry new caption registry.
* @see #captionRegistry()
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public final void captionRegistry(final @NonNull CaptionRegistry captionRegistry) {
this.captionRegistry = captionRegistry;
}
/**
* Replace the default caption registry
*
* @param captionRegistry Caption registry to use
* @deprecated Use {@link #captionRegistry(CaptionRegistry)} These methods are identical.
*/
@Deprecated
@API(status = API.Status.DEPRECATED)
public final void registerDefaultCaptions(final @NonNull CaptionRegistry captionRegistry) {
this.captionRegistry = captionRegistry;
}
/**
* Check if the command sender has the required permission. If the permission node is
* empty, this should return {@code true}
*
* @param sender Command sender
* @param permission Permission node
* @return {@code true} if the sender has the permission, else {@code false}
*/
public abstract boolean hasPermission(@NonNull C sender, @NonNull String permission);
/**
* Deletes the given {@code rootCommand}.
*
* 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> node = this.commandTree.getNamedNode(rootCommand);
if (node == null) {
// If the node doesn't exist, we don't really need to delete it...
return;
}
// 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, true, this.commands::remove);
// 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) arg)
.map(StaticArgument::getName)
.collect(Collectors.toList());
}
/**
* 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 is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param aliases Command aliases
* @param description Description for the root literal
* @param meta Command meta
* @return Builder instance
* @deprecated for removal since 1.4.0. Use
* {@link #commandBuilder(String, Collection, ArgumentDescription, CommandMeta)} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.4.0")
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull Collection aliases,
final @NonNull Description description,
final @NonNull CommandMeta meta
) {
return this.commandBuilder(name, aliases, (ArgumentDescription) description, meta);
}
/**
* 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 is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param aliases Command aliases
* @param description Description for the root literal
* @param meta Command meta
* @return Builder instance
* @since 1.4.0
*/
@API(status = API.Status.STABLE, since = "1.4.0")
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull Collection aliases,
final @NonNull ArgumentDescription description,
final @NonNull CommandMeta meta
) {
return Command.newBuilder(
name,
meta,
description,
aliases.toArray(new String[0])
).manager(this);
}
/**
* Create a new command builder with an empty description.
*
* This will also register the creating manager in the command
* builder using {@link Command.Builder#manager(CommandManager)}, so that the command
* builder is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param aliases Command aliases
* @param meta Command meta
* @return Builder instance
*/
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull Collection aliases,
final @NonNull CommandMeta meta
) {
return Command.newBuilder(
name,
meta,
ArgumentDescription.empty(),
aliases.toArray(new String[0])
).manager(this);
}
/**
* 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 is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param meta Command meta
* @param description Description for the root literal
* @param aliases Command aliases
* @return Builder instance
* @deprecated for removal since 1.4.0. Use {@link #commandBuilder(String, CommandMeta, ArgumentDescription, String...)}
* instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.4.0")
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull CommandMeta meta,
final @NonNull Description description,
final @NonNull String... aliases
) {
return this.commandBuilder(name, meta, (ArgumentDescription) description, aliases);
}
/**
* 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 is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param meta Command meta
* @param description Description for the root literal
* @param aliases Command aliases
* @return Builder instance
* @since 1.4.0
*/
@API(status = API.Status.STABLE, since = "1.4.0")
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull CommandMeta meta,
final @NonNull ArgumentDescription description,
final @NonNull String... aliases
) {
return Command.newBuilder(
name,
meta,
description,
aliases
).manager(this);
}
/**
* Create a new command builder with an empty description.
*
* This will also register the creating manager in the command
* builder using {@link Command.Builder#manager(CommandManager)}, so that the command
* builder is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param meta Command meta
* @param aliases Command aliases
* @return Builder instance
*/
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull CommandMeta meta,
final @NonNull String... aliases
) {
return Command.newBuilder(
name,
meta,
ArgumentDescription.empty(),
aliases
).manager(this);
}
/**
* Create a new command builder using default command meta created by {@link #createDefaultCommandMeta()}.
*
* This will also register the creating manager in the command
* builder using {@link Command.Builder#manager(CommandManager)}, so that the command
* builder is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param description Description for the root literal
* @param aliases Command aliases
* @return Builder instance
* @throws UnsupportedOperationException If the command manager does not support default command meta creation
* @see #createDefaultCommandMeta() Default command meta creation
* @deprecated for removal since 1.4.0. Use {@link #commandBuilder(String, ArgumentDescription, String...)} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.4.0")
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull Description description,
final @NonNull String... aliases
) {
return this.commandBuilder(name, (ArgumentDescription) description, aliases);
}
/**
* Create a new command builder using default command meta created by {@link #createDefaultCommandMeta()}.
*
* This will also register the creating manager in the command
* builder using {@link Command.Builder#manager(CommandManager)}, so that the command
* builder is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param description Description for the root literal
* @param aliases Command aliases
* @return Builder instance
* @throws UnsupportedOperationException If the command manager does not support default command meta creation
* @see #createDefaultCommandMeta() Default command meta creation
* @since 1.4.0
*/
@API(status = API.Status.STABLE, since = "1.4.0")
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull ArgumentDescription description,
final @NonNull String... aliases
) {
return Command.newBuilder(
name,
this.createDefaultCommandMeta(),
description,
aliases
).manager(this);
}
/**
* Create a new command builder using default command meta created by {@link #createDefaultCommandMeta()}, and
* an empty description.
*
* This will also register the creating manager in the command
* builder using {@link Command.Builder#manager(CommandManager)}, so that the command
* builder is associated with the creating manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}
*
* This method will not register the command in the manager. To do that, {@link #command(Command.Builder)}
* or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed
* {@link Command command} instance
*
* @param name Command name
* @param aliases Command aliases
* @return Builder instance
* @throws UnsupportedOperationException If the command manager does not support default command meta creation
* @see #createDefaultCommandMeta() Default command meta creation
*/
public Command.@NonNull Builder commandBuilder(
final @NonNull String name,
final @NonNull String... aliases
) {
return Command.newBuilder(
name,
this.createDefaultCommandMeta(),
ArgumentDescription.empty(),
aliases
).manager(this);
}
/**
* Create a new command argument builder.
*
* This will also invoke {@link CommandArgument.Builder#manager(CommandManager)}
* so that the argument is associated with the calling command manager. This allows for parser inference based on
* the type, with the help of the {@link ParserRegistry parser registry}.
*
* @param type Argument type
* @param name Argument name
* @param Generic argument name
* @return Argument builder
*/
public CommandArgument.@NonNull Builder argumentBuilder(
final @NonNull Class type,
final @NonNull String name
) {
return CommandArgument.ofType(type, name).manager(this);
}
/**
* Create a new command flag builder
*
* @param name Flag name
* @return Flag builder
*/
public CommandFlag.@NonNull Builder flagBuilder(final @NonNull String name) {
return CommandFlag.builder(name);
}
/**
* Get the internal command tree. This should not be accessed unless you know what you
* are doing
*
* @return Command tree
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #commandTree()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public @NonNull CommandTree getCommandTree() {
return this.commandTree();
}
/**
* Returns the internal command tree.
*
* Be careful when accessing the command tree. Do not interact with it, unless you
* absolutely know what you're doing.
*
* @return the command tree
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull CommandTree commandTree() {
return this.commandTree;
}
/**
* Construct a default command meta instance
*
* @return Default command meta
* @throws UnsupportedOperationException If the command manager does not support this operation
*/
public abstract @NonNull CommandMeta createDefaultCommandMeta();
/**
* Register a new command preprocessor. The order they are registered in is respected, and they
* are called in LIFO order
*
* @param processor Processor to register
* @see #preprocessContext(CommandContext, LinkedList) Preprocess a context
*/
public void registerCommandPreProcessor(final @NonNull CommandPreprocessor processor) {
this.servicePipeline.registerServiceImplementation(
new TypeToken>() {
},
processor,
Collections.emptyList()
);
}
/**
* Register a new command postprocessor. The order they are registered in is respected, and they
* are called in LIFO order
*
* @param processor Processor to register
* @see #preprocessContext(CommandContext, LinkedList) Preprocess a context
*/
public void registerCommandPostProcessor(final @NonNull CommandPostprocessor processor) {
this.servicePipeline.registerServiceImplementation(new TypeToken>() {
}, processor,
Collections.emptyList()
);
}
/**
* Preprocess a command context instance
*
* @param context Command context
* @param inputQueue Command input as supplied by sender
* @return {@link State#ACCEPTED} if the command should be parsed and executed, else {@link State#REJECTED}
* @see #registerCommandPreProcessor(CommandPreprocessor) Register a command preprocessor
*/
public State preprocessContext(
final @NonNull CommandContext context,
final @NonNull LinkedList<@NonNull String> inputQueue
) {
this.servicePipeline.pump(new CommandPreprocessingContext<>(context, inputQueue))
.through(new TypeToken>() {
})
.getResult();
return context.getOptional(AcceptingCommandPreprocessor.PROCESSED_INDICATOR_KEY).orElse("").isEmpty()
? State.REJECTED
: State.ACCEPTED;
}
/**
* Postprocess a command context instance
*
* @param context Command context
* @param command Command instance
* @return {@link State#ACCEPTED} if the command should be parsed and executed, else {@link State#REJECTED}
* @see #registerCommandPostProcessor(CommandPostprocessor) Register a command postprocessor
*/
public State postprocessContext(
final @NonNull CommandContext context,
final @NonNull Command command
) {
this.servicePipeline.pump(new CommandPostprocessingContext<>(context, command))
.through(new TypeToken>() {
})
.getResult();
return context.getOptional(AcceptingCommandPostprocessor.PROCESSED_INDICATOR_KEY).orElse("").isEmpty()
? State.REJECTED
: State.ACCEPTED;
}
/**
* Get the command suggestions processor instance currently used in this command manager
*
* @return Command suggestions processor
* @see #commandSuggestionProcessor(CommandSuggestionProcessor) Setting the suggestion processor
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #commandSuggestionProcessor()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public @NonNull CommandSuggestionProcessor getCommandSuggestionProcessor() {
return this.commandSuggestionProcessor();
}
/**
* Returns the command suggestion processor used in this command manager.
*
* @return the command suggestion processor
* @since 1.7.0
* @see #commandSuggestionProcessor(CommandSuggestionProcessor)
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull CommandSuggestionProcessor commandSuggestionProcessor() {
return this.commandSuggestionProcessor;
}
/**
* Set the command suggestions processor for this command manager. This will be called every
* time {@link #suggest(Object, String)} is called, to process the list of suggestions
* before it's returned to the caller
*
* @param commandSuggestionProcessor New command suggestions processor
* @deprecated for removal since 1.7.0. Use the non-prefixed setter
* {@link #commandSuggestionProcessor(CommandSuggestionProcessor)} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public void setCommandSuggestionProcessor(final @NonNull CommandSuggestionProcessor commandSuggestionProcessor) {
this.commandSuggestionProcessor(commandSuggestionProcessor);
}
/**
* Sets the command suggestion processor.
*
* This will be called ever time {@link #suggest(Object, String)} is called, in order to process the list
* of suggestions before it's returned to the caller.
*
* @param commandSuggestionProcessor the new command sugesstion processor
* @since 1.7.0
* @see #commandSuggestionProcessor()
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public void commandSuggestionProcessor(final @NonNull CommandSuggestionProcessor commandSuggestionProcessor) {
this.commandSuggestionProcessor = commandSuggestionProcessor;
}
/**
* Get the parser registry instance. The parser registry contains default
* mappings to {@link ArgumentParser}
* and allows for the registration of custom mappings. The parser registry also
* contains mappings of annotations to {@link ParserParameter}
* which allows for annotations to be used to customize parser settings.
*
* When creating a new parser type, it is recommended to register it in the parser
* registry. In particular, default parser types (shipped with cloud implementations)
* should be registered in the constructor of the platform {@link CommandManager}
*
* @return Parser registry instance
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #parserRegistry()} instead.
*/
@Deprecated
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull ParserRegistry getParserRegistry() {
return this.parserRegistry();
}
/**
* Returns the parser registry intance.
*
* The parser registry contains default mappings to {@link ArgumentParser argument parsers} and
* allows for the registryion of custom mappings. The parser registry also contains mappings between
* annotations and {@link ParserParameter}, which allows for the customization of parser settings by
* using annotations.
*
* When creating a new parser type, it is highly recommended to register it in the parser registry.
* In particular, default parser types (shipped with cloud implementations) should be registered in the
* constructor of the platform {@link CommandManager}.
*
* @return the parser registry instance
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull ParserRegistry parserRegistry() {
return this.parserRegistry;
}
/**
* Get the parameter injector registry instance
*
* @return Parameter injector registry
* @since 1.3.0
*/
public final @NonNull ParameterInjectorRegistry parameterInjectorRegistry() {
return this.parameterInjectorRegistry;
}
/**
* Get the exception handler for an exception type, if one has been registered
*
* @param clazz Exception class
* @param Exception type
* @return Exception handler, or {@code null}
* @see #registerCommandPreProcessor(CommandPreprocessor) Registering an exception handler
*/
@SuppressWarnings("unchecked")
public final @Nullable BiConsumer<@NonNull C, @NonNull E>
getExceptionHandler(final @NonNull Class clazz) {
final BiConsumer consumer = this.exceptionHandlers.get(clazz);
if (consumer == null) {
return null;
}
return (BiConsumer) consumer;
}
/**
* Register an exception handler for an exception type. This will then be used
* when {@link #handleException(Object, Class, Exception, BiConsumer)} is called
* for the particular exception type
*
* @param clazz Exception class
* @param handler Exception handler
* @param Exception type
*/
public final void registerExceptionHandler(
final @NonNull Class clazz,
final @NonNull BiConsumer<@NonNull C, @NonNull E> handler
) {
this.exceptionHandlers.put(clazz, handler);
}
/**
* Handle an exception using the registered exception handler for the exception type, or using the
* provided default handler if no exception handler has been registered for the exception type
*
* @param sender Executing command sender
* @param clazz Exception class
* @param exception Exception instance
* @param defaultHandler Default exception handler. Will be called if there is no exception
* handler stored for the exception type
* @param Exception type
*/
public final void handleException(
final @NonNull C sender,
final @NonNull Class clazz,
final @NonNull E exception,
final @NonNull BiConsumer defaultHandler
) {
Optional.ofNullable(this.getExceptionHandler(clazz)).orElse(defaultHandler).accept(sender, exception);
}
/**
* Get a collection containing all registered commands.
*
* @return Unmodifiable view of all registered commands
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #commands()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final @NonNull Collection<@NonNull Command> getCommands() {
return this.commands();
}
/**
* Returns an unmodifiable view of all registered commands.
*
* @return unmodifiable view of all registered commands
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public final @NonNull Collection<@NonNull Command> commands() {
return Collections.unmodifiableCollection(this.commands);
}
/**
* Get a command help handler instance. This can be used to assist in the production
* of command help menus, etc. This command help handler instance will display
* all commands registered in this command manager.
*
* @return Command help handler. A new instance will be created
* each time this method is called.
* @deprecated for removal since 1.7.0. Use {@link #createCommandHelpHandler()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final @NonNull CommandHelpHandler getCommandHelpHandler() {
return this.createCommandHelpHandler();
}
/**
* Creates a new command help handler instance.
*
* The command helper handler can be used to assist in the production of commad help menus, etc.
*
* This command help handler instance will display all commands registered in this command manager.
*
* @return a new command helper handler instance
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public final @NonNull CommandHelpHandler createCommandHelpHandler() {
return new CommandHelpHandler<>(this, cmd -> true);
}
/**
* Get a command help handler instance. This can be used to assist in the production
* of command help menus, etc. A predicate can be specified to filter what commands
* registered in this command manager are visible in the help menu.
*
* @param commandPredicate Predicate that filters what commands are displayed in
* the help menu.
* @return Command help handler. A new instance will be created
* each time this method is called.
* @deprecated for removal since 1.7.0. Use {@link #createCommandHelpHandler(Predicate)} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final @NonNull CommandHelpHandler getCommandHelpHandler(
final @NonNull Predicate> commandPredicate
) {
return this.createCommandHelpHandler(commandPredicate);
}
/**
* Creates a new command help handler instance.
*
* The command helper handler can be used to assist in the production of commad help menus, etc.
*
* A predicate can be specified to filter what commands
* registered in this command manager are visible in the help menu.
*
* @param commandPredicate predicate that filters what commands are displayed in
* the help menu.
* @return a new command helper handler instance
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public final @NonNull CommandHelpHandler createCommandHelpHandler(
final @NonNull Predicate> commandPredicate
) {
return new CommandHelpHandler<>(this, commandPredicate);
}
/**
* Get a command manager setting
*
* @param setting Setting
* @return {@code true} if the setting is activated or {@code false} if it's not
* @see #setSetting(ManagerSettings, boolean) Update a manager setting
*/
public boolean getSetting(final @NonNull ManagerSettings setting) {
return this.managerSettings.contains(setting);
}
/**
* Update a command manager setting
*
* @param setting Setting to update
* @param value Value. In most cases {@code true} will enable a feature, whereas {@code false} will disable it.
* The value passed to the method will be reflected in {@link #getSetting(ManagerSettings)}
* @see #getSetting(ManagerSettings) Get a manager setting
*/
@SuppressWarnings("unused")
public void setSetting(
final @NonNull ManagerSettings setting,
final boolean value
) {
if (value) {
this.managerSettings.add(setting);
} else {
this.managerSettings.remove(setting);
}
}
/**
* Returns the command execution coordinator used in this manager
*
* @return Command execution coordinator
* @since 1.6.0
*/
@API(status = API.Status.STABLE, since = "1.6.0")
public @NonNull CommandExecutionCoordinator commandExecutionCoordinator() {
return this.commandExecutionCoordinator;
}
/**
* Transition from the {@code in} state to the {@code out} state, if the manager is not already in that state.
*
* @param in The starting state
* @param out The ending state
* @throws IllegalStateException if the manager is in any state but {@code in} or {@code out}
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
protected final void transitionOrThrow(final @NonNull RegistrationState in, final @NonNull RegistrationState out) {
if (!this.transitionIfPossible(in, out)) {
throw new IllegalStateException("Command manager was in state " + this.state.get() + ", while we were expecting a state "
+ "of " + in + " or " + out + "!");
}
}
/**
* Transition from the {@code in} state to the {@code out} state, if the manager is not already in that state.
*
* @param in The starting state
* @param out The ending state
* @return {@code true} if the state transition was successful, or the manager was already in the desired state
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
protected final boolean transitionIfPossible(final @NonNull RegistrationState in, final @NonNull RegistrationState out) {
return this.state.compareAndSet(in, out) || this.state.get() == out;
}
/**
* Require that the commands manager is in a certain state.
*
* @param expected The required state
* @throws IllegalStateException if the manager is not in the expected state
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
protected final void requireState(final @NonNull RegistrationState expected) {
if (this.state.get() != expected) {
throw new IllegalStateException("This operation required the commands manager to be in state " + expected + ", but it "
+ "was in " + this.state.get() + " instead!");
}
}
/**
* Transition the command manager from either {@link RegistrationState#BEFORE_REGISTRATION} or
* {@link RegistrationState#REGISTERING} to {@link RegistrationState#AFTER_REGISTRATION}.
*
* @throws IllegalStateException if the manager is not in the expected state
* @since 1.4.0
*/
@API(status = API.Status.STABLE, since = "1.4.0")
protected final void lockRegistration() {
if (this.registrationState() == RegistrationState.BEFORE_REGISTRATION) {
this.transitionOrThrow(RegistrationState.BEFORE_REGISTRATION, RegistrationState.AFTER_REGISTRATION);
return;
}
this.transitionOrThrow(RegistrationState.REGISTERING, RegistrationState.AFTER_REGISTRATION);
}
/**
* Get the active registration state for this manager.
*
* If this state is {@link RegistrationState#AFTER_REGISTRATION}, commands can no longer be registered
*
* @return The current state
* @since 1.2.0
* @deprecated for removal since 1.7.0. Use the non-prefixed getter {@link #registrationState()} instead.
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.7.0")
public final @NonNull RegistrationState getRegistrationState() {
return this.registrationState();
}
/**
* Returns the active registration state for this manager.
*
* If the state is {@link RegistrationState#AFTER_REGISTRATION}, commands can no longer be registered.
*
* @return the current state
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public final @NonNull RegistrationState registrationState() {
return this.state.get();
}
/**
* Check if command registration is allowed.
*
* On platforms where unsafe registration is possible, this can be overridden by enabling the
* {@link ManagerSettings#ALLOW_UNSAFE_REGISTRATION} setting.
*
* @return {@code true} if the registration is allowed, else {@code false}
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
public boolean isCommandRegistrationAllowed() {
return this.getSetting(ManagerSettings.ALLOW_UNSAFE_REGISTRATION) || this.state.get() != RegistrationState.AFTER_REGISTRATION;
}
/**
* Configurable command related settings
*
* @see CommandManager#setSetting(ManagerSettings, boolean) Set a manager setting
* @see CommandManager#getSetting(ManagerSettings) Get a manager setting
*/
@API(status = API.Status.STABLE)
public enum ManagerSettings {
/**
* Do not create a compound permission and do not look greedily
* for child permission values, if a preceding command in the tree path
* has a command handler attached
*/
ENFORCE_INTERMEDIARY_PERMISSIONS,
/**
* Force sending of an empty suggestion (i.e. a singleton list containing an empty string)
* when no suggestions are present
*/
FORCE_SUGGESTION,
/**
* Allow registering commands even when doing so has the potential to produce inconsistent results.
*
* For example, if a platform serializes the command tree and sends it to clients,
* this will allow modifying the command tree after it has been sent, as long as these modifications are not blocked by
* the underlying platform
*
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
ALLOW_UNSAFE_REGISTRATION,
/**
* Enables overriding of existing commands on supported platforms.
*
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
OVERRIDE_EXISTING_COMMANDS,
/**
* Allows parsing flags at any position after the last literal by appending flag argument nodes between each command node.
* It can have some conflicts when integrating with other command systems like Brigadier,
* and code inspecting the command tree may need to be adjusted.
*
* @since 1.8.0
*/
@API(status = API.Status.EXPERIMENTAL, since = "1.8.0")
LIBERAL_FLAG_PARSING
}
/**
* The point in the registration lifecycle for this commands manager
*
* @since 1.2.0
*/
@API(status = API.Status.STABLE, since = "1.2.0")
public enum RegistrationState {
/**
* The point when no commands have been registered yet.
*
*
At this point, all configuration options can be changed.
*/
BEFORE_REGISTRATION,
/**
* When at least one command has been registered, and more commands have been registered.
*
* In this state, some options that affect how commands are registered with the platform are frozen. Some platforms
* will remain in this state for their lifetime.
*/
REGISTERING,
/**
* Once registration has been completed.
*
* At this point, the command manager is effectively immutable. On platforms where command registration happens via
* callback, this state is achieved the first time the manager's callback is executed for registration.
*/
AFTER_REGISTRATION
}
}