Skip to content
Jonathan edited this page Oct 25, 2020 · 6 revisions

Creating commands using KWCommands is very simple, you could either use Java/Kotlin API or JSON files.

Java/Kotlin API

Simple command

Command command = Command.builder()
        .name("play")
        .description(Text.of("Request a song to be played"))
        .handler((commandContainer, informationProviders, resultHandler) -> {
                System.out.println("... Song requested ...");
                return Unit.INSTANCE;
         })
        .build();

Let register and test it:

CommandProcessor processor = Processors.createCommonProcessor();
Command command = Command.builder()
        .name("play")
        .description(Text.of("Request a song to be played"))
        .handler((commandContainer, informationProviders, resultHandler) -> {
                System.out.println("... Song requested ...");
                return Unit.INSTANCE;
         })
        .build();

processor.getCommandManager().registerCommand(command, this);
processor.parseAndDispatch("play", null, InformationProvidersVoid.INSTANCE);

Prints: ... Song requested ...

You could also provide aliases for your command through alias function, example:

Command command = Command.builder()
        .name("play")
        .addAlias("request")
        .description(Text.of("Request a song to be played"))
        .handler((commandContainer, informationProviders, resultHandler) -> {
            System.out.println("... Song requested ...");
            return Unit.INSTANCE;
        })
        .build();

Arguments

Arguments are values provided to command to change its behavior or to provide additional and/or important information for command handling. There are various bultin types of argument parsers, such as: Enum, String, Int and Double, also there are various argument types that could be configured, such as: SingleArgumentType, ListArgumentType, MapArgumentType, and even your own ArgumentType, also you could even define the argument configuration provider, which there is only one builtin provider: StaticListArguments.

Static arguments

Static arguments is a type of argument configuration provider which provides a set of arguments which will never change, you will understand this behavior later, for now, lets declare a Music argument which provides the song to request.

public class KwDocs {

    public static void main(String[] args) {
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(CommonArgTypesKt.enumArgumentType(Music.class))
                        .build()
                )
                .build()
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    Music songArg = commandContainer.<Music>getArgumentValueOptional("song").get();
                    System.out.printf("... Song requested: %s ...%n", songArg.name());
                    return Unit.INSTANCE;
                })
                .build();

        processor.getCommandManager().registerCommand(command, new KwDocs());
        processor.parseAndDispatch("play beautiful", null, InformationProvidersVoid.INSTANCE);

    }
}

public enum Music {
    THIS,
    IS,
    A,
    BEAUTIFUL,
    MUSIC
}

Output: ... Song requested: BEAUTIFUL ...

In this case, we used an Enum argument, which is an argument that only accepts the values of enum as an input, if a value that is not a valid enum is provided, the command parsing will fail. We will explain how to handle errors later.

Information

Information is a piece of data provided to command handler by the command dispatcher, it helps you to provide different data, such as logged user, to the handler which will treat the command execution. In order to provide this data, you will need an Information identification, which is used to identify the information via Reflection API, JSON and Java/Kotlin Command API, this is very useful to make each information unique too.

Creating your own Information identification:

private static final Information.Id<User> USER_INFORMATION_ID = new Information.Id<>(TypeInfo.of(User.class), new String[]{"user"});

User class:

public class User {
    private final String name;
    private final String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

The string array provided to Information.Id constructor is used to retrieve this information, this value must be unique for each information, you could also use project domain if you like to, there are no constraint around this information, you are free to declare this in the way you want to.

After declaring Information Identification, you must provide it to command handler through InformationProviders:

InformationProviders informationProviders = new InformationProvidersImpl();
informationProviders.registerInformation(new Information<>(USER_INFORMATION_ID, user, "Logged in user."));

And in dispatch invocation, pass the informationProviders you have created, and in command handler, access information through informationProviders provided in handler parameters, see the full version of a command using information:

public class KwDocs {

    private static final Information.Id<User> USER_INFORMATION_ID = new Information.Id<>(TypeInfo.of(User.class), new String[]{"user"});

