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>&quot;a/b c/&quot;</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