001package jargs.gnu; 002 003import java.text.NumberFormat; 004import java.text.ParseException; 005import java.util.Hashtable; 006import java.util.Vector; 007import java.util.Enumeration; 008import 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.5 $ 018 * @see jargs.examples.gnu.OptionTest 019 */ 020public 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) { super(msg); } 027 } 028 029 /** 030 * Thrown when the parsed command-line contains an option that is not 031 * recognised. <code>getMessage()</code> returns 032 * an error string suitable for reporting the error to the user (in 033 * English). 034 */ 035 public static class UnknownOptionException extends OptionException { 036 UnknownOptionException( String optionName ) { 037 super("unknown option '" + optionName + "'"); 038 this.optionName = optionName; 039 } 040 041 /** 042 * @return the name of the option that was unknown (e.g. "-u") 043 */ 044 public String getOptionName() { return this.optionName; } 045 private String optionName = null; 046 } 047 048 /** 049 * Thrown when an illegal or missing value is given by the user for 050 * an option that takes a value. <code>getMessage()</code> returns 051 * an error string suitable for reporting the error to the user (in 052 * English). 053 */ 054 public static class IllegalOptionValueException extends OptionException { 055 public IllegalOptionValueException( Option opt, String value ) { 056 super("illegal value '" + value + "' for option -" + 057 opt.shortForm() + "/--" + opt.longForm()); 058 this.option = opt; 059 this.value = value; 060 } 061 062 /** 063 * @return the name of the option whose value was illegal (e.g. "-u") 064 */ 065 public Option getOption() { return this.option; } 066 067 /** 068 * @return the illegal value 069 */ 070 public String getValue() { return this.value; } 071 private Option option; 072 private String value; 073 } 074 075 /** 076 * Representation of a command-line option 077 */ 078 public static abstract class Option { 079 080 protected Option( char shortForm, String longForm, 081 boolean wantsValue ) { 082 if ( longForm == null ) 083 throw new IllegalArgumentException("null arg forms not allowed"); 084 this.shortForm = new String(new char[]{shortForm}); 085 this.longForm = longForm; 086 this.wantsValue = wantsValue; 087 } 088 089 public String shortForm() { return this.shortForm; } 090 091 public String longForm() { return this.longForm; } 092 093 /** 094 * Tells whether or not this option wants a value 095 */ 096 public boolean wantsValue() { return this.wantsValue; } 097 098 public final Object getValue( String arg, Locale locale ) 099 throws IllegalOptionValueException { 100 if ( this.wantsValue ) { 101 if ( arg == null ) { 102 throw new IllegalOptionValueException(this, ""); 103 } 104 return this.parseValue(arg, locale); 105 } 106 else { 107 return Boolean.TRUE; 108 } 109 } 110 111 /** 112 * Override to extract and convert an option value passed on the 113 * command-line 114 */ 115 protected Object parseValue( String arg, Locale locale ) 116 throws IllegalOptionValueException { 117 return null; 118 } 119 120 private String shortForm = null; 121 private String longForm = null; 122 private boolean wantsValue = false; 123 124 public static class BooleanOption extends Option { 125 public BooleanOption( char shortForm, String longForm ) { 126 super(shortForm, longForm, false); 127 } 128 } 129 130 /** 131 * An option that expects an integer value 132 */ 133 public static class IntegerOption extends Option { 134 public IntegerOption( char shortForm, String longForm ) { 135 super(shortForm, longForm, true); 136 } 137 protected Object parseValue( String arg, Locale locale ) 138 throws IllegalOptionValueException { 139 try { 140 return new Integer(arg); 141 } 142 catch (NumberFormatException e) { 143 throw new IllegalOptionValueException(this, arg); 144 } 145 } 146 } 147 148 /** 149 * An option that expects a floating-point value 150 */ 151 public static class DoubleOption extends Option { 152 public DoubleOption( char shortForm, String longForm ) { 153 super(shortForm, longForm, true); 154 } 155 protected Object parseValue( String arg, Locale locale ) 156 throws IllegalOptionValueException { 157 try { 158 NumberFormat format = NumberFormat.getNumberInstance(locale); 159 Number num = (Number)format.parse(arg); 160 return new Double(num.doubleValue()); 161 } 162 catch (ParseException e) { 163 throw new IllegalOptionValueException(this, arg); 164 } 165 } 166 } 167 168 /** 169 * An option that expects a string value 170 */ 171 public static class StringOption extends Option { 172 public StringOption( char shortForm, String longForm ) { 173 super(shortForm, longForm, true); 174 } 175 protected Object parseValue( String arg, Locale locale ) { 176 return arg; 177 } 178 } 179 } 180 181 /** 182 * Add the specified Option to the list of accepted options 183 */ 184 public final Option addOption( Option opt ) { 185 this.options.put("-" + opt.shortForm(), opt); 186 this.options.put("--" + opt.longForm(), opt); 187 return opt; 188 } 189 190 /** 191 * Convenience method for adding a string option. 192 * @return the new Option 193 */ 194 public final Option addStringOption( char shortForm, String longForm ) { 195 Option opt = new Option.StringOption(shortForm, longForm); 196 addOption(opt); 197 return opt; 198 } 199 200 /** 201 * Convenience method for adding an integer option. 202 * @return the new Option 203 */ 204 public final Option addIntegerOption( char shortForm, String longForm ) { 205 Option opt = new Option.IntegerOption(shortForm, longForm); 206 addOption(opt); 207 return opt; 208 } 209 210 /** 211 * Convenience method for adding a double option. 212 * @return the new Option 213 */ 214 public final Option addDoubleOption( char shortForm, String longForm ) { 215 Option opt = new Option.DoubleOption(shortForm, longForm); 216 addOption(opt); 217 return opt; 218 } 219 220 /** 221 * Convenience method for adding a boolean option. 222 * @return the new Option 223 */ 224 public final Option addBooleanOption( char shortForm, String longForm ) { 225 Option opt = new Option.BooleanOption(shortForm, longForm); 226 addOption(opt); 227 return opt; 228 } 229 230 /** 231 * @return the parsed value of the given Option, or null if the 232 * option was not set 233 */ 234 public final Object getOptionValue( Option o ) { 235 return values.get(o.longForm()); 236 } 237 238 /** 239 * @return the non-option arguments 240 */ 241 public final String[] getRemainingArgs() { 242 return this.remainingArgs; 243 } 244 245 /** 246 * Extract the options and non-option arguments from the given 247 * list of command-line arguments. The default locale is used for 248 * parsing options whose values might be locale-specific. 249 */ 250 public final void parse( String[] argv ) 251 throws IllegalOptionValueException, UnknownOptionException { 252 parse(argv, Locale.getDefault()); 253 } 254 255 /** 256 * Extract the options and non-option arguments from the given 257 * list of command-line arguments. The specified locale is used for 258 * parsing options whose values might be locale-specific. 259 */ 260 public final void parse( String[] argv, Locale locale ) 261 throws IllegalOptionValueException, UnknownOptionException { 262 Vector otherArgs = new Vector(); 263 int position = 0; 264 this.values = new Hashtable(10); 265 while ( position < argv.length ) { 266 String curArg = argv[position]; 267 if ( curArg.startsWith("-") ) { 268 if ( curArg.equals("--") ) { // end of options 269 position += 1; 270 break; 271 } 272 String valueArg = null; 273 if ( curArg.startsWith("--") ) { // handle --arg=value 274 int equalsPos = curArg.indexOf("="); 275 if ( equalsPos != -1 ) { 276 valueArg = curArg.substring(equalsPos+1); 277 curArg = curArg.substring(0,equalsPos); 278 } 279 } 280 Option opt = (Option)this.options.get(curArg); 281 if ( opt == null ) { 282 throw new UnknownOptionException(curArg); 283 } 284 Object value = null; 285 if ( opt.wantsValue() ) { 286 if ( valueArg == null ) { 287 position += 1; 288 valueArg = null; 289 if ( position < argv.length ) { 290 valueArg = argv[position]; 291 } 292 } 293 value = opt.getValue(valueArg, locale); 294 } 295 else { 296 value = opt.getValue(null, locale); 297 } 298 this.values.put(opt.longForm(), value); 299 position += 1; 300 } 301 else { 302 break; 303 } 304 } 305 for ( ; position < argv.length; ++position ) { 306 otherArgs.addElement(argv[position]); 307 } 308 309 this.remainingArgs = new String[otherArgs.size()]; 310 int i = 0; 311 for (Enumeration e = otherArgs.elements(); e.hasMoreElements(); ++i) { 312 this.remainingArgs[i] = (String)e.nextElement(); 313 } 314 } 315 316 private String[] remainingArgs = null; 317 private Hashtable options = new Hashtable(10); 318 private Hashtable values = new Hashtable(10); 319}