I recently reviewed some Java code that was designed to process a set of arguments; my first thought… there has to be a better way! I did a quick Google search and found multiple command line processor implementations. My favorite seems to be the Args4J implementation. I like the simple Java Bean approach, taking advantage of Java 1.5 annotations, enumerations, and generics. The best part is the auto-magic type conversion from string arguments to bean properties. It feels a little like Apache BeanUtils, but the annotation processing makes it more flexible. A pretty slick implementation, but with a couple of small tweaks, would have been even better.
The Args4J implementation provides an ample collection of option handlers to support automatic type conversion of the basic Java types. Option handlers are provided for the all of the numeric types, URLs, Files, bounded lists (enumerated values), as well as unbounded lists. The implementation is easily extended; custom option handlers can be added to support your own object types or specific input formats, such as dates.
Overall, Args4J is very well done; I would have liked to see a little more support in the following areas:
- Args4J provides no default support for handling Java Date Types. I created a simple abstract option handler to support any data format. A concrete class is required for each desired format; but the subclass only needs to provide the requested date string format. An abstract date handler class might be a nice addition to Args4J, even though it was very simple.
- Args4J does not provide any type of argument validation, beyond basic type conversion. In my AbstractDateHandler class, I implemented a validate(<T> value) method. The validate method is invoked before the date instance is “set” on the bean. This implementation allowed me to ensure the specified date was before today’s date. This was really pretty clean; if the specified date was in the future, the option handler threw a CmdLineException containing the validation error in the message. This exception was handled up the call stack, just like any other type conversion issue, no special coding required.
- I can see this being very useful for numeric types as well. Maybe only positive integers are valid for as a specific parameter. The current Args4J implementation provides no way to seamlessly provide this type of validation via the Option Handlers (without cloning an the existing handler).
- This type of validation can be implemented in the Java bean, but the annotation must be specified on the setter, rather than the property. Some developers might actually prefer the validation logic it in the bean. Personally, I would rather see this logic managed by the option handler.
- The biggest hole in the implementation is the management of argument dependencies. For example, it would be nice to validate if the specified start date parameter is before the end date parameter, or if the print parameter is specified, the filename parameter is not allowed. This type of validation can be implemented in the bean, but would be invoked outside of the argument processing flow. You will have to mange exception logic in two places, once for the “type conversions” and another for the intra-argument dependencies.
- There are some flaws in my theory, but I thought it would be better if the option handlers had some knowledge of their own dependencies. My theory works well for required options, but not so well for optional ones.
- I created a custom CmdLineProcess class, which was hidden by suggestion #4 below. This class owned a dependency manager instance which made the previously processed command line values available to the current option handler.
- An ArgumentsDependency class was accessible during the validation process. The handler could now ask the dependency manager for values that were processed by other handlers. In this example, the EndDateHandler is asking for the dependency manager for the value parsed by the StartDateHandler.
- I know this is not perfect, but I really like the way the validation is implemented in a consistent manner, making it possible to handle all exceptions the exact same way.
- The implementation also provides a Starter class, which eliminates the need for the developer to write a main() method. It is an interesting idea, but I need to give this style a little more thought! I like having my options class independent of the execution class. The Starter approach merges the options and driver (main) logic into one single class. I would have preferred to see an argument processor class, with a method like: public T parseArgs(final String[] args, final Class<T> clazz). The argument processor would catch all of the exceptions and output the validation errors and usage information; the caller would need to do no extra processing or validation.
I hope this gives a little more insight into how Args4J is actually implemented and one possible method for extending it. Maybe someone has already handled these problems in a more elegant manner, but I did not to change any of the Args4J source code. That restriction did not give me a lot of flexibility to implement my enhancements! Overall, Args4J is a simple and elegant solution for sometimes messy problem.