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