Skip to main content

Parsing command line options in JDK 5.0 style: args4j

Posted by kohsuke on May 11, 2005 at 12:20 AM PDT

Parsing command line options in your program has always been a boring work; you loop through String[] and write a whole bunch of arg.equals("-foo") and arg.equals("-bar"). There are some libraries that attempt to solve this, such as Apache Commons CLI.
I tried many of those, but I didn't quite like any of those. I felt that I can write a better one by taking advantanges of JDK 5.0 features. That eventually became args4j.

With args4j, you first write a Java class that represents all the options that you are going to define. I call this class an option bean (although it doesn't have to be a Java Bean), and it can look like this:

public class MyOptions {
   
    private boolean recursive;

    private File out;

    private String str = "(default value)";

    private int num;
}

You then put args4j annotations on this class. The annotations tell args4j which option maps to which field. You can also specify the human-readable description for the option, which args4j will use to generate the usage screen.

I also added another field that receives arguments (the inputs to the command line other than options)

public class MyOptions {
   
    @Option(name="-r",usage="recursively run something")
    private boolean recursive;

    @Option(name="-o",usage="output to this file")
    private File out;

    @Option(name="-str")        // no usage
    private String str = "(default value)";

    @Option(name="-n",usage="usage can have new lines in it\n and also it can be long")
    private int num;

    // receives other command line parameters than options
    @Argument
    private List arguments = new ArrayList();
}

In the above example I annotated fields, but I can also annotate a setter method with the same annotation. That will cause args4j to invoke the setter instead of accessing the field directly. That allows you to perform additional semantic check on the parameter, or define a set of options that interact with each other in some application-specific fashion.

Given all those annotations, I can parse arguments just like this:

public void main(String[] args) throws IOException {
    MyOptions bean = new MyOptions()
    CmdLineParser parser = new CmdLineParser(bean);
    parser.parseArgument(args);

This will parse the string array as parameters, and args4j will set the values to fields or invoke setters appropriately.

What happens if the user types a wrong option name? Just surround the parseArgument method with a try-catch block like this:

try {
    parser.parseArgument(args);
} catch( CmdLineException e ) {
    System.err.println(e.getMessage());
    System.err.println("java -jar myprogram.jar [options...] arguments...");
    parser.printUsage(System.err);
    return;
}

CmdLineException contains a human-readable error message that you can just print out. Then you can also use args4j to generate a list of options. With args4j, you don't need to maintain a separate list of options just for showing the usage screen.

The benefit of using annotations is that you can generate the list of options not only at the runtime but also at the development time. args4j comes with a tool that lets you generate HTML/XML list of all options. This is ideal for keeping the documentation of your tool in sync with the code. This can be done by running the following command:

$ java -jar args4j-tools.jar path/to/MyOptions.java

Both the usage screen generation and the XML/HTML generation supports internalization adequately.

If you are interested in argsj, visit the project home page and play with it. Let me know how you think of this. In the future, I'm thinking about using this annotation to parse the Ant task into the same option bean, so that you can have a single code for the CLI and the Ant task interface.

Related Topics >>

Comments

Hi, is there any way for using multiple beans in CmdLine ...

Hi, is there any way for using multiple beans in CmdLine parser ?
I have set of tools that share different option subsets with each other.
so I want something like this: 4 beans (each for own option set),
tool 1 uses options from beans 1 and 2,
tool 2 uses beans 2 and 3,
tool 3 uses beans 1,3,4, etc.

But I do not want to copy-paste option definition and getters from one tool to another.
Instead I'd like to have something new CmdLineParser(Object[] beans), or new CmdLineParser(Collection<?> beans)
or aggregate needed beans into a tool specific one: class Tool3Bean {
Bean1 bean1;
Bean3 bean3;
Bean4 bean4;
}
new CmdLineParser(new Tool3Bean())

Im diggin out a corpse with this but i think you would like ...

Im diggin out a corpse with this but i think you would like to know that im "forced" to use your this tool by my Professor on a German University!

How to validate arguments

I have a command lien syntax that includes commands, which I interpret as arguments. How can I define such a thing. If I annotate a property as Arguments(), I can't validate for valid arguments. If I annotate a setter method for the arguments, I get an error no OptionHandler set for List, the arguments format. What is the thing to do here?

Is there a user list/discussion forum?

Hi, I'm using this lib and I have some questions. Is t there a user mailing list or a forum that discussed the lib? Or is this the place to ask questions?

How to exclude -D args from being handled

Greetings, Is there an elegant way to exclude specific options from being handled? For example: public class StartupOptions { @Option(name="-a", usage="Option A") private boolean optionA; @Option(name="-df",usage="Option DF") private boolean optionDF; } startupOptions = new StartupOptions(); CmdLineParser parser = new CmdLineParser(startupOptions); parser.parseArgument(args); I want to ignore some params such as: -Dcom.sun.management.jmxremote.port=1099 or -Dcom.sun.management.jmxremote.authenticate=false etc... Is there a good way to handle this in the startupOptions class? Thanks

alive?

Is the project still active? I need some additional functionality but as I can see in the issue tracker there are some other unanswered issues too. Does it make sense to send additional patches or rather I should fork the project?

 I still use args4j in many

 I still use args4j in many of my projects. If you have patches, send them in!