Implement custom option handler in args4j

In the previous post about args4j, I showed how to write a custom multi-value option handler based on a built-in handlers.

This post guides you through writing your own option/argument handler. I’m going to show you an example of a handler that sets a java.time.Duration option.

First of all, you have to decide which base class to extend: either OptionHander or OneArgumentOptionHandler. I preferred the latter one, because it can be used for multi-value options (with a DelimitedOptionHandler). In both cases you must implement a ‘boilerplate’ constructor that delegates to the superclass. Here is the stub of the handler:

public class SimplifiedDurationHandler extends OneArgumentOptionHandler<Duration> {
    public SimplifiedDurationHandler(CmdLineParser parser, OptionDef option, Setter<? super Duration> setter) {
        super(parser, option, setter);
    }

    @Override
    protected Duration parse(String argument) throws CmdLineException {
        // ...
    }
}

The next step is to implement the actual parse method. Extending the OneArgumentOptionHandler gives another advantage. You don’t have to deal with Setters. You just return the parsed value and the base class will set the option.

In my example, I’m going to match the argument against a regular expression. In case of a successful match, I create a Duration from the amount and unit parts. In case of a mis-match, I signal the error to the library by throwing a CmdlineException:

@Override
protected Duration parse(String argument) throws CmdLineException {
    Matcher matcher = PATTERN.matcher(argument);
    if (!matcher.matches()) {
        throw misformattedArgumentException(argument);
    }
    return Duration.of(Integer.valueOf(amount(matcher)), unit(matcher));
}

private CmdLineException misformattedArgumentException(String token) {
    return new CmdLineException(owner, Messages.ILLEGAL_OPERAND, option.toString(), token);
}

By passing a Messages.ILLEGAL_OPERAND and option.toString() to the CmdLineException constructor, you’ll get a nice error message:

"1h" is not a valid value for "-d (--duration)"

for an @Option, or:

"1h" is not a valid value for "duration"

for an @Argument.

You can see the full source code together with an integration test on Github:

Advertisements