d150db2c9b6051d2c910b039c5627043485feb41
[jalview.git] / src / jalview / bin / ArgParser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import java.net.URLDecoder;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.EnumSet;
29 import java.util.Enumeration;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34
35 import jalview.util.Platform;
36
37 public class ArgParser
38 {
39   private static final String NEGATESTRING = "no";
40
41   private static final String DEFAULTLINKEDID = "";
42
43   private static enum Opt
44   {
45     BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED
46   }
47
48   public enum Arg
49   {
50     /*
51     NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
52     FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
53     NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
54     OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
55     VSESS;
56     */
57     HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
58     COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
59     ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
60     USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
61     VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
62     TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
63     NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, DEBUG("d");
64
65     static
66     {
67       HELP.setOptions(Opt.UNARY);
68       CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
69                                                  // expecting "--nocalculation"
70       MENUBAR.setOptions(true, Opt.BOOLEAN);
71       STATUS.setOptions(true, Opt.BOOLEAN);
72       SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
73       ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
74       COLOUR.setOptions(Opt.STRING, Opt.LINKED);
75       FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
76       GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
77       GROUPS.setOptions(Opt.STRING, Opt.LINKED);
78       HEADLESS.setOptions(Opt.UNARY);
79       JABAWS.setOptions(Opt.STRING);
80       ANNOTATION.setOptions(true, Opt.BOOLEAN);
81       ANNOTATION2.setOptions(true, Opt.BOOLEAN);
82       DISPLAY.setOptions(true, Opt.BOOLEAN);
83       GUI.setOptions(true, Opt.BOOLEAN);
84       NEWS.setOptions(true, Opt.BOOLEAN);
85       NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
86                                              // expects a string value
87       SORTBYTREE.setOptions(true, Opt.BOOLEAN);
88       USAGESTATS.setOptions(true, Opt.BOOLEAN);
89       OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
90       OPEN2.setOptions(Opt.STRING, Opt.LINKED);
91       PROPS.setOptions(Opt.STRING);
92       QUESTIONNAIRE.setOptions(Opt.STRING);
93       SETPROP.setOptions(Opt.STRING);
94       TREE.setOptions(Opt.STRING);
95
96       VDOC.setOptions(Opt.UNARY);
97       VSESS.setOptions(Opt.UNARY);
98
99       OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
100       OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
101
102       SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
103       NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
104       TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
105       TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
106       TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
107       TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
108       TITLE.setOptions(Opt.STRING, Opt.LINKED);
109       PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
110       NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
111       STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
112       WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
113       IMAGE.setOptions(Opt.STRING, Opt.LINKED);
114       QUIT.setOptions(Opt.UNARY);
115       DEBUG.setOptions(Opt.BOOLEAN);
116     }
117
118     private final String[] argNames;
119
120     private Opt[] argOptions;
121
122     private boolean defaultBoolValue = false;
123
124     private int argIndex = -1;
125
126     public String toLongString()
127     {
128       StringBuilder sb = new StringBuilder();
129       sb.append("Arg: ").append(this.name());
130       for (String name : getNames())
131       {
132         sb.append(", '").append(name).append("'");
133       }
134       sb.append("\nOptions: ");
135       boolean first = true;
136       for (Opt o : argOptions)
137       {
138         if (!first)
139         {
140           sb.append(", ");
141         }
142         sb.append(o.toString());
143         first = false;
144       }
145       sb.append("\n");
146       return sb.toString();
147     }
148
149     private Arg()
150     {
151       this(new String[0]);
152     }
153
154     private Arg(String... names)
155     {
156       int length = (names == null || names.length == 0
157               || (names.length == 1 && names[0] == null)) ? 1
158                       : names.length + 1;
159       this.argNames = new String[length];
160       this.argNames[0] = this.getName();
161       if (length > 1)
162         System.arraycopy(names, 0, this.argNames, 1, names.length);
163     }
164
165     public String[] getNames()
166     {
167       return argNames;
168     }
169
170     public String getName()
171     {
172       return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
173     }
174
175     @Override
176     public final String toString()
177     {
178       return getName();
179     }
180
181     public boolean hasOption(Opt o)
182     {
183       if (argOptions == null)
184         return false;
185       for (Opt option : argOptions)
186       {
187         if (o == option)
188           return true;
189       }
190       return false;
191     }
192
193     protected void setOptions(Opt... options)
194     {
195       setOptions(false, options);
196     }
197
198     protected void setOptions(boolean defaultBoolValue, Opt... options)
199     {
200       this.defaultBoolValue = defaultBoolValue;
201       argOptions = options;
202     }
203
204     protected boolean getDefaultBoolValue()
205     {
206       return defaultBoolValue;
207     }
208
209     private void setArgIndex(int i)
210     {
211       this.argIndex = i;
212     }
213
214     protected int getArgIndex()
215     {
216       return this.argIndex;
217     }
218   }
219
220   public static class ArgValues
221   {
222     private Arg arg;
223
224     private int argCount = 0;
225
226     private boolean boolValue = false;
227
228     private boolean negated = false;
229
230     private int singleArgIndex = -1;
231
232     private List<Integer> argsIndexes;
233
234     private List<ArgValue> argsList;
235
236     protected ArgValues(Arg a)
237     {
238       this.arg = a;
239       this.argsList = new ArrayList<ArgValue>();
240       this.boolValue = arg.getDefaultBoolValue();
241     }
242
243     public Arg arg()
244     {
245       return arg;
246     }
247
248     protected int getCount()
249     {
250       return argCount;
251     }
252
253     protected void incrementCount()
254     {
255       argCount++;
256     }
257
258     protected void setNegated(boolean b)
259     {
260       this.negated = b;
261     }
262
263     protected boolean isNegated()
264     {
265       return this.negated;
266     }
267
268     protected void setBoolean(boolean b)
269     {
270       this.boolValue = b;
271     }
272
273     protected boolean getBoolean()
274     {
275       return this.boolValue;
276     }
277
278     @Override
279     public String toString()
280     {
281       if (argsList == null)
282         return null;
283       StringBuilder sb = new StringBuilder();
284       sb.append(arg.toLongString());
285       if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
286         sb.append("Boolean: ").append(boolValue).append("; Default: ")
287                 .append(arg.getDefaultBoolValue()).append("; Negated: ")
288                 .append(negated).append("\n");
289       if (arg.hasOption(Opt.STRING))
290       {
291         sb.append("Values:");
292         boolean first = true;
293         for (ArgValue av : argsList)
294         {
295           String v = av.getValue();
296           if (!first)
297             sb.append(",");
298           sb.append("\n  '");
299           sb.append(v).append("'");
300           first = false;
301         }
302         sb.append("\n");
303       }
304       sb.append("Count: ").append(argCount).append("\n");
305       return sb.toString();
306     }
307
308     protected void addValue()
309     {
310       addValue(null, -1);
311     }
312
313     protected void addValue(String val, int argIndex)
314     {
315       addValue(val, argIndex, false);
316     }
317
318     protected void addValue(String val, int argIndex, boolean noDuplicates)
319     {
320       if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
321               || (noDuplicates && argsList.contains(val)))
322         return;
323       if (argsList == null)
324       {
325         argsList = new ArrayList<ArgValue>();
326       }
327       argsList.add(new ArgValue(val, argIndex));
328     }
329
330     protected boolean hasValue(String val)
331     {
332       return argsList.contains(val);
333     }
334
335     protected ArgValue getArgValue()
336     {
337       if (arg.hasOption(Opt.MULTI))
338         Console.warn("Requesting single value for multi value argument");
339       return argsList.size() > 0 ? argsList.get(0) : null;
340     }
341
342     /*
343     protected String getValue()
344     {
345     ArgValue av = getArgValue();
346     return av == null ? null : av.getValue();
347     }
348     */
349
350     protected List<ArgValue> getArgValueList()
351     {
352       return argsList;
353     }
354   }
355
356   // old style
357   private List<String> vargs = null;
358
359   private boolean isApplet;
360
361   // private AppletParams appletParams;
362
363   public boolean isApplet()
364   {
365     return isApplet;
366   }
367
368   public String nextValue()
369   {
370     return vargs.remove(0);
371   }
372
373   public int getSize()
374   {
375     return vargs.size();
376   }
377
378   public String getValue(String arg)
379   {
380     return getValue(arg, false);
381   }
382
383   public String getValue(String arg, boolean utf8decode)
384   {
385     int index = vargs.indexOf(arg);
386     String dc = null, ret = null;
387     if (index != -1)
388     {
389       ret = vargs.get(index + 1).toString();
390       vargs.remove(index);
391       vargs.remove(index);
392       if (utf8decode && ret != null)
393       {
394         try
395         {
396           dc = URLDecoder.decode(ret, "UTF-8");
397           ret = dc;
398         } catch (Exception e)
399         {
400           // TODO: log failure to decode
401         }
402       }
403     }
404     return ret;
405   }
406
407   /*
408   public Object getAppletValue(String key, String def, boolean asString)
409   {
410     Object value;
411     return (appletParams == null ? null
412             : (value = appletParams.get(key.toLowerCase())) == null ? def
413                     : asString ? "" + value : value);
414   }
415   */
416
417   // new style
418   private static final Map<String, Arg> argMap;
419
420   private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
421
422   private List<String> linkedOrder = null;
423
424   private List<Arg> argList;
425
426   static
427   {
428     argMap = new HashMap<>();
429     for (Arg a : EnumSet.allOf(Arg.class))
430     {
431       ARGNAME: for (String argName : a.getNames())
432       {
433         if (argMap.containsKey(argName))
434         {
435           Console.warn("Trying to add argument name multiple times: '"
436                   + argName + "'"); // RESTORE THIS WHEN MERGED
437           if (argMap.get(argName) != a)
438           {
439             Console.error(
440                     "Trying to add argument name multiple times for different Args: '"
441                             + argMap.get(argName).getName() + ":" + argName
442                             + "' and '" + a.getName() + ":" + argName
443                             + "'");
444           }
445           continue ARGNAME;
446         }
447         argMap.put(argName, a);
448       }
449     }
450   }
451
452   public ArgParser(String[] args)
453   {
454     // old style
455     vargs = new ArrayList<>();
456     isApplet = (args.length > 0 && args[0].startsWith("<applet"));
457     if (isApplet)
458     {
459       // appletParams = AppletParams.getAppletParams(args, vargs);
460     }
461     else
462     {
463       if (Platform.isJS())
464
465       {
466         isApplet = true;
467         // appletParams =
468         // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
469       }
470       for (int i = 0; i < args.length; i++)
471       {
472         String arg = args[i].trim();
473         if (arg.charAt(0) == '-')
474         {
475           arg = arg.substring(1);
476         }
477         vargs.add(arg);
478       }
479     }
480
481     // new style
482     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
483     int argIndex = 0;
484     while (argE.hasMoreElements())
485     {
486       String arg = argE.nextElement();
487       String argName = null;
488       String val = null;
489       String linkedId = null;
490       if (arg.startsWith("--"))
491       {
492         int equalPos = arg.indexOf('=');
493         if (equalPos > -1)
494         {
495           argName = arg.substring(2, equalPos);
496           val = arg.substring(equalPos + 1);
497         }
498         else
499         {
500           argName = arg.substring(2);
501         }
502         int idOpen = argName.indexOf('[');
503         int idClose = argName.indexOf(']');
504
505         if (idOpen > -1 && idClose == argName.length() - 1)
506         {
507           linkedId = argName.substring(idOpen + 1, idClose);
508           argName = argName.substring(0, idOpen);
509         }
510
511         Arg a = argMap.get(argName);
512         // check for boolean prepended by "no"
513         boolean negated = false;
514         if (a == null && argName.startsWith(NEGATESTRING) && argMap
515                 .containsKey(argName.substring(NEGATESTRING.length())))
516         {
517           argName = argName.substring(NEGATESTRING.length());
518           a = argMap.get(argName);
519           negated = true;
520         }
521
522         // check for config errors
523         if (a == null)
524         {
525           // arg not found
526           Console.error("Argument '" + arg + "' not recognised. Ignoring.");
527           continue;
528         }
529         if (!a.hasOption(Opt.BOOLEAN) && negated)
530         {
531           // used "no" with a non-boolean option
532           Console.error("Argument '--" + NEGATESTRING + argName
533                   + "' not a boolean option. Ignoring.");
534           continue;
535         }
536         if (!a.hasOption(Opt.STRING) && equalPos > -1)
537         {
538           // set --argname=value when arg does not accept values
539           Console.error("Argument '--" + argName
540                   + "' does not expect a value (given as '" + arg
541                   + "').  Ignoring.");
542           continue;
543         }
544         if (!a.hasOption(Opt.LINKED) && linkedId != null)
545         {
546           // set --argname[linkedId] when arg does not use linkedIds
547           Console.error("Argument '--" + argName
548                   + "' does not expect a linked id (given as '" + arg
549                   + "'). Ignoring.");
550           continue;
551         }
552
553         if (a.hasOption(Opt.STRING) && equalPos == -1)
554         {
555           // take next arg as value if required, and '=' was not found
556           if (!argE.hasMoreElements())
557           {
558             // no value to take for arg, which wants a value
559             Console.error("Argument '" + a.getName()
560                     + "' requires a value, none given. Ignoring.");
561             continue;
562           }
563           val = argE.nextElement();
564         }
565
566         // use default linkedId for linked arguments
567         if (a.hasOption(Opt.LINKED) && linkedId == null)
568           linkedId = DEFAULTLINKEDID;
569
570         if (!linkedArgs.containsKey(linkedId))
571           linkedArgs.put(linkedId, new HashMap<>());
572
573         Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
574         if (!valuesMap.containsKey(a))
575           valuesMap.put(a, new ArgValues(a));
576
577         ArgValues values = valuesMap.get(a);
578         if (values == null)
579         {
580           values = new ArgValues(a);
581         }
582         // store appropriate value
583         if (a.hasOption(Opt.STRING))
584         {
585           values.addValue(val, argIndex);
586         }
587         else if (a.hasOption(Opt.BOOLEAN))
588         {
589           values.setBoolean(!negated);
590           values.setNegated(negated);
591         }
592         else if (a.hasOption(Opt.UNARY))
593         {
594           values.setBoolean(true);
595         }
596         values.incrementCount();
597
598         // store in appropriate place
599         if (a.hasOption(Opt.LINKED))
600         {
601           // allow a default linked id for single usage
602           if (linkedId == null)
603             linkedId = DEFAULTLINKEDID;
604           // store the order of linkedIds
605           if (linkedOrder == null)
606             linkedOrder = new ArrayList<>();
607           if (!linkedOrder.contains(linkedId))
608             linkedOrder.add(linkedId);
609         }
610         // store the ArgValues
611         valuesMap.put(a, values);
612
613         // store arg in the list of args
614         if (argList == null)
615           argList = new ArrayList<>();
616         if (!argList.contains(a))
617           argList.add(a);
618       }
619     }
620   }
621
622   public boolean isSet(Arg a)
623   {
624     return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
625   }
626
627   public boolean isSet(String linkedId, Arg a)
628   {
629     Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
630     return m == null ? false : m.containsKey(a);
631   }
632
633   public boolean getBool(Arg a)
634   {
635     if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
636     {
637       Console.warn("Getting boolean from non boolean Arg '" + a.getName()
638               + "'.");
639     }
640     return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
641   }
642
643   public boolean getBool(String linkedId, Arg a)
644   {
645     Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
646     if (m == null)
647       return a.getDefaultBoolValue();
648     ArgValues v = m.get(a);
649     return v == null ? a.getDefaultBoolValue() : v.getBoolean();
650   }
651
652   public List<String> linkedIds()
653   {
654     return linkedOrder;
655   }
656
657   public HashMap<Arg, ArgValues> linkedArgs(String id)
658   {
659     return linkedArgs.get(id);
660   }
661
662   @Override
663   public String toString()
664   {
665     StringBuilder sb = new StringBuilder();
666     sb.append("UNLINKED\n");
667     sb.append(argMapToString(linkedArgs.get(null)));
668     if (linkedIds() != null)
669     {
670       sb.append("LINKED\n");
671       for (String id : linkedIds())
672       {
673         // already listed these as UNLINKED args
674         if (id == null)
675           continue;
676
677         Map<Arg, ArgValues> m = linkedArgs(id);
678         sb.append("ID: '").append(id).append("'\n");
679         sb.append(argMapToString(m));
680       }
681     }
682     return sb.toString();
683   }
684
685   private static String argMapToString(Map<Arg, ArgValues> m)
686   {
687     if (m == null)
688       return null;
689     StringBuilder sb = new StringBuilder();
690     for (Arg a : m.keySet())
691     {
692       ArgValues v = m.get(a);
693       sb.append(v.toString());
694       sb.append("\n");
695     }
696     return sb.toString();
697   }
698
699   // Helper methods with safety checks
700   protected static ArgValues getArgValues(Map<Arg, ArgValues> m, Arg a)
701   {
702     return m == null ? null : m.get(a);
703   }
704
705   public static List<ArgValue> getArgValueList(Map<Arg, ArgValues> m, Arg a)
706   {
707     ArgValues av = getArgValues(m, a);
708     return av == null ? null : av.getArgValueList();
709   }
710
711   public static ArgValue getArgValue(Map<Arg, ArgValues> m, Arg a)
712   {
713     List<ArgValue> vals = getArgValueList(m, a);
714     return (vals == null || vals.size() == 0) ? null : vals.get(0);
715   }
716
717   public static String getValue(Map<Arg, ArgValues> m, Arg a)
718   {
719     ArgValue av = getArgValue(m, a);
720     return av == null ? null : av.getValue();
721   }
722
723   public static boolean hasValue(Map<Arg, ArgValues> m, Arg a)
724   {
725     if (!m.containsKey(a))
726       return false;
727     return getArgValue(m, a) != null;
728   }
729
730   public static boolean getBoolean(Map<Arg, ArgValues> m, Arg a)
731   {
732     ArgValues av = getArgValues(m, a);
733     return av == null ? false : av.getBoolean();
734   }
735
736   public static SubVals getSubVals(String item)
737   {
738     return new SubVals(item);
739   }
740
741   /**
742    * A helper class to keep an index of argument position with argument values
743    */
744   public static class ArgValue
745   {
746     private int argIndex;
747
748     private String value;
749
750     protected ArgValue(String value, int argIndex)
751     {
752       this.value = value;
753       this.argIndex = argIndex;
754     }
755
756     protected String getValue()
757     {
758       return value;
759     }
760
761     protected int getArgIndex()
762     {
763       return argIndex;
764     }
765   }
766
767   /**
768    * A helper class to parse a string of the possible forms "content"
769    * "[index]content", "[keyName=keyValue]content" and return the integer index,
770    * the strings keyName and keyValue, and the content after the square brackets
771    * (if present). Values not set `will be -1 or null.
772    */
773   public static class SubVals
774   {
775     private static int NOTSET = -1;
776
777     private int index = NOTSET;
778
779     private Map<String, String> subVals = null;
780
781     private static char SEPARATOR = ';';
782
783     private String content = null;
784
785     public SubVals(String item)
786     {
787       this.parseVals(item);
788     }
789
790     public void parseVals(String item)
791     {
792       if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
793       {
794         int openBracket = item.indexOf('[');
795         int closeBracket = item.indexOf(']');
796         String subvalsString = item.substring(openBracket + 1,
797                 closeBracket);
798         this.content = item.substring(closeBracket + 1);
799         boolean setIndex = false;
800         for (String subvalString : subvalsString
801                 .split(Character.toString(SEPARATOR)))
802         {
803           int equals = subvalString.indexOf('=');
804           if (equals > -1)
805           {
806             if (subVals == null)
807               subVals = new HashMap<>();
808             subVals.put(subvalString.substring(0, equals),
809                     subvalString.substring(equals + 1));
810           }
811           else
812           {
813             try
814             {
815               this.index = Integer.parseInt(subvalString);
816               setIndex = true;
817             } catch (NumberFormatException e)
818             {
819               Console.warn("Failed to obtain subvalue or index from '"
820                       + item + "'. Setting index=0 and using content='"
821                       + content + "'.");
822             }
823           }
824         }
825         if (!setIndex)
826           this.index = NOTSET;
827       }
828       else
829       {
830         this.content = item;
831       }
832     }
833
834     public boolean notSet()
835     {
836       // notSet is true if content present but nonsensical
837       return index == NOTSET && subVals == null;
838     }
839
840     public String get(String key)
841     {
842       return subVals == null ? null : subVals.get(key);
843     }
844
845     public boolean has(String key)
846     {
847       return subVals == null ? false : subVals.containsKey(key);
848     }
849
850     public int getIndex()
851     {
852       return index;
853     }
854
855     public String getContent()
856     {
857       return content;
858     }
859   }
860
861   private static final Collection<String> bootstrapArgs = new ArrayList(
862           Arrays.asList("props", "debug"));
863
864   public static Map<String, String> bootstrapArgs(String[] args)
865   {
866     Map<String, String> argMap = new HashMap<>();
867     if (args == null)
868       return argMap;
869     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
870     while (argE.hasMoreElements())
871     {
872       String arg = argE.nextElement();
873       String argName = null;
874       String val = null;
875       if (arg.startsWith("--"))
876       {
877         int equalPos = arg.indexOf('=');
878         if (equalPos > -1)
879         {
880           argName = arg.substring(2, equalPos);
881           val = arg.substring(equalPos + 1);
882         }
883         else
884         {
885           argName = arg.substring(2);
886         }
887         if (bootstrapArgs.contains(argName))
888           argMap.put(argName, val);
889       }
890     }
891     return argMap;
892   }
893 }