MainArgsHandler
MainArgsHandler
?Being someone who hates having to solve the same problem over and over again, I always found it tedious to have to write code to check the command-line values passed to the main
method of a Java application. So I wrote the utility class MainArgsHandler
to automate as much of the data validation process as possible. MainArgsHandler
offers the following features in handling command-line arguments to a Java application:
Pattern
which defines what values are valid.IllegalArgumentException
is thrown if invalid flags, variables or values have been provided.String
summary of all of the command-line flags and variables accepted by your application, so you can display it to the user if they need assistance in running your application.List
of values provided for each command-line variable.The source code for MainArgsHandler
is full of JavaDoc comments which give full technical details about the use of each method, so this page will act as a short introduction to using MainArgsHandler
. You can find the source code in my MainArgsHandler repository on Codeberg, and I've made it available under a Mozilla Public License v2 so you can use it in your own projects.
MainArgsHandler
exampleSuppose you have a Swing application which adds an icon to the system notification area (often wrongly called the system tray) and you want to allow end-users to request that your application startup minimized so that on loading the icon appears in the notification area but no windows are opened. The easiest way to do this is to allow your application to accept a command-line argument, so that your application is run from the command-line with a call that looks something like this:
java SwingApplication --minimize-to-notification-area
We can use MainArgsHandler
to check for this command-line flag. A flag is a value which is either present in the command-line arguments ("on") or not present ("off"). A flag does not take any values; it simply toggles a feature on if it's found in the command-line arguments. Writing the code to tell MainArgsHandler
to check for this is simple:
public static void main(String[] args) {
MainArgsHandler handler = MainArgsHandler.getHandler();
handler.permitFlag("minimize-to-notification-area");
handler.processMainArgs(args);
if(handler.foundFlag("minimize-to-notification-area")) {
// Tell your Swing application to startup minimized.
}
// Rest of your application main method continues as usual.
}
And that's it. The MainArgsHandler
instance is told to allow a flag called "minimize-to-notification-area", then it's told to process the actual arguments passed from the command-line to the main
method, then it's asked whether or not those arguments contain the flag "minimize-to-notification-area". If the flag is found then you can tell your Swing application to start off in the notification area.
Command-line flags are always optional, but they can only be passed to your application if you have declared them to MainArgsHandler
using the permitFlag
method. If a flag is passed to your application which is not permitted then MainArgsHandler
will throw an IllegalArgumentException
to indicate that your application has been called with invalid command-line arguments. So it's probably best to wrap the call to processMainArgs
in a try-catch block so that you can tell the user they have made a mistake if this happens. Something like this:
try {
handler.processMainArgs(args);
} catch(IllegalArgumentException iae) {
System.out.println("You provided invalid arguments to this application. " +
"Please see the command-line usage:");
System.out.println(handler.getUsageSummary());
}
The getUsageSummary
method produces a String
which contains a full list of command-line flags and variables which are accepted by your application, so the end-user can see what the options are.
MainArgsHandler
Suppose you have written a Java application which produces a compressed output file from a set of source files, and you need to be able to pass to this application a list of source files, an output file path, an optional output format, and flags to enable additional settings. Calling this application on the command-line might look like this (with line breaks added to make it easier to read on this webpage):
java CompressFiles --source=/home/bob/File_one.txt \ --source="/home/bob/File two.txt" \ --output-file=~/filepack.tar.gz \ --force-overwrite --output-format=tarball
This is easy to do using MainArgsHandler
and the code might go something like this:
public static void main(String[] args) {
// Define a few Interval<Integer> instances for convenience.
Interval<Integer> ZERO_OR_ONE = new GenericInterval<Integer>(0, 1);
Interval<Integer> ONE_EXACTLY = new GenericInterval<Integer>(1, 1);
Interval<Integer> ONE_OR_MORE = new GenericInterval<Integer>(1, null);
// Define a Pattern which explicitly states which values are
// acceptable for the command-line variable "output-format".
Pattern OUTPUT_FORMAT_PATTERN = Pattern.compile("(?:zip|tarball)");
// Get the sole instance of MainArgsHandler and then declare the
// command-line variables and flags which this Java application accepts.
MainArgsHandler handler = MainArgsHandler.getHandler();
handler.permitVariable("source", ONE_OR_MORE,
"The path to a source file to be added to the compressed file.");
handler.permitVariable("output-file", ONE_EXACTLY,
"The path of the compressed output file.");
handler.permitVariable("output-format", ZERO_OR_ONE,
"The compressed format which should be used to create the output " +
"file. Can be either zip or tarball. Defaults to zip if " +
"neither is explicitly provided on the command-line.",
OUTPUT_FORMAT_PATTERN);
handler.permitFlag("force-overwrite",
"Overwrite the output file if it already exists.");
handler.permitFlag("skip-missing-source-files",
"Continue regardless if a source file is not found.");
// Now that all command-line variables and flags have been
// declared, call the processMainArgs method and be prepared for an
// IllegalArgumentException to be thrown if the command-line arguments
// passed to this application do not follow the declared specification.
try {
handler.processMainArgs(args);
} catch(IllegalArgumentException iae) {
System.out.println("You provided invalid arguments to this application. " +
"Please see the command-line usage:");
System.out.println(handler.getUsageSummary());
}
if(handler.foundFlag("force-overwrite")) {
// Tell your application to overwrite the output file without warning.
}
if(handler.foundFlag("skip-missing-source-files")) {
// Tell your application to skip missing source files without warning.
}
List<String> sourceFileList = handler.getValuesFromVariable("source");
String outputFile = handler.getValuesFromVariable("output-file").get(0);
String outputFormat;
if(handler.foundVariable("output-format") {
outputFormat = handler.getValuesFromVariable("output-format").get(0);
} else {
outputFormat = "zip";
}
// Now tell your application to process the files whose paths are found in the
// sourceFileList and write the resulting compressed file to the path held
// in the outputFile String, using the format held in the outputFormat String.
}
That's it. The MainArgsHandler
object handles the validation and data extraction of command-line arguments based on the flag and variable specification your code declares to it. Then your code just needs to use the foundFlag
, foundVariable
and getValuesFromVariable
methods to check for the presence of flags and variables and get values from variables that were provided.
Note that you don't need to call foundVariable
for the source or output-file command-line variables in the example above, because the specification has stated that both of these must occur at least once. If the user calls your application without providing each of these at least once then an IllegalArgumentException
will cause the application to exit after processMainArgs
is called. So, once you get past that, it's safe to assume that both of these variables must be found.
But the output-format command-line variable is optional so you ought to call foundVariable
to determine whether or not it was provided by the user. You can simply call getValuesFromVariable
and an empty List will be returned if the user did not supply a value for output-format, but remember that calling get(0)
on an empty list will cause an IndexOutOfBoundsException
to be thrown.
Notice that a Pattern
has been specified in the declaration of the output-format command-line variable. This means that the value provided by the user for output-format must entirely (not partially) match the specified Pattern
. In this case the pattern is a simple choice between "zip" or "tarball" but any valid Pattern
object can be provided. If the command-line value for output-format does not entirely match the specified pattern then an IllegalArgumentException
will be thrown.
When using MainArgsHandler
, command-line flags are supplied in the command-line call to your application by typing two hyphens followed by the flag name. A flag name must start and end with lowercase a-z characters, and single hyphens can be used within the name to effect spacing. So the name "example-flag-name" is valid, but "example--flag--name" is not valid (because double-hyphens are being used as spacing), nor is "example-name-" (because the name ends with a hyphen).
Command-line variables are supplied in the command-line call to your application by typing two hyphens, then the variable name, then an equals symbol, then the value the user wants to provide for that variable. A command-line variable name must follow the same rules which apply to a flag name. The value can be any sequence of printable characters, but bear in mind that the user will need to wrap the value in double-quote symbols if the value contains spaces, otherwise the space will cause the command-line to think that a new argument has been found. Also be aware that when double-quote symbols are used to wrap a value, these double-quote delimiters are stripped out before the value reaches the main
method, so they do not form part of the value.
You cannot use the same name for both command-line flags and command-line variables.
MainArgsHandler
The source code for MainArgsHandler
can be found in my repository on Codeberg. Be aware that it also depends on the JavaIntervals package so you'll need to grab that too.
If you find any issues with MainArgsHandler, please raise them on the Codeberg issues page rather than contact me directly.