Parameter Modifiers - adamtomi/grapefruit GitHub Wiki

What Are Modifiers?

Modifiers are basically annotations that influence the conversion process of each argument. Each parameter may have more modifiers. Basically, modifiers set certain requirements for parameters that have to be met, otherwise the mapping process will fail.

Builtin Modifiers

There are a couple of builtin modifiers:

Modifier Required type Description
@Greedy java.lang.String Joins all remaining strings into one string (the delimiter is a single whitespace). Only the last parameter can be annotated with this annotation.
@Quotable java.lang.String Joins all string between double quotes (e.g.: "this is a quoted string")
@Regex java.lang.String Tests the string against given regex and throws ParameterMappingException, if the test fails.
@Flag ALL Turns the argument into a flag argument. (Read more about them here)
@Mapper ALL Specifies which ParameterMapper should be used when converting the argument.
@OptParam ALL Turns the argument into an optional argument.
@Range Any number type extending java.lang.Number and their primitive counterparts (if there is one) Determines a range. The value of the argument has to be between it's boundaries.
@Source S (command source type) Indicates that the value of the argument should be the source itself. Only the first parameter can be annotated with this annotation.

Custom Modifiers

Let's imagine that there is a class called User. Each user may be a regular user or an admin, and we need to differentiate between the two. This is where modifiers shine!

First, create an annotation:

@Modifier
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface IsAdmin {}

Notice the @Modifier annotation! Grapefruit need this to identify modifiers.

Now we need to check for @IsAdmin:

public class UserMapper extends AbstractParamterMapper<CommandSource, User> {
    private final UserManager userManager;

    public UserMapper(final @NotNull UserManager userManager) {
        super(TypeToken.of(User.class));
        this.userManager = requireNonNull(userManager, "userManager cannot be null");
    }

    @Override
    public @NotNull User map(final @NotNull CommandContext<CommandSource> context,
                             final @NotNull Queue<CommandInput> args,
                             final @NotNull AnnotationList modifiers) throws ParameterMappingException {
        final String rawInput = args.element().rawArg();
        final User user = this.userManager.findUser(rawInput).orElseThrow(() -> new ParameterMappingException(Message.of(
                        MyMessageKeys.USER_NOT_FOUND,
                        Template.of("{name}", rawInput))));
        if (modifiers.has(IsAdmin.class) && !user.isAdmin()) {
             throw new ParameterMappingException(Message.of(MyMessageKeys.USER_NOT_ADMIN, Template.of("{name}", rawInput)));
        }

        return user;
    }

    @Override
    public @NotNull List<String> listSuggestions(final @NotNull CommandContext<CommandSource> context,
                                                 final @NotNull String currentArg,
                                                 final @NotNull AnnotationList modifiers) {
        return this.userManager.getKnownUsers()
                .stream()
                .map(User::getName)
                .toList();
    }
}

The command may look something like this:

@CommandDefinition(route = "test")
public void onTestCommand(final User user, final @IsAdmin User admin) {}
⚠️ **GitHub.com Fallback** ⚠️