Args-inject step-by-step tutorial

This tutorial assumes you will read also some other pages on the site, and concentrates on stating rules and recommendations in each step.

Creating a simple commandline application

Basic steps

  • Add args-inject to your compile dependencies (== javac classpath)
  • Create SubCommand class
  • Define options methods
  • Create Application class
  • Creating wrapper shell script

Adding args-inject to dependencies

Maven2 project

If you use Maven2, you have a project with a pom.xml so you will need to add something like this (with the released version that you selected, of course):

  <dependency>
    <groupId>net.kozelka</groupId>
    <artifactId>args-inject</artifactId>
    <version>0.1-SNAPSHOT</version>
  </dependency>
Ant project

If you compile your code with Ant, then fragments of following code might be useful to you:

  <project name="my-project"
      xmlns="antlib:org.apache.tools.ant"
      xmlns:artifact="urn:maven-artifact-ant">
    <!-- add maven-ant-tasks -->
    <typedef classpath="${user.home}/.m2/lib/maven-ant-tasks-2.2.1.jar"
        resource="org/apache/maven/artifact/ant/antlib.xml"
        uri="urn:maven-artifact-ant"/>
    <!-- resolve the dependency -->
    <artifact:dependencies>
      <artifact:dependency groupId="net.kozelka" artifactId="args-inject" version="0.1-SNAPSHOT"/>
    </artifact:dependencies>
    <!-- use the dependency as a file -->
    <echo>add this path to javac classpath: ${net.kozelka:args-inject:jar}</echo>
    <!-- ... -->

Note that this uses the ant library maven-ant-tasks which is an excellent library for reusing Maven artifacts in Ant code; I highly recommend it to your attention.

Direct download link

In some cases, it might still be useful to have direct download links. Here they are:

Artifact MD5 SHA1 Description
args-inject-0.1-SNAPSHOT.jar MD5 SHA1 library JAR
args-inject-0.1-SNAPSHOT-sources.jar MD5 SHA1 library sources
Any other build system or IDE

Well, reading the above sections, you get the idea - use your skills to setup your buildsystem so that it compiles against args-inject, among other external libraries.

Creating SubCommand class

Rules

  • the class must implement ArgsCommand
  • class should be annottated with @SubCommand with a little description
  • create constructor with taking mandatory parameters of the command
  • to allow unlimited number of params, your last constructor's parameter can be Java varargs array (using notation "SomeType... myarray" )
  • the functionality must be implemented within the Integer call() method; usually you will want to return 0 from it

Hints

  • create a common ancestor for your subcommand classes, to share functionality and option declarations
  • to share only option declarations between commands, use dedicated interface
  • unnamed subcommand class can only be used for default subcommand
  • returning any other exitCode from call() makes only sense if you want your application to fail silently - or if your code already printed an error message
Sample subcommand class
package net.kozelka.args;

import java.io.File;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import net.kozelka.args.annotation.AnnottationAwareSetup;
import net.kozelka.args.annotation.Option;
import net.kozelka.args.annotation.SubCommand;
import net.kozelka.args.api.ArgsCommand;

@SubCommand(name = "ls", description = "list files (demo)")
public class DemoFileLister implements ArgsCommand {
    private final boolean deep;
    private final int maxCount;
    final File[] files;
    private boolean colorful;
    private final Properties properties = new Properties();
    private TimeUnit timeunit = TimeUnit.NANOSECONDS;
    private File[] locations;
    private int[] numbers;

    public DemoFileLister(boolean deep, int maxCount, File... files) {
        this.deep = deep;
        this.maxCount = maxCount;
        this.files = files;
    }

    @Option(longName = "--locations", description = "shows that default separator for files is File.pathSeparator")
    public void setLocations(File[] locations) {
        this.locations = locations;
    }

    @Option(longName = "--numbers", description = "shows that default separator is comma")
    public void setNumbers(int[] numbers) {
        this.numbers = numbers;
    }

    @Option(longName = "--time-unit", shortName = "-u")
    public void setTimeunit(TimeUnit timeunit) {
        this.timeunit = timeunit;
    }

    @Option(longName = "--property", shortName = "-D")
    public void addProperty(String propertyName, String propertyValue) {
        properties.put(propertyName, propertyValue);
    }

    @Option(longName = "--color", shortName = "-C", description = "set output colors")
    public void setColorful(boolean colorful) {
        this.colorful = colorful;
    }

    public Integer call() throws Exception {
        System.out.println("colorful = " + colorful);
        System.out.println("deep = " + deep);
        System.out.println("maxCount = " + maxCount);
        System.out.println("files = " + Arrays.asList(files));
        System.out.println("properties = " + properties);
        System.out.println("timeunit = " + timeunit);
        return 0;
    }

    static int run(String... args) throws Exception {
        final AnnottationAwareSetup setup = new AnnottationAwareSetup("DemoFileLister");
        setup.setDefaultSubCommand(DemoFileLister.class);
        return BasicArgsParser.process(setup, args);
    }
}

Defining option method

Rules

  • option method must be annottated with @Option, specifying at least one of shortName or longName attributes
  • option method must return void and cannot be static
  • option method is invoked for every occurence on the commandline
  • method param(s) map to option value(s)
  • varargs cannot be used for options (it works just as array)

Hints

  • when design your commandline structure, you should make options really optional - "mandatory options" are not nice, and args-inject does not provide any specific support for it
  • option can have as many values as you wish; however, using 0..2 params should usually make you happy enough
  • make the functionality independent of mutual commandline order - it makes the functionality easier to predict

Creating application class

Rules

  • implement both main and run as shown in MiniCalc sample
  • your application should always return with 0 on success and with 1 on failure; this is conveniently handled by method BasicArgsParser.process

Hints

  • use default help command
Sample main method
    public static void main(String[] args) throws Exception {
        final AnnottationAwareSetup setup = new AnnottationAwareSetup("myapp");
        setup.setDefaultSubCommand(DemoFileLister.class);
        final int exitCode = BasicArgsParser.process(setup, args);
        if (exitCode != 0) {
            System.exit(exitCode); // indicate failure to shell
        }
    }

Creating wrapper shell script

Rules

  • use the name you specified in main() as the base name; that is, something.bat or something.cmd on Windows, and just something on Linux
  • be sure to pass exitcode from JVM also out from the script; putting java as the latest command usually does the job
  • remember to pass script arguments to your main class - on Windows, use "%*", on Linux, use "$*".

Hints

  • remember to include all runtime dependencies in the resulting classpath
  • keep the shell script as brief as possible; long scripts are difficult to translate under certain platforms, especially Windows
  • on Linux, if your last command of the script is java (recommended!), then it is good to call it with exec to reuse script's process for JVM:

    exec java -classpath $CP net.kozelka.args.minicalc.MiniCalc $*

  • there are several tools to ensure that your output jar does not require any external libraries, which makes the launcher a bit simpler. Have a look at:

Finetuning

  • using default command - see Subcommands
  • specifying symbolic names to improve help outputs - see Params
  • working with arrays - see Options
  • TODO: using help as the default