Skip to content

Value resolvers

Revxrsal edited this page Sep 21, 2021 · 1 revision

Value resolvers are resolvers for command parameters that fetch their value from the command's input arguments. That is, for example:

  • numbers
  • enums
  • players by name
  • discord channels by name

These types retrieve their values by reading input from the arguments, then extracting the value, checking its validity and finally returning.

Usage

All value resolvers must inherit from ValueResolver, and should be registered with CommandHandler#registerValueResolver... methods.

All value resolvers get access to a mutable argument stack (list of strings), where each value resolver must consume at least one value from the argument stack.

The argument stack contains useful methods for dealing with the appropriate value. Most value resolvers should either use ArgumentStack#pop(), ArgumentStack#removeFirst() or ArgumentStack#popForParameter(CommandParameter). All these methods achieve similar functionality, which is returning the value on the top of the stack and removing it.

Examples

We can write a value resolver for parsing integer parameters.

commandHandler.registerValueResolver(int.class, (arguments, actor, parameter, command) -> {
    String value = arguments.pop();
    try {
       return Integer.parseInt(value);
    } catch (NumberFormatException e) {
       throw new CommandErrorException("Expected a number, but found '" + value + "'.");
    }
});

((by default primitives and few other types have built-in value resolvers, and do not need to be registered explicitly))

We can then instantly use int in our commands without having to parse it and check its validity.

@Command("repeat")  
public void repeat(CommandActor actor, int times, String value) {  
    for (int i = 0; i < times; i++)  
        actor.reply("#" + i + ": " + value);  
}

ValueResolverFactories

In certain cases it may be desired to provide custom value resolvers for certain parameters that have an annotation, or extend a certain interface or superclass. In such cases it is possible to use ValueResolverFactory to handle this.

For example, we can write a value resolver factory that caches all constants of enums and returns a value resolver for them. We also respect enums that would like their values to be fetched case-sensitive by checking against a custom @CaseSensitive annotation.

public enum EnumResolverFactory implements ValueResolverFactory {

    INSTANCE;

    @Override public @Nullable ValueResolver<?> create(@NotNull CommandParameter parameter) {
        Class<?> type = parameter.getType();
        if (!type.isEnum()) return null;
        Class<? extends Enum> enumType = type.asSubclass(Enum.class);
        Map<String, Enum<?>> values = new HashMap<>();
        boolean caseSensitive = parameter.hasAnnotation(CaseSensitive.class);
        for (Enum<?> enumConstant : enumType.getEnumConstants()) {
            if (caseSensitive)
                values.put(enumConstant.name(), enumConstant);
            else
                values.put(enumConstant.name().toLowerCase(), enumConstant);
        }
        return (ValueResolver<Enum<?>>) (arguments, actor, parameter1, command) -> {
            String value = arguments.pop();
            Enum<?> v = values.get(caseSensitive ? value : value.toLowerCase());
            if (v == null)
                throw new EnumNotFoundException(parameter, value);
            return v;
        };
    }
}