-
Notifications
You must be signed in to change notification settings - Fork 0
Guice Guide
In this guide, we're going to take a quick look at how to use mela-command in combination with Google Guice.
To understand this guide, you need to have an overview of mela-command and you need to know the basics of Guice. To understand the bind framework part, you should know the bind framework guide.
In a normal mela-command application, the root group is built using ImmutableGroupBuilder
, like so:
CommandGroup root = ImmutableGroup.builder()
.add(new FooCommand())
.group("bar")
.add(new BarCommand())
.parent()
.build(); // or compile(...) if you use anything other than the core framework
With Guice, all of the setup that's usually done in your main code is moved to modules. You can setup your command structure in a Module
using a CommandBinder
.
public class CommandModule implements Module {
@Override
public void configure(Binder binder) {
CommandBinder commands = CommandBinder.create(binder);
commands.root()
.add(FooCommand.class)
.group("bar")
.add(BarCommand.class)
}
}
The CommandBinder
makes use of a Multibinder
that contributes to a Set<UncompiledGroup>
. This means that any amount of modules can create their own CommandBinder
and contribute to that set of uncompiled root groups.
Those uncompiled groups on their own aren't very useful yet. But as soon as you try to inject or obtain an instance of CommandGroup
from Guice, those uncompiled groups will be compiled using the bound CommandCompiler
and merged. It is recommended to keep that default implementation.
The CommandBinder
also makes a multibinding for the command objects of the type @Commands Set<Object>
. As a consequence, this means that you don't need to instantiate your classes in the module but may let Guice do it for you.
To change the CommandCompiler
(by default IdentityCompiler
) or CommandDispatcher
(by default DefaultDispatcher
) implementations, you just need to bind them to something else:
binder.bind(CommandCompiler.class).to(MethodHandleCompiler.class); // if you're using the bind framework
binder.bind(CommandDispatcher.class).to(CustomDispatcher.class);
This means that the following things are bound and can be injected by users:
-
@Commands Set<Object>
- the registered command objects -
Set<UncompiledGroup>
- the set of all bound root groups in an uncompiled state -
CommandGroup
- the root group -
CommandCompiler
- the command compiler -
CommandDispatcher
- the bound dispatcher
The bind framework provides an own kind of Guice module for CommandBindings
- CommandBindingsModule
.
This module type also uses multibindings so that multiple modules may contribute to the overall CommandBindings
. The methods are similar to the ones of CommandBindingsBuilder
, although they allow more freedom regarding instantiation, because they directly interface with Guice.
The Guice equivalent to the ProvidedBindings
is ProvidedBindingsModule
, which, in contrast to ProvidedBindings
, already includes a mapper binding for CommandInput
.
This is what the file explorer setup could look like using mela-command's Guice components:
public class FileExplorerModule extends AbstractModule {
@Override
protected void configure() {
bind(CommandCompiler.class).to(MethodHandleCompiler.class);
CommandBinder commands = CommandBinder.create(binder());
commands.root()
.add(ApplicationCommands.class)
.add(HelpCommand.class)
.add(ListCommand.class)
.add(CdCommand.class)
.group("file", "f")
.add(FileCommands.class)
}
}
public class FileExplorerBindingsModule extends CommandBindingsModule {
@Override
protected void configure() {
bindMapper(Path.class).to(PathMapper.class);
bindMappingInterceptor(Existent.class)
.toInstance(new PathValidator<>(Files::exists, "Path must exist"));
bindMappingInterceptor(NonExistent.class)
.toInstance(new PathValidator<>(Files::notExists, "Path must not exist"));
bindMappingInterceptor(File.class)
.toInstance(new PathValidator<>(Files::isRegularFile, "Path must point to a file"));
bindMappingInterceptor(Directory.class)
.toInstance(new PathValidator<>(Files::isDirectory, "Path must point to a directory"));
bindHandler(AccessDeniedException.class)
.toInstance(new PrintExceptionHandler<>("Access denied"));
bindHandler(MappingProcessException.class)
.toInstance(new PrintExceptionHandler<>("Invalid argument"));
bindHandler(ArgumentException.class)
.toInstance(new PrintExceptionHandler<>("Wrong number of arguments provided"));
bindHandler(IOException.class)
.toInstance(new PrintExceptionHandler<>("An I/O problem occurred"));
}
}
Injector injector = Guice.createInjector(
new FileExplorerModule(), new ProvidedBindingsModule(), new FileExplorerBindingsModule());
CommandDispatcher dispatcher = injector.getInstance(CommandDispatcher.class);
- Command Implementation
- Command Groups
- Dispatching Commands
- Command Contexts
- Argument Parsing
- Compile API
- Overview
- Mapper Bindings
- Mapping Interceptor Bindings