JAL-4134 hold ctrl to select groups containing rows and columns under mouse over...
[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   public static SubVals getSubVals(String item)
700   {
701     return new SubVals(item);
702   }
703
704   /**
705    * A helper class to keep an index of argument position with argument values
706    */
707   public static class ArgValue
708   {
709     private int argIndex;
710
711     private String value;
712
713     protected ArgValue(String value, int argIndex)
714     {
715       this.value = value;
716       this.argIndex = argIndex;
717     }
718
719     protected String getValue()
720     {
721       return value;
722     }
723
724     protected int getArgIndex()
725     {
726       return argIndex;
727     }
728   }
729
730   /**
731    * A helper class to parse a string of the possible forms "content"
732    * "[index]content", "[keyName=keyValue]content" and return the integer index,
733    * the strings keyName and keyValue, and the content after the square brackets
734    * (if present). Values not set `will be -1 or null.
735    */
736   public static class SubVals
737   {
738     private static int NOTSET = -1;
739
740     private int index = NOTSET;
741
742     private Map<String, String> subVals = null;
743
744     private static char SEPARATOR = ';';
745
746     private String content = null;
747
748     public SubVals(String item)
749     {
750       this.parseVals(item);
751     }
752
753     public void parseVals(String item)
754     {
755       if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
756       {
757         int openBracket = item.indexOf('[');
758         int closeBracket = item.indexOf(']');
759         String subvalsString = item.substring(openBracket + 1,
760                 closeBracket);
761         this.content = item.substring(closeBracket + 1);
762         boolean setIndex = false;
763         for (String subvalString : subvalsString
764                 .split(Character.toString(SEPARATOR)))
765         {
766           int equals = subvalString.indexOf('=');
767           if (equals > -1)
768           {
769             if (subVals == null)
770               subVals = new HashMap<>();
771             subVals.put(subvalString.substring(0, equals),
772                     subvalString.substring(equals + 1));
773           }
774           else
775           {
776             try
777             {
778               this.index = Integer.parseInt(subvalString);
779               setIndex = true;
780             } catch (NumberFormatException e)
781             {
782               Console.warn("Failed to obtain subvalue or index from '"
783                       + item + "'. Setting index=0 and using content='"
784                       + content + "'.");
785             }
786           }
787         }
788         if (!setIndex)
789           this.index = NOTSET;
790       }
791       else
792       {
793         this.content = item;
794       }
795     }
796
797     public boolean notSet()
798     {
799       // notSet is true if content present but nonsensical
800       return index == NOTSET && subVals == null;
801     }
802
803     public String get(String key)
804     {
805       return subVals == null ? null : subVals.get(key);
806     }
807
808     public boolean has(String key)
809     {
810       return subVals == null ? false : subVals.containsKey(key);
811     }
812
813     public int getIndex()
814     {
815       return index;
816     }
817
818     public String getContent()
819     {
820       return content;
821     }
822   }
823
824   /**
825    * Helper class to allow easy extraction of information about specific
826    * argument values (without having to check for null etc all the time)
827    */
828   protected static class ArgValuesMap
829   {
830     protected Map<Arg, ArgValues> m;
831
832     protected ArgValuesMap(Map<Arg, ArgValues> map)
833     {
834       this.m = map;
835     }
836
837     protected ArgValues getArgValues(Arg a)
838     {
839       return m == null ? null : m.get(a);
840     }
841
842     protected List<ArgValue> getArgValueList(Arg a)
843     {
844       ArgValues av = getArgValues(a);
845       return av == null ? null : av.getArgValueList();
846     }
847
848     protected ArgValue getArgValue(Arg a)
849     {
850       List<ArgValue> vals = getArgValueList(a);
851       return (vals == null || vals.size() == 0) ? null : vals.get(0);
852     }
853
854     protected String getValue(Arg a)
855     {
856       ArgValue av = getArgValue(a);
857       return av == null ? null : av.getValue();
858     }
859
860     protected boolean hasValue(Arg a)
861     {
862       if (!m.containsKey(a))
863         return false;
864       return getArgValue(a) != null;
865     }
866
867     protected boolean getBoolean(Arg a)
868     {
869       ArgValues av = getArgValues(a);
870       return av == null ? false : av.getBoolean();
871     }
872   }
873
874   private static final Collection<String> bootstrapArgs = new ArrayList(
875           Arrays.asList("props", "debug"));
876
877   public static Map<String, String> bootstrapArgs(String[] args)
878   {
879     Map<String, String> argMap = new HashMap<>();
880     if (args == null)
881       return argMap;
882     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
883     while (argE.hasMoreElements())
884     {
885       String arg = argE.nextElement();
886       String argName = null;
887       String val = null;
888       if (arg.startsWith("--"))
889       {
890         int equalPos = arg.indexOf('=');
891         if (equalPos > -1)
892         {
893           argName = arg.substring(2, equalPos);
894           val = arg.substring(equalPos + 1);
895         }
896         else
897         {
898           argName = arg.substring(2);
899         }
900         if (bootstrapArgs.contains(argName))
901           argMap.put(argName, val);
902       }
903     }
904     return argMap;
905   }
906 }