Thursday, October 26, 2017

Java Command-Line Interfaces (Part 29): Do-It-Yourself

This series on parsing command line arguments from Java has briefly introduced 28 open source libraries that can be used to process command-line arguments from Java code. Even with these 28 libraries covered, the series has not covered all available open source libraries for parsing command line options from Java. For example, this series has not covered docopt, dolphin getopt, DPML CLI, the "other" JArgP, java-getopt, ritopt, cli-args, clio, TE-CODE Command, and likely many other libraries I'm not aware of. This post looks at considerations one might make when attempting to decide whether to roll one's own command line argument parsing code in Java versus using one of the plethora of command line parsing libraries that is already available.

At first glance, it would be easy to say that someone developer their own command-line parsing code in Java might be suffering from Not Invented Here Syndrome. However, I still occasionally write my own simple command line processing code and will outline the situations in which I do this.

Many of the libraries covered in this series are small. However, for cases where the command line parsing is very simple, even these smaller libraries may be heavier than what is needed for the job at hand. The examples I show in this post are the type that might fit this category. The likelihood of a developer developing custom command line processing code likely increases as the complexity of required command line parsing increases and as the difficultly of introducing new libraries to one's deployment environment decreases. Process can also influence the decision as some developers may choose to implement their own command line processing code rather than wait for requisite approvals to use the identified library.

The easiest situation to choose to not use a command-line parsing library for Java is obviously those situations in which command line arguments are not necessary. In fact, it is likely that far more Java developers never or rarely use command-line options given that so many use web servers, application servers, or other containers (such as Spring) to run that they don't think about command-line parsing for their application. Even some simple command-line-based applications may be able to assume values or read values from an assumed location and don't need arguments passed to them.

If I only have a single argument to read from the command line, I'll write that simple code myself. The Java Tutorials feature a section on Command-Line Arguments that introduces basic handling of command line arguments in Java. The zero to many strings on the command line following the Java executable application's name are provided to the Java application via the String[] or String... arguments to the classic "public static void main" function. The simple code listing below indicates how a single expected command-line argument might be processed.

Parsing Single Required Argument

/**
 * Demonstrate processing a single provided argument.
 *
 * @param arguments Command-line arguments; expecting a
 *    String-based name.
 */
public static void main(final String[] arguments)
{
   if (arguments.length < 1)
   {
      out.println("\nNo name provided; please provide a name.\n");
      out.println("\tUSAGE: SingleArgMain <name>");
   }
   else
   {
      out.println("Hello " + arguments[0] + "!");
   }
}

The above code was easy to write because there was one command line option, it did not have an argument to go with the option, and it was required. With all of these assumptions in place, it is relatively easy to write command line parsing code.

If the application requires two arguments, it is still pretty straightforward to handle this directly in Java without a third-party library. This is demonstrated in the next code listing that simulates an application that accepts the name/path of an XML file to be validated and the name/path of the XSD against which that XML is to be validated.

Parsing Two Required Arguments

/**
 * Demonstrate processing two required provided arguments.
 *
 * @param arguments Command-line arguments; expecting a String-based
 *    path and file name of an XML file to be validated and a
 *    String-based path and file name of the XSD file against which
 *    the XML file will be validated.
 */
public static void main(final String...arguments)
{
   if (arguments.length < 2)
   {
      out.println("\nXML file path/name and XSD file path/name not provided.\n");
      out.println("\tUSAGE: TwoArgsMain <xmlFilePathAndName> <xsdFilePathAndName>");
   }
   else
   {
      out.println("The provided XML file is '" + arguments[0]
         + "' and the provided XSD file is '" + arguments[1] + "'.");
   }
}

