Annotation Command Framework (ACF)

repository·master·Indexed 20 days ago

https://github.com/aikar/commands

A platform-agnostic, annotation-driven command handling library for Java applications. ACF reduces boilerplate for command validation, dependency injection, and tab completion. It provides specific artifacts for Bukkit, Paper, Sponge, BungeeCord, and JDA (Discord), and includes specialized utilities for Bukkit environments such as location serialization, smart player matching, and built-in command completions for worlds, players, and chat colors.

Tokens
53.8K
Snippets
168
Records
217
Agent score
70%

What's inside ACF

  1. Overview of Annotation Command Framework (ACF)

    master

    Annotation Command Framework (ACF) is a platform-agnostic Java command framework designed to reduce boilerplate code in command handlers. It uses annotations to abstract complex logic such as:

    • Dependency Injection: Automatically injecting required objects into command methods.
    • Validation: Ensuring command arguments meet specific criteria.
    • Tab Completion: Providing intuitive autocomplete for users.
    • Help Documentation & Syntax Advice: Automatically generating help menus and usage instructions.
    • Stateful Conditions: Handling logic based on the current state of the application or user.

    While originally built for Bukkit, it can be used in any Java-based application via its core library and platform-specific adapters.

  2. Get started with ACF installation and usage

    master

    To integrate ACF into your project, follow these steps:

    1. Choose your build tool: Use the dedicated setup guides for Maven or Gradle.
    2. Add the appropriate artifact: Select the platform-specific artifact (e.g., acf-paper, acf-bukkit) based on your target environment.
    3. Follow the Getting Started guide: Refer to the Using ACF wiki page for step-by-step instructions on adding ACF to your plugin.
    4. Review Examples: For real-world implementation patterns, consult the Examples documentation.
  3. Understand the RegisteredCommand lifecycle and execution

    master

    A RegisteredCommand represents a specific method in your command class that has been mapped to a command or subcommand. When a command is invoked, the following lifecycle occurs:

    1. Permission Check: The framework verifies if the CommandIssuer has the required permissions defined by @CommandPermission on the method or the parent class.
    2. Pre-Command: The preCommand() method is called (can be overridden in subclasses).
    3. Condition Validation: The framework validates any @Conditions defined on the method.
    4. Argument Resolution: The framework attempts to resolve the provided command arguments into the method's parameter types using ContextResolvers.
    5. Method Invocation: The actual command method is invoked with the resolved arguments.
    6. Post-Command: The postCommand() method is called (can be overridden in subclasses).

    If the method returns a CompletionStage<?>, the framework handles the asynchronous result and manages exceptions via handleException.

  4. How ProxyCommandMap handles command registration and dispatching

    master

    In the Bukkit implementation of ACF, ProxyCommandMap acts as a wrapper around the server's native CommandMap. It allows ACF to intercept commands managed by the BukkitCommandManager while delegating all other commands (such as standard Bukkit/Spigot commands) to the original proxied command map.

    When a command is registered, dispatched, or tab-completed, ProxyCommandMap checks if the command belongs to the ACF manager. If it does, it handles the logic internally; otherwise, it passes the request through to the underlying server command map. This ensures that ACF commands can coexist seamlessly with native plugin commands.

  5. How command completions work

    master

    Command completions in ACF follow a specific resolution hierarchy:

    1. Explicit ID: The command parameter specifies a completion ID (e.g., @my_id).
    2. Default Type Completion: If no ID is specified, ACF looks up the parameter's type in the defaultCompletions map.
    3. Enum Support: If the parameter is an Enum, ACF automatically generates completions based on the enum's constant names using a built-in DEFAULT_ENUM_ID (@__defaultenum__).
    4. Pipe and Config Syntax: Completions can be combined or configured using special syntax:
      • id1|id2: Provides completions from either id1 OR id2.
      • id:config: Passes a config string to the completion handler (e.g., @range:0-10).
      • repeat@id: Tells the system to repeat the completion logic for multi-word arguments.

    When a completion handler is invoked, it receives a CommandCompletionContext which provides access to the current input, arguments, and configuration.

  6. Configure command prefixes via CommandConfig

    master
    Command prefixes are determined by the CommandConfig associated with the event. The manager checks the getCommandPrefixes() list from the config. If a message starts with one of these prefixes, it is treated as a command invocation. You can customize this behavior using a CommandConfigProvider to return different configurations based on the specific MessageReceivedEvent.
  7. Configure command parameters with annotations

    master

    When defining command methods in ACF, you can use several annotations on method parameters to control how they are parsed, validated, and displayed in help text.

    Parameter Annotations

    • @Name: Sets the internal name of the parameter (used for localization keys).
    • @Default: Provides a default value if the parameter is not provided in the command input.
    • @Description: Provides a human-readable description for the parameter.
    • @Conditions: Defines requirements/conditions that must be met for this parameter to be valid.
    • @Optional: Marks the parameter as optional. A parameter is also considered optional if it has a @Default value or if it is the last parameter and is of type String[].
    • @Single: When used with a String type, prevents the parameter from consuming all remaining input (rest consumption).
    • @Syntax: Overrides the automatically generated syntax display (e.g., <name> or [name]).
    • @Values: Restricts the parameter to a specific set of allowed values.
    • @Flags: Defines command-line style flags for the parameter using a comma-separated list of key=value or key pairs.
    • @ConsumesRest: Explicitly marks the parameter as one that consumes all remaining input.
    • @CommandPermission: Specifies the permission(s) required to use this specific parameter.

    Syntax Generation Logic

    If no @Syntax is provided, ACF generates syntax based on the parameter's state:

    • Required Input: <DisplayName>
    • Optional Input: [DisplayName]
  8. Define a command group using BaseCommand

    master

    In ACF, a BaseCommand is a command group of related commands. You can use a single BaseCommand to represent a root command, where each actionable command is implemented as a subcommand. You can also organize commands by creating one BaseCommand per command in your application.

    Subcommands can be implemented as methods within the BaseCommand class (using @Subcommand) or as nested classes that extend BaseCommand.

    public abstract class MyCommandGroup extends BaseCommand {
        // Subcommands can be methods or nested classes
    }
  9. Implement custom command conditions

    master

    ACF allows you to define custom validation logic for commands and their parameters using two types of conditions:

    1. Command Conditions (Condition<I>): Used for general command-level validation (e.g., checking if a command is available in a specific scope). These are validated against a ConditionContext.

    2. Parameter Conditions (ParameterCondition<P, CEC, I>): Used to validate specific command arguments. These have access to the CommandExecutionContext and the actual value being passed, allowing for type-specific validation (e.g., checking if a string is a valid username).

    To use them, you must register them with the CommandManager using addCondition.

    // Example of a Parameter Condition implementation
    public class MyParameterCondition implements ParameterCondition<String, MyExecutionContext, MyIssuer> {
        @Override
        public void validateCondition(ConditionContext<MyIssuer> context, MyExecutionContext execContext, String value) throws InvalidCommandArgument {
            if (!value.startsWith("prefix_")) {
                throw new InvalidCommandArgument("Value must start with prefix_");
            }
        }
    }
  10. Initialize and use CommandManager

    master

    The CommandManager is the central entry point for the Annotation Command Framework (ACF). It is an abstract class that you must extend to implement your specific command handling logic. It manages command registration, dependency injection, locales, and exception handling.

    To use it, you must implement the abstract methods such as registerCommand, getCommandContexts, getCommandCompletions, and getCommandIssuer.

    public class MyCommandManager extends CommandManager<MyType, MyIssuer, MyFormat, MyMessageFormatter, MyExecutionContext, MyConditionContext> {
        @Override
        public void registerCommand(BaseCommand command) {
            // Implementation for registering commands
        }
    
        @Override
        public CommandContexts<?> getCommandContexts() {
            return null; // Implementation
        }
    
        @Override
        public CommandCompletions<?> getCommandCompletions() {
            return null; // Implementation
        }
    
        @Override
        public MyIssuer getCommandIssuer(Object issuer) {
            return (MyIssuer) issuer;
        }
    
        @Override
        public boolean isCommandIssuer(Class<?> type) {
            return MyIssuer.class.isAssignableFrom(type);
        }
    
        @Override
        public RootCommand createRootCommand(String cmd) {
            return null; // Implementation
        }
    
        @Override
        public Locales getLocales() {
            return null; // Implementation
        }
    
        @Override
        public CommandExecutionContext<?> createCommandContext(RegisteredCommand command, CommandParameter parameter, CommandIssuer sender, List<String> args, int i, Map<String, Object> passedArgs) {
            return null; // Implementation
        }
    
        @Override
        public CommandCompletionContext createCompletionContext(RegisteredCommand command, CommandIssuer sender, String input, String config, String[] args) {
            return null; // Implementation
        }
    
        @Override
        public void log(LogLevel level, String message, Throwable throwable) {
            // Implementation
        }
    
        @Override
        public Collection<RootCommand> getRegisteredRootCommands() {
            return null; // Implementation
        }
    }
  11. Use flags to constrain numeric arguments

    master

    When using numeric context resolvers (like Integer, Double, etc.), you can use command flags to enforce minimum and maximum values. ACF will automatically validate the parsed number against these constraints and throw an InvalidCommandArgument if the value is out of range.

    Supported flags for numeric types:

    • min: The minimum allowed value.
    • max: The maximum allowed value.
    • suffixes: Enables parsing of human-readable suffixes (e.g., '10k' for 10000).