Wednesday, August 23, 2017

Java Command-Line Interfaces (Part 12): CLAJR

The featured library for my twelfth post on processing command-line arguments in Java is Command-Line Arguments with Java Reflection (CLAJR). This "library" is a single Java source file (CLAJR-0.9.java) available for download on SourceForge. The main page for CLAJR currently shows a 2006 copyright date and the downloadable source zip file CLAJR-0.9-src.zip is dated 6 December 2008. Although CLAJR appears to be largely unsupported in recent years and although it's unlikely that I'd choose CLAJR over some of the alternative libraries already discussed in this series for processing command line arguments from Java code, I believe CLAJR warrants a post focused on it. There are some fairly unique characteristics of CLAJR that make it interesting, even if one chooses not to use it.

CLAJR is not provided as a JAR. Instead, it's provided as a single zipped file with a single Java source code file contained in that ZIP file. Being available as a single source code file is not unique to CLAJR; Picocli also is provided in a single Java source code file. However, Picocli also makes a JAR available on the Maven repository (which I used when working with Picocli), but I'm not aware of a pre-built JAR with the appropriate .class files for CLAJR. Therefore, because I prefer a JAR over a source code file when using third-party libraries, the first thing I did when experimenting with CLAJR was to build its provided source code into a small JAR file.

Here are the steps I used to build a JAR with CLAJR (I had to make a slight change to the source file and that change is described later in this post):

  1. Download CLAJR-0.9-src.zip from SourceForge.
  2. Unzip CLAJR-0.9-src.zip to extract CLAJR-0.9.java.
  3. Create a directory clajr to represent the Java clajr package that the Java source class should exist within.
  4. Move the CLAJR-0.9.java file to the clajr directory and rename it CLAJR.java to match the name of the class inside that source file.
    • I also had to edit the source code to make a minor change; this is discussed in more detail later in the post.
  5. Use javac to compile the CLAJR.java file into the multiple class files.
  6. Use jar to assembled the compiled .class files into a JAR (I named it CLAJR-0.9.jar).

The above steps are illustrated in the following two screen snapshots.

The Java-based command-line parsing libraries covered so far in this series tend to use annotations or specific programmatic APIs to allow for the definition, parsing, and interrogation of command-line arguments in Java code. CLAJR, as its full name suggests, instead uses Java reflection for defining the expected arguments to be parsed.

The main CLAJR page describes why the author chose reflection on methods' names for defining command-line arguments. CLAJR looks for methods that are named to correspond with the parameter or parameters being processed. A single underscore precedes a method name for a single-hyphen command-line argument while a double underscore precedes a method name for a double-hyphen command-line argument. A single method can be named to align with multiple command-line arguments when the multiple command-line arguments do the same thing.

As with the other posts in this series, my example for this post demonstrates use of the CLAJR library to model --file/-f command-line arguments and --verbose/-v command-line arguments. To "define" these in CLAJR, I need to name my methods _f__file and _v__verbose to correspond to -f/--file and -v/--verbose arguments. This is demonstrated in the partial snippet of a nested class Options in the next code listing.

"Definition" Stage in CLAJR: Reflection

/**
 * Used reflectively by CLAJR to parse and interrogate command line
 * options defined as fields in this class.
 */
public static class Options
{
   private String file;
   private boolean verbose;

   public void _v__verbose()
   {
      verbose = true;
   }

   public void _f__file(String newFilePathAndName)
   {
      file = newFilePathAndName;
   }

Parsing with CLAJR is a matter of a single statement. The next two lines of code demonstrate calling the static CLAJR.parse(String[], Object...) and passing to it the command-line arguments and just-instantiated instance of the Options class partially shown above.

"Parsing" Stage in CLAJR

final Options options = new Options();
CLAJR.parse(arguments, options);

In the single line parsing shown above, there is no return value from the parse method. Instead, the passed-in "options" instance has its fields populated according to which arguments are provided on the command line. CLAJR uses reflection on the "options" instance's methods to find the methods to invoke when corresponding arguments are discovered on the command line. It is this instance of Options that one can "interrogate" to find the values of arguments on the command line. This is demonstrated in the next code listing.

"Interrogation" Stage with CLAJR

out.println("File is '" + options.getFile() + "' and verbosity is set to '"
   + options.isVerbose() + "'.");

CLAJR supports providing of help/usage information on a per-method basis through reflection. In this case, methods are named with a convention similar to those for the command-line arguments themselves, but with help in front of the method name. This is demonstrated for the two combinations of arguments shown earlier in the next code listing.

CLAJR "Help" Methods

public String help_v__verbose()
{
   return "Enables verbosity of output.";
}

public String help_f__file()
{
   return "Path and name of file.";
}

The methods in the code listing just shown provide "help" strings for the -v/--verbose and -f/--file arguments. The static CLAJR method CLAJR.getHelp() method provides a String representing command-line usage that is based on these methods. CLAJR provides different exceptions that can be captured and these are often useful in conjunction with the "help" methods. These are demonstrated in the next code listing which shows the multiple catches that can be performed related to different error cases and other situations that warrant display of error information.

CLAJR's CLAJR.getHelp() and CLAJR Exceptions

catch (CLAJR.EmptyArgumentListException emptyArgsEx)
{
   out.println("Usage: Main -f|--file  [-v|--verbose]");
}
catch (CLAJR.HelpNeededException helpNeededEx)
{
   out.println(CLAJR.getHelp());
}
catch (CLAJR.ParseException parseEx)
{
   out.println(parseEx.getMessage());
   out.println(CLAJR.getHelp());
}
catch (Throwable throwable)  // CLAJR.parse throws Throwable
{
   out.println(throwable.getMessage());
}

CLAJR 0.9's CLAJR.parse(String[], Object...) method throws a Throwable and so the code above catches that Throwable.

The three screen snapshots that follow demonstrate this simple example. The first image shows the usage printed when no argument is provided. The second image depicts a normal flow of defining, parsing, and interrogating command-line arguments. The third image demonstrates display of help information when an unexpected argument is provided on the command line.

To get my examples to work, I had to change the CLAJR.java source file. Specifically, I changed line 491 in the invoke(String, String, boolean, List) method from if (tails == null) to if (tails == null || tails.isEmpty()). This is one of the advantages of open source; one can adjust or fix the code as necessary.

CLAJR allows a method named with a single underscore to be reflectively accessed. This - method corresponds to the "tail" parameter or parameters. I didn't use this in this example, but it's interesting to note that this will not work in JDK 9 because a single underscore is not allowed as an identifier in JDK 9. In fact, JDK 8's javac warns about this now as shown in the next screen snapshot.

Here are some additional characteristics of CLAJR to consider when selecting a framework or library to help with command-line parsing in Java.

  • CLAJR is open source and licensed with the GNU Lesser General Public License (version 2.1 or later).
  • CLAJR is available for download as an approximately 11 KB zip file called CLAJR-0.9-src.zip that contains the single file CLAJR-0.9.java.
  • CLAJR 0.9 does not require any third-party libraries to be downloaded.
  • CLAJR has been around for a while, but appears to not have been updated for several years. I had to make a minor change to the source code to get it to work properly for me.

CLAJR is a Java-based command-line processing library that is primarily distinguished from other libraries covered in this series by its use of reflection. Reflection is used to define expected command line arguments as well as the help messages associated with those arguments. CLAJR offers some unique ideas and approaches related to command-line processing in Java, but it's a somewhat dated library.

Additional Resources

No comments: