Advanced - fedups/com.obdobion.argument GitHub Wiki

Embedded Parsers

Although embedded parsers sounds complex and is in fact in the advanced section of the documentation it is a pretty simple thing in practice. It is when you are annotating a variable that is a type of class that also has annotated variables. Here is a very straightforward example.

public static class Name {
    @Arg
    private String firstName;
    @Arg
    private char middleInitial;
}

public static class Address {
    @Arg
    private String city;
}

public static class Person {
    @Arg
    private Name name;

    @Arg
    private Address address;
}

The code to load the command line into this structure is the same as it was to load into a single level structure:

Person person = new Person();
CmdLine.load(args, person);

Notice how the Person class annotates two other classes and each of those has annotations in them. Argument deals with this by requiring the user to enter parentheses around the embedded classes.

> ... --name (--firstName Chris --middleInitial J) --address (--city Lisle)

Another example is in specifying keys to a sort. I won't provide all of the details (see funnelsort for a complete working example) but you can see that an array is also possible.

public static class Key {
    @Arg(positional=true)
    private String fieldName;
 
    @Arg(positional=true, inList={"ASC","DESC"}, defaultValues="ASC")
    private String direction;
}

@Arg
private Key[] key;

And the user would enter it like this for 3 keys.

>... --key(field1, ASC)(field2, DESC)(anotherField)

Commas are optional, btw. Every set of parentheses create an additional element in the array. Argument is nothing if not flexible. The user could also get the exact same results with this command by being more explicit.

>... --key(field1, ASC) --key(field2, DESC) --key(anotherField)

Polymorphism

Argument can manage a variable through annotations that implements an Interface or a super class of a hierarchy of concrete classes. These are types that can not be created simply based on the definition. This an extreme example and probably not what should be done.

@Arg
private List<Object> myListOfAnything;

The only allowable command line that the user would be able to enter is variations of this.

> ... --myListOfAnything()

And it would create a list of Object instances. Not very useful. I don't want to take this obviously contrived example too far but it is useful to show a few things about Argument that can easily be extrapolated into your application.

The question is, "How to get useful instances into a polymorphic collection?". One answer is by providing an "instanceClass" parameter to the annotation. Another is to go full-blown factory generator. Lets start with the instanceClass.

Using InstanceClass

InstanceClass is a parameter to the @Arg annotation. It allows you to specify a class name that will be used to create instances when the argument is found on the command line. Using instanceClass overrides the variable type which is usually used for creating instances. Care must be taken to make certain that the instances that are created can be assigned to the variable - Argument will complain otherwise.

First, lets make up a new class called SpecificInstance.

public class SpecificInstance {}

And modify the previous example a bit so that Objects are not created, instead instances of SpecificInstance are.

@Arg (instanceClass="SpecificInstance")
private List<Object> myListOfAnything;

There still is very limited user options on the command line, but now we have instances of SpecificInstance rather that the bland instances of Object. Here we are creating a list of 2 SpecificInstance instances.

> ... --myListOfAnything()()

If this is all we wanted then it would have been more straightforward to just create our list a little differently. But since I am demonstrating polymorphism, we will push on defining yet another specific instance class, lets call it AnotherSpecificInstance because I feel extra creative.

public class AnotherSpecificInstance {}

What I want to introduce here is using multiple @Arg annotations on the same variable. We have to override the longName otherwise Argument would complain that there are duplicate long names since the variable name itself (myListOfAnything) is the default.

@Arg (longName="type1", instanceClass="SpecificInstance")
@Arg (longName="type2", instanceClass="AnotherSpecificInstance")
private List<Object> myListOfAnything;

Now the user can enter this on the command line to create the proper instances...

> ... --type1()() --type2()

This would create a List (myListOfAnything) with 3 entries, 2 of them instances of SpecificInstance and 1 being an instance of AnotherSpecificInstance. Any number of @Arg definitions can be stacked on a variable in this way.

Using Factories

Factories produce instances of classes based on logic. Argument has a few annotation parameters to have a factory of yours called each time a new instance needs to be created for the variable that is being annotated. These examples are a bit involved to set up but in a real application these types of relationships can be quite normal.

Factory using a simple String argument

The factory method that we are interfacing to in these examples is one that expects a String as its only argument. This is implemented on the interface as a static method.

This is the interface used for the List.

public interface IAnimal {
    static public IAnimal create(final String sound)
    {
        if ("meow".equals(sound))
            return new Cat();
        if ("woof".equals(sound))
            return new Dog();
        return new CatDog();
    }
}

These are the concrete implementations of the IAnimal interface.

public class Cat    implements IAnimal {}
public class Dog    implements IAnimal {}
public class CatDog implements IAnimal {}

And this is the List that will contain the instances that are generated based on command line input.

@Arg(
    factoryMethod = "create",
    factoryArgName = Arg.SELF_REFERENCING_ARGNAME)
private List<IAnimal> animals;

The factoryMethod identifies the method that will be called when a Argument creates a new instance of this List. Since create is defined on the IAnimal interface Argument will find it without any additional information. The SELF_REFERENCE_ARGNAME means that a String will be entered by the user whenever --animals is used.

Your user may want to define 2 animals by typing this at the command line.

>... --animals woof, meow

The Strings (woof and meow) will be used as the parameter to the create method on IAnimal.

Factory using an owned variable

In this example of creating instances using a factory I am going to use an annotated variable for the parameter to the factory method. The variable is defined on the abstract super class of the concrete classes.

