Loader
The central registry for commands, interceptors, and editors, with META-INF/services and Main.builder() integration.
The Loader is the central registry for all classes Crest needs to discover. It returns @Command classes, @CrestInterceptor classes, @Editor classes, and @GlobalOptions classes. Crest inspects each class and registers it according to its annotations.
Implementing a Loader
Implement the org.tomitribe.crest.api.Loader interface. The Loader.of() helper method makes it easy to return a fixed set of classes:
public class MyLoader implements Loader {
@Override
public Iterator<Class<?>> iterator() {
return Loader.of(
// Command classes
ConfigCommands.class,
S3Commands.class,
CustomerCommands.class,
// Interceptor classes
AuditInterceptor.class,
// Editor classes
InstantEditor.class
).iterator();
}
}
Crest handles the classification automatically – it checks each class for @Editor, @GlobalOptions, @CrestInterceptor, or @Command annotations and registers it in the appropriate registry.
What Crest Checks
Every class a Loader returns is checked as it is registered, and a mistake fails Main construction rather than surfacing later as a missing command:
- A
@Commandmethod must be public and concrete. A private, protected, package-private, or abstract@Commandmethod anywhere in the class, its superclasses, or its interfaces is rejected with anInvalidCommandUsageExceptionthat lists every offender. - A class-level
@Commandmust have at least one@Commandmethod. - A class returned more than once, by one
Loaderor by several, is registered once.
META-INF/services Registration
Register your Loader implementation using Java’s ServiceLoader mechanism. Create a file at META-INF/services/org.tomitribe.crest.api.Loader containing the fully qualified class name:
com.example.cli.MyLoader
Crest discovers and invokes your Loader at startup via ServiceLoader.
Main.builder()
For programmatic setup without ServiceLoader discovery, use Main.builder(). It provides two methods for registering classes:
command(Class<?>)– adds a@Commandclass. If no classes are added viacommand(), discovery falls back to the classpathLoader.load(Class<?>)– adds any class theLoaderwould return:@Editor,@CrestInterceptor, or@Command.
Main.builder()
.command(ConfigCommands.class)
.command(S3Commands.class)
.load(AuditInterceptor.class)
.load(InstantEditor.class)
.build();
The distinction matters: command() is specific to @Command classes, while load() accepts any class and lets Crest determine its role from annotations. Use command() for command classes and load() for editors and interceptors.