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