Java annotations make declarative programming easy


Java annotations make declarative programming easy

Someone on java.net just recently had some terrible XML format for command line arguments and someone suggested in the comments that maybe they should be using annotations instead. This is a really good application for them actually…

Update: I’ve started adding some more the features that I talked about and some suggestions of people who were interested in the project. I added property access (just put the annotation on the set method), type conversions (through String constructors), and a usage message. You can checkout the code from here and you can see the current build results here.

Parsing command line arguments usually involves using some big library like common-cli from apache or doing some quick hack. This sort of like a combination of the two in so much as it is quick hack but its really easy to use and powerful. I’m going to forgoe the usage message for now to keep it really simple, though extending it with usage and all the other cute CLI extensions should be quite straight-forward. The first thing we will need is a nice little annotation:

package com.sampullara.cli;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) public @interface Argument {
String value() default "";
boolean required() default false;
String description() default "";
}

Now let’s look at an example of what we want the usage of the annotation to look like. Say we are going to make something takes an input and and output file. The simplest case would probably look something like:

package example;
import com.sampullara.cli.Argument;
import com.sampullara.cli.Args;
import java.util.List;
public class InputOutput {
@Argument(value = "input", description = "This is the input file", required = true)
private String inputFilename;
@Argument(value = "output", description = "This is the output file", required = true)
private String outputFilename;
@Argument(description = "This flag can optionally be set")
private boolean someflag;
public static void main(String[] args) {
InputOutput io = new InputOutput();
List<String> extra = Args.parse(io, args);
io.doit(extra);
}
public void doit(List<String> extra) {
System.out.println("Input: " + inputFilename);
System.out.println("Output: " + outputFilename);
System.out.println("Someflag: " + someflag);
System.out.println("Extra: " + extra);
}
}
Thats certainly quite a bit easier than writing an XML document and I can see right in the code what the arguments are going to look like without thinking too much about it. Now I just have to implement that pesky Args class so we can actually get the values of the arguments. This code could be better:
/*
* Copyright (c) 2005, Sam Pullara. All Rights Reserved.
* You may modify and redistribute as long as this attribution remains.
*/
package com.sampullara.cli;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class Args {
public static List parse(Object target, String[] args) {
List arguments = new ArrayList();
arguments.addAll(Arrays.asList(args));
Class clazz = target.getClass();
for (Field field : clazz.getDeclaredFields()) {
Argument argument = field.getAnnotation(Argument.class);
if (argument != null) {
boolean set = false;
for (Iterator i = arguments.iterator(); i.hasNext();) {
String arg = i.next();
if (arg.startsWith("-")) {
Object value;
String name = argument.value();
if (name.equals("")) {
name = field.getName();
}
if (arg.substring(1).equals(name)) {
i.remove();
Class type = field.getType();
if (type == Boolean.TYPE || type == Boolean.class) {
value = true;
} else {
if (i.hasNext()) {
value = i.next();
i.remove();
} else {
throw new IllegalArgumentException("Must have a value for non-boolean argument " + argument.value());
}
}
setField(field, target, value);
set = true;
}
if (set) break;
}
}
if (!set && argument.required()) {
throw new IllegalArgumentException("You must set argument " + argument.value());
}
}
}
for (String argument : arguments) {
if (argument.startsWith("-")) {
throw new IllegalArgumentException("Invalid argument: " + argument);
}
}
return arguments;
}
private static void setField(Field field, Object target, Object value) {
if (!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
try {
field.set(target, value);
} catch (IllegalAccessException iae) {
throw new IllegalArgumentException("Could not set field " + field, iae);
}
}
}
One of the other cool things about this is that I don't need to specify the default for an argument in the annotation, instead I can just set the variable to the default which is much more intuitive and DRY. You could probably spend an afternoon on this code and make a really nice system with detailed usage messages, advanced options like allowing Arrays with delimited values, doing conversions for more than just booleans (File, int, etc), calling setters rather than setting fields for validation, and possibly even just integrating a set of annotations into common-cli rather than reinventing the wheel. Or you could just use this tiny amount of code and not think about it much.
Anyway, the point is that annotations can make declarative programming very simple, you just need a little bit of reflection magic to get started.
Complete project with build file:cli.jar