This is the interface used for the List.

public interface IAnimal {
    static public IAnimal create(final String sound)
    {
        if ("meow".equals(sound))
            return new Cat();
        if ("woof".equals(sound))
            return new Dog();
        return new CatDog();
    }
}

The argument is defined on the abstract super class of all animals.

public abstract class Animal implements IAnimal {
    @Arg(required = true)
    private String sound;
}

These are the concrete implementations of the Animal.

public class Cat    extends Animal {}
public class Dog    extends Animal {}
public class CatDog extends Animal {}

And this is the List that will contain the instances that are generated based on command line input.

@Arg(
    factoryMethod = "create",
    factoryArgName = "sound",
    instanceClass = "Animal")
private List<IAnimal> animals;

The instanceClass overrides the type of the variable which was IAnimal. Since IAnimal is an interface it does not have any variables defined and therefore can not have the "sound" argument annotated like it needs to be. Because of this it is necessary to provide that variable on the abstract class and specify it as the instanceClass.

Your user may want to define 2 animals by typing this at the command line.

>... --animals (--sound woof), (--sound meow)

The Strings (woof and meow) will be used as the parameter to the create method on IAnimal.

Annotating classes you don't own

The variable parameter to the @Arg annotation lets you annotate classes you don't have access to modify. This is typically the case if you want to have a Java class as an argument to your program. In this example I am going to let the user enter a Dimension - a class coming straight from the JDK.

@Arg(longName="panel")
@Arg(variable = "width", positional = true, help = "The width of the dimension")
@Arg(variable = "height", positional = true, help = "The height of the dimension")
private Dimension       dim;

The first @Arg annotation is for the dim variable itself. It says that the user will be able to use --panel on the command line. Between this @Arg and the actual variable there can be additional @Arg definitions that include the variable parameter. Each variable parameter identifies a single variable in the class (Dimension in this example) and the remainder of the parameters behave as if the annotation was actually in the class.

The user could enter this...

>... --panel(12, 4)

Argument input from many sources

Argument is able to read (and in the export section you will see that it can also write) from different sources. Naturally it can read arguments from the command line. But properties files and XML can also be used.

Regardless of where the arguments are actually sourced from, each argument is always defined the same; with the @Arg annotation. That is no different from one source to the next. The difference is when you tell CmdLine to load the arguments.

Command Line

CmdLine.load(instance, args);

instance is the instance of the class that contains annotations. And args is from the command-line, usually via the main method of your application. There is not much additional to be said for command line input that I haven't already written. Except that args is not necessarily assumed to come from the command line. It can be any array of Strings. The "howto" application I wrote to demonstrate this package uses an interactive console and sends the interactions to this load method.

Properties File

CmdLine.loadProperties(instance, file);

instance is the instance of the class that contains annotations. And file is an instance of File that identifies the location of a properties file with arguments in it. Properties files have the format of key=value on each line. The value part of this is just the same as would be typed on the command line. And for the most part the key part is the same as well. But the key is a little different for embedded parsers, multiple value arguments, and positional arguments.

This makes Argument a real good fit for handling configuration files for your application. Not just command lines.

Embedded Parsers in Property Files

In review, an embedded parser is when a variable with an @Arg annotation is of a type that also has @Arg annotations in it. On the command line this depth is handled with parentheses (--key (--field NAME --dir ASC) as an example). This is handled with .s in the properties file. The key has the dots. The value is the same. The same specification would be written like this in a property file

key.field=NAME
key.dir=ASC

The assumption is that key is defined like this.

@Arg
private SortKey key;

And that SortKey has two fields annotated.

public class SortKey {
    @Arg private String field;
    @Arg private String dir;
}

Multi-value Arguments in Property Files

A multi-valued argument is one that the user can enter more than one value. This is handled on the command line by following the key word with one or more values. In a property file it is handle with an array type of notation. In the previous section I use the SortKey example. Usually sorts allow more than one key. So lets expand that example accordingly.

The sort will now have the key defined as a multi-value argument. That means I just made the variable an array.

@Arg
private SortKey[] key;

This would have looked like this for a command line --key(--field LastName --dir ASC) (--field FirstName --dir ASC) to get an array of 2 SortKeys. The corresponding format for a property file has the array notation.

key[0].field=LastName
key[0].dir=ASC
key[1].field=FirstName
key[1].dir=ASC

Positional Arguments in Property Files

Positional arguments are not named, their value's location within the command determines which argument they belong to. Again, for clarity sake, I am going to make the sort only allow for a single key.

@Arg
private SortKey key;

But the definition of the SortKey class will change to make both of the arguments positional.

public class SortKey {
    @Arg(positional=true) private String field;
    @Arg(positional=true) private String dir;
}

The corresponding command line for arguments defined this way would be --key(LastName ASC). For a property file we have to handle the fact that there is no name associated with the key part. So we just leave it empty.

key.=LastName
key.=ASC

Include Files

@filename is used to cause the contents of another file (filename) to be inserted at that point.

Comments

Lines with a '#' in the first position will be ignored.

Reset to default

-!<argnames> is useful when an include file sets a value and you need it to be something different.

Comments

Lines with a '#' in the first position will be ignored.

XML

CmdLine.loadXml(instance, file);

instance is the instance of the class that contains annotations. And file is an instance of File that identifies the location of an XML file with arguments in it.

Exporting

⚠️ **GitHub.com Fallback** ⚠️