1 package jargs.gnu;
2
3 import java.text.NumberFormat;
4 import java.text.ParseException;
5 import java.util.Hashtable;
6 import java.util.Vector;
7 import java.util.Enumeration;
8 import java.util.Locale;
9
10 /***
11 * Largely GNU-compatible command-line options parser. Has short (-v) and
12 * long-form (--verbose) option support, and also allows options with
13 * associated values (-d 2, --debug 2, --debug=2). Option processing
14 * can be explicitly terminated by the argument '--'.
15 *
16 * @author Steve Purcell
17 * @version Revision: 1.5
18 * @version $Id: CmdLineParser.java,v 1.1 2004/05/13 01:22:32 dquintela Exp $
19 * @see jargs.examples.gnu.OptionTest
20 */
21 public class CmdLineParser {
22
23 /***
24 * Base class for exceptions that may be thrown when options are parsed
25 */
26 public static abstract class OptionException extends Exception {
27 OptionException(String msg) { super(msg); }
28 }
29
30 /***
31 * Thrown when the parsed command-line contains an option that is not
32 * recognised. <code>getMessage()</code> returns
33 * an error string suitable for reporting the error to the user (in
34 * English).
35 */
36 public static class UnknownOptionException extends OptionException {
37 UnknownOptionException( String optionName ) {
38 super("unknown option '" + optionName + "'");
39 this.optionName = optionName;
40 }
41
42 /***
43 * @return the name of the option that was unknown (e.g. "-u")
44 */
45 public String getOptionName() { return this.optionName; }
46 private String optionName = null;
47 }
48
49 /***
50 * Thrown when an illegal or missing value is given by the user for
51 * an option that takes a value. <code>getMessage()</code> returns
52 * an error string suitable for reporting the error to the user (in
53 * English).
54 */
55 public static class IllegalOptionValueException extends OptionException {
56 public IllegalOptionValueException( Option opt, String value ) {
57 super("illegal value '" + value + "' for option -" +
58 opt.shortForm() + "/--" + opt.longForm());
59 this.option = opt;
60 this.value = value;
61 }
62
63 /***
64 * @return the name of the option whose value was illegal (e.g. "-u")
65 */
66 public Option getOption() { return this.option; }
67
68 /***
69 * @return the illegal value
70 */
71 public String getValue() { return this.value; }
72 private Option option;
73 private String value;
74 }
75
76 /***
77 * Representation of a command-line option
78 */
79 public static abstract class Option {
80
81 protected Option( char shortForm, String longForm,
82 boolean wantsValue ) {
83 if ( longForm == null )
84 throw new IllegalArgumentException("null arg forms not allowed");
85 this.shortForm = new String(new char[]{shortForm});
86 this.longForm = longForm;
87 this.wantsValue = wantsValue;
88 }
89
90 public String shortForm() { return this.shortForm; }
91
92 public String longForm() { return this.longForm; }
93
94 /***
95 * Tells whether or not this option wants a value
96 */
97 public boolean wantsValue() { return this.wantsValue; }
98
99 public final Object getValue( String arg, Locale locale )
100 throws IllegalOptionValueException {
101 if ( this.wantsValue ) {
102 if ( arg == null ) {
103 throw new IllegalOptionValueException(this, "");
104 }
105 return this.parseValue(arg, locale);
106 }
107 else {
108 return Boolean.TRUE;
109 }
110 }
111
112 /***
113 * Override to extract and convert an option value passed on the
114 * command-line
115 */
116 protected Object parseValue( String arg, Locale locale )
117 throws IllegalOptionValueException {
118 return null;
119 }
120
121 private String shortForm = null;
122 private String longForm = null;
123 private boolean wantsValue = false;
124
125 public static class BooleanOption extends Option {
126 public BooleanOption( char shortForm, String longForm ) {
127 super(shortForm, longForm, false);
128 }
129 }
130
131 /***
132 * An option that expects an integer value
133 */
134 public static class IntegerOption extends Option {
135 public IntegerOption( char shortForm, String longForm ) {
136 super(shortForm, longForm, true);
137 }
138 protected Object parseValue( String arg, Locale locale )
139 throws IllegalOptionValueException {
140 try {
141 return new Integer(arg);
142 }
143 catch (NumberFormatException e) {
144 throw new IllegalOptionValueException(this, arg);
145 }
146 }
147 }
148
149 /***
150 * An option that expects a floating-point value
151 */
152 public static class DoubleOption extends Option {
153 public DoubleOption( char shortForm, String longForm ) {
154 super(shortForm, longForm, true);
155 }
156 protected Object parseValue( String arg, Locale locale )
157 throws IllegalOptionValueException {
158 try {
159 NumberFormat format = NumberFormat.getNumberInstance(locale);
160 Number num = (Number)format.parse(arg);
161 return new Double(num.doubleValue());
162 }
163 catch (ParseException e) {
164 throw new IllegalOptionValueException(this, arg);
165 }
166 }
167 }
168
169 /***
170 * An option that expects a string value
171 */
172 public static class StringOption extends Option {
173 public StringOption( char shortForm, String longForm ) {
174 super(shortForm, longForm, true);
175 }
176 protected Object parseValue( String arg, Locale locale ) {
177 return arg;
178 }
179 }
180 }
181
182 /***
183 * Add the specified Option to the list of accepted options
184 */
185 public final Option addOption( Option opt ) {
186 this.options.put("-" + opt.shortForm(), opt);
187 this.options.put("--" + opt.longForm(), opt);
188 return opt;
189 }
190
191 /***
192 * Convenience method for adding a string option.
193 * @return the new Option
194 */
195 public final Option addStringOption( char shortForm, String longForm ) {
196 Option opt = new Option.StringOption(shortForm, longForm);
197 addOption(opt);
198 return opt;
199 }
200
201 /***
202 * Convenience method for adding an integer option.
203 * @return the new Option
204 */
205 public final Option addIntegerOption( char shortForm, String longForm ) {
206 Option opt = new Option.IntegerOption(shortForm, longForm);
207 addOption(opt);
208 return opt;
209 }
210
211 /***
212 * Convenience method for adding a double option.
213 * @return the new Option
214 */
215 public final Option addDoubleOption( char shortForm, String longForm ) {
216 Option opt = new Option.DoubleOption(shortForm, longForm);
217 addOption(opt);
218 return opt;
219 }
220
221 /***
222 * Convenience method for adding a boolean option.
223 * @return the new Option
224 */
225 public final Option addBooleanOption( char shortForm, String longForm ) {
226 Option opt = new Option.BooleanOption(shortForm, longForm);
227 addOption(opt);
228 return opt;
229 }
230
231 /***
232 * @return the parsed value of the given Option, or null if the
233 * option was not set
234 */
235 public final Object getOptionValue( Option o ) {
236 return values.get(o.longForm());
237 }
238
239 /***
240 * @return the non-option arguments
241 */
242 public final String[] getRemainingArgs() {
243 return this.remainingArgs;
244 }
245
246 /***
247 * Extract the options and non-option arguments from the given
248 * list of command-line arguments. The default locale is used for
249 * parsing options whose values might be locale-specific.
250 */
251 public final void parse( String[] argv )
252 throws IllegalOptionValueException, UnknownOptionException {
253 parse(argv, Locale.getDefault());
254 }
255
256 /***
257 * Extract the options and non-option arguments from the given
258 * list of command-line arguments. The specified locale is used for
259 * parsing options whose values might be locale-specific.
260 */
261 public final void parse( String[] argv, Locale locale )
262 throws IllegalOptionValueException, UnknownOptionException {
263 Vector otherArgs = new Vector();
264 int position = 0;
265 this.values = new Hashtable(10);
266 while ( position < argv.length ) {
267 String curArg = argv[position];
268 if ( curArg.startsWith("-") ) {
269 if ( curArg.equals("--") ) {
270 position += 1;
271 break;
272 }
273 String valueArg = null;
274 if ( curArg.startsWith("--") ) {
275 int equalsPos = curArg.indexOf("=");
276 if ( equalsPos != -1 ) {
277 valueArg = curArg.substring(equalsPos+1);
278 curArg = curArg.substring(0,equalsPos);
279 }
280 }
281 Option opt = (Option)this.options.get(curArg);
282 if ( opt == null ) {
283 throw new UnknownOptionException(curArg);
284 }
285 Object value = null;
286 if ( opt.wantsValue() ) {
287 if ( valueArg == null ) {
288 position += 1;
289 valueArg = null;
290 if ( position < argv.length ) {
291 valueArg = argv[position];
292 }
293 }
294 value = opt.getValue(valueArg, locale);
295 }
296 else {
297 value = opt.getValue(null, locale);
298 }
299 this.values.put(opt.longForm(), value);
300 position += 1;
301 }
302 else {
303 break;
304 }
305 }
306 for ( ; position < argv.length; ++position ) {
307 otherArgs.addElement(argv[position]);
308 }
309
310 this.remainingArgs = new String[otherArgs.size()];
311 int i = 0;
312 for (Enumeration e = otherArgs.elements(); e.hasMoreElements(); ++i) {
313 this.remainingArgs[i] = (String)e.nextElement();
314 }
315 }
316
317 private String[] remainingArgs = null;
318 private Hashtable options = new Hashtable(10);
319 private Hashtable values = new Hashtable(10);
320 }