Skip to content

DefaultArgumentHandlersExplained

Mark Johnson edited this page Sep 5, 2016 · 9 revisions

Mandate provides built-in support for a number of command-executable method parameter types, providing a reasonably expansive set of functionality to work with when designing commands.

Numbers

All default Java implementations of the Number interface are supported, including Byte, Short, Integer, Long, Float, Double, BigInteger, and BigDecimal. The primitive number types corresponding to those wrapper classes are also fully supported. A command defined like this is fully supported:

    @Executes
    @Syntax(root = {"acceptNumbers"})
    public ExitCode acceptNumbers(Byte b, short s, Integer i, long j, Float f, double d, BigInteger bi, BigDecimal bd) {
        ...
    }

The default number argument handler also supports imposing limits on the kinds of numbers that are valid for any given parameter through the use of the @IntClamp and @RealClamp annotations, applicable to integral and real number types respectively. Both annotations have two members, min and max, that define the upper and lower bounds for any given number. By default, min is the smallest supported number for the given parameter type and max is the largest supported number for that type. An example of their uses is:

    @Syntax(root = {"account", "acc"})
    @Executes(tree = {"balance|bal", "increment|add"})
    public ExitCode incrementAccountBalance(@IntClamp(min=0) long accountId, @RealClamp(min=0.0) BigDecimal amount) {
        ...
    }

A declaration like this would guarantee that the provided account ID will not be less than zero, and that the amount by which the account balance will be incremented will also not be less than zero.

CharSequences

All default Java implementations of the CharSequence interface are supported, including CharBuffer, Segment, String, StringBuffer, and StringBuilder. A command defined like this is fully supported:

    @Executes
    @Syntax(root = {"acceptChars"})
    public ExitCode acceptChars(CharBuffer chars, Segment seg, String str, StringBuffer buf, StringBuilder sb) {
        ...
    }

Similar to how limits on numbers can be imposed, there are certain restrictions that can be applied to CharSequence arguments during argument processing. This can be done by using the @Equals and @Length annotations, which serve to limit the kinds of strings that are supported.

  • @Equals serves as a string equality tester. The annotation has two members, value and regex, that define what string patterns are valid for the annotation parameter and whether or not those patterns should be compiled to regular expressions prior to checking if any user arguments match it, respectively. By default, regex is set to false.
  • @Length is a general-purpose annotation that, when applied to a CharSequence parameter, limits what the minimum and maximum string length for any arguments passed to it are. The annotation has two members, min and max, that define the minimum and maximum number of characters that can exist in a given CharSequence. By default, min is 0 and max is the maximum supported length of an array of characters.

An example of their uses is:

    @Executes
    @Syntax(root = {"connect"})
    public ExitCode connectToServer(
            @Equals(value={"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"}, regex=true) String ipAddress,
            @IntClamp(min=0, max=65535) int port) {
        ...
    }

Though the example is admittedly contrived and forgoes the possibility of having a custom error message if the input string does not match the supplied regex, this command will ensure than only valid IPv4 addresses and valid ports can be provided as arguments. It might be more practical to use a custom argument handler supplying InetAddress instances for a practical implementation of this.

Enums

Any Enumeration can be used as a parameter type in a command. This can be used to limit the number of possible values of an argument without having to resort to using an equality-bounded CharSequence argument. An example of its use might be:

    @Executes
    @Syntax(root = {"matchdays"})
    public ExitCode listMatchingDaysInMonth(Month month, DayOfWeek day) {
        ...
    }

In this example, the possible values of month are limited to valid English month names, and the possible values of day are limited to valid English day names. Both Enum#name and Enum#toString are considered as valid names to match against when parsing user input.

Arrays

Arrays of any type can be used as command-executable method parameter types. This is one of several ways of supporting arguments that consist of more than one individual piece of input. An example of this is:

    @Syntax(root = {"keybind", "bind"})
    @Executes(tree={"add|a", "multi|m"})
    public ExitCode addMultiKeyKeybind(CustomBindableObject bindableObject, @Length(max=4) String[] keyNames) {
        ...
    }

