001 // The four files
002 // Option.java
003 // OptionGroup.java
004 // Options.java
005 // Unpublicized.java
006 // together comprise the implementation of command-line processing.
007
008 package daikon.util;
009
010 import java.io.*;
011 import java.util.*;
012 import java.util.regex.*;
013 import java.lang.reflect.*;
014 import java.lang.annotation.*;
015 import com.sun.javadoc.Doc;
016 import com.sun.javadoc.RootDoc;
017 import com.sun.javadoc.ClassDoc;
018 import com.sun.javadoc.FieldDoc;
019 import com.sun.javadoc.Tag;
020 import com.sun.javadoc.SeeTag;
021
022 /**
023 * The Options class parses command-line options and sets fields in your
024 * program accordingly. Each field that is annotated with @{@link
025 * plume.Option} is set from a command-line argument of the same name.
026 * The Options class can also create usage messages and HTML documentation.
027 * The Options class interprets annotations and Javadoc comments, so that
028 * you do not have to write duplicative, boilerplate code and
029 * documentation that could get out of sync with the rest of your program.
030 * <p>
031 *
032 * The main entry point is {@link #parse_or_usage(String[])}.
033 * Typical use in your program is:
034 * <!-- Maybe expand this example a bit. -->
035 * <pre>
036 * public static void main(String[] args) {
037 * MyProgram myInstance = new MyProgram();
038 * Options options = new Options("MyProgram [options] infile outfile",
039 * myInstance, MyUtilityClass.class);
040 * // Sets fields in object myInstance, and sets static fields in
041 * // class MyUtilityClass.
042 * // Returns the original command line, with all options removed.
043 * String[] file_args = options.parse_or_usage (args);
044 * ...
045 * </pre>
046 *
047 * The @{@link Option} annotation on a field specifies user documentation
048 * and, optionally, a one-character short name that users may supply on the
049 * command line. The long name is taken from the name of the variable;
050 * when the name contains an underscore, the user may substitute a hyphen on
051 * the command line instead. <p>
052 *
053 * On the command line, the values for options are specified in the form
054 * '--longname=value', '-shortname=value', '--longname value', or '-shortname
055 * value'. If {@link #use_single_dash(boolean)} is true, then the long names
056 * take the form '-longname=value' or '-longname value'. The value is
057 * mandatory for all options except booleans. Booleans are set to true if no
058 * value is specified. <p>
059 *
060 * All arguments that start with '-' are processed as options. To
061 * terminate option processing at the first non-option argument, see {@link
062 * #parse_options_after_arg(boolean)}. Also, the special option '--'
063 * terminates option processing; all subsequent arguments are passed to the
064 * program (along with any preceding non-option arguments) without being
065 * scanned for options. <p>
066 *
067 * A user may provide an option multiple times on the command line. If the
068 * field is a list, each entry is be added to the list. If the field is
069 * not a list, then only the last occurrence is used (subsequent
070 * occurrences overwrite the previous value). <p>
071 *
072 * <b>Unpublicized options</b> <p>
073 * The @{@link Unpublicized} annotation causes an option not to be displayed
074 * in the usage message. This can be useful for options that are
075 * preliminary, experimental, or for internal purposes only. The @{@link
076 * Unpublicized} annotation must be specified in addition to the @{@link
077 * Option} annotation. <p>
078 *
079 * <b>Option groups</b> <p>
080 * The @{@link OptionGroup} annotation can be used to assign a name to a set of
081 * related options. This is useful for providing organization when working with
082 * many options. Options in the same group are displayed under the same heading
083 * in usage texts. Option groups themselves can be unpublicized causing the
084 * set of options belonging to the group to not be displayed in the default
085 * usage message. <p>
086 *
087 * The @{@link OptionGroup} annotation must be specified on a field in addition
088 * to an @{@link Option} annotation. The <code>@OptionGroup</code> annotation
089 * acts like a delimiter—all <code>@Option</code>-annotated fields up to
090 * the next <code>@OptionGroup</code> annotation belong to the same group.
091 * When using option groups, the first <code>@Option</code>-annotated field of
092 * every class and object passed to the {@link #Options(String, Object...)}
093 * constructor must have an <code>@OptionGroup</code> annotation. Furthermore,
094 * the first parameter of an <code>@OptionGroup</code> annotation (the group
095 * name) must be unique among all classes and objects passed to the {@link
096 * #Options(String, Object...)} constructor. <p>
097 *
098 * When an @{@link Unpublicized} annotation is used on a field that is in an
099 * unpublicized option group, that field is excluded in <b>all</b> usage
100 * messages, even when passing the group's name explicitly as a parameter to
101 * {@link #usage(String...)}. <p>
102 *
103 * <b>Option aliases</b> <p>
104 * The @{@link Option} annotation has an optional parameter <code>aliases</code>
105 * which accepts an array of strings. Each string in the array is an alias for
106 * the option being defined and can be used in place of an option's long name
107 * or short name. Aliases should start with a single dash or double dash. It
108 * is the user's responsibility to ensure that aliases does not cause ambiguity
109 * and do not collide with other options. <p>
110 *
111 * Note that parameters must be named when passing more than one parameter to
112 * an annotation, as in the following examples. <p>
113 * For option groups:
114 * <pre>
115 * @OptionGroup(value="Debugging Options", unpublicized=true)
116 * </pre>
117 * For option aliases:
118 * <pre>
119 * @Option(value="-h Print the detailed help", aliases={"-help", "--help"})
120 * </pre>
121 *
122 * <b>Supported field types</b> <p>
123 * The field may be of the following types:
124 * <ul>
125 * <li>Primitive types: boolean, int, long, float, double.
126 * (Primitives can also be represented as wrappers (Boolean,
127 * Integer, Long, Float, Double). Use of a wrapper type allows the
128 * argument to have no default value.)
129 * <li>Reference types that have a constructor with a single string
130 * parameter.
131 * <li>java.util.regex.Pattern.
132 * <li>Lists of any supported reference type. Lists must be initialized
133 * to a valid list (e.g., the empty list) before using Options on
134 * that list.
135 * </ul> <p>
136 *
137 * <b>Example:</b> <p>
138 *
139 * <!-- Example needs some more words of explanation and example command lines. -->
140 * <!-- Given this code: --> <pre>
141 *
142 * public static class Test {
143 *
144 * @Option ("-o <filename> the output file ")
145 * public static File outfile = new File("/tmp/foobar");
146 *
147 * @Option ("-i ignore case")
148 * public static boolean ignore_case;
149 *
150 * @Option ("-t set the initial temperature")
151 * public static double temperature = 75.0;
152 *
153 * public static void main (String[] args) {
154 * Options options = new Options ("Test [options] files", new Test());
155 * String[] file_args = options.parse_or_usage (args);
156 * }
157 * }
158 *</pre>
159 *
160 * For an example of this library being used in practice see {@link
161 * plume.Lookup}. <p>
162 *
163 * <b>Limitations</b> <ul>
164 *
165 * <li> Short options are only supported as separate entries
166 * (e.g., "-a -b") and not as a single group (e.g., "-ab").
167 *
168 * <li> Not all primitive types are supported.
169 *
170 * <li> Types without a string constructor are not supported.
171 *
172 * <li> The "--no-long" option to turn off a boolean option named "long"
173 * is not supported; use "--long=false" instead.
174 *
175 * </ul>
176 *
177 * <b>Possible enhancements</b> <ul>
178 * <li> Positional arguments (non-options that must be provided in a given
179 * order) could be supported.
180 * </ul>
181 *
182 * @see plume.Option
183 * @see plume.OptionGroup
184 * @see plume.Unpublicized
185 **/
186 public class Options {
187
188 @SuppressWarnings("nullness") // line.separator property always exists
189 private static String eol = System.getProperty("line.separator");
190
191 /** Information about an option **/
192 private class OptionInfo {
193
194 /** Field containing the value of the option **/
195 Field field;
196
197 /** Option information for the field **/
198 Option option;
199
200 /** Object containing the field. Null if the field is static. **/
201 /*@Nullable*/ Object obj;
202
203 /** Short (one character) argument name **/
204 /*@Nullable*/ String short_name;
205
206 /** Long argument name **/
207 String long_name;
208
209 /** Aliases for this option **/
210 String[] aliases;
211
212 /** Argument description **/
213 String description;
214
215 /** Javadoc description **/
216 /*@Nullable*/ String jdoc;
217
218 /**
219 * Name of the argument type. Defaults to the type of the field, but
220 * user can override this in the option string.
221 */
222 String type_name;
223
224 /**
225 * Class type of this field. If the field is a list, the basetype
226 * of the list.
227 */
228 Class<?> base_type;
229
230 /** Default value of the option as a string **/
231 /*@Nullable*/ String default_str = null;
232
233 /** If the option is a list, this references that list. **/
234 /*@LazyNonNull*/ List<Object> list = null;
235
236 /** Constructor that takes one String for the type **/
237 /*@Nullable*/ Constructor<?> constructor = null;
238
239 /** Factory that takes a string (some classes don't have a string constructor) and always returns non-null. */
240 /*@Nullable*/ Method factory = null;
241
242 /**
243 * If true, this OptionInfo is not output when printing documentation.
244 * @see #usage()
245 */
246 boolean unpublicized;
247
248 /**
249 * Create the specified option. If obj is null, the field must be
250 * static. The short name, type name, and description are taken
251 * from the option annotation. The long name is the name of the
252 * field. The default value is the current value of the field.
253 */
254 OptionInfo (Field field, Option option, /*@Nullable*/ Object obj, boolean unpublicized) {
255 this.field = field;
256 this.option = option;
257 this.obj = obj;
258 this.base_type = field.getType();
259 this.unpublicized = unpublicized;
260 this.aliases = option.aliases();
261
262 // The long name is the name of the field
263 long_name = field.getName();
264 if (use_dashes)
265 long_name = long_name.replace ('_', '-');
266
267 // Get the default value (if any)
268 Object default_obj = null;
269 if (!Modifier.isPublic (field.getModifiers()))
270 throw new Error ("option field is not public: " + field);
271 try {
272 default_obj = field.get (obj);
273 if (default_obj != null)
274 default_str = default_obj.toString();
275 } catch (Exception e) {
276 throw new Error ("Unexpected error getting default for " + field, e);
277 }
278
279 // Handle lists. When a list argument is specified multiple times,
280 // each argument value is appended to the list.
281 Type gen_type = field.getGenericType();
282 if (gen_type instanceof ParameterizedType) {
283 ParameterizedType pt = (ParameterizedType) gen_type;
284 Type raw_type = pt.getRawType();
285 if (!raw_type.equals (List.class))
286 throw new Error ("@Option does not support type " + pt + " for field " + field);
287 if (default_obj == null)
288 throw new Error ("List option " + field + " must be initialized");
289 @SuppressWarnings("unchecked")
290 List<Object> default_obj_as_list = (List<Object>) default_obj;
291 this.list = default_obj_as_list;
292 // System.out.printf ("list default = %s%n", list);
293 this.base_type = (Class<?>) pt.getActualTypeArguments()[0];
294
295 // System.out.printf ("Param type for %s = %s%n", field, pt);
296 // System.out.printf ("raw type = %s, type = %s%n", pt.getRawType(),
297 // pt.getActualTypeArguments()[0]);
298 }
299
300 // Get the short name, type name, and description from the annotation
301 ParseResult pr = parse_option (option.value());
302 short_name = pr.short_name;
303 if (pr.type_name != null) {
304 type_name = pr.type_name;
305 } else {
306 type_name = type_short_name (base_type);
307 if (list != null)
308 type_name += "[]";
309 }
310 description = pr.description;
311
312 // Get a constructor for non-primitive base types
313 if (!base_type.isPrimitive() && !base_type.isEnum()) {
314 try {
315 if (base_type == Pattern.class) {
316 factory = Pattern.class.getMethod ("compile", String.class);
317 } else { // look for a string constructor
318 assert base_type != null; // nullness checker: problem with flow
319 constructor = base_type.getConstructor (String.class);
320 }
321 } catch (Exception e) {
322 throw new Error ("Option " + field
323 + " does not have a string constructor", e);
324 }
325 }
326 }
327
328 /**
329 * Returns whether or not this option has a required argument.
330 */
331 public boolean argument_required() {
332 Class<?> type = field.getType();
333 return ((type != Boolean.TYPE) && (type != Boolean.class));
334 }
335
336 /**
337 * Returns a short synopsis of the option in the form
338 * -s --long=<type>
339 * <strong>or</strong>
340 * -s -long=<type>
341 * if use_single_dash is true.
342 **/
343 public String synopsis() {
344 String prefix = use_single_dash ? "-" : "--";
345 String name = prefix + long_name;
346 if (short_name != null)
347 name = String.format ("-%s %s", short_name, name);
348 name += String.format ("=<%s>", type_name);
349 return (name);
350 }
351
352 /**
353 * Returns a one-line description of the option.
354 */
355 public String toString() {
356 String prefix = use_single_dash ? "-" : "--";
357 String short_name_str = "";
358 if (short_name != null)
359 short_name_str = "-" + short_name + " ";
360 return String.format ("%s%s%s field %s", short_name_str, prefix,
361 long_name, field);
362 }
363
364 /** Returns the class that declares this option. **/
365 public Class<?> get_declaring_class() {
366 return field.getDeclaringClass();
367 }
368 }
369
370 /** Information about an option group **/
371 private class OptionGroupInfo {
372
373 /** The name of this option group **/
374 String name;
375
376 /** If true, this group of options will not be printed in usage output by
377 * default. However, the usage information for this option group can be
378 * printed by specifying the group explicitly in the call to {@link
379 * #usage}.
380 */
381 boolean unpublicized;
382
383 /** List of options that belong to this group **/
384 List<OptionInfo> optionList;
385
386 OptionGroupInfo(String name, boolean unpublicized) {
387 optionList = new ArrayList<OptionInfo>();
388 this.name = name;
389 this.unpublicized = unpublicized;
390 }
391
392 OptionGroupInfo(OptionGroup optionGroup) {
393 optionList = new ArrayList<OptionInfo>();
394 this.name = optionGroup.value();
395 this.unpublicized = optionGroup.unpublicized();
396 }
397
398 }
399
400
401 /**
402 * Whether to parse options after a non-option command-line argument.
403 * @see #parse_options_after_arg(boolean)
404 **/
405 private boolean parse_options_after_arg = true;
406
407 /** All of the argument options as a single string **/
408 private String options_str = "";
409
410 /** First specified class. Void stands for "not yet initialized". **/
411 private Class<?> main_class = Void.TYPE;
412
413 /** List of all of the defined options **/
414 private List<OptionInfo> options = new ArrayList<OptionInfo>();
415
416 /** Map from short or long option names (with leading dashes) to option information **/
417 private Map<String,OptionInfo> name_map
418 = new LinkedHashMap<String,OptionInfo>();
419
420 /** Map from option group name to option group information **/
421 private Map<String, OptionGroupInfo> group_map
422 = new LinkedHashMap<String, OptionGroupInfo>();
423
424 /**
425 * If, after the Options constructor is called, use_groups is true, then the
426 * user is using @OptionGroup annotations correctly (as per the requirement
427 * specified above). If false, then @OptionGroup annotations have not been
428 * specified on any @Option-annotated fields. When @OptionGroup annotations
429 * are used incorrectly, an Error is thrown by the Options constructor.
430 */
431 private boolean use_groups;
432
433 /**
434 * Convert underscores to dashes in long options in usage messages. Users
435 * may specify either the underscore or dashed name on the command line.
436 */
437 private boolean use_dashes = true;
438
439 /**
440 * When true, long options take the form -longOption with a single dash,
441 * rather than the default --longOption with two dashes.
442 */
443 private boolean use_single_dash = false;
444
445 @Option ("Split arguments to lists on blanks")
446 public static boolean split_lists = false;
447
448 /**
449 * Synopsis of usage. Example: "prog [options] arg1 arg2 ..."
450 * <p>
451 * This variable is public so that clients can reset it (useful for
452 * masquerading as another program, based on parsed options).
453 **/
454 public /*@Nullable*/ String usage_synopsis = null;
455
456 // Debug loggers
457 private SimpleLog debug_options = new SimpleLog (false);
458
459 /**
460 * Prepare for option processing. Creates an object that will set fields
461 * in all the given arguments. An argument to this method may be a
462 * Class, in which case its static fields are set. The names of all the
463 * options (that is, the fields annotated with @{@link Option}) must be
464 * unique across all the arguments.
465 */
466 public Options (Object... args) {
467 this ("", args);
468 }
469
470 /**
471 * Prepare for option processing. Creates an object that will set fields
472 * in all the given arguments. An argument to this method may be a
473 * Class, in which case its static fields are set. The names of all the
474 * options (that is, the fields annotated with @{@link Option}) must be
475 * unique across all the arguments.
476 * @param usage_synopsis A synopsis of how to call your program
477 */
478 public Options (String usage_synopsis, Object... args) {
479
480 if (args.length == 0) {
481 throw new Error("Must pass at least one object to Options constructor");
482 }
483
484 this.usage_synopsis = usage_synopsis;
485
486 this.use_groups = false;
487
488 // true once the first @Option annotation is observed, false until then.
489 boolean seen_first_opt = false;
490
491 // Loop through each specified object or class
492 for (Object obj : args) {
493 boolean is_class = obj instanceof Class<?>;
494 String current_group = null;
495
496 Field[] fields;
497 if (is_class) {
498 if (main_class == Void.TYPE)
499 main_class = (Class<?>) obj;
500 fields = ((Class<?>) obj).getDeclaredFields();
501 } else {
502 if (main_class == Void.TYPE)
503 main_class = obj.getClass();
504 fields = obj.getClass().getDeclaredFields();
505 }
506
507 for (Field f : fields) {
508 debug_options.log ("Considering field %s of object %s with annotations %s%n",
509 f, obj, Arrays.toString(f.getDeclaredAnnotations()));
510 Option option = safeGetAnnotation(f, Option.class);
511 if (option == null)
512 continue;
513
514 boolean unpublicized = safeGetAnnotation(f, Unpublicized.class) != null;
515
516 if (is_class && !Modifier.isStatic (f.getModifiers()))
517 throw new Error ("non-static option " + f + " in class " + obj);
518
519 OptionInfo oi = new OptionInfo(f, option, is_class ? null : obj, unpublicized);
520 options.add(oi);
521
522 OptionGroup optionGroup = safeGetAnnotation(f, OptionGroup.class);
523
524 if (!seen_first_opt) {
525 seen_first_opt = true;
526 // This is the first @Option annotation encountered so we can decide
527 // now if the user intends to use option groups.
528 if (optionGroup != null)
529 use_groups = true;
530 else
531 continue;
532 }
533
534 if (!use_groups) {
535 if (optionGroup != null)
536 // The user included an @OptionGroup annotation in their code
537 // without including an @OptionGroup annotation on the first
538 // @Option-annotated field, hence violating the requirement.
539
540 // NOTE: changing this error string requires changes to TestPlume
541 throw new Error("missing @OptionGroup annotation on the first " +
542 "@Option-annotated field of class " + main_class);
543 else
544 continue;
545 }
546
547 // use_groups is true at this point. The variable current_group is set
548 // to null at the start of every iteration through 'args'. This is so
549 // we can check that the first @Option-annotated field of every
550 // class/object in 'args' has an @OptionGroup annotation when use_groups
551 // is true, as required.
552 if (current_group == null && optionGroup == null) {
553 // NOTE: changing this error string requires changes to TestPlume
554 throw new Error("missing @OptionGroup annotation in field "
555 + f + " of class " + obj);
556 } else if (optionGroup != null) {
557 String name = optionGroup.value();
558 if (group_map.containsKey(name))
559 throw new Error("option group " + name + " declared twice");
560 OptionGroupInfo gi = new OptionGroupInfo(optionGroup);
561 group_map.put(name, gi);
562 current_group = name;
563 } // current_group is non-null at this point
564 @SuppressWarnings("nullness") // map key
565 /*@NonNull*/ OptionGroupInfo ogi = group_map.get(current_group);
566 ogi.optionList.add(oi);
567
568 } // loop through fields
569 } // loop through args
570
571 String prefix = use_single_dash ? "-" : "--";
572
573 // Add each option to the option name map
574 for (OptionInfo oi : options) {
575 if (oi.short_name != null) {
576 if (name_map.containsKey ("-" + oi.short_name))
577 throw new Error ("short name " + oi + " appears twice");
578 name_map.put ("-" + oi.short_name, oi);
579 }
580 if (name_map.containsKey (prefix + oi.long_name))
581 throw new Error ("long name " + oi + " appears twice");
582 name_map.put (prefix + oi.long_name, oi);
583 if (use_dashes && oi.long_name.contains ("-"))
584 name_map.put (prefix + oi.long_name.replace ('-', '_'), oi);
585 if (oi.aliases.length > 0) {
586 for (String alias : oi.aliases) {
587 if (name_map.containsKey (alias))
588 throw new Error ("alias " + oi + " appears twice");
589 name_map.put (alias, oi);
590 }
591 }
592 }
593 }
594
595 /**
596 * Like getAnnotation, but returns null (and prints a warning) rather
597 * than throwing an exception.
598 */
599 private <T extends Annotation> /*@Nullable*/ T
600 safeGetAnnotation(Field f, Class<T> annotationClass) {
601 /*@Nullable*/ T annotation;
602 try {
603 annotation = f.getAnnotation(annotationClass);
604 } catch (Exception e) {
605 // Can get
606 // java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
607 // when an annotation is not present at run time (example: @NonNull)
608 System.out.printf("Exception in call to f.getAnnotation(%s)%n for f=%s%n"
609 + " %s%nClasspath =%n", annotationClass, f, e.getMessage());
610 //e.printStackTrace();
611 JWhich.printClasspath();
612 annotation = null;
613 }
614
615 return annotation;
616 }
617
618 /**
619 * If true, Options will parse arguments even after a non-option
620 * command-line argument. Setting this to true is useful to permit users
621 * to write options at the end of a command line. Setting this to false
622 * is useful to avoid processing arguments that are actually
623 * options/arguments for another program that this one will invoke.
624 */
625 public void parse_options_after_arg (boolean val) {
626 parse_options_after_arg = val;
627 }
628
629 /** @deprecated Use {@link #parse_options_after_arg(boolean)}. */
630 @Deprecated
631 public void ignore_options_after_arg (boolean val) {
632 parse_options_after_arg = !val;
633 }
634
635 /**
636 * If true, long options (those derived from field names) will be parsed with
637 * a single dash prefix as in -longOption. The default is false and long
638 * options will be parsed with a double dash prefix as in --longOption.
639 */
640 public void use_single_dash (boolean val) {
641 use_single_dash = val;
642 }
643
644 /**
645 * Parses a command line and sets the options accordingly.
646 * @return all non-option arguments
647 * @throws ArgException if the command line contains unknown option or
648 * misused options.
649 */
650 public String[] parse (String[] args) throws ArgException {
651
652 List<String> non_options = new ArrayList<String>();
653 // If true, then "--" has been seen and any argument starting with "-"
654 // is processed as an ordinary argument, not as an option.
655 boolean ignore_options = false;
656
657 // Loop through each argument
658 for (int ii = 0; ii < args.length; ii++) {
659 String arg = args[ii];
660 if (arg.equals ("--")) {
661 ignore_options = true;
662 } else if ((arg.startsWith ("--") || arg.startsWith("-")) && !ignore_options) {
663 String arg_name;
664 String arg_value;
665 int eq_pos = arg.indexOf ('=');
666 if (eq_pos == -1) {
667 arg_name = arg;
668 arg_value = null;
669 } else {
670 arg_name = arg.substring (0, eq_pos);
671 arg_value = arg.substring (eq_pos+1);
672 }
673 OptionInfo oi = name_map.get (arg_name);
674 if (oi == null) {
675 StringBuilder msg = new StringBuilder();
676 msg.append(String.format("unknown option name '%s' in arg '%s'",
677 arg_name, arg));
678 if (false) { // for debugging
679 msg.append("; known options:");
680 for (String option_name : UtilMDE.sortedKeySet(name_map)) {
681 msg.append(" ");
682 msg.append(option_name);
683 }
684 }
685 throw new ArgException (msg.toString());
686 }
687 if (oi.argument_required() && (arg_value == null)) {
688 ii++;
689 if (ii >= args.length)
690 throw new ArgException ("option %s requires an argument", arg);
691 arg_value = args[ii];
692 }
693 // System.out.printf ("arg_name = '%s', arg_value='%s'%n", arg_name,
694 // arg_value);
695 set_arg (oi, arg_name, arg_value);
696 } else { // not an option
697 if (! parse_options_after_arg)
698 ignore_options = true;
699 non_options.add (arg);
700 }
701
702 }
703 String[] result = non_options.toArray (new String[non_options.size()]);
704 return result;
705 }
706
707 /**
708 * Parses a command line and sets the options accordingly. This method
709 * splits the argument string into command line arguments, respecting
710 * single and double quotes, then calls parse(String[]).
711 * @return all non-option arguments
712 * @throws ArgException if the command line contains unknown option or
713 * misused options.
714 * @see #parse(String[])
715 */
716 public String[] parse (String args) throws ArgException {
717
718 // Split the args string on whitespace boundaries accounting for quoted
719 // strings.
720 args = args.trim();
721 List<String> arg_list = new ArrayList<String>();
722 String arg = "";
723 char active_quote = 0;
724 for (int ii = 0; ii < args.length(); ii++) {
725 char ch = args.charAt (ii);
726 if ((ch == '\'') || (ch == '"')) {
727 arg+= ch;
728 ii++;
729 while ((ii < args.length()) && (args.charAt(ii) != ch))
730 arg += args.charAt(ii++);
731 arg += ch;
732 } else if (Character.isWhitespace (ch)) {
733 // System.out.printf ("adding argument '%s'%n", arg);
734 arg_list.add (arg);
735 arg = "";
736 while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii)))
737 ii++;
738 if (ii < args.length())
739 ii--;
740 } else { // must be part of current argument
741 arg += ch;
742 }
743 }
744 if (!arg.equals (""))
745 arg_list.add (arg);
746
747 String[] argsArray = arg_list.toArray (new String[arg_list.size()]);
748 return parse (argsArray);
749 }
750
751 /**
752 * Parses a command line and sets the options accordingly. If an error
753 * occurs, prints the usage and terminates the program. The program is
754 * terminated rather than throwing an error to create cleaner output.
755 * @return all non-option arguments
756 * @see #parse(String[])
757 */
758 public String[] parse_or_usage (String[] args) {
759
760 String non_options[] = null;
761
762 try {
763 non_options = parse (args);
764 } catch (ArgException ae) {
765 String message = ae.getMessage();
766 if (message != null) {
767 print_usage (message);
768 } else {
769 print_usage ();
770 }
771 System.exit (-1);
772 // throw new Error ("usage error: ", ae);
773 }
774 return (non_options);
775 }
776
777 /**
778 * Parses a command line and sets the options accordingly. If an error
779 * occurs, prints the usage and terminates the program. The program is
780 * terminated rather than throwing an error to create cleaner output.
781 * This method splits the argument string into command line arguments,
782 * respecting single and double quotes, then calls parse_or_usage(String[]).
783 * @return all non-option arguments
784 * @see #parse_or_usage(String[])
785 */
786 public String[] parse_or_usage (String args) {
787
788 String non_options[] = null;
789
790 try {
791 non_options = parse (args);
792 } catch (ArgException ae) {
793 String message = ae.getMessage();
794 if (message != null) {
795 print_usage (message);
796 } else {
797 print_usage ();
798 }
799 System.exit (-1);
800 // throw new Error ("usage error: ", ae);
801 }
802 return (non_options);
803 }
804
805 /** @deprecated Use {@link #parse_or_usage(String[])}. */
806 @Deprecated
807 public String[] parse_and_usage (String[] args) {
808 return parse_or_usage(args);
809 }
810
811 /** @deprecated Use {@link #parse_or_usage(String)}. */
812 @Deprecated
813 public String[] parse_and_usage (String args) {
814 return parse_or_usage(args);
815 }
816
817
818 /// This is a lot of methods, but it does save a tad of typing for the
819 /// programmer.
820
821 /**
822 * Prints usage information. Uses the usage synopsis passed into the
823 * constructor, if any.
824 */
825 public void print_usage (PrintStream ps) {
826 if (usage_synopsis != null) {
827 ps.printf ("Usage: %s%n", usage_synopsis);
828 }
829 ps.println(usage());
830 }
831
832 /**
833 * Prints, to standard output, usage information.
834 */
835 public void print_usage () {
836 print_usage (System.out);
837 }
838
839 // This method is distinct from
840 // print_usage (PrintStream ps, String format, Object... args)
841 // because % characters in the message are not interpreted.
842 /**
843 * Prints a message followed by indented usage information.
844 * The message is printed in addition to (not replacing) the usage synopsis.
845 **/
846 public void print_usage (PrintStream ps, String msg) {
847 ps.println (msg);
848 print_usage (ps);
849 }
850
851 /**
852 * Prints, to standard output, a message followed by usage information.
853 * The message is printed in addition to (not replacing) the usage synopsis.
854 **/
855 public void print_usage (String msg) {
856 print_usage (System.out, msg);
857 }
858
859 /**
860 * Prints a message followed by usage information.
861 * The message is printed in addition to (not replacing) the usage synopsis.
862 */
863 public void print_usage (PrintStream ps, String format, /*@Nullable*/ Object... args) {
864 ps.printf (format, args);
865 if (! format.endsWith("%n")) {
866 ps.println();
867 }
868 print_usage (ps);
869 }
870
871 /**
872 * Prints, to standard output, a message followed by usage information.
873 * The message is printed in addition to (not replacing) the usage synopsis.
874 */
875 public void print_usage (String format, /*@Nullable*/ Object... args) {
876 print_usage(System.out, format, args);
877 }
878
879 /**
880 * Returns the String containing the usage message for command-line options.
881 * @param group_names The list of option groups to include in the usage
882 * message. If empty, will return usage for all option groups which are not
883 * unpublicized. Or if option groups are not being used, will return usage
884 * for all options which are not unpublicized.
885 */
886 public String usage(String... group_names) {
887 if (!use_groups) {
888 if (group_names.length > 0) {
889 throw new IllegalArgumentException(
890 "This instance of Options does not have any option groups defined");
891 }
892 return format_options(options, max_opt_len(options));
893 }
894
895 List<OptionGroupInfo> groups = new ArrayList<OptionGroupInfo>();
896 if (group_names.length > 0) {
897 for (String group_name : group_names) {
898 if (!group_map.containsKey(group_name))
899 throw new IllegalArgumentException("invalid option group: " + group_name);
900 else
901 groups.add(group_map.get(group_name));
902 }
903 } else { // return usage for all groups which are not unpublicized
904 for (OptionGroupInfo gi : group_map.values()) {
905 if (gi.unpublicized)
906 continue;
907 groups.add(gi);
908 }
909 }
910
911 List<Integer> lengths = new ArrayList<Integer>();
912 for (OptionGroupInfo gi : groups)
913 lengths.add(max_opt_len(gi.optionList));
914 int max_len = Collections.max(lengths);
915
916 StringBuilderDelimited buf = new StringBuilderDelimited(eol);
917 for (OptionGroupInfo gi : groups) {
918 buf.append(String.format("%n%s:", gi.name));
919 buf.append(format_options(gi.optionList, max_len));
920 }
921
922 return buf.toString();
923 }
924
925 /**
926 * Entry point for creating HTML documentation. HTML documentation includes
927 * unpublicized option groups but not <code>@Unpublicized</code> options.
928 */
929 public void jdoc (RootDoc doc) {
930 // Find the overall documentation (on the main class)
931 ClassDoc main = find_class_doc (doc, main_class);
932 if (main == null) {
933 throw new Error ("can't find main class " + main_class);
934 }
935
936 // Process each option and add in the javadoc info
937 for (OptionInfo oi : options) {
938 ClassDoc opt_doc = find_class_doc (doc, oi.get_declaring_class());
939 String nameWithUnderscores = oi.long_name.replace('-', '_');
940 if (opt_doc != null) {
941 for (FieldDoc fd : opt_doc.fields()) {
942 if (fd.name().equals (nameWithUnderscores)) {
943 oi.jdoc = format_comment(fd);
944 break;
945 }
946 }
947 }
948 }
949
950 // Write out the info as HTML
951 System.out.println (format_comment(main));
952 System.out.println ("<p>Command line options: </p>");
953 System.out.println ("<ul>");
954
955 if (!use_groups)
956 System.out.println(format_options_html(options, 2));
957 else {
958 for (OptionGroupInfo gi : group_map.values()) {
959 System.out.println(" <li>" + gi.name);
960 System.out.println(" <ul>");
961 System.out.println(format_options_html(gi.optionList, 6));
962 System.out.println(" </ul>");
963 System.out.println(" </li>");
964 }
965 }
966 System.out.println ("</ul>");
967 }
968
969 /*@Nullable*/ ClassDoc find_class_doc (RootDoc doc, Class<?> c) {
970
971 for (ClassDoc cd : doc.classes()) {
972 if (cd.qualifiedName().equals (c.getName())) {
973 return cd;
974 }
975 }
976 return (null);
977 }
978
979 /**
980 * Format a javadoc comment to HTML by wrapping the text of inline @link tags
981 * and block @see tags in HTML 'code' tags. This keeps most of the
982 * information in the comment while still being presentable. <p>
983 *
984 * This is only a temporary solution. Ideally, a custom doclet (perhaps
985 * subclassing HtmlDoclet) would be created which integrates command-line
986 * option documentation with the rest of the javadoc documentation for a
987 * project.
988 */
989 private String format_comment(Doc doc) {
990 StringBuilder buf = new StringBuilder();
991 Tag[] tags = doc.inlineTags();
992 for (Tag tag : tags) {
993 if (tag instanceof SeeTag)
994 buf.append("<code>" + tag.text() + "</code>");
995 else
996 buf.append(tag.text());
997 }
998 SeeTag[] seetags = doc.seeTags();
999 if (seetags.length > 0) {
1000 buf.append(" See: ");
1001 StringBuilderDelimited seebuf = new StringBuilderDelimited(", ");
1002 for (SeeTag tag : seetags)
1003 seebuf.append("<code>" + tag.text() + "</code>");
1004 buf.append(seebuf);
1005 buf.append(".");
1006 }
1007 return buf.toString();
1008 }
1009
1010 /**
1011 * Format a list of options for use in generating usage messages.
1012 */
1013 private String format_options(List<OptionInfo> opt_list, int max_len) {
1014 StringBuilderDelimited buf = new StringBuilderDelimited(eol);
1015 for (OptionInfo oi : opt_list) {
1016 if (oi.unpublicized)
1017 continue;
1018 String default_str = "[no default]";
1019 if (oi.default_str != null)
1020 default_str = String.format("[default %s]", oi.default_str);
1021 String use = String.format(" %-" + max_len + "s - %s %s",
1022 oi.synopsis(), oi.description, default_str);
1023 buf.append(use);
1024 }
1025 return buf.toString();
1026 }
1027
1028 /**
1029 * Format a list of options with HTML for use in generating the HTML
1030 * documentation.
1031 */
1032 private String format_options_html(List<OptionInfo> opt_list, int indent) {
1033 StringBuilderDelimited buf = new StringBuilderDelimited(eol);
1034 for (OptionInfo oi : opt_list) {
1035 if (oi.unpublicized)
1036 continue;
1037 String default_str = "[no default]";
1038 if (oi.default_str != null)
1039 default_str = String.format ("[default %s]", oi.default_str);
1040 String synopsis = oi.synopsis();
1041 synopsis = synopsis.replaceAll ("<", "<");
1042 synopsis = synopsis.replaceAll (">", ">");
1043 String alias_str = "";
1044 if (oi.aliases.length > 0) {
1045 Iterator<String> it = Arrays.asList(oi.aliases).iterator();
1046 StringBuilderDelimited b = new StringBuilderDelimited(", ");
1047 while (it.hasNext())
1048 b.append(String.format("<b>%s</b>", it.next()));
1049 alias_str = "<i>Aliases</i>: " + b.toString() + ". ";
1050 }
1051 buf.append(String.format("%" + indent + "s<li> <b>%s</b>. %s %s%s</li>",
1052 "", synopsis, oi.jdoc, alias_str, default_str));
1053 }
1054 return buf.toString();
1055 }
1056
1057 private int max_opt_len(List<OptionInfo> opt_list) {
1058 int max_len = 0;
1059 for (OptionInfo oi : opt_list) {
1060 if (oi.unpublicized)
1061 continue;
1062 int len = oi.synopsis().length();
1063 if (len > max_len)
1064 max_len = len;
1065 }
1066 return max_len;
1067 }
1068
1069 /**
1070 * Set the specified option to the value specified in arg_value. Throws
1071 * an ArgException if there are any errors.
1072 */
1073 private void set_arg (OptionInfo oi, String arg_name, /*@Nullable*/ String arg_value)
1074 throws ArgException {
1075
1076 Field f = oi.field;
1077 Class<?> type = oi.base_type;
1078
1079 // Keep track of all of the options specified
1080 if (options_str.length() > 0)
1081 options_str += " ";
1082 options_str += arg_name;
1083 if (arg_value != null) {
1084 if (! arg_value.contains (" ")) {
1085 options_str += "=" + arg_value;
1086 } else if (! arg_value.contains ("'")) {
1087 options_str += "='" + arg_value + "'";
1088 } else if (! arg_value.contains ("\"")) {
1089 options_str += "=\"" + arg_value + "\"";
1090 } else {
1091 throw new ArgException("Can't quote for interal debugging: " + arg_value);
1092 }
1093 }
1094 // Argument values are required for everything but booleans
1095 if (arg_value == null) {
1096 if ((type != Boolean.TYPE)
1097 || (type != Boolean.class)) {
1098 arg_value = "true";
1099 } else {
1100 throw new ArgException ("Value required for option " + arg_name);
1101 }
1102 }
1103
1104 try {
1105 if (type.isPrimitive()) {
1106 if (type == Boolean.TYPE) {
1107 boolean val;
1108 String arg_value_lowercase = arg_value.toLowerCase();
1109 if (arg_value_lowercase.equals ("true") || (arg_value_lowercase.equals ("t")))
1110 val = true;
1111 else if (arg_value_lowercase.equals ("false") || arg_value_lowercase.equals ("f"))
1112 val = false;
1113 else
1114 throw new ArgException ("Value \"%s\" for argument %s is not a boolean",
1115 arg_value, arg_name);
1116 arg_value = (val) ? "true" : "false";
1117 // System.out.printf ("Setting %s to %s%n", arg_name, val);
1118 f.setBoolean (oi.obj, val);
1119 } else if (type == Integer.TYPE) {
1120 int val;
1121 try {
1122 val = Integer.decode (arg_value);
1123 } catch (Exception e) {
1124 throw new ArgException ("Value \"%s\" for argument %s is not an integer",
1125 arg_value, arg_name);
1126 }
1127 f.setInt (oi.obj, val);
1128 } else if (type == Long.TYPE) {
1129 long val;
1130 try {
1131 val = Long.decode (arg_value);
1132 } catch (Exception e) {
1133 throw new ArgException ("Value \"%s\" for argument %s is not a long integer",
1134 arg_value, arg_name);
1135 }
1136 f.setLong (oi.obj, val);
1137 } else if (type == Float.TYPE) {
1138 Float val;
1139 try {
1140 val = Float.valueOf (arg_value);
1141 } catch (Exception e) {
1142 throw new ArgException ("Value \"%s\" for argument %s is not a float",
1143 arg_value, arg_name);
1144 }
1145 f.setFloat (oi.obj, val);
1146 } else if (type == Double.TYPE) {
1147 Double val;
1148 try {
1149 val = Double.valueOf (arg_value);
1150 } catch (Exception e) {
1151 throw new ArgException ("Value \"%s\" for argument %s is not a double",
1152 arg_value, arg_name);
1153 }
1154 f.setDouble (oi.obj, val);
1155 } else { // unexpected type
1156 throw new Error ("Unexpected type " + type);
1157 }
1158 } else { // reference type
1159
1160 // If the argument is a list, add repeated arguments or multiple
1161 // blank separated arguments to the list, otherwise just set the
1162 // argument value.
1163 if (oi.list != null) {
1164 if (split_lists) {
1165 String[] aarr = arg_value.split (" *");
1166 for (String aval : aarr) {
1167 Object val = get_ref_arg (oi, arg_name, aval);
1168 oi.list.add (val); // uncheck cast
1169 }
1170 } else {
1171 Object val = get_ref_arg (oi, arg_name, arg_value);
1172 oi.list.add (val);
1173 }
1174 } else {
1175 Object val = get_ref_arg (oi, arg_name, arg_value);
1176 f.set (oi.obj, val);
1177 }
1178 }
1179 } catch (ArgException ae) {
1180 throw ae;
1181 } catch (Exception e) {
1182 throw new Error ("Unexpected error ", e);
1183 }
1184 }
1185
1186 /**
1187 * Create an instance of the correct type by passing the argument value
1188 * string to the constructor. The only expected error is some sort
1189 * of parse error from the constructor.
1190 */
1191 private /*@NonNull*/ Object get_ref_arg (OptionInfo oi, String arg_name,
1192 String arg_value) throws ArgException {
1193
1194 Object val = null;
1195 try {
1196 if (oi.constructor != null) {
1197 val = oi.constructor.newInstance (arg_value);
1198 } else if (oi.base_type.isEnum()) {
1199 @SuppressWarnings({"unchecked","rawtypes"})
1200 Object tmpVal = Enum.valueOf ((Class<? extends Enum>)oi.base_type,
1201 arg_value);
1202 val = tmpVal;
1203 } else {
1204 if (oi.factory == null) {
1205 throw new Error("No constructor or factory for argument " + arg_name);
1206 }
1207 val = oi.factory.invoke (null, arg_value);
1208 }
1209 } catch (Exception e) {
1210 throw new ArgException ("Invalid argument (%s) for argument %s",
1211 arg_value, arg_name);
1212 }
1213
1214 assert val != null : "@SuppressWarnings(nullness)";
1215 return val;
1216 }
1217
1218 /**
1219 * Returns a short name for the specified type for use in messages.
1220 */
1221 private static String type_short_name (Class<?> type) {
1222
1223 if (type.isPrimitive())
1224 return type.getName();
1225 else if (type == File.class)
1226 return "filename";
1227 else if (type == Pattern.class)
1228 return "regex";
1229 else if (type.isEnum())
1230 return ("enum");
1231 else
1232 return UtilMDE.unqualified_name (type.getName()).toLowerCase();
1233 }
1234
1235 /**
1236 * Returns a string containing all of the options that were set and their
1237 * arguments. This is essentially the contents of args[] with all
1238 * non-options removed.
1239 * @see #settings()
1240 */
1241 public String get_options_str() {
1242 return (options_str);
1243 }
1244
1245 /**
1246 * Returns a string containing the current setting for each option, in a
1247 * format that can be parsed by Options. This differs from
1248 * get_options_str() in that it contains each known option exactly once:
1249 * it never contains duplicates, and it contains every known option even
1250 * if the option was not specified on the command line.
1251 */
1252 public String settings () {
1253 StringBuilderDelimited out = new StringBuilderDelimited(eol);
1254
1255 // Determine the length of the longest name
1256 int max_len = max_opt_len(options);
1257
1258 // Create the settings string
1259 for (OptionInfo oi : options) {
1260 String use = String.format ("%-" + max_len + "s = ", oi.long_name);
1261 try {
1262 use += oi.field.get (oi.obj);
1263 } catch (Exception e) {
1264 throw new Error ("unexpected exception reading field " + oi.field, e);
1265 }
1266 out.append(use);
1267 }
1268
1269 return out.toString();
1270 }
1271
1272 /**
1273 * Returns a description of all of the known options.
1274 * Each option is described on its own line in the output.
1275 */
1276 public String toString() {
1277 StringBuilderDelimited out = new StringBuilderDelimited(eol);
1278
1279 for (OptionInfo oi: options) {
1280 out.append(oi);
1281 }
1282
1283 return out.toString();
1284 }
1285
1286 /**
1287 * Exceptions encountered during argument processing.
1288 */
1289 public static class ArgException extends Exception {
1290 static final long serialVersionUID = 20051223L;
1291 public ArgException (String s) { super (s); }
1292 public ArgException (String format, /*@Nullable*/ Object... args) {
1293 super (String.format (format, args));
1294 }
1295 }
1296
1297
1298 private static class ParseResult {
1299 /*@Nullable*/ String short_name;
1300 /*@Nullable*/ String type_name;
1301 String description;
1302 ParseResult(/*@Nullable*/ String short_name, /*@Nullable*/ String type_name, String description) {
1303 this.short_name = short_name;
1304 this.type_name = type_name;
1305 this.description = description;
1306 }
1307 }
1308
1309
1310 /**
1311 * Parse an option value and return its three components (short_name,
1312 * type_name, and description). The short_name and type_name are null
1313 * if they are not specified in the string.
1314 */
1315 private static ParseResult parse_option (String val) {
1316
1317 // Get the short name, long name, and description
1318 String short_name;
1319 String type_name;
1320 /*@NonNull*/ String description;
1321
1322 // Get the short name (if any)
1323 if (val.startsWith("-")) {
1324 assert val.substring(2,3).equals(" ");
1325 short_name = val.substring (1, 2);
1326 description = val.substring (3);
1327 } else {
1328 short_name = null;
1329 description = val;
1330 }
1331
1332 // Get the type name (if any)
1333 if (description.startsWith ("<")) {
1334 type_name = description.substring (1).replaceFirst (">.*", "");
1335 description = description.replaceFirst ("<.*> ", "");
1336 } else {
1337 type_name = null;
1338 }
1339
1340 // Return the result
1341 return new ParseResult(short_name, type_name, description);
1342 }
1343
1344 // /**
1345 // * Test class with some defined arguments.
1346 // */
1347 // private static class Test {
1348 //
1349 // @Option ("generic") List<Pattern> lp = new ArrayList<Pattern>();
1350 // @Option ("-a <filename> argument 1") String arg1 = "/tmp/foobar";
1351 // @Option ("argument 2") String arg2;
1352 // @Option ("-d double value") double temperature;
1353 // @Option ("-f the input file") File input_file;
1354 // }
1355 //
1356 // /**
1357 // * Simple example
1358 // */
1359 // private static void main (String[] args) throws ArgException {
1360 //
1361 // Options options = new Options ("test", new Test());
1362 // System.out.printf ("Options:%n%s", options);
1363 // options.parse_or_usage (args);
1364 // System.out.printf ("Results:%n%s", options.settings());
1365 // }
1366
1367 }