    public static void main(String[] args) {
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(CommonArgTypesKt.enumArgumentType(Music.class))
                        .build()
                )
                .build()
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    User user = Objects.requireNonNull(informationProviders.find(USER_INFORMATION_ID)).getValue();
                    Music songArg = commandContainer.<Music>getArgumentValueOptional("song").get();
                    System.out.printf("... User '%s' requested the song %s ...%n", user.getName(), songArg.name());
                    return Unit.INSTANCE;
                })
                .build();

        User user = new User("test", "test@test.test");

        InformationProviders informationProviders = new InformationProvidersImpl();
        informationProviders.registerInformation(new Information<>(USER_INFORMATION_ID, user, "Logged in user."));

        processor.getCommandManager().registerCommand(command, new KwDocs());

        processor.parseAndDispatch("play beautiful", null, informationProviders);

    }

}

Note that it is a bit unsafe, what if we don't provide the information? This will raise a NullPointerException, which is not desirable, to avoid this, register the information as required, this will enforce that the command handler will never be executed if the information is not provided, and return the correct error for the execution, which could be treated later. See the code below with the information registered as required:

public class KwDocs {

    private static final Information.Id<User> USER_INFORMATION_ID = new Information.Id<>(TypeInfo.of(User.class), new String[]{"user"});

    public static void main(String[] args) {
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(CommonArgTypesKt.enumArgumentType(Music.class))
                        .build()
                )
                .build()
                .addRequiredInfo(new RequiredInformation(USER_INFORMATION_ID))
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    User user = Objects.requireNonNull(informationProviders.find(USER_INFORMATION_ID)).getValue();
                    Music songArg = commandContainer.<Music>getArgumentValueOptional("song").get();
                    System.out.printf("... User '%s' requested the song %s ...%n", user.getName(), songArg.name());
                    return Unit.INSTANCE;
                })
                .build();

        User user = new User("test", "test@test.test");

        InformationProviders informationProviders = new InformationProvidersImpl();
        informationProviders.registerInformation(new Information<>(USER_INFORMATION_ID, user, "Logged in user."));

        processor.getCommandManager().registerCommand(command, new KwDocs());

        processor.parseAndDispatch("play beautiful", null, informationProviders);

    }

}

Provided information

Now that you have explored Information API a bit, it is time to go a little deeper, there is another type of Information, those which must be provided dynamically, instead of statically. The core difference, is that provided information are evaluated only when requested, and provides the value of the moment, you could use for example, to get the Instant of the time the command started to be handled, instead of the Instant of the time that command was dispatched, also they are not Information.Id specific, this means that they could provide information with different identifications, lets change our example a little bit:

public class KwDocs {

    private static final Information.Id<User> USER_INFORMATION_ID = new Information.Id<>(TypeInfo.of(User.class), new String[]{"user"});

    public static void main(String[] args) {
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(CommonArgTypesKt.enumArgumentType(Music.class))
                        .build()
                )
                .build()
                .addRequiredInfo(new RequiredInformation(USER_INFORMATION_ID))
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    User user = Objects.requireNonNull(informationProviders.find(USER_INFORMATION_ID)).getValue();
                    Music songArg = commandContainer.<Music>getArgumentValueOptional("song").get();
                    System.out.printf("... User '%s' requested the song %s ...%n", user.getName(), songArg.name());
                    return Unit.INSTANCE;
                })
                .build();


        InformationProviders informationProviders = new InformationProvidersImpl();
        informationProviders.registerInformationProvider(InformationProvider.Companion.safeFor(TypeInfo.of(User.class), (id, providers) -> {
            if (id.equals(USER_INFORMATION_ID)) {
                User user = new User("test", "test@test.test");
                return new Information<>(USER_INFORMATION_ID, user, "Logged in user");
            } else {
                return Information.Companion.empty();
            }
        }));

        processor.getCommandManager().registerCommand(command, new KwDocs());

        processor.parseAndDispatch("play beautiful", null, informationProviders);

    }

}

Note that, if you have a Provided Information and set is as required information, the information providers is evaluated twice (or even more), one for checking if Information is being provided, and the other one to retrieve the information (in the handler code). Information provided by an InformationProvider is not cached because the intention of InformationProvider is to provide a new value each time you request the information, even within same command handler, if a Lazy evaluated Information is what are you looking for, give kotlin Lazy type a shot.