When entering input intended for an array parameter, input should be structured as

[firstElement, secondElement, thirdElement, ...]

The square brackets used above can be replaced with curly braces ({ and }, respectively) if the user so chooses.

During parsing, the array argument handler will check if the component type of the array has a matching argument handler registered to the CommandContext associated with the parsing attempt. If a suitable argument handler is found, the individual elements of the input provided by the user are sent off to the new argument handler and all results are collected into the array instance that the command method receives.

The @Length annotations is also applicable to arrays, and will set the minimum and maximum number of elements that can exist in the given array

Because all argument handlers are checked when searching for one that can handle the array component type, multidimensional arrays are also implicitly supported. A definition such as

    @Syntax(root = {"matrices"})
    @Executes(tree={"cache"})
    public ExitCode cacheMatrix(int[][] matrix, String name) {
        ...
    }

is perfectly legal, seeing as the argument handler will detect the component type of the 2D matrix array as int[] and parse the individual sub-arrays correctly. You cannot, however, use the @Length annotation with the sub-arrays inside the 2D matrix array to control their length.

Lists

Java Lists are also supported as parameter types, operating in a fashion syntactically similar to array parameters. Lists may be useful when you require a List for compatability with existing APIs, or if you just want easy access to the convenience methods available from a List. An example of this is:

    @Executes
    @Syntax(root = {"readfiles"})
    public ExitCode readInputFromFiles(List<String> filePaths) {
        ...
    }

The syntax for entering input intended for a List parameter is identical to the syntax for an array parameter.

Similar to how arrays handle nested types, the generic type of the list parameter is resolved at runtime and a suitable argument handler is found. This also allows any number of nested generic type declarations within the parameter, supporting more complex parameter configurations.

The @Length annotation is also applicable to lists in the same way as to arrays.

Maps

Key -> Value mappings are supported as command parameter types in the form of Maps, allowing for fairly complex bulk data operations. An example of this is:

    @Syntax(root = {"properties", "props"})
    @Executes(tree={"load"})
    public ExitCode loadPropertyMapping(Map<String, String> properties) {
        ...
    }

When entering input intended for a Map parameter, input should be structured as

[firstKey -> firstValue, secondKey -> secondValue, thirdKey -> thirdValue, ...]

The square brackets used above can be replaced with curly braces ({ and }, respectively) if the user so chooses. Furthermore, the spaces between the "->" token and both the key and value are not required, and may be excluded if the user so chooses.

Similar to List parameters, the @Length annotation can be used to define the minimum and maximum number of mappings in any given map.

Maps can also be nested as key or value types of a Map command parameter, permitting further complexity in command parameter declaration:

    @Syntax(root = {"statistics", "stats"})
    @Executes(tree={"loadbulk"})
    public ExitCode loadBulkStatisticsMapping(Map<String, Map<String, Integer> perObjectStatistics) {
        ...
    }

Similar to how nested arrays or lists structure their syntax, a correctly formatted call to this command might look like:

props loadbulk [codebase -> [lines -> 50000, tests -> 86], repository -> [branches -> 5, commits -> 1068]]

Though this particular example is fairly contrived, it serves to demonstrate how type-specific syntaxes can be nested and still operate effectively.

Sets

Sets are also supported as command parameter types. They are useful for situations in which having duplicate values in an argument sequence is undesirable, and it serves to reduce the complexity of the code of any commands using it when that behavior is intended. An example of this is:

    @Syntax(root = {"update"})
    @Executes(tree={"numbers", "nums"})
    public ExitCode updateAndLogMinimumAndMaximum(String userId, Set<Integer> input) {
        ...
    }

The syntax for providing arguments matching set parameters is identical to that of both lists and arrays. The @Length annotation is applied in the same way, but its bounds are only checked after removing any duplicate entries in the set.

Clone this wiki locally