In the posts in this series, I've used examples that expect a required option specifying file path/name and an optional option expressing enabled verbosity. In all of those examples, the file path/name option was a flag name (-f and/or --file) followed by a an "argument" or "value" for that option. For those examples, the verbosity option did not have an argument or value associated with it and the existence of -v or --verbose implied enabled verbosity. This is particularly easy to accomplish directory in Java without a library if I'm willing to change the approach slightly and assume the the first command line option is the file path/name and to assume that the verbosity flag, if provided, occurs after the file path/name. The other assumption that makes this easy is to assume that because the file path/name is first, I don't need to actually use a flag such as -file or -f. With all of these assumptions in place, the code example is shown next.

Series Example: Parsing One Required Option and One Optional Option

/**
 * Demonstrate parsing of command-line options for required file
 * path/name and for optional verbosity.
 *
 * @param arguments Expected command-line arguments; first String
 *    should be file path/name and, if applicable, second String
 *    should be the verbosity flag (-v or --verbose).
 */
public static void main(final String[] arguments)
{
   if (arguments.length < 1)
   {
      out.println("\nNo file path/name provided; please provide a file path/name.\n");
      out.println("\tUSAGE: SeriesExample <filePathAndName> [-v|--verbose]");
   }
   else
   {
      final String file = arguments[0];
      final String verboseString = arguments.length > 1 ? arguments[1] : "";
      final boolean verbose = verboseString.equals("-v") || verboseString.equals("--verbose");
      out.println("File path/name is '" + file + "' and verbosity is " + verbose);
   }
}

I've had relatively easy command-line parsing options so far because of these characteristics of these examples:

  • Order of command line arguments was assumed and unchangeable.
  • Never had more than one optional command line argument and the optional argument was expected last.
  • Never needed a command line argument that consisted of flag and value associated with that flag.
  • No option had a dependency on any other option.

The just-mentioned characteristics made for easier parsing of command line options from Java because the number of permutations and combinations to be prepared for were significantly reduced by requiring the ordering of the options, by not allowing for flags with associated values that must be handled together (each string in the provided String[] is independent of all other strings in that array), and by only allowing one optional argument at most (and requiring it to be last).

As the command-line arguments situation gets more complicated, my desire to use a third-party library increases. If I want to have multiple optional arguments or want to have options that consist of flags with associated values, I'm more likely to make the jump to the third-party libraries for parsing command-line arguments in Java. Using most of the third-party libraries covered in this series removes the need for me to worry about option ordering and option name/flag associations.

One situation in which it might be desirable to roll one's own command-line parsing code in Java is when those parsing needs are highly specific to a particular situation that is not handled well by the existing libraries or when none of the existing libraries adequately meet one's needs. However, with 30+ libraries available, I doubt this would occur very frequently for most people.

When developing one's own command-line parsing code in Java, other options besides writing it from scratch include forking and extending one of the open source libraries or building one's code on a framework such as that introduced in the article "Parsing Command Line Arguments with Java: Using an effective Java framework to write command line tools" (pages 20 and 22 of this Java Developer's Journal).

For small Java-based tools, the simple command-line parsing approaches shown in this post are often sufficient, especially if I'm the only one likely to use the tool. However, as the potential user base increases for the Java application, the requirements outlined in this post can become onerous and the use of third-party libraries covered in this series of posts can be helpful in creating a more user-friendly command-line argument experience. For the simplest of Java-based tools and applications, I may be able to get away with my own homemade command-line parsing code. However, for most Java applications of significance, a third-party library will make more sense because it offers significantly greater flexibility and ease of use for the end users.

Additional References

3 comments:

Akihiko said...

@DustinMarx Hi, I'm a fan of your great these articles, Java Command-Line Interfaces. After you complete these series, do you have any plan to write the article to conclude the best library for command line interface?
If you have, it would be very helpful.
Anyway thank you for your publishing this kind of articles!

@DustinMarx said...

Hello Akihiko,

Thanks for the feedback and kind words.

The next post in this series ("Part 30") will provide an overview and summary of observations related to the earlier 29 posts on command line parsing libraries in Java. Although I probably won't be able to name a single "best" library, I do plan to narrow down to 5 or so libraries that should be generally appealing to most developers for most use cases.

Dustin

Akihiko said...

That's excellent. I'm looking forward to reading it!