Skip to content

Services Overview

alexstaeding edited this page Feb 26, 2020 · 2 revisions

Anvil comes with several built-in services. The usage of a specific service can be found in the appropriately named section (under Services). Services are provided by guice. Use the following syntax for injection:

public class CommonFoo {

    @Inject
    KickService kickService;
    ...
}

Some services are generically typed. Classes that inject these must, therefore, provide the type. This can be done by declaring the type parameters in the class signature (if the class is in common) or by directly providing the concrete type (if the class is in a platform module)

In the common module:

public class CommonFoo<TString, TCommandSource> {

    @Inject
    StringResult<TString, TCommandSource> stringResult;
    ...
}

In a platform module:

public class SpongeFoo {

    @Inject
    StringResult<Text, CommandSource> stringResult;
    ...
}

Problems with generic bounds

There may be cases where conversion between objects of a generic type is necessary. In this example, we'll try to convert a TPlayer to a TCommandSource.

public class CommonFoo<
    TUser,
    TPlayer extends TCommandSource,
    TString,
    TCommandSource> {

    @Inject
    StringResult<TString, TCommandSource> stringResult;
    
    @Inject
    UserService<TUser, TPlayer> userService;

    public void bar(UUID userUUID) {

        TPlayer player = userService.getPlayer(userUUID)
            .orElseThrow(IllegalStateException::new);

        stringResult.builder()
            .green().append("Hello!")
            .sendTo(player);
    }
}

The above code will work and is the ideal solution to the problem of converting between objects of generic types. However, there is a more complicated case where this will not work. In the following example, our TPlayer needs to be bounded by both TCommandSource but also TSubject. This does not work in java because while it is possible to write:

TPlayer extends TCommandSource

It is not possible to write:

TPlayer extends TCommandSource & TSubject

Instead, we recommend deferring the type checking of generic types to runtime, by casting between types instead of writing bounds in the class signature. While it is an imperfect solution, we have to suck it up until java allows generic intersection bounds.

This is illustrated in the following example:

public class CommonFoo<
    TPlayer, // extends TCommandSource & TSubject doesnt work
    TUser,
    TSubject,
    TString,
    TCommandSource> {

    @Inject
    StringResult<TString, TCommandSource> stringResult;

    @Inject
    UserService<TUser, TPlayer> userService;

    @Inject
    PermissionService<TSubject> permissionService;

    @Override
    public void foo(UUID userUUID) {

        TPlayer player = userService.getPlayer(userUUID)
            .orElseThrow(IllegalStateException::new);

        // casting to TSubject
        if (permissionService.hasPermission((TSubject) player, "somePerm")) {
            // casting to TCommandSource
            stringResult.builder()
                .green().append("Hello")
                .sendTo((TCommandSource) player);
        } else {
            // casting to TCommandSource
            stringResult.builder()
                .red().append("Error")
                .sendTo((TCommandSource) player);
        }
    }
}