// The five files
// Option.java
// OptionGroup.java
// Options.java
// Unpublicized.java
// OptionsDoclet.java
// together comprise the implementation of command-line processing.
package plume;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.lang.reflect.*;
import java.lang.annotation.*;
/**
* The Options class:
* <ul>
* <li>parses command-line options and sets fields in your program accordingly,</li>
* <li>creates usage messages (such as printed by a <tt>--help</tt> option), and</li>
* <li>creates documentation suitable for a manual or manpage.</li>
* </ul>
* Thus, the programmer is freed from writing duplicative, boilerplate code
* and documentation that could get out of sync with the rest of the program.
* <p>
*
* The programmer does not have to write any code, only declare and
* document variables. Each field that is annotated with @{@link
* plume.Option} is automatically set from a command-line argument of the
* same name.
* <p>
*
* The main entry point is {@link #parse_or_usage(String[])}.
* Typical use is:
*
* <!-- Example needs some more words of explanation and example command lines. -->
* <!-- Given this code: --> <pre>
* public static class MyProgram {
*
* @Option("-o <filename> the output file ")
* public static File outfile = new File("/tmp/foobar");
*
* @Option("-i ignore case")
* public static boolean ignore_case;
*
* @Option("set the initial temperature")
* public static double temperature = 75.0;
*
* public static void main(String[] args) {
* MyProgram myInstance = new MyProgram();
* Options options = new Options("MyProgram [options] infile outfile",
* myInstance, MyUtilityClass.class);
* String[] remaining_args = options.parse_or_usage(args);
* ...
* }
* }</pre>
* A user may invoke the program using the command-line arguments
* <tt>-o</tt>, <tt>--outfile</tt>, <tt>-i</tt>, <tt>--ignore-case</tt>,
* and, <tt>--temperature</tt>. <p>
*
* The call to {@link #parse_or_usage} sets fields in object myInstance,
* and sets static fields in class MyUtilityClass. It returns the original
* command line, with all options removed. <p>
*
* <b>@Option indicates a command-line option</b> <p>
* The @{@link Option} annotation on a field specifies brief user documentation
* and, optionally, a one-character short name that users may supply on the
* command line. The long name is taken from the name of the variable;
* when the name contains an underscore, the user may substitute a hyphen
* on the command line instead; for example, the --multi-word-variable
* command-line option would set the variable multi_word_variable. <p>
*
* On the command line, the values for options are specified in the form
* '--longname=value', '-shortname=value', '--longname value', or '-shortname
* value'. If {@link #use_single_dash(boolean)} is true, then the long names
* take the form '-longname=value' or '-longname value'. The value is
* mandatory for all options except booleans. Booleans are set to true if no
* value is specified. <p>
*
* All arguments that start with '-' are processed as options. To
* terminate option processing at the first non-option argument, see {@link
* #parse_options_after_arg(boolean)}. Also, the special option '--'
* terminates option processing; method <tt>parse_or_usage</tt> returns
* all subsequent arguments (along with any preceding non-option arguments)
* without scanning them for options. <p>
*
* A user may provide an option multiple times on the command line. If the
* field is a list, each entry is added to the list. If the field is
* not a list, then only the last occurrence is used (subsequent
* occurrences overwrite the previous value). <p>
*
* <b>Unpublicized options</b> <p>
* The @{@link Unpublicized} annotation causes an option not to be displayed
* in the usage message. This can be useful for options that are
* preliminary, experimental, or for internal purposes only. The @{@link
* Unpublicized} annotation must be specified in addition to the @{@link
* Option} annotation. <p>
*
* There are forms of the usage-message methods that can include even
* unpublicized options; for example, see {@link #usage(boolean,String...)}.
* <p>
*
* <b>Option groups</b> <p>
* The @{@link OptionGroup} annotation can be used to assign a name to a set of
* related options. This is useful for organizing a list of
* options. Options in the same group are displayed under the same heading
* in usage texts. <p>
*
* The @{@link OptionGroup} annotation must be specified on a field in addition
* to an @{@link Option} annotation. Note that, due to a deficiency in
* Javadoc, an <code>@OptionGroup</code> annotation must appear underneath any
* Javadoc comment for the field it applies to. <p>
*
* The <code>@OptionGroup</code> annotation acts like a delimiter — all
* <code>@Option</code>-annotated fields up to the next
* <code>@OptionGroup</code> annotation belong to the same group. When using
* option groups, the first <code>@Option</code>-annotated field of every class
* and object passed to the {@link #Options(String, Object...)} constructor
* must have an <code>@OptionGroup</code> annotation. Furthermore, the first
* parameter of an <code>@OptionGroup</code> annotation (the group name) must
* be unique among all classes and objects passed to the {@link
* #Options(String, Object...)} constructor. <p>
*
* If an option group itself is unpublicized:
* <ul>
* <li>The default usage message omits the group and all options belonging
* to it.
* <li>An unpublicized option group (that has any publicized options) is
* included in documentation for a manual.
* <li>A field with an @{@link Unpublicized} annotation is excluded
* even when passing the group's name
* explicitly as a parameter to {@link #usage(String...)}.
* </ul>
*
* If an option group is not unpublicized but contains only unpublicized
* options, it will not be included in the default usage message. <p>
*
* <b>Option aliases</b> <p>
* The @{@link Option} annotation has an optional parameter <code>aliases</code>,
* which accepts an array of strings. Each string in the array is an alias for
* the option being defined and can be used in place of an option's long name
* or short name. Aliases should start with a single dash or double dash. It
* is the user's responsibility to ensure that aliases does not cause ambiguity
* and do not collide with other options. <p>
*
* For example:
* <pre>
* // The user may supply --help, -h, or -help, all of which mean the same thing and set this variable
* @Option(value="-h Print a help message", aliases={"-help"})
* public static boolean help;</pre>
*
* <b>Generating documentation for a manual or manpage</b> <p>
* The class Javadoc for a class that has a main method should generally
* contain a summary of all command-line options. Such a summary can also
* be useful in other circumstances.
* See the {@link plume.OptionsDoclet} class for instructions about generating
* HTML documentation. <p>
*
* <b>Supported field types</b> <p>
* A field with an @{@link Option} annotation may be of the following types:
* <ul>
* <li>Primitive types: boolean, int, long, float, double.
* (Primitives can also be represented as wrappers: Boolean,
* Integer, Long, Float, Double. Use of a wrapper type allows the
* argument to have no default value.)
* <li>Reference types that have a constructor with a single string
* parameter.
* <li>java.util.regex.Pattern.
* <li>enums.
* <li>Lists of any of the above reference types.
* </ul> <p>
*
* <b>More examples</b> <p>
*
* Example clients of the Options library include {@link
* plume.Lookup} and the <code>Main</code> class of
* <a href="http://code.google.com/p/javarifier/">Javarifier</a>. <p>
*
* <b>Limitations</b> <ul>
*
* <li> Short options are only supported as separate entries
* (e.g., "-a -b") and not as a single group (e.g., "-ab").
*
* <li> Not all primitive types are supported.
*
* <li> Types without a constructor that takes a single <tt>String</tt>
* argument are not supported.
*
* <li> The "--no-long" option to turn off a boolean option named "long"
* is not supported; use "--long=false" instead.
*
* </ul>
*
* <b>Possible enhancements</b> <ul>
* <li> Positional arguments (non-options that must be provided in a given
* order) could be supported.
* </ul>
*
* @see plume.Option
* @see plume.OptionGroup
* @see plume.Unpublicized
* @see plume.OptionsDoclet
**/
public class Options {
@SuppressWarnings("nullness") // line.separator property always exists
private static String eol = System.getProperty("line.separator");
/** Information about an option **/
class OptionInfo {
/** Field containing the value of the option **/
Field field;
/** Option information for the field **/
Option option;
/** Object containing the field. Null if the field is static. **/
/*@Nullable*/ Object obj;
/** Short (one character) argument name **/
/*@Nullable*/ String short_name;
/** Long argument name **/
String long_name;
/** Aliases for this option **/
String[] aliases;
/** Argument description **/
String description;
/** Javadoc description **/
/*@Nullable*/ String jdoc;
/**
* Name of the argument type. Defaults to the type of the field, but
* user can override this in the option string.
*/
String type_name;
/**
* Class type of this field. If the field is a list, the basetype
* of the list.
*/
Class<?> base_type;
/** Default value of the option as a string **/
/*@Nullable*/ String default_str = null;
/**
* If true, the default value string for this option will be excluded from
* OptionsDoclet documentation.
*/
boolean no_doc_default = false;
/** If the option is a list, this references that list. **/
/*@LazyNonNull*/ List<Object> list = null;
/** Constructor that takes one String for the type **/
/*@Nullable*/ Constructor<?> constructor = null;
/** Factory that takes a string (some classes don't have a string constructor) and always returns non-null. */
/*@Nullable*/ Method factory = null;
/**
* If true, this OptionInfo is not output when printing documentation.
* @see #usage()
*/
boolean unpublicized;
/**
* Create the specified option. If obj is null, the field must be
* static. The short name, type name, and description are taken
* from the option annotation. The long name is the name of the
* field. The default value is the current value of the field.
*/
OptionInfo (Field field, Option option, /*@Nullable*/ Object obj, boolean unpublicized) {
this.field = field;
this.option = option;
this.obj = obj;
this.base_type = field.getType();
this.unpublicized = unpublicized;
this.aliases = option.aliases();
this.no_doc_default = option.noDocDefault();
// The long name is the name of the field
long_name = field.getName();
if (use_dashes)
long_name = long_name.replace ('_', '-');
// Get the default value (if any)
Object default_obj = null;
if (!Modifier.isPublic (field.getModifiers()))
throw new Error ("option field is not public: " + field);
try {
default_obj = field.get (obj);
if (default_obj != null)
default_str = default_obj.toString();
} catch (Exception e) {
throw new Error ("Unexpected error getting default for " + field, e);
}
// Handle lists. When a list argument is specified multiple times,
// each argument value is appended to the list.
Type gen_type = field.getGenericType();
if (gen_type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) gen_type;
Type raw_type = pt.getRawType();
if (!raw_type.equals (List.class))
throw new Error ("@Option does not support type " + pt + " for field " + field);
if (default_obj == null) {
List<Object> new_list = new ArrayList<Object>();
try {
field.set(obj, new_list);
} catch (Exception e) {
throw new Error ("Unexpected error setting default for " + field, e);
}
default_obj = new_list;
}
if (((List<?>) default_obj).isEmpty())
default_str = null;
@SuppressWarnings("unchecked")
List<Object> default_obj_as_list = (List<Object>) default_obj;
this.list = default_obj_as_list;
// System.out.printf ("list default = %s%n", list);
this.base_type = (Class<?>) pt.getActualTypeArguments()[0];
// System.out.printf ("Param type for %s = %s%n", field, pt);
// System.out.printf ("raw type = %s, type = %s%n", pt.getRawType(),
// pt.getActualTypeArguments()[0]);
}
// Get the short name, type name, and description from the annotation
ParseResult pr = parse_option (option.value());
short_name = pr.short_name;
if (pr.type_name != null) {
type_name = pr.type_name;
} else {
type_name = type_short_name (base_type);
}
description = pr.description;
// Get a constructor for non-primitive base types
if (!base_type.isPrimitive() && !base_type.isEnum()) {
try {
if (base_type == Pattern.class) {
factory = Pattern.class.getMethod ("compile", String.class);
} else { // look for a string constructor
assert base_type != null; // nullness checker: problem with flow
constructor = base_type.getConstructor (String.class);
}
} catch (Exception e) {
throw new Error ("Option " + field
+ " does not have a string constructor", e);
}
}
}
/**
* Returns whether or not this option has a required argument.
*/
public boolean argument_required() {
Class<?> type = field.getType();
return ((type != Boolean.TYPE) && (type != Boolean.class));
}
/**
* Returns a short synopsis of the option in the form
* -s --long=<type>
* <strong>or</strong>
* -s -long=<type>
* if use_single_dash is true.
**/
public String synopsis() {
String prefix = use_single_dash ? "-" : "--";
String name = prefix + long_name;
if (short_name != null)
name = String.format ("-%s %s", short_name, name);
name += String.format ("=<%s>", type_name);
if (list != null)
name += " [+]";
return (name);
}
/**
* Returns a one-line description of the option.
*/
public String toString() {
String prefix = use_single_dash ? "-" : "--";
String short_name_str = "";
if (short_name != null)
short_name_str = "-" + short_name + " ";
return String.format ("%s%s%s field %s", short_name_str, prefix,
long_name, field);
}
/** Returns the class that declares this option. **/
public Class<?> get_declaring_class() {
return field.getDeclaringClass();
}
}
/** Information about an option group **/
class OptionGroupInfo {
/** The name of this option group **/
String name;
/**
* If true, this group of options will not be printed in usage output by
* default. However, the usage information for this option group can be
* printed by specifying the group explicitly in the call to {@link
* #usage}.
*/
boolean unpublicized;
/** List of options that belong to this group **/
List<OptionInfo> optionList;
OptionGroupInfo(String name, boolean unpublicized) {
optionList = new ArrayList<OptionInfo>();
this.name = name;
this.unpublicized = unpublicized;
}
OptionGroupInfo(OptionGroup optionGroup) {
optionList = new ArrayList<OptionInfo>();
this.name = optionGroup.value();
this.unpublicized = optionGroup.unpublicized();
}
/**
* If false, this group of options does not contain any publicized options,
* so it will not be included in the default usage message.
*/
boolean any_publicized() {
for (OptionInfo oi : optionList)
if (!oi.unpublicized)
return true;
return false;
}
}
/**
* Whether to parse options after a non-option command-line argument.
* @see #parse_options_after_arg(boolean)
**/
private boolean parse_options_after_arg = true;
/** All of the argument options as a single string **/
private String options_str = "";
/** First specified class. Void stands for "not yet initialized". **/
private Class<?> main_class = Void.TYPE;
/** List of all of the defined options **/
private List<OptionInfo> options = new ArrayList<OptionInfo>();
/** Map from short or long option names (with leading dashes) to option information **/
private Map<String,OptionInfo> name_map
= new LinkedHashMap<String,OptionInfo>();
/** Map from option group name to option group information **/
private Map<String, OptionGroupInfo> group_map
= new LinkedHashMap<String, OptionGroupInfo>();
/**
* If, after the Options constructor is called, use_groups is true, then the
* user is using @OptionGroup annotations correctly (as per the requirement
* specified above). If false, then @OptionGroup annotations have not been
* specified on any @Option-annotated fields. When @OptionGroup annotations
* are used incorrectly, an Error is thrown by the Options constructor.
*/
private boolean use_groups;
/**
* Convert underscores to dashes in long options in usage messages. Users
* may specify either the underscore or dashed name on the command line.
*/
private boolean use_dashes = true;
/**
* When true, long options take the form -longOption with a single dash,
* rather than the default --longOption with two dashes.
*/
private boolean use_single_dash = false;
/**
* String describing "[+]" (copied from Mercurial).
*/
private static String list_help = "[+] marked option can be specified multiple times";
/**
* Whether printing the usage message should print list_help. The default is
* to print list_help if the usage message contains an option that accepts a
* list as a parameter.
*/
private boolean print_list_help = false;
/**
* When true, an argument to a option of list type is split, on
* whitespace, into multiple arguments each of which is added to the
* list. When false, each argument to an option of list type is treated
* as a single element, no matter what characters it contains.
*/
@Option ("Treat arguments to lists as space-separated.")
public static boolean split_lists = false;
/**
* Synopsis of usage. Example: "prog [options] arg1 arg2 ..."
* <p>
* This variable is public so that clients can reset it (useful for
* masquerading as another program, based on parsed options).
**/
public /*@Nullable*/ String usage_synopsis = null;
// Debug loggers
private SimpleLog debug_options = new SimpleLog (false);
/**
* Prepare for option processing. Creates an object that will set fields
* in all the given arguments. An argument to this method may be a
* Class, in which case its static fields are set. The names of all the
* options (that is, the fields annotated with @{@link Option}) must be
* unique across all the arguments.
*/
public Options (Object... args) {
this ("", args);
}
/**
* Prepare for option processing. Creates an object that will set fields
* in all the given arguments. An argument to this method may be a
* Class, in which case its static fields are set. The names of all the
* options (that is, the fields annotated with @{@link Option}) must be
* unique across all the arguments.
* @param usage_synopsis A synopsis of how to call your program
*/
public Options (String usage_synopsis, Object... args) {
if (args.length == 0) {
throw new Error("Must pass at least one object to Options constructor");
}
this.usage_synopsis = usage_synopsis;
this.use_groups = false;
// true once the first @Option annotation is observed, false until then.
boolean seen_first_opt = false;
// Loop through each specified object or class
for (Object obj : args) {
boolean is_class = obj instanceof Class<?>;
String current_group = null;
Field[] fields;
if (is_class) {
if (main_class == Void.TYPE)
main_class = (Class<?>) obj;
fields = ((Class<?>) obj).getDeclaredFields();
} else {
if (main_class == Void.TYPE)
main_class = obj.getClass();
fields = obj.getClass().getDeclaredFields();
}
for (Field f : fields) {
debug_options.log ("Considering field %s of object %s with annotations %s%n",
f, obj, Arrays.toString(f.getDeclaredAnnotations()));
Option option = safeGetAnnotation(f, Option.class);
if (option == null)
continue;
boolean unpublicized = safeGetAnnotation(f, Unpublicized.class) != null;
if (is_class && !Modifier.isStatic (f.getModifiers()))
throw new Error ("non-static option " + f + " in class " + obj);
OptionInfo oi = new OptionInfo(f, option, is_class ? null : obj, unpublicized);
options.add(oi);
// FIXME: should also check that the option does not belong to an
// unpublicized option group
if (oi.list != null && !oi.unpublicized)
print_list_help = true;
OptionGroup optionGroup = safeGetAnnotation(f, OptionGroup.class);
if (!seen_first_opt) {
seen_first_opt = true;
// This is the first @Option annotation encountered so we can decide
// now if the user intends to use option groups.
if (optionGroup != null)
use_groups = true;
else
continue;
}
if (!use_groups) {
if (optionGroup != null)
// The user included an @OptionGroup annotation in their code
// without including an @OptionGroup annotation on the first
// @Option-annotated field, hence violating the requirement.
// NOTE: changing this error string requires changes to TestPlume
throw new Error("missing @OptionGroup annotation on the first " +
"@Option-annotated field of class " + main_class);
else
continue;
}
// use_groups is true at this point. The variable current_group is set
// to null at the start of every iteration through 'args'. This is so
// we can check that the first @Option-annotated field of every
// class/object in 'args' has an @OptionGroup annotation when use_groups
// is true, as required.
if (current_group == null && optionGroup == null) {
// NOTE: changing this error string requires changes to TestPlume
throw new Error("missing @OptionGroup annotation in field "
+ f + " of class " + obj);
} else if (optionGroup != null) {
String name = optionGroup.value();
if (group_map.containsKey(name))
throw new Error("option group " + name + " declared twice");
OptionGroupInfo gi = new OptionGroupInfo(optionGroup);
group_map.put(name, gi);
current_group = name;
} // current_group is non-null at this point
@SuppressWarnings("nullness") // map key
/*@NonNull*/ OptionGroupInfo ogi = group_map.get(current_group);
ogi.optionList.add(oi);
} // loop through fields
} // loop through args
String prefix = use_single_dash ? "-" : "--";
// Add each option to the option name map
for (OptionInfo oi : options) {
if (oi.short_name != null) {
if (name_map.containsKey ("-" + oi.short_name))
throw new Error ("short name " + oi + " appears twice");
name_map.put ("-" + oi.short_name, oi);
}
if (name_map.containsKey (prefix + oi.long_name))
throw new Error ("long name " + oi + " appears twice");
name_map.put (prefix + oi.long_name, oi);
if (use_dashes && oi.long_name.contains ("-"))
name_map.put (prefix + oi.long_name.replace ('-', '_'), oi);
if (oi.aliases.length > 0) {
for (String alias : oi.aliases) {
if (name_map.containsKey (alias))
throw new Error ("alias " + oi + " appears twice");
name_map.put (alias, oi);
}
}
}
}
/**
* Like getAnnotation, but returns null (and prints a warning) rather
* than throwing an exception.
*/
private <T extends Annotation> /*@Nullable*/ T
safeGetAnnotation(Field f, Class<T> annotationClass) {
/*@Nullable*/ T annotation;
try {
annotation = f.getAnnotation(annotationClass);
} catch (Exception e) {
// Can get
// java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
// when an annotation is not present at run time (example: @NonNull)
System.out.printf("Exception in call to f.getAnnotation(%s)%n for f=%s%n"
+ " %s%nClasspath =%n", annotationClass, f, e.getMessage());
//e.printStackTrace();
JWhich.printClasspath();
annotation = null;
}
return annotation;
}
/**
* If true, Options will parse arguments even after a non-option
* command-line argument. Setting this to true is useful to permit users
* to write options at the end of a command line. Setting this to false
* is useful to avoid processing arguments that are actually
* options/arguments for another program that this one will invoke.
*/
public void parse_options_after_arg (boolean val) {
parse_options_after_arg = val;
}
/** @deprecated Use {@link #parse_options_after_arg(boolean)}. */
@Deprecated
public void ignore_options_after_arg (boolean val) {
parse_options_after_arg = !val;
}
/**
* If true, long options (those derived from field names) will be parsed with
* a single dash prefix as in -longOption. The default is false and long
* options will be parsed with a double dash prefix as in --longOption.
*/
public void use_single_dash (boolean val) {
use_single_dash = val;
}
/**
* Parses a command line and sets the options accordingly.
* @return all non-option arguments
* @throws ArgException if the command line contains unknown option or
* misused options.
*/
public String[] parse (String[] args) throws ArgException {
List<String> non_options = new ArrayList<String>();
// If true, then "--" has been seen and any argument starting with "-"
// is processed as an ordinary argument, not as an option.
boolean ignore_options = false;
// Loop through each argument
for (int ii = 0; ii < args.length; ii++) {
String arg = args[ii];
if (arg.equals ("--")) {
ignore_options = true;
} else if ((arg.startsWith ("--") || arg.startsWith("-")) && !ignore_options) {
String arg_name;
String arg_value;
int eq_pos = arg.indexOf ('=');
if (eq_pos == -1) {
arg_name = arg;
arg_value = null;
} else {
arg_name = arg.substring (0, eq_pos);
arg_value = arg.substring (eq_pos+1);
}
OptionInfo oi = name_map.get (arg_name);
if (oi == null) {
StringBuilder msg = new StringBuilder();
msg.append(String.format("unknown option name '%s' in arg '%s'",
arg_name, arg));
if (false) { // for debugging
msg.append("; known options:");
for (String option_name : UtilMDE.sortedKeySet(name_map)) {
msg.append(" ");
msg.append(option_name);
}
}
throw new ArgException (msg.toString());
}
if (oi.argument_required() && (arg_value == null)) {
ii++;
if (ii >= args.length)
throw new ArgException ("option %s requires an argument", arg);
arg_value = args[ii];
}
// System.out.printf ("arg_name = '%s', arg_value='%s'%n", arg_name,
// arg_value);
set_arg (oi, arg_name, arg_value);
} else { // not an option
if (! parse_options_after_arg)
ignore_options = true;
non_options.add (arg);
}
}
String[] result = non_options.toArray (new String[non_options.size()]);
return result;
}
/**
* Parses a command line and sets the options accordingly. This method
* splits the argument string into command-line arguments, respecting
* single and double quotes, then calls {@link #parse(String[])}.
* <p>
* {@link #parse(String[])} is usually a better method to call. This one
* is appropriate when the <tt>String[]</tt> version of the arguments is
* not available — for example, for the <tt>premain</tt> method of
* a Java agent.
*
* @return all non-option arguments
* @throws ArgException if the command line contains misused options or an unknown option.
* @see #parse(String[])
*/
public String[] parse (String args) throws ArgException {
// Split the args string on whitespace boundaries accounting for quoted
// strings.
args = args.trim();
List<String> arg_list = new ArrayList<String>();
String arg = "";
char active_quote = 0;
for (int ii = 0; ii < args.length(); ii++) {
char ch = args.charAt (ii);
if ((ch == '\'') || (ch == '"')) {
arg+= ch;
ii++;
while ((ii < args.length()) && (args.charAt(ii) != ch))
arg += args.charAt(ii++);
arg += ch;
} else if (Character.isWhitespace (ch)) {
// System.out.printf ("adding argument '%s'%n", arg);
arg_list.add (arg);
arg = "";
while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii)))
ii++;
if (ii < args.length())
ii--;
} else { // must be part of current argument
arg += ch;
}
}
if (!arg.equals (""))
arg_list.add (arg);
String[] argsArray = arg_list.toArray (new String[arg_list.size()]);
return parse (argsArray);
}
/**
* Parses a command line and sets the options accordingly. If an error
* occurs, prints the usage message and terminates the program. The program is
* terminated rather than throwing an error to create cleaner output.
* @return all non-option arguments
* @see #parse(String[])
*/
public String[] parse_or_usage (String[] args) {
String non_options[] = null;
try {
non_options = parse (args);
} catch (ArgException ae) {
String message = ae.getMessage();
if (message != null) {
print_usage (message);
} else {
print_usage ();
}
System.exit (-1);
// throw new Error ("usage error: ", ae);
}
return (non_options);
}
/**
* Parses a command line and sets the options accordingly. If an error
* occurs, prints the usage message and terminates the program. The program is
* terminated rather than throwing an error to create cleaner output.
* <p>
* This method splits the argument string into command-line arguments,
* respecting single and double quotes, then calls
* {@link #parse_or_usage(String[])}.
* <p>
* {@link #parse(String[])} is usually a better method to call. This one
* is appropriate when the <tt>String[]</tt> version of the arguments is
* not available — for example, for the <tt>premain</tt> method of
* a Java agent.
*
* @return all non-option arguments
* @see #parse_or_usage(String[])
*/
public String[] parse_or_usage (String args) {
String non_options[] = null;
try {
non_options = parse (args);
} catch (ArgException ae) {
String message = ae.getMessage();
if (message != null) {
print_usage (message);
} else {
print_usage ();
}
System.exit (-1);
// throw new Error ("usage error: ", ae);
}
return (non_options);
}
/** @deprecated Use {@link #parse_or_usage(String[])}. */
@Deprecated
public String[] parse_and_usage (String[] args) {
return parse_or_usage(args);
}
/** @deprecated Use {@link #parse_or_usage(String)}. */
@Deprecated
public String[] parse_and_usage (String args) {
return parse_or_usage(args);
}
/// This is a lot of methods, but it does save a tad of typing for the
/// programmer.
/**
* Prints usage information. Uses the usage synopsis passed into the
* constructor, if any.
*/
public void print_usage (PrintStream ps) {
if (usage_synopsis != null) {
ps.printf ("Usage: %s%n", usage_synopsis);
}
ps.println(usage());
if (print_list_help) {
ps.println();
ps.println(list_help);
}
}
/**
* Prints, to standard output, usage information.
*/
public void print_usage () {
print_usage (System.out);
}
// This method is distinct from
// print_usage (PrintStream ps, String format, Object... args)
// because % characters in the message are not interpreted.
/**
* Prints a message followed by indented usage information.
* The message is printed in addition to (not replacing) the usage synopsis.
**/
public void print_usage (PrintStream ps, String msg) {
ps.println (msg);
print_usage (ps);
}
/**
* Prints, to standard output, a message followed by usage information.
* The message is printed in addition to (not replacing) the usage synopsis.
**/
public void print_usage (String msg) {
print_usage (System.out, msg);
}
/**
* Prints a message followed by usage information.
* The message is printed in addition to (not replacing) the usage synopsis.
*/
public void print_usage (PrintStream ps, String format, /*@Nullable*/ Object... args) {
ps.printf (format, args);
if (! format.endsWith("%n")) {
ps.println();
}
print_usage (ps);
}
/**
* Prints, to standard output, a message followed by usage information.
* The message is printed in addition to (not replacing) the usage synopsis.
*/
public void print_usage (String format, /*@Nullable*/ Object... args) {
print_usage(System.out, format, args);
}
/**
* Returns the String containing the usage message for command-line options.
*
* @param group_names The list of option groups to include in the usage
* message. If empty and option groups are being used, will return usage
* for all option groups that are not unpublicized. If empty and option
* groups are not being used, will return usage for all options that are
* not unpublicized.
*/
public String usage(String... group_names) {
return usage(false, group_names);
}
/**
* Returns the String containing the usage message for command-line options.
*
* @param include_unpublicized If true, treat all unpublicized options
* and option groups as publicized
* @param group_names The list of option groups to include in the usage
* message. If empty and option groups are being used, will return usage
* for all option groups that are not unpublicized. If empty and option
* groups are not being used, will return usage for all options that are
* not unpublicized.
*/
public String usage(boolean include_unpublicized, String... group_names) {
if (!use_groups) {
if (group_names.length > 0) {
throw new IllegalArgumentException(
"This instance of Options does not have any option groups defined");
}
return format_options(options, max_opt_len(options, include_unpublicized),
include_unpublicized);
}
List<OptionGroupInfo> groups = new ArrayList<OptionGroupInfo>();
if (group_names.length > 0) {
for (String group_name : group_names) {
if (!group_map.containsKey(group_name))
throw new IllegalArgumentException("invalid option group: " + group_name);
OptionGroupInfo gi = group_map.get(group_name);
if (!include_unpublicized && !gi.any_publicized())
throw new IllegalArgumentException("group does not contain any publicized options: " + group_name);
else
groups.add(group_map.get(group_name));
}
} else { // return usage for all groups that are not unpublicized
for (OptionGroupInfo gi : group_map.values()) {
if ((gi.unpublicized || !gi.any_publicized()) && !include_unpublicized)
continue;
groups.add(gi);
}
}
List<Integer> lengths = new ArrayList<Integer>();
for (OptionGroupInfo gi : groups)
lengths.add(max_opt_len(gi.optionList, include_unpublicized));
int max_len = Collections.max(lengths);
StringBuilderDelimited buf = new StringBuilderDelimited(eol);
for (OptionGroupInfo gi : groups) {
buf.append(String.format("%n%s:", gi.name));
buf.append(format_options(gi.optionList, max_len, include_unpublicized));
}
return buf.toString();
}
/**
* Format a list of options for use in generating usage messages.
*/
private String format_options(List<OptionInfo> opt_list, int max_len, boolean include_unpublicized) {
StringBuilderDelimited buf = new StringBuilderDelimited(eol);
for (OptionInfo oi : opt_list) {
if (oi.unpublicized && ! include_unpublicized)
continue;
String default_str = "";
if (oi.default_str != null)
default_str = String.format(" [default %s]", oi.default_str);
String use = String.format(" %-" + max_len + "s - %s%s",
oi.synopsis(), oi.description, default_str);
buf.append(use);
}
return buf.toString();
}
/**
* Returns the length of the longest synopsis message in a list of options.
* Useful for aligning options in usage strings.
*/
private int max_opt_len(List<OptionInfo> opt_list, boolean include_unpublicized) {
int max_len = 0;
for (OptionInfo oi : opt_list) {
if (oi.unpublicized && ! include_unpublicized)
continue;
int len = oi.synopsis().length();
if (len > max_len)
max_len = len;
}
return max_len;
}
/**
* Package-private accessors/utility methods that are needed by the
* OptionsDoclet class to generate HTML documentation.
*/
boolean isUsingGroups() {
return use_groups;
}
boolean isUsingSingleDash() {
return use_single_dash;
}
List<OptionInfo> getOptions() {
return options;
}
Collection<OptionGroupInfo> getOptionGroups() {
return group_map.values();
}
/**
* Set the specified option to the value specified in arg_value. Throws
* an ArgException if there are any errors.
*/
private void set_arg (OptionInfo oi, String arg_name, /*@Nullable*/ String arg_value)
throws ArgException {
Field f = oi.field;
Class<?> type = oi.base_type;
// Keep track of all of the options specified
if (options_str.length() > 0)
options_str += " ";
options_str += arg_name;
if (arg_value != null) {
if (! arg_value.contains (" ")) {
options_str += "=" + arg_value;
} else if (! arg_value.contains ("'")) {
options_str += "='" + arg_value + "'";
} else if (! arg_value.contains ("\"")) {
options_str += "=\"" + arg_value + "\"";
} else {
throw new ArgException("Can't quote for internal debugging: " + arg_value);
}
}
// Argument values are required for everything but booleans
if (arg_value == null) {
if ((type != Boolean.TYPE)
|| (type != Boolean.class)) {
arg_value = "true";
} else {
throw new ArgException ("Value required for option " + arg_name);
}
}
try {
if (type.isPrimitive()) {
if (type == Boolean.TYPE) {
boolean val;
String arg_value_lowercase = arg_value.toLowerCase();
if (arg_value_lowercase.equals ("true") || (arg_value_lowercase.equals ("t")))
val = true;
else if (arg_value_lowercase.equals ("false") || arg_value_lowercase.equals ("f"))
val = false;
else
throw new ArgException ("Value \"%s\" for argument %s is not a boolean",
arg_value, arg_name);
arg_value = (val) ? "true" : "false";
// System.out.printf ("Setting %s to %s%n", arg_name, val);
f.setBoolean (oi.obj, val);
} else if (type == Integer.TYPE) {
int val;
try {
val = Integer.decode (arg_value);
} catch (Exception e) {
throw new ArgException ("Value \"%s\" for argument %s is not an integer",
arg_value, arg_name);
}
f.setInt (oi.obj, val);
} else if (type == Long.TYPE) {
long val;
try {
val = Long.decode (arg_value);
} catch (Exception e) {
throw new ArgException ("Value \"%s\" for argument %s is not a long integer",
arg_value, arg_name);
}
f.setLong (oi.obj, val);
} else if (type == Float.TYPE) {
Float val;
try {
val = Float.valueOf (arg_value);
} catch (Exception e) {
throw new ArgException ("Value \"%s\" for argument %s is not a float",
arg_value, arg_name);
}
f.setFloat (oi.obj, val);
} else if (type == Double.TYPE) {
Double val;
try {
val = Double.valueOf (arg_value);
} catch (Exception e) {
throw new ArgException ("Value \"%s\" for argument %s is not a double",
arg_value, arg_name);
}
f.setDouble (oi.obj, val);
} else { // unexpected type
throw new Error ("Unexpected type " + type);
}
} else { // reference type
// If the argument is a list, add repeated arguments or multiple
// blank separated arguments to the list, otherwise just set the
// argument value.
if (oi.list != null) {
if (split_lists) {
String[] aarr = arg_value.split (" *");
for (String aval : aarr) {
Object val = get_ref_arg (oi, arg_name, aval);
oi.list.add (val); // uncheck cast
}
} else {
Object val = get_ref_arg (oi, arg_name, arg_value);
oi.list.add (val);
}
} else {
Object val = get_ref_arg (oi, arg_name, arg_value);
f.set (oi.obj, val);
}
}
} catch (ArgException ae) {
throw ae;
} catch (Exception e) {
throw new Error ("Unexpected error ", e);
}
}
/**
* Create an instance of the correct type by passing the argument value
* string to the constructor. The only expected error is some sort
* of parse error from the constructor.
*/
private /*@NonNull*/ Object get_ref_arg (OptionInfo oi, String arg_name,
String arg_value) throws ArgException {
Object val = null;
try {
if (oi.constructor != null) {
val = oi.constructor.newInstance (arg_value);
} else if (oi.base_type.isEnum()) {
@SuppressWarnings({"unchecked","rawtypes"})
Object tmpVal = getEnumValue ((Class<? extends Enum>)oi.base_type,
arg_value);
val = tmpVal;
} else {
if (oi.factory == null) {
throw new Error("No constructor or factory for argument " + arg_name);
}
val = oi.factory.invoke (null, arg_value);
}
} catch (Exception e) {
throw new ArgException ("Invalid argument (%s) for argument %s",
arg_value, arg_name);
}
assert val != null : "@SuppressWarnings(nullness)";
return val;
}
/**
* Behaves like {@link java.lang.Enum#valueOf}, except that <code>name</code>
* is case insensitive and hyphen insensitive (hyphens can be used in place of
* underscores). This allows for greater flexibility when specifying enum
* types as command-line arguments.
*/
private <T extends Enum<T>> T getEnumValue(Class<T> enumType, String name) {
T[] constants = enumType.getEnumConstants();
if (constants == null)
throw new IllegalArgumentException(enumType.getName() + " is not an enum type");
for (T constant : constants)
if (((Enum<?>) constant).name().equalsIgnoreCase(name.replace('-', '_')))
return constant;
// same error that's thrown by Enum.valueOf()
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
/**
* Returns a short name for the specified type for use in messages.
*/
private static String type_short_name (Class<?> type) {
if (type.isPrimitive())
return type.getName();
else if (type == File.class)
return "filename";
else if (type == Pattern.class)
return "regex";
else if (type.isEnum())
return ("enum");
else
return UtilMDE.unqualified_name (type.getName()).toLowerCase();
}
/**
* Returns a string containing all of the options that were set and their
* arguments. This is essentially the contents of args[] with all
* non-options removed.
* @see #settings()
*/
public String get_options_str() {
return (options_str);
}
/**
* Returns a string containing the current setting for each option, in a
* format that can be parsed by Options. This differs from
* get_options_str() in that it contains each known option exactly once:
* it never contains duplicates, and it contains every known option even
* if the option was not specified on the command line.
*/
public String settings () {
return settings(false);
}
/**
* Returns a string containing the current setting for each option, in a
* format that can be parsed by Options. This differs from
* get_options_str() in that it contains each known option exactly once:
* it never contains duplicates, and it contains every known option even
* if the option was not specified on the command line.
*
* @param include_unpublicized If true, treat all unpublicized options
* and option groups as publicized
*/
public String settings (boolean include_unpublicized) {
StringBuilderDelimited out = new StringBuilderDelimited(eol);
// Determine the length of the longest name
int max_len = max_opt_len(options, include_unpublicized);
// Create the settings string
for (OptionInfo oi : options) {
String use = String.format ("%-" + max_len + "s = ", oi.long_name);
try {
use += oi.field.get (oi.obj);
} catch (Exception e) {
throw new Error ("unexpected exception reading field " + oi.field, e);
}
out.append(use);
}
return out.toString();
}
/**
* Returns a description of all of the known options.
* Each option is described on its own line in the output.
*/
public String toString() {
StringBuilderDelimited out = new StringBuilderDelimited(eol);
for (OptionInfo oi: options) {
out.append(oi);
}
return out.toString();
}
/**
* Exceptions encountered during argument processing.
*/
public static class ArgException extends Exception {
static final long serialVersionUID = 20051223L;
public ArgException (String s) { super (s); }
public ArgException (String format, /*@Nullable*/ Object... args) {
super (String.format (format, args));
}
}
private static class ParseResult {
/*@Nullable*/ String short_name;
/*@Nullable*/ String type_name;
String description;
ParseResult(/*@Nullable*/ String short_name, /*@Nullable*/ String type_name, String description) {
this.short_name = short_name;
this.type_name = type_name;
this.description = description;
}
}
/**
* Parse an option value and return its three components (short_name,
* type_name, and description). The short_name and type_name are null
* if they are not specified in the string.
*/
private static ParseResult parse_option (String val) {
// Get the short name, long name, and description
String short_name;
String type_name;
/*@NonNull*/ String description;
// Get the short name (if any)
if (val.startsWith("-")) {
assert val.substring(2,3).equals(" ");
short_name = val.substring (1, 2);
description = val.substring (3);
} else {
short_name = null;
description = val;
}
// Get the type name (if any)
if (description.startsWith ("<")) {
type_name = description.substring (1).replaceFirst (">.*", "");
description = description.replaceFirst ("<.*> ", "");
} else {
type_name = null;
}
// Return the result
return new ParseResult(short_name, type_name, description);
}
// /**
// * Test class with some defined arguments.
// */
// private static class Test {
//
// @Option ("generic") List<Pattern> lp = new ArrayList<Pattern>();
// @Option ("-a <filename> argument 1") String arg1 = "/tmp/foobar";
// @Option ("argument 2") String arg2;
// @Option ("-d double value") double temperature;
// @Option ("-f the input file") File input_file;
// }
//
// /**
// * Simple example
// */
// private static void main (String[] args) throws ArgException {
//
// Options options = new Options ("test", new Test());
// System.out.printf ("Options:%n%s", options);
// options.parse_or_usage (args);
// System.out.printf ("Results:%n%s", options.settings());
// }
}