001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002 // The Molly framework is freely distributable under the terms of an 003 // MIT-style license. For details, see the molly pages web site at: 004 // http://www.mollypages.org/. Use, modify, have fun ! 005 006 package fc.util; 007 008 import java.io.*; 009 import java.util.*; 010 import java.text.*; 011 012 import fc.io.*; 013 014 /** 015 Manages arguments passed to an application. Arguments can be optional or 016 required. Arguments received by this class have to be either of the 017 following formats: 018 <blockquote> 019 <tt> 020 -flagname1 value 021 -flagname2=value2 022 </tt>. 023 </blockquote> 024 Arguments consist of flag name and flag value pairs. All flags must 025 begin with a <tt>'-'</tt> character and any new word beginning 026 with a <tt>'-'</tt> character is seen as the start of a new flag name 027 (the <tt>'-'</tt> character is always permissible <b>inside</b> a 028 token, i.e., after the first character). Additionaly, a flag name is 029 delimited from it's corresponding value by either a space or the 030 <tt>'='</tt> character. 031 <p>Note, in the above example, <tt>- flagname1</tt> is separated from 032 it's value by a space whereas <tt>- flagname2</tt> is seperated from 033 it's value by a <tt>'='</tt> character. Both delimiters are valid and 034 can be intermixed on the same line. 035 </p><p> 036 Note also that <b>if the delimiter is a space, then the value part cannot 037 begin with the <tt>-</tt> character</b>, but if the delimiter is <tt>=</tt>, 038 then the value part can begin with the <tt>-</tt> character. That is, the 039 following input is <b>valid</b>: 040 <blockquote><tt>-flagname1=-value-for-flag1</tt></blockquote> 041 <p>whereas this is <b>invalid</b>: 042 <blockquote><tt>-flagname1 -value-for-flag1</tt></blockquote> 043 Here, <tt>-value-for-flag1</tt> is seen as a new flag itself. 044 </p><p> 045 Following a flag name, all following values are assigned to that flagname, 046 up until another flagname is encountered or the end of input is reached. 047 Therefore, spaces do not have to be quoted. The following example, 048 <blockquote> 049 <tt>-path \some file\some where else -port 80</tt> 050 </blockquote> 051 is parsed as: 052 <dl> 053 <dt><tt><b>path</b></tt></dt> 054 <dd><tt>\some file\some where else</tt></dd> 055 <dt><tt><b>port</b></tt></dt> 056 <dd><tt>80</tt></dd> 057 </dl> 058 </p><p> 059 Different operating systems (and their command interpreters) also 060 transform/modify command lines in various ways. This class (and 061 Java/JVM) can only parse arguments that it recieves from the operating 062 system and those may be different than what you may have specified on 063 the command line, due to the aforementioned transformation. The 064 following example, 065 <blockquote> 066 <tt>-path "/foo/bar /baz" -anotherpath \"a/b c/\"</tt> 067 </blockquote> 068 is parsed as: 069 <dl> 070 <dt><tt><b>path</b></tt></dt> 071 <dd><tt>/foo/bar /baz</tt></dd> 072 <dt><tt><b>anotherpath</b></tt></dt> 073 <dd><tt>"a/b c/"</tt></dd> 074 </dl> 075 Note, that the <tt>path</tt> value has lost it's double quotes whereas 076 the <tt>anotherpath</tt> value has not. This is because the O.S., in this case, 077 stripped the quotes for <tt>path</tt> since they were not escaped on the 078 command line. Note also that various unix'en convert command line args into 079 space delimited tokens. <code>bash</code> for example tokenizes the lines 080 using the IFS variable, and then feeds the tokens (separated by space) to the 081 invoked program, in this case, the java interpreter. 082 </p><p> 083 Sometimes flags are specified <b>without</b> any corresponding value, with the 084 existence of the flag itself implying some condition. However for clarity, 085 it's sometimes better to explicitly set an option to yes or no. Therefore 086 instead of saying <tt>-flag</tt>, always say <tt>-flag=[yes|no]</tt>, with 087 the value part being "yes" or "no". However, the existence of the flag by 088 itself <b>can</b> be simply checked by using the <code>flagExists()</code> method. 089 </p><p> 090 Typical usage of this class may look like: 091 <xmp> 092 //inside the main method 093 Args myargs = new Args(args); 094 args.setUsage("java myApp -port 80 -foo=moo -debugport=5432"); //to inform user 095 int port = args.getRequired("port"); //get required "-port" flag 096 String abc = args.get("foo"); //get optional "-foo" flag 097 String bar = args.get("bar","5000"); //get optional "bar" flag, default to "5000" 098 </xmp> 099 </p> 100 101 Thread Safety: This class <b>is</b> thread safe and can be used by multiple 102 threads after it's instantiated. 103 104 @author hursh jain 105 @version 1.0 7/15/2001 106 **/ 107 public class Args 108 { 109 static boolean _internal_testing = false; 110 111 protected String usage; 112 protected String[] input; 113 protected Map flagMap; 114 115 /** 116 If a flag name is not well formed, then the corresponding value (if any) 117 is stored under this name. For example: <blockquote><tt>- abc</tt></blockquote> 118 is missing a flagname (<tt>'-'</tt> followed by whitespace), therefore the 119 value <tt>abc</tt> is stored under this default name. 120 **/ 121 public static String DEFAULT_FLAGNAME = "default"; 122 123 /** 124 If a flag name is repeated, then all corresponding values for that name are 125 concatenated with each other and the concatenated values are <b>delimited</b> 126 by this value. It's value in this class is <tt>,</tt>. For example: 127 <blockquote><tt>-foo abc -foo xyz</tt></blockquote> has the same flag repeated 128 twice (<tt>foo</tt>) and the resulting value for this flag will be <blockquote> 129 <tt>abc<b>,</b>xyz</tt></blockquote> 130 **/ 131 public static String FLAG_REPEAT_VALUE_DELIM = ","; 132 133 /** 134 Creates a new instance, with the specified String[] to read arguments from. 135 Typically, the <tt>static void main(String[] args)</tt> method gets a String[] 136 array from the JVM, containing the arguments passed to the application. This 137 array will typically be be passed to this constructor. 138 **/ 139 public Args(String args[]) 140 { 141 input = args; 142 parseFlags(); 143 } 144 145 /** 146 Creates a new instance, with the specified InputStream to read arguments from. 147 Reads as much as possible from the specified stream, so as to not block and 148 delay the construction of this object. Any data not available via the specified 149 InputStream at the time of construction, is not used later on and is essentially 150 lost. The stream encoding is assumed to be ISO-8859-1, i.e., the flags names 151 and values read from the stream are treated as normal ascii characters. Be aware 152 of differences between parsing command line args and parsing args from a file, 153 because typically command line args are modified by the shell before being passed 154 to the application. 155 **/ 156 public Args(InputStream in) 157 { 158 toStringArray(in); 159 parseFlags(); 160 } 161 162 163 /** 164 Checks if the specified flag exits, regardless of whether a corresponding 165 value exist for that flag. For example, suppose the invoked arguments looked 166 like: 167 <blockquote><tt>-foo -bar=baz</tt></blockquote>. Then the flag <tt>foo</tt> 168 would exist but have no corresponding value. 169 @param flagname the flagname to check 170 @return true if flag exists, false otherwise 171 **/ 172 public boolean flagExists(String flagname) 173 { 174 boolean exists = false; 175 if (this.flagMap.containsKey(flagname)) 176 { 177 exists = true; 178 } 179 return exists; 180 } 181 182 /** Returns the total number of flags available**/ 183 public int getFlagCount() { 184 return flagMap.size(); 185 } 186 187 /** 188 Returns the fully qualified name of the class that contained the <tt>main()</tt> method used to 189 invoke the application. <b>Note: </b> this method must be called from the same thread (the "main" 190 thread) that started the application. Newly started threads that call this method will instead 191 obtain the class name containing that thread's <tt>run()</tt> method. 192 **/ 193 public String getMainClassName() 194 { 195 return ClassUtil.getMainClassName(); 196 } 197 198 199 /** 200 Returns an argument by positional value. <tt>get(n)</tt> 201 is the same as <tt>static void main(String[] args) --> args[n] 202 </tt> and is provided here for convenience. 203 204 @param n the 0-based index of the String[] passed to main() 205 @throws NullPointerException 206 if the specified index is greater than the number 207 of arguments 208 **/ 209 public String get(int n) { 210 return input[n]; 211 } 212 213 /** 214 Returns a required argument by positional value. <tt>get(n)</tt> 215 is the same as <tt>static void main(String[] args) --> args[n] 216 </tt> and is provided here for convenience. Ff the specified 217 index is greater than the number of arguments, then the error 218 is handled in the same way as {@link #getRequired(String)} 219 220 @param n the 0-based index of the String[] passed to main() 221 **/ 222 public String getRequired(int n) { 223 if (n < 0 || n > (input.length - 1)) { 224 handleError("Missing required argument at position [" + n + "]"); 225 } 226 return input[n]; 227 } 228 229 230 /** 231 Returns the value corresponding to the specified flag. 232 233 @param flagname the name of the flag <b>without</b> the leading <tt>-</tt> character 234 @return <tt>null</tt> either if the flag itself was not found, or if the 235 flag was found but no associated value was found. 236 **/ 237 public String get(String flagname) 238 { 239 return (String) flagMap.get(flagname); 240 } 241 242 /** 243 Returns the value corresponding to the specified flag. If 244 the flag and/or it's value is not found, this method returns 245 the specified backup value instead. 246 247 @param flagname the name of the flag <b>without</b> the leading <tt>-</tt> character 248 @param backup the value to return if flag not found 249 @return value of specified flag or backup string 250 **/ 251 public String get(String flagname, String backup) 252 { 253 String val = get(flagname); 254 if (val == null) 255 { 256 return backup; 257 } 258 else return val; 259 } 260 261 262 /** 263 Returns the value corresponding to the specified flag. If 264 the flag and/or it's value is not found, this method returns 265 the specified backup value instead. 266 267 If the property <b>is</b> present, then the corresponding 268 value is <tt>true</tt> if the property value is any of the 269 following (case-insensitive): 270 <blockquote> 271 <code> 272 yes, 1, true 273 </code> 274 <blockquote> 275 else <tt>false</tt> is returned. Also see {@link #flagExists}. 276 277 @param flagname the name of the flag <b>without</b> 278 the leading <tt>-</tt> character 279 @param backup value to return if the property for the 280 specified property is <b>not</b> present 281 282 @return value of specified key or backup string 283 */ 284 public boolean getBoolean(String flagname, boolean backup) 285 { 286 String val = get(flagname); 287 288 if (val == null) 289 return backup; 290 else 291 return toBoolean(val); 292 } 293 294 295 /** 296 Returns the value corresponding to the specified flag. If the flag and/or it's value is not found 297 or is found but cannot be converted into an integer (via a {@link Integer.parseInt(String)} call), 298 the backup value will be returned. 299 300 @param flagname the name of the flag <b>without</b> the leading <tt>-</tt> character 301 @param backup value to return if the property for the specified property is <b>not</b> 302 present 303 304 @return value of specified key or backup string 305 */ 306 public int getInt(String flagname, int backup) 307 { 308 String val = get(flagname); 309 if (val != null) 310 { 311 try { 312 return Integer.parseInt(val); 313 } 314 catch (NumberFormatException e) { 315 System.err.println("Cannot convert flag '" + flagname + "', into a number, returning backup value " + backup); 316 } 317 } 318 return backup; 319 } 320 321 322 /** 323 Returns the value corresponding to the specified flag. If the flag and/or it's value is not found 324 or is found but cannot be converted into an long (via a {@link Long.parseLong(String)} call), 325 the backup value will be returned. 326 327 @param flagname the name of the flag <b>without</b> the leading <tt>-</tt> character 328 @param backup value to return if the property for the specified property is <b>not</b> 329 present 330 331 @return value of specified key or backup string 332 */ 333 public long getLong(String flagname, long backup) 334 { 335 String val = get(flagname); 336 if (val != null) 337 { 338 try { 339 return Long.parseLong(val); 340 } 341 catch (NumberFormatException e) { 342 System.err.println("Cannot convert flag '" + flagname + "', into a number, returning backup value " + backup); 343 } 344 } 345 return backup; 346 } 347 348 349 /** 350 Returns the value corresponding to the specified flag. If the flag's value is not found (even if 351 the flag name exists), this method calls the <code>error method</code>, which by default prints a 352 stack trace and exits the application. 353 354 <p>If the handleError method is overriden to <b>not</b> exit the application, then this method will 355 return <tt>null</tt> if the specified flag is not found. 356 357 @param flagname the name of the flag <b>without</b> the leading <tt>'-'</tt> character 358 @return value of specified flag 359 @see #handleError 360 **/ 361 public String getRequired(String flagname) 362 { 363 if (flagname.equals("")) { 364 handleError("This program expects invocation flag(s)."); 365 return null; 366 } 367 368 String val = get(flagname); 369 if (val == null) { 370 handleError("Missing required flag: " + flagname); 371 } 372 373 return val; 374 } 375 376 /** another alias for {@link getRequired(String)} */ 377 public String getRequiredString(String flagname) 378 { 379 return getRequired(flagname); 380 } 381 382 /* 383 Returns the value obtained via {@link getRequired(String)} as a boolean. The boolean value 384 returned is <code>true</code> if the property value is any of the following (case-insensitive): 385 <blockquote> 386 <code> 387 yes, 1, true 388 </code> 389 <blockquote> 390 else <tt>false</tt> is returned. 391 392 <p>If the handleError method is overriden to <b>not</b> exit the application, then this method will 393 return <tt>false</tt> if the specified flag is not found. 394 395 @param flagname the name of the flag <b>without</b> the leading <tt>'-'</tt> character 396 @return value of specified flag as a boolean 397 @see #handleError 398 */ 399 public boolean getRequiredBoolean(String flagname) 400 { 401 String val = getRequired(flagname); 402 return toBoolean(val); 403 } 404 405 406 /** 407 Returns the value obtained via {@link getRequired(String)} as a File. 408 409 <p>If the handleError method is overriden to <b>not</b> exit the application, then this method will 410 return <tt>false</tt> if the specified file does not exist. 411 */ 412 public File getRequiredFile(String flagname) 413 { 414 String val = getRequired(flagname); 415 File f = new File(val); 416 if (! f.exists()) { 417 handleError(flagname + " does not exist/is not a valid file"); 418 } 419 return f; 420 } 421 422 /* 423 Returns the value obtained via {@link getRequired(String)} as as an integer. If the property cannot 424 be converted into an integer (via a {@link Integer.parseInt(String)} call), the resulting action 425 would be the same as if the required flag was not present. 426 427 <p>If the handleError method is overriden to <b>not</b> exit the application, then this method will 428 return <tt>0</tt> if the specified flag is not found. 429 430 @param flagname the name of the flag <b>without</b> the leading <tt>'-'</tt> character 431 @return value of specified flag as a int 432 @see #handleError 433 */ 434 public int getRequiredInt(String flagname) 435 { 436 String val = getRequired(flagname); 437 438 int result = 0; 439 440 try { 441 result = Integer.parseInt(val); 442 } 443 catch (NumberFormatException ne) { 444 System.err.println("Cannot convert flag '" + flagname + "', into a number"); 445 handleError(flagname); 446 } 447 448 return result; 449 } 450 451 452 /** another alias for {@link getRequiredInt(String)} */ 453 public int getRequiredInteger(String flagname) 454 { 455 return getRequiredInt(flagname); 456 } 457 458 459 /* 460 Returns the value obtained via {@link getRequired(String)} as as an long. If the property cannot 461 be converted into an long (via a {@link Long.parseLong(String)} call), the resulting action 462 would be the same as if the required flag was not present. 463 464 <p>If the handleError method is overriden to <b>not</b> exit the application, then this method will 465 return <tt>0</tt> if the specified flag is not found. 466 467 @param flagname the name of the flag <b>without</b> the leading <tt>'-'</tt> character 468 @return value of specified flag as a long 469 @see #handleError 470 */ 471 public long getRequiredLong(String flagname) 472 { 473 String val = getRequired(flagname); 474 475 long result = 0; 476 477 try { 478 result = Long.parseLong(val); 479 } 480 catch (NumberFormatException ne) { 481 System.err.println("Cannot convert flag '" + flagname + "', into a number"); 482 handleError(flagname); 483 } 484 485 return result; 486 } 487 488 489 /** 490 Returns the value as a Date. Understands the following (case-insensitive) values 491 <dl> 492 <dt><tt>now</tt>, <tt>today</tt></dt> 493 <dd>today's date (this instant)</dd> 494 <dt><tt>tomorrow</tt></dt> 495 <dd>tomorrow's date (24 hours from now)</dd> 496 <dt><tt>nextweek</tt></dt> 497 <dd>a date representing 1 week from now</dd> 498 <dt><tt>atweekbegin</tt>, <tt>atbeginweek</tt>, <tt>atbeginofweek</tt></dt> 499 <dd>first day of the current week</dd> 500 </dl> 501 <p> 502 If none of these special keywords are found, returns the value by trying to parsing 503 it as: <tt>yyyy-MM-dd HH:mm:ss</tt> or <tt>yyyy-MM-dd</tt> 504 </p><p> 505 If the date cannot be parsed using either of the above patterns, an exception is 506 thrown. 507 */ 508 public Date getRequiredFriendlyDate(String flagname) 509 { 510 Calendar cal = Calendar.getInstance(Locale.US); 511 512 java.util.Date date = null; 513 514 String flagval = getRequired(flagname); 515 flagval = flagval.toLowerCase().intern(); 516 517 if (flagval == "now" || flagval == "today") { 518 date = new java.util.Date(); 519 } 520 else if (flagval == "tomorrow") { 521 date = new java.util.Date(System.currentTimeMillis() + CalendarUtil.TWENTY_FOUR_HOUR); 522 } 523 else if (flagval == "nextweek") { 524 date = new java.util.Date(System.currentTimeMillis() + CalendarUtil.ONE_WEEK); 525 } 526 else if (flagval == "atweekbegin" || flagval == "atbeginweek" 527 || flagval == "atbeginofweek") { 528 date = CalendarUtil.getFirstDayOfWeek(cal, new Date()); 529 } 530 else{ 531 try { 532 date = parseDate(flagval); 533 } 534 catch (ParseException e) { 535 handleError("Cannot parse flag name=["+ flagname + "], value=[" + flagval + "] as a friendly date.."); 536 } 537 } 538 539 return date; 540 } 541 542 543 /** 544 Returns the value as a Date. Understands the following (case-insensitive) values 545 <dl> 546 <dt><tt>now</tt>, <tt>today</tt></dt> 547 <dd>today's date (this instant)</dd> 548 <dt><tt>tomorrow</tt></dt> 549 <dd>tomorrow's date (24 hours from now)</dd> 550 <dt><tt>nextweek</tt></dt> 551 <dd>a date representing 1 week from now</dd> 552 <dt><tt>atweekbegin</tt>, <tt>atbeginweek</tt>, <tt>atbeginofweek</tt></dt> 553 <dd>first day of the current week</dd> 554 </dl> 555 <p> 556 If none of these special keywords are found, returns the value by trying to parsing 557 it as: <tt>yyyy-MM-dd HH:mm:ss</tt> or <tt>yyyy-MM-dd</tt> 558 </p><p> 559 The flag is optional but if it is present and cannot be parsed using either of the above patterns, an exception is 560 thrown. 561 */ 562 public Date getFriendlyDate(String flagname) 563 { 564 Calendar cal = Calendar.getInstance(Locale.US); 565 566 java.util.Date date = null; 567 568 String flagval = get(flagname); 569 if (flagval == null) { 570 return null; 571 } 572 573 return getRequiredFriendlyDate(flagname); 574 } 575 576 577 /** 578 Checks to see that one and only 1 of the specified flags is present. Useful for mutualy exclusive arguments. 579 */ 580 public void onlyOneOf(String... flags) 581 { 582 if (flags == null) { 583 throw new IllegalArgumentException("Specify at least one argument to this method!"); 584 } 585 586 BitSet b = new BitSet(flags.length); 587 588 for (int n = 0; n < flags.length; n++) { 589 if (flagExists(flags[n])) { 590 b.set(n); 591 } 592 } 593 594 if (! (b.cardinality() == 1)) { 595 handleError( 596 b.cardinality() + " mutually exclusive flags specified. Can only specify one of the following: \n " + Arrays.toString(flags) + "\n"); 597 } 598 } 599 600 /** 601 Checks to see that one and only 1 of the specified flags is present. Useful for mutualy exclusive arguments. 602 Alias for the {@link onlyOneOf} method. 603 */ 604 public void isMutuallyExclusive(String... flags) 605 { 606 onlyOneOf(flags); 607 } 608 609 610 private static java.util.Date parseDate(String s) throws ParseException 611 { 612 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 613 SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd"); 614 615 java.util.Date date = null; 616 try { 617 date = df.parse(s); 618 } 619 catch (ParseException e) { 620 date = df2.parse(s); 621 } 622 623 return date; 624 } 625 626 627 /** 628 Returns the raw String[] passed to this object during construction. If 629 an InputStream was passed in via construction, returns a String[] created 630 from that InputStream (tokenized by whitespace). The returned String[] 631 is modifiable but it's modification will not affect any other method in 632 this class. 633 @return String[] of unparsed values 634 **/ 635 public String[] rawValues() 636 { 637 return this.input; 638 } 639 640 641 /** 642 Returns a Map containing all flagname/flagvalue pairs. The returned 643 map is an <b>unmodifiable</b> view of the map contained in this class. 644 @return Map of flagnames/values 645 **/ 646 public Map values() 647 { 648 return Collections.unmodifiableMap(this.flagMap); 649 } 650 651 652 /** 653 Specify program usage information to be output when an error occurs. This 654 information should contain a short description of expected and/or optional 655 flags and information that the application expects. 656 @param str Usage information 657 **/ 658 public void setUsage(String str) 659 { 660 this.usage = str; 661 } 662 663 /** 664 Convenience method to display the usage information in case 665 of program error. Reads and sets the usage file found in the 666 same directory/resource location as the calling object's 667 package. For example, if clazz <tt>a.b.foo</tt> calls this 668 method, then the usage file should exist in the <tt>a/b</tt> 669 directory (typically starting from the classpath or a 670 jar/zip file). 671 <p> 672 The name of the usage file is the same as the class name of 673 the specified object (without the ending ".java") appended 674 with "<tt>_usage.txt</tt>". Therefore the usage file for 675 code residing in <tt>a.b.foo.java</tt> should be called 676 <tt>foo_usage.txt</tt> and reside in the <tt>a/b</tt> 677 directory. 678 679 @param caller the calling object <i>or</i> {@link java.lang.Class} Class 680 corresponding to that object 681 **/ 682 public void setDefaultUsage(Object caller) 683 { 684 try { 685 Class clazz = caller.getClass(); 686 687 String usageFile = StringUtil.fileName(clazz.getName().replace('.', '/')) + "_usage.txt"; 688 689 InputStream in = clazz.getResourceAsStream(usageFile); 690 if (in != null) 691 setUsage( IOUtil.inputStreamToString(in, false) ); 692 else 693 setUsage( "Cannot display usage instructions, " + usageFile + " not found"); 694 } 695 catch (IOException e) { 696 e.printStackTrace(); 697 } 698 } 699 700 /** 701 Convenience method to display the usage information in case of 702 program error. Reads and sets the usage file found specified by 703 the <tt>pathToFile</tt> argument of this method. This path should 704 be a path to a file reachable from any directory in the classpath 705 (i.e., relative to the classpath) and should not start with a "/" 706 **/ 707 public void setDefaultUsage(String pathToFile) 708 { 709 try { 710 InputStream in = ClassLoader.getSystemResourceAsStream(pathToFile); 711 if (in != null) 712 setUsage( IOUtil.inputStreamToString(in, false) ); 713 else 714 setUsage( "Cannot display usage instructions, " + pathToFile + " not found"); 715 } 716 catch (IOException e) { 717 e.printStackTrace(); 718 } 719 } 720 721 /** 722 Invoking this method gives the same results as invoking 723 {@link #getRequired(String) getRequired} with a non-existent flag name. 724 **/ 725 public void showError() { 726 getRequired(""); 727 } 728 729 /** 730 Returns the PrintStream where error messages will be sent. This 731 method can be overriden in sublasses to return a different PrintStream. 732 **/ 733 protected PrintStream getOutputStream() 734 { 735 return System.err; 736 } 737 738 final boolean toBoolean(String val) 739 { 740 val = val.trim().toLowerCase().intern(); 741 if ( val == "true" || val == "yes" || val == "1" ) 742 return true; 743 return false; 744 } 745 746 /** 747 Handles an error situation such as when a required flag is 748 not found. By default, calls the <tt>getOutputStream</tt> method, 749 prints an error message and exits the JVM by calling <tt>System.exit(1)</tt> 750 This method can be overriden in subclasses to handle errors differently. 751 @param str the error message 752 @see #getOutputStream 753 **/ 754 protected void handleError(String str) 755 { 756 PrintStream out = getOutputStream(); 757 out.println(str); 758 if (usage != null) { 759 out.println(usage); 760 } 761 out.println(); 762 out.println("-------- Stack Trace --------"); 763 new Exception().printStackTrace(out); 764 if (! _internal_testing) { 765 System.exit(1); 766 } 767 } 768 769 public static void main(String[] args) throws Exception 770 { 771 new Test(args); 772 } 773 774 static private class Test 775 { 776 Test(String[] args) throws Exception 777 { 778 Args myarg = new Args(args); 779 System.out.println("Main Class Name: " + myarg.getMainClassName()); 780 System.out.println("Command line parsed as: "+myarg.values()); 781 782 String testbytes = 783 "-port 80 -port2=5000 -flow c:\\program files -foo " + 784 "-moo \"moo moo\" -x aaa -x bbb - default fasf"; 785 786 Args myarg2 = new Args(new ByteArrayInputStream(testbytes.getBytes("ISO-8859-1"))); 787 Map values = myarg2.values(); 788 System.out.println("testbyte string parsed as: " + values); 789 try { 790 values.put("foo","bar"); 791 } 792 catch (Exception e) { e.printStackTrace(); } 793 String[] rawvals = myarg2.rawValues(); 794 System.out.println("raw values="+Arrays.asList(rawvals)); 795 System.out.println("get(port2)="+myarg2.get("port2")); 796 System.out.println("get(default)="+myarg2.get(DEFAULT_FLAGNAME)); 797 System.out.println("get(x)="+myarg2.get("x")); 798 System.out.println("flagExists(moo)="+myarg2.flagExists("flow")); 799 System.out.println("flagExists(moo)="+myarg2.flagExists("foo")); 800 System.out.println("flagExists(doesnotexits)="+myarg2.flagExists("doesnotexist")); 801 802 testbytes = "-testdate now -testdate2 nextweek -testdate3 atbeginweek -testdate4 2015-03-01"; 803 Args myarg3 = new Args(new ByteArrayInputStream(testbytes.getBytes("ISO-8859-1"))); 804 System.out.println("friendly date test, using as input:\n\t" + testbytes); 805 System.out.println("now="+new Date()); 806 System.out.println("testdate1="+myarg3.getRequiredFriendlyDate("testdate")); 807 System.out.println("testdate2="+myarg3.getRequiredFriendlyDate("testdate2")); 808 System.out.println("testdate3="+myarg3.getRequiredFriendlyDate("testdate3")); 809 System.out.println("testdate4="+myarg3.getRequiredFriendlyDate("testdate4")); 810 811 //prevents system.exit being called. 812 _internal_testing = true; 813 814 try { 815 myarg2.setUsage("Usage: java Args.java your arguments here"); 816 myarg2.getRequired("doesnotexist"); 817 } 818 catch (Exception e) { 819 System.out.println(IOUtil.throwableToString(e)); 820 } 821 822 try { 823 testbytes = "-flag1 -flag2 -flag3"; 824 Args myarg4 = new Args(new ByteArrayInputStream(testbytes.getBytes("ISO-8859-1"))); 825 826 myarg4.setUsage("Testing only 1 mutually flag present"); 827 System.out.print("this should work...myarg4.onlyOneOf(\"flag1\")"); 828 myarg4.onlyOneOf("flag1"); 829 System.out.println("..yes"); 830 831 System.out.println("this should fail.."); 832 myarg4.onlyOneOf("flag1", "flag2"); 833 } 834 catch (Exception e) { 835 System.out.println(IOUtil.throwableToString(e)); 836 } 837 } 838 } //~class Test 839 840 //-------------------- implementation helper methods ---------------------- 841 842 //converts the inputstream into a whitespace delimited array of tokens 843 //and then saves those tokens as the input (a String[]) instance variable 844 private void toStringArray(InputStream in) 845 { 846 try { 847 int len = in.available(); 848 byte[] arr = new byte[len]; 849 //will read upto len bytes into arr, shouldn't block 850 new BufferedInputStream(in).read(arr, 0, len); 851 String temp = new String(arr, "ISO-8859-1"); 852 //default tokenizer delimits by all whitespace and does 853 //not return delimiters themselves. 854 StringTokenizer st = new StringTokenizer(temp); 855 int toknum = st.countTokens(); 856 this.input = new String[toknum]; 857 int n = 0; 858 while (st.hasMoreTokens()) { 859 this.input[n++] = st.nextToken().trim(); 860 } 861 } 862 catch (IOException e) { 863 this.input = null; 864 } 865 } 866 867 //reads this.input(String[]), parses and stores the arguments in this.flagMap (as 868 //key=value pairs). A state machine approach to this is *really* painful, surprisingly 869 //so. (i tried it and it was 5 times the lines of code over this method and it was still 870 //kinda buggy), while this method, although apparently hacky, works like a charm. 871 private void parseFlags() 872 { 873 if (input == null) { 874 return; 875 } 876 flagMap = new HashMap(); 877 String flagname = null, flagvalue = null; 878 int fp = findNextFlag(0); 879 int nextfp = 0; 880 int endvalp = 0; //current flagvalue to be read until this position 881 while (fp != -1) 882 { 883 flagname = input[fp]; 884 flagvalue = null; 885 int eqindex = flagname.indexOf('='); 886 if (eqindex != -1) { 887 flagvalue = flagname.substring(eqindex+1, flagname.length()); 888 flagname = flagname.substring(0, eqindex); 889 } 890 nextfp = findNextFlag(fp+1); 891 endvalp = (nextfp == -1) ? input.length : nextfp; 892 for (int n = fp + 1; n < endvalp; n++) { 893 if (flagvalue == null ) { flagvalue = input[n]; } 894 else { flagvalue += " " + input[n]; } 895 } 896 if (flagname.length() == 1) { 897 flagname = DEFAULT_FLAGNAME; 898 } 899 else { 900 flagname = flagname.substring(1,flagname.length()); 901 } 902 if (flagMap.containsKey(flagname)) { //append if flagvalue already exists 903 flagvalue = flagMap.get(flagname) + FLAG_REPEAT_VALUE_DELIM + flagvalue; 904 } 905 flagMap.put(flagname, flagvalue); 906 fp = nextfp; 907 // for debugging 908 // System.out.println("flagname="+flagname); 909 // System.out.println(eqindex); 910 // System.out.println("fp, nextfp, endvalp = "+fp+","+nextfp+","+endvalp); 911 // System.out.println("flagname, flagvalue="+flagname +"," + flagvalue); 912 } //~while 913 } //~parseFlags 914 915 //finds the position of the next -flag starting from pos or -1 if no flag found 916 private int findNextFlag(int pos) 917 { 918 for (int n = pos; n < input.length; n++) 919 { 920 // System.out.println("n, input[n]="+n+","+input[n]); 921 if (input[n].startsWith("-")) 922 { 923 return n; 924 } 925 } 926 return -1; 927 } 928 929 } //~class Args