Compile-time annotation processor generates platform-specific command wrappers. No runtime reflection for command execution.
craftcommand/
├── annotations/ @Command, @Default, @Resolve, @Greedy, @Suggest, @Name
├── runtime/ CommandManager, ArgumentResolver, CommandInfo
├── bukkit/
│ ├── annotations/ @Permission
│ ├── runtime/ BukkitCommandManager
│ └── processor/ BukkitCommandProcessor → *$BukkitCommand
├── paper/
│ ├── runtime/ PaperCommandManager
│ └── processor/ PaperCommandProcessor → *$PaperCommand
├── standalone/
│ ├── runtime/ StandaloneCommandManager
│ └── processor/ StandaloneCommandProcessor → *$StandaloneCommand
├── validation/
│ ├── annotations/ @Min, @Max, @ValidateWith
│ └── processor/ MinHandler, MaxHandler, ValidateWithHandler (SPI)
├── processor/ BaseCommandProcessor, model, extension SPI
└── docs/ This documentation
@Command class
→ CommandParser builds CommandModel tree
→ BaseCommandProcessor generates wrapper via template anchors
→ Platform processor fills anchors (type setup, entry methods, helpers)
→ JavaFile written to build/generated/sources
The base processor defines a 6-step template. Platform processors override hooks:
| Step | Method Hook | Purpose |
|---|---|---|
| 1 | configureClass |
Set superclass/interface |
| 2 | addPlatformFields |
Extra fields |
| 3 | addConstructorStatements |
Constructor setup & subcommands |
| 4 | generateEntryMethods |
execute/tabComplete/getCommandNode |
| 5 | generateHelpers |
Subcommand routing & helpers |
| 6 | buildCommandInfo |
BaseCommand.getCommandInfo() |
manager.register(new MyCommand())
→ Class.forName(suffix)
→ MethodHandle instantiation
→ Platform registration (CommandMap / LifecycleEvents / HashMap)
| Interface | Module | Purpose |
|---|---|---|
ParameterAnnotationHandler |
processor SPI | Custom parameter annotations |
MethodAnnotationHandler |
processor SPI | Custom method annotations |
ArgumentResolver |
runtime | Custom type resolution & suggestions |
ArgumentResolverProvider |
runtime | Dynamic resolver lookup |
- Generate, not reflect — wrappers are plain Java
- MethodHandle instantiation — no constructor reflection at runtime
- Java 8 target — no records, no var
- SPI for extensions — third-party annotations and suggestions