Note that you could set RequiredInformation to only use non-provided information, and you could do the same for the providers.find, just look at the the constructor and method signature respectively.

Note 2: Lambdas can not be used with generic methods, because of that, InformationProvider should either be implemented using anonymous or concrete class, or you could use helper methods, such as safeFor and unsafe.

Requirements

Requirements are conditions that must be met for a command handler be invoked, they are useful for implementing Permission mechanism, for example. Requirements could be based either in Information or in Argument, in other words, the value to be checked against a requirement must be present either in argument provided to the command or in Information provided to the dispatch, currently these are the only subjects for Requirement checking, for most (if not all) cases, this is enough. Lets check an example of Requirement:

Permission class:

public enum Permission {
    SONG_REQUEST
}

UserPermissionManager class:

public interface UserPermissionManager {
    boolean hasPermission(User user, Permission permission);
}

PermissionRequirementTester class:

public class PermissionRequirementTester implements RequirementTester<User, Permission> {

    private final UserPermissionManager userPermissionManager;

    public PermissionRequirementTester(UserPermissionManager userPermissionManager) {
        this.userPermissionManager = userPermissionManager;
    }

    @NotNull
    @Override
    public TextComponent getName() {
        return RequirementTester.DefaultImpls.getName(this);
    }

    @Override
    public boolean test(@NotNull Requirement<User, Permission> requirement,
                        User user) {
        return this.userPermissionManager.hasPermission(user, requirement.getRequired());
    }
}

Command definition with Requirement:

public class KwDocs {

    private static final Information.Id<User> USER_INFORMATION_ID = new Information.Id<>(TypeInfo.of(User.class), new String[]{"user"});

    public static void main(String[] args) {
        UserPermissionManager userPermissionManager = (user, permission) -> true;

        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(CommonArgTypesKt.enumArgumentType(Music.class))
                        .build()
                )
                .build()
                .addRequiredInfo(new RequiredInformation(USER_INFORMATION_ID))
                .addRequirements(Requirement.<User, Permission>builder() // REQUIREMENT CONFIGURATION
                        .required(Permission.SONG_REQUEST)
                        .type(TypeInfo.of(Permission.class))
                        .tester(new PermissionRequirementTester(userPermissionManager))
                        .subject(new InformationRequirementSubject<>(USER_INFORMATION_ID))
                        .build()
                )
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    User user = Objects.requireNonNull(informationProviders.find(USER_INFORMATION_ID)).getValue();
                    Music songArg = commandContainer.<Music>getArgumentValueOptional("song").get();
                    System.out.printf("... User '%s' requested the song %s ...%n", user.getName(), songArg.name());
                    return Unit.INSTANCE;
                })
                .build();


        InformationProviders informationProviders = new InformationProvidersImpl();
        informationProviders.registerInformationProvider(InformationProvider.Companion.safeFor(TypeInfo.of(User.class), (id, providers) -> {
            if (id.equals(USER_INFORMATION_ID)) {
                User user = new User("test", "test@test.test");
                return new Information<>(USER_INFORMATION_ID, user, "Logged in user");
            } else {
                return Information.Companion.empty();
            }
        }));

        processor.getCommandManager().registerCommand(command, new KwDocs());

        processor.parseAndDispatch("play beautiful", null, informationProviders);

    }

}

Output: ... User 'test' requested the song BEAUTIFUL ...

You could try to change UserPermissionManager userPermissionManager = (user, permission) -> true; to UserPermissionManager userPermissionManager = (user, permission) -> false; and see what happens.

Interesting notes

  • Not only commands could have handlers, arguments could also have handlers too, and argument handlers are invoked before command handlers.
    • Also is important to know that command dispatching is a different task from command parsing, it applies to arguments too, so argument handlers have total access to values provided to arguments within their handler code.
  • Arguments could have RequiredInformation and Requirements too.
    • They work in the same way as for commands, with the exception that, if the argument is optional and is not present in the command, their Requirements and Information Requirements will not be evaluated.

Next steps

Now that we have dived into every important aspect of Command API, is time to look more into ArgumentTypes