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