001: package jargs.gnu;
002:
003: import java.text.NumberFormat;
004: import java.text.ParseException;
005: import java.util.Hashtable;
006: import java.util.Vector;
007: import java.util.Enumeration;
008: import java.util.Locale;
009:
010: /**
011: * Largely GNU-compatible command-line options parser. Has short (-v) and
012: * long-form (--verbose) option support, and also allows options with
013: * associated values (-d 2, --debug 2, --debug=2). Option processing
014: * can be explicitly terminated by the argument '--'.
015: *
016: * @author Steve Purcell
017: * @version $Revision: 1.10 $
018: * @see jargs.examples.gnu.OptionTest
019: */
020: public class CmdLineParser {
021:
022: /**
023: * Base class for exceptions that may be thrown when options are parsed
024: */
025: public static abstract class OptionException extends Exception {
026: OptionException(String msg) {
027: super (msg);
028: }
029: }
030:
031: /**
032: * Thrown when the parsed command-line contains an option that is not
033: * recognised. <code>getMessage()</code> returns
034: * an error string suitable for reporting the error to the user (in
035: * English).
036: */
037: public static class UnknownOptionException extends OptionException {
038: UnknownOptionException(String optionName) {
039: this (optionName, "Unknown option '" + optionName + "'");
040: }
041:
042: UnknownOptionException(String optionName, String msg) {
043: super (msg);
044: this .optionName = optionName;
045: }
046:
047: /**
048: * @return the name of the option that was unknown (e.g. "-u")
049: */
050: public String getOptionName() {
051: return this .optionName;
052: }
053:
054: private String optionName = null;
055: }
056:
057: /**
058: * Thrown when the parsed commandline contains multiple concatenated
059: * short options, such as -abcd, where one is unknown.
060: * <code>getMessage()</code> returns an english human-readable error
061: * string.
062: * @author Vidar Holen
063: */
064: public static class UnknownSuboptionException extends
065: UnknownOptionException {
066: private char suboption;
067:
068: UnknownSuboptionException(String option, char suboption) {
069: super (option, "Illegal option: '" + suboption + "' in '"
070: + option + "'");
071: this .suboption = suboption;
072: }
073:
074: public char getSuboption() {
075: return suboption;
076: }
077: }
078:
079: /**
080: * Thrown when the parsed commandline contains multiple concatenated
081: * short options, such as -abcd, where one or more requires a value.
082: * <code>getMessage()</code> returns an english human-readable error
083: * string.
084: * @author Vidar Holen
085: */
086: public static class NotFlagException extends UnknownOptionException {
087: private char notflag;
088:
089: NotFlagException(String option, char unflaggish) {
090: super (option, "Illegal option: '" + option + "', '"
091: + unflaggish + "' requires a value");
092: notflag = unflaggish;
093: }
094:
095: /**
096: * @return the first character which wasn't a boolean (e.g 'c')
097: */
098: public char getOptionChar() {
099: return notflag;
100: }
101: }
102:
103: /**
104: * Thrown when an illegal or missing value is given by the user for
105: * an option that takes a value. <code>getMessage()</code> returns
106: * an error string suitable for reporting the error to the user (in
107: * English).
108: */
109: public static class IllegalOptionValueException extends
110: OptionException {
111: public IllegalOptionValueException(Option opt, String value) {
112: super ("Illegal value '"
113: + value
114: + "' for option "
115: + (opt.shortForm() != null ? "-" + opt.shortForm()
116: + "/" : "") + "--" + opt.longForm());
117: this .option = opt;
118: this .value = value;
119: }
120:
121: /**
122: * @return the name of the option whose value was illegal (e.g. "-u")
123: */
124: public Option getOption() {
125: return this .option;
126: }
127:
128: /**
129: * @return the illegal value
130: */
131: public String getValue() {
132: return this .value;
133: }
134:
135: private Option option;
136: private String value;
137: }
138:
139: /**
140: * Representation of a command-line option
141: */
142: public static abstract class Option {
143:
144: protected Option(String longForm, boolean wantsValue) {
145: this (null, longForm, wantsValue);
146: }
147:
148: protected Option(char shortForm, String longForm,
149: boolean wantsValue) {
150: this (new String(new char[] { shortForm }), longForm,
151: wantsValue);
152: }
153:
154: private Option(String shortForm, String longForm,
155: boolean wantsValue) {
156: if (longForm == null)
157: throw new IllegalArgumentException(
158: "Null longForm not allowed");
159: this .shortForm = shortForm;
160: this .longForm = longForm;
161: this .wantsValue = wantsValue;
162: }
163:
164: public String shortForm() {
165: return this .shortForm;
166: }
167:
168: public String longForm() {
169: return this .longForm;
170: }
171:
172: /**
173: * Tells whether or not this option wants a value
174: */
175: public boolean wantsValue() {
176: return this .wantsValue;
177: }
178:
179: public final Object getValue(String arg, Locale locale)
180: throws IllegalOptionValueException {
181: if (this .wantsValue) {
182: if (arg == null) {
183: throw new IllegalOptionValueException(this , "");
184: }
185: return this .parseValue(arg, locale);
186: } else {
187: return Boolean.TRUE;
188: }
189: }
190:
191: /**
192: * Override to extract and convert an option value passed on the
193: * command-line
194: */
195: protected Object parseValue(String arg, Locale locale)
196: throws IllegalOptionValueException {
197: return null;
198: }
199:
200: private String shortForm = null;
201: private String longForm = null;
202: private boolean wantsValue = false;
203:
204: public static class BooleanOption extends Option {
205: public BooleanOption(char shortForm, String longForm) {
206: super (shortForm, longForm, false);
207: }
208:
209: public BooleanOption(String longForm) {
210: super (longForm, false);
211: }
212: }
213:
214: /**
215: * An option that expects an integer value
216: */
217: public static class IntegerOption extends Option {
218: public IntegerOption(char shortForm, String longForm) {
219: super (shortForm, longForm, true);
220: }
221:
222: public IntegerOption(String longForm) {
223: super (longForm, true);
224: }
225:
226: protected Object parseValue(String arg, Locale locale)
227: throws IllegalOptionValueException {
228: try {
229: return new Integer(arg);
230: } catch (NumberFormatException e) {
231: throw new IllegalOptionValueException(this , arg);
232: }
233: }
234: }
235:
236: /**
237: * An option that expects a long integer value
238: */
239: public static class LongOption extends Option {
240: public LongOption(char shortForm, String longForm) {
241: super (shortForm, longForm, true);
242: }
243:
244: public LongOption(String longForm) {
245: super (longForm, true);
246: }
247:
248: protected Object parseValue(String arg, Locale locale)
249: throws IllegalOptionValueException {
250: try {
251: return new Long(arg);
252: } catch (NumberFormatException e) {
253: throw new IllegalOptionValueException(this , arg);
254: }
255: }
256: }
257:
258: /**
259: * An option that expects a floating-point value
260: */
261: public static class DoubleOption extends Option {
262: public DoubleOption(char shortForm, String longForm) {
263: super (shortForm, longForm, true);
264: }
265:
266: public DoubleOption(String longForm) {
267: super (longForm, true);
268: }
269:
270: protected Object parseValue(String arg, Locale locale)
271: throws IllegalOptionValueException {
272: try {
273: NumberFormat format = NumberFormat
274: .getNumberInstance(locale);
275: Number num = (Number) format.parse(arg);
276: return new Double(num.doubleValue());
277: } catch (ParseException e) {
278: throw new IllegalOptionValueException(this , arg);
279: }
280: }
281: }
282:
283: /**
284: * An option that expects a string value
285: */
286: public static class StringOption extends Option {
287: public StringOption(char shortForm, String longForm) {
288: super (shortForm, longForm, true);
289: }
290:
291: public StringOption(String longForm) {
292: super (longForm, true);
293: }
294:
295: protected Object parseValue(String arg, Locale locale) {
296: return arg;
297: }
298: }
299: }
300:
301: /**
302: * Add the specified Option to the list of accepted options
303: */
304: public final Option addOption(Option opt) {
305: if (opt.shortForm() != null)
306: this .options.put("-" + opt.shortForm(), opt);
307: this .options.put("--" + opt.longForm(), opt);
308: return opt;
309: }
310:
311: /**
312: * Convenience method for adding a string option.
313: * @return the new Option
314: */
315: public final Option addStringOption(char shortForm, String longForm) {
316: return addOption(new Option.StringOption(shortForm, longForm));
317: }
318:
319: /**
320: * Convenience method for adding a string option.
321: * @return the new Option
322: */
323: public final Option addStringOption(String longForm) {
324: return addOption(new Option.StringOption(longForm));
325: }
326:
327: /**
328: * Convenience method for adding an integer option.
329: * @return the new Option
330: */
331: public final Option addIntegerOption(char shortForm, String longForm) {
332: return addOption(new Option.IntegerOption(shortForm, longForm));
333: }
334:
335: /**
336: * Convenience method for adding an integer option.
337: * @return the new Option
338: */
339: public final Option addIntegerOption(String longForm) {
340: return addOption(new Option.IntegerOption(longForm));
341: }
342:
343: /**
344: * Convenience method for adding a long integer option.
345: * @return the new Option
346: */
347: public final Option addLongOption(char shortForm, String longForm) {
348: return addOption(new Option.LongOption(shortForm, longForm));
349: }
350:
351: /**
352: * Convenience method for adding a long integer option.
353: * @return the new Option
354: */
355: public final Option addLongOption(String longForm) {
356: return addOption(new Option.LongOption(longForm));
357: }
358:
359: /**
360: * Convenience method for adding a double option.
361: * @return the new Option
362: */
363: public final Option addDoubleOption(char shortForm, String longForm) {
364: return addOption(new Option.DoubleOption(shortForm, longForm));
365: }
366:
367: /**
368: * Convenience method for adding a double option.
369: * @return the new Option
370: */
371: public final Option addDoubleOption(String longForm) {
372: return addOption(new Option.DoubleOption(longForm));
373: }
374:
375: /**
376: * Convenience method for adding a boolean option.
377: * @return the new Option
378: */
379: public final Option addBooleanOption(char shortForm, String longForm) {
380: return addOption(new Option.BooleanOption(shortForm, longForm));
381: }
382:
383: /**
384: * Convenience method for adding a boolean option.
385: * @return the new Option
386: */
387: public final Option addBooleanOption(String longForm) {
388: return addOption(new Option.BooleanOption(longForm));
389: }
390:
391: /**
392: * Equivalent to {@link #getOptionValue(Option, Object) getOptionValue(o,
393: * null)}.
394: */
395: public final Object getOptionValue(Option o) {
396: return getOptionValue(o, null);
397: }
398:
399: /**
400: * @return the parsed value of the given Option, or null if the
401: * option was not set
402: */
403: public final Object getOptionValue(Option o, Object def) {
404: Vector v = (Vector) values.get(o.longForm());
405:
406: if (v == null) {
407: return def;
408: } else if (v.isEmpty()) {
409: return null;
410: } else {
411: Object result = v.elementAt(0);
412: v.removeElementAt(0);
413: return result;
414: }
415: }
416:
417: /**
418: * @return A Vector giving the parsed values of all the occurrences of the
419: * given Option, or an empty Vector if the option was not set.
420: */
421: public final Vector getOptionValues(Option option) {
422: Vector result = new Vector();
423:
424: while (true) {
425: Object o = getOptionValue(option, null);
426:
427: if (o == null) {
428: return result;
429: } else {
430: result.addElement(o);
431: }
432: }
433: }
434:
435: /**
436: * @return the non-option arguments
437: */
438: public final String[] getRemainingArgs() {
439: return this .remainingArgs;
440: }
441:
442: /**
443: * Extract the options and non-option arguments from the given
444: * list of command-line arguments. The default locale is used for
445: * parsing options whose values might be locale-specific.
446: */
447: public final void parse(String[] argv)
448: throws IllegalOptionValueException, UnknownOptionException {
449:
450: // It would be best if this method only threw OptionException, but for
451: // backwards compatibility with old user code we throw the two
452: // exceptions above instead.
453:
454: parse(argv, Locale.getDefault());
455: }
456:
457: /**
458: * Extract the options and non-option arguments from the given
459: * list of command-line arguments. The specified locale is used for
460: * parsing options whose values might be locale-specific.
461: */
462: public final void parse(String[] argv, Locale locale)
463: throws IllegalOptionValueException, UnknownOptionException {
464:
465: // It would be best if this method only threw OptionException, but for
466: // backwards compatibility with old user code we throw the two
467: // exceptions above instead.
468:
469: Vector otherArgs = new Vector();
470: int position = 0;
471: this .values = new Hashtable(10);
472: while (position < argv.length) {
473: String curArg = argv[position];
474: if (curArg.startsWith("-")) {
475: if (curArg.equals("--")) { // end of options
476: position += 1;
477: break;
478: }
479: String valueArg = null;
480: if (curArg.startsWith("--")) { // handle --arg=value
481: int equalsPos = curArg.indexOf("=");
482: if (equalsPos != -1) {
483: valueArg = curArg.substring(equalsPos + 1);
484: curArg = curArg.substring(0, equalsPos);
485: }
486: } else if (curArg.length() > 2) { // handle -abcd
487: for (int i = 1; i < curArg.length(); i++) {
488: Option opt = (Option) this .options.get("-"
489: + curArg.charAt(i));
490: if (opt == null)
491: throw new UnknownSuboptionException(curArg,
492: curArg.charAt(i));
493: if (opt.wantsValue())
494: throw new NotFlagException(curArg, curArg
495: .charAt(i));
496: addValue(opt, opt.getValue(null, locale));
497:
498: }
499: position++;
500: continue;
501: }
502:
503: Option opt = (Option) this .options.get(curArg);
504: if (opt == null) {
505: throw new UnknownOptionException(curArg);
506: }
507: Object value = null;
508: if (opt.wantsValue()) {
509: if (valueArg == null) {
510: position += 1;
511: if (position < argv.length) {
512: valueArg = argv[position];
513: }
514: }
515: value = opt.getValue(valueArg, locale);
516: } else {
517: value = opt.getValue(null, locale);
518: }
519:
520: addValue(opt, value);
521:
522: position += 1;
523: } else {
524: otherArgs.addElement(curArg);
525: position += 1;
526: }
527: }
528: for (; position < argv.length; ++position) {
529: otherArgs.addElement(argv[position]);
530: }
531:
532: this .remainingArgs = new String[otherArgs.size()];
533: otherArgs.copyInto(remainingArgs);
534: }
535:
536: private void addValue(Option opt, Object value) {
537: String lf = opt.longForm();
538:
539: Vector v = (Vector) values.get(lf);
540:
541: if (v == null) {
542: v = new Vector();
543: values.put(lf, v);
544: }
545:
546: v.addElement(value);
547: }
548:
549: private String[] remainingArgs = null;
550: private Hashtable options = new Hashtable(10);
551: private Hashtable values = new Hashtable(10);
552: }
|