Skip to content

Guice Setup

alexstaeding edited this page Mar 7, 2020 · 1 revision

Guice is the glue that binds the modules together. If you haven't used it before, read https://github.com/google/guice/wiki/GettingStarted for an overview. As with Project Structure, this page is not meant to be a baby-steps "tutorial" but rather a reference.

Firstly, create a class CommonModule in your common Gradle module. This Guice module will define all the API to common bindings. It should look similar to this:

Please note that if you do not pass getClass() to the TypeToken constructor, you will get an error.

package org.anvilpowered.simpletickets.common.module;

... imports not included ...

// TypeTokens are "unstable"
@SuppressWarnings("UnstableApiUsage")
public class CommonModule<
    TUser,
    TPlayer,
    TString,
    TCommandSource> extends AbstractModule {

    @Override
    protected void configure() {

        // Helper methods for binding stuff
        // not required, but simplifies code a lot
        BindingExtensions be = Anvil.getBindingExtensions(binder());

        // bind MongoDB repository
        be.bind(
            new TypeToken<TicketRepository<?, ?>>(getClass()) {
            },
            new TypeToken<TicketRepository<ObjectId, Datastore>>(getClass()) {
            },
            new TypeToken<CommonMongoTicketRepository>(getClass()) {
            },
            MongoDBComponent.class
        );

        // bind Xodus Repository
        be.bind(
            new TypeToken<TicketRepository<?, ?>>(getClass()) {
            },
            new TypeToken<TicketRepository<EntityId, PersistentEntityStore>>(getClass()) {
            },
            new TypeToken<CommonXodusTicketRepository>(getClass()) {
            },
            XodusComponent.class
        );

        // bind Manager
        be.bind(
            new TypeToken<TicketManager<TString, TCommandSource>>(getClass()) {
            },
            new TypeToken<CommonTicketManager<TUser, TPlayer, TString, TCommandSource>>(getClass()) {
            }
        );

        be.bind(
            new TypeToken<BasicPluginInfo>(getClass()) {
            },
            new TypeToken<SimpleTicketsPluginInfo<TString, TCommandSource>>(getClass()) {
            }
        );

        be.bind(
            new TypeToken<PluginInfo<TString>>(getClass()) {
            },
            new TypeToken<SimpleTicketsPluginInfo<TString, TCommandSource>>(getClass()) {
            }
        );

        // tell Anvil we want to use MongoDB and Xodus
        be.withContexts(MongoDBComponent.class, XodusComponent.class);

        // bind our configuration service
        bind(ConfigurationService.class).to(CommonConfigurationService.class);

        // bind our registry
        bind(Registry.class).to(CommonRegistry.class);
    }
}

In most cases, this is not enough. There are some classes that need to be bound at the platform level rather than the common level. To do this, we extend the common Guice module in our platform module. We then override the configure() method, keeping in mind that we still need to call super.configure() because we still want to create the bindings for the common module.

This class is also important because we must define every type parameter from the common module with a concrete type in the sponge Guice module. If we don't do this, we won't be able to create any typed bindings.

package org.anvilpowered.simpletickets.sponge.module;

... imports not included ...

public class SpongeModule extends CommonModule<User, Player, Text, CommandSource> {

    @Override
    protected void configure() {

        // create bindings from CommonModule
        super.configure();

        // bind our root command node
        bind(new TypeLiteral<CommandNode<CommandSpec>>() {
        }).to(SpongeTicketCommandNode.class);

        // Sponge only injects an annotated ConfigurationLoader,
        // so this in-between class is required
        bind(CommonConfigurationService.class).to(SpongeConfigurationService.class);
    }
}

If you're wondering why we're binding a SpongeConfigurationService, here's the explanation. The constructor signature for CommonConfigurationService looks like this:

@Inject
public CommonConfigurationService(ConfigurationLoader<CommentedConfigurationNode> configLoader)

In order to build this service, there must be a binding for ConfigurationLoader<CommondedConfigurationNode> in our injector. The problem is that in Sponge, you must provide an annotation to get this binding. The entire SpongeConfigurationService therefore looks like this:

public class SpongeConfigurationService extends CommonConfigurationService {

    @Inject
    public SpongeConfigurationService(
        @DefaultConfig(sharedRoot = false)
            ConfigurationLoader<CommentedConfigurationNode> configLoader) {
        super(configLoader);
    }
}

Sponge will only provide the ConfigurationLoader<CommentedConfigurationNode> for us if we have an annotation like @DefaultConfig(sharedRoot = false). Visit https://docs.spongepowered.org/stable/en/plugin/injection.html for more information.