JAL-629 Firm up bootstrapArgs. Correct '-colour' in test. Separate --headless and...
[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     public String toLongString()
125     {
126       StringBuilder sb = new StringBuilder();
127       sb.append("Arg: ").append(this.name());
128       for (String name : getNames())
129       {
130         sb.append(", '").append(name).append("'");
131       }
132       sb.append("\nOptions: ");
133       boolean first = true;
134       for (Opt o : argOptions)
135       {
136         if (!first)
137         {
138           sb.append(", ");
139         }
140         sb.append(o.toString());
141         first = false;
142       }
143       sb.append("\n");
144       return sb.toString();
145     }
146
147     private Arg()
148     {
149       this(new String[0]);
150     }
151
152     private Arg(String... names)
153     {
154       int length = (names == null || names.length == 0
155               || (names.length == 1 && names[0] == null)) ? 1
156                       : names.length + 1;
157       this.argNames = new String[length];
158       this.argNames[0] = this.getName();
159       if (length > 1)
160         System.arraycopy(names, 0, this.argNames, 1, names.length);
161     }
162
163     public String[] getNames()
164     {
165       return argNames;
166     }
167
168     public String getName()
169     {
170       return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
171     }
172
173     @Override
174     public final String toString()
175     {
176       return getName();
177     }
178
179     public boolean hasOption(Opt o)
180     {
181       if (argOptions == null)
182         return false;
183       for (Opt option : argOptions)
184       {
185         if (o == option)
186           return true;
187       }
188       return false;
189     }
190
191     protected void setOptions(Opt... options)
192     {
193       setOptions(false, options);
194     }
195
196     protected void setOptions(boolean defaultBoolValue, Opt... options)
197     {
198       this.defaultBoolValue = defaultBoolValue;
199       argOptions = options;
200     }
201
202     protected boolean getDefaultBoolValue()
203     {
204       return defaultBoolValue;
205     }
206   }
207
208   public static class ArgValues
209   {
210     private Arg arg;
211
212     private int argCount = 0;
213
214     private boolean boolValue = false;
215
216     private boolean negated = false;
217
218     private int singleArgIndex = -1;
219
220     private List<Integer> argsIndexes;
221
222     private List<ArgValue> argsList;
223
224     protected ArgValues(Arg a)
225     {
226       this.arg = a;
227       this.argsList = new ArrayList<ArgValue>();
228       this.boolValue = arg.getDefaultBoolValue();
229     }
230
231     public Arg arg()
232     {
233       return arg;
234     }
235
236     protected int getCount()
237     {
238       return argCount;
239     }
240
241     protected void incrementCount()
242     {
243       argCount++;
244     }
245
246     protected void setNegated(boolean b)
247     {
248       this.negated = b;
249     }
250
251     protected boolean isNegated()
252     {
253       return this.negated;
254     }
255
256     protected void setBoolean(boolean b)
257     {
258       this.boolValue = b;
259     }
260
261     protected boolean getBoolean()
262     {
263       return this.boolValue;
264     }
265
266     @Override
267     public String toString()
268     {
269       if (argsList == null)
270         return null;
271       StringBuilder sb = new StringBuilder();
272       sb.append(arg.toLongString());
273       if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
274         sb.append("Boolean: ").append(boolValue).append("; Default: ")
275                 .append(arg.getDefaultBoolValue()).append("; Negated: ")
276                 .append(negated).append("\n");
277       if (arg.hasOption(Opt.STRING))
278       {
279         sb.append("Values:");
280         boolean first = true;
281         for (ArgValue av : argsList)
282         {
283           String v = av.getValue();
284           if (!first)
285             sb.append(",");
286           sb.append("\n  '");
287           sb.append(v).append("'");
288           first = false;
289         }
290         sb.append("\n");
291       }
292       sb.append("Count: ").append(argCount).append("\n");
293       return sb.toString();
294     }
295
296     protected void addValue()
297     {
298       addValue(null, -1);
299     }
300
301     protected void addValue(String val, int argIndex)
302     {
303       addValue(val, argIndex, false);
304     }
305
306     protected void addValue(String val, int argIndex, boolean noDuplicates)
307     {
308       if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
309               || (noDuplicates && argsList.contains(val)))
310         return;
311       if (argsList == null)
312       {
313         argsList = new ArrayList<ArgValue>();
314       }
315       argsList.add(new ArgValue(val, argIndex));
316     }
317
318     protected boolean hasValue(String val)
319     {
320       return argsList.contains(val);
321     }
322
323     protected ArgValue getArgValue()
324     {
325       if (arg.hasOption(Opt.MULTI))
326         Console.warn("Requesting single value for multi value argument");
327       return argsList.size() > 0 ? argsList.get(0) : null;
328     }
329
330     /*
331     protected String getValue()
332     {
333     ArgValue av = getArgValue();
334     return av == null ? null : av.getValue();
335     }
336     */
337
338     protected List<ArgValue> getArgValueList()
339     {
340       return argsList;
341     }
342   }
343
344   // old style
345   private List<String> vargs = null;
346
347   private boolean isApplet;
348
349   // private AppletParams appletParams;
350
351   public boolean isApplet()
352   {
353     return isApplet;
354   }
355
356   public String nextValue()
357   {
358     return vargs.remove(0);
359   }
360
361   public int getSize()
362   {
363     return vargs.size();
364   }
365
366   public String getValue(String arg)
367   {
368     return getValue(arg, false);
369   }
370
371   public String getValue(String arg, boolean utf8decode)
372   {
373     int index = vargs.indexOf(arg);
374     String dc = null, ret = null;
375     if (index != -1)
376     {
377       ret = vargs.get(index + 1).toString();
378       vargs.remove(index);
379       vargs.remove(index);
380       if (utf8decode && ret != null)
381       {
382         try
383         {
384           dc = URLDecoder.decode(ret, "UTF-8");
385           ret = dc;
386         } catch (Exception e)
387         {
388           // TODO: log failure to decode
389         }
390       }
391     }
392     return ret;
393   }
394
395   /*
396   public Object getAppletValue(String key, String def, boolean asString)
397   {
398     Object value;
399     return (appletParams == null ? null
400             : (value = appletParams.get(key.toLowerCase())) == null ? def
401                     : asString ? "" + value : value);
402   }
403   */
404
405   // new style
406   private static final Map<String, Arg> argMap;
407
408   private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
409
410   private List<String> linkedOrder = null;
411
412   private List<Arg> argList;
413
414   static
415   {
416     argMap = new HashMap<>();
417     for (Arg a : EnumSet.allOf(Arg.class))
418     {
419       ARGNAME: for (String argName : a.getNames())
420       {
421         if (argMap.containsKey(argName))
422         {
423           Console.warn("Trying to add argument name multiple times: '"
424                   + argName + "'"); // RESTORE THIS WHEN MERGED
425           if (argMap.get(argName) != a)
426           {
427             Console.error(
428                     "Trying to add argument name multiple times for different Args: '"
429                             + argMap.get(argName).getName() + ":" + argName
430                             + "' and '" + a.getName() + ":" + argName
431                             + "'");
432           }
433           continue ARGNAME;
434         }
435         argMap.put(argName, a);
436       }
437     }
438   }
439
440   public ArgParser(String[] args)
441   {
442     // old style
443     vargs = new ArrayList<>();
444     isApplet = (args.length > 0 && args[0].startsWith("<applet"));
445     if (isApplet)
446     {
447       // appletParams = AppletParams.getAppletParams(args, vargs);
448     }
449     else
450     {
451       if (Platform.isJS())
452
453       {
454         isApplet = true;
455         // appletParams =
456         // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
457       }
458       for (int i = 0; i < args.length; i++)
459       {
460         String arg = args[i].trim();
461         if (arg.charAt(0) == '-')
462         {
463           arg = arg.substring(1);
464         }
465         vargs.add(arg);
466       }
467     }
468
469     // new style
470     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
471     int argIndex = 0;
472     while (argE.hasMoreElements())
473     {
474       String arg = argE.nextElement();
475       String argName = null;
476       String val = null;
477       String linkedId = null;
478       if (arg.startsWith("--"))
479       {
480         int equalPos = arg.indexOf('=');
481         if (equalPos > -1)
482         {
483           argName = arg.substring(2, equalPos);
484           val = arg.substring(equalPos + 1);
485         }
486         else
487         {
488           argName = arg.substring(2);
489         }
490         int idOpen = argName.indexOf('[');
491         int idClose = argName.indexOf(']');
492
493         if (idOpen > -1 && idClose == argName.length() - 1)
494         {
495           linkedId = argName.substring(idOpen + 1, idClose);
496           argName = argName.substring(0, idOpen);
497         }
498
499         Arg a = argMap.get(argName);
500         // check for boolean prepended by "no"
501         boolean negated = false;
502         if (a == null && argName.startsWith(NEGATESTRING) && argMap
503                 .containsKey(argName.substring(NEGATESTRING.length())))
504         {
505           argName = argName.substring(NEGATESTRING.length());
506           a = argMap.get(argName);
507           negated = true;
508         }
509
510         // check for config errors
511         if (a == null)
512         {
513           // arg not found
514           Console.error("Argument '" + arg + "' not recognised. Ignoring.");
515           continue;
516         }
517         if (!a.hasOption(Opt.BOOLEAN) && negated)
518         {
519           // used "no" with a non-boolean option
520           Console.error("Argument '--" + NEGATESTRING + argName
521                   + "' not a boolean option. Ignoring.");
522           continue;
523         }
524         if (!a.hasOption(Opt.STRING) && equalPos > -1)
525         {
526           // set --argname=value when arg does not accept values
527           Console.error("Argument '--" + argName
528                   + "' does not expect a value (given as '" + arg
529                   + "').  Ignoring.");
530           continue;
531         }
532         if (!a.hasOption(Opt.LINKED) && linkedId != null)
533         {
534           // set --argname[linkedId] when arg does not use linkedIds
535           Console.error("Argument '--" + argName
536                   + "' does not expect a linked id (given as '" + arg
537                   + "'). Ignoring.");
538           continue;
539         }
540
541         if (a.hasOption(Opt.STRING) && equalPos == -1)
542         {
543           // take next arg as value if required, and '=' was not found
544           if (!argE.hasMoreElements())
545           {
546             // no value to take for arg, which wants a value
547             Console.error("Argument '" + a.getName()
548                     + "' requires a value, none given. Ignoring.");
549             continue;
550           }
551           val = argE.nextElement();
552         }
553
554         // use default linkedId for linked arguments
555         if (a.hasOption(Opt.LINKED) && linkedId == null)
556           linkedId = DEFAULTLINKEDID;
557
558         if (!linkedArgs.containsKey(linkedId))
559           linkedArgs.put(linkedId, new HashMap<>());
560
561         Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
562         if (!valuesMap.containsKey(a))
563           valuesMap.put(a, new ArgValues(a));
564
565         ArgValues values = valuesMap.get(a);
566         if (values == null)
567         {
568           values = new ArgValues(a);
569         }
570         // store appropriate value
571         if (a.hasOption(Opt.STRING))
572         {
573           values.addValue(val, argIndex);
574         }
575         else if (a.hasOption(Opt.BOOLEAN))
576         {
577           values.setBoolean(!negated);
578           values.setNegated(negated);
579         }
580         else if (a.hasOption(Opt.UNARY))
581         {
582           values.setBoolean(true);
583         }
584         values.incrementCount();
585
586         // store in appropriate place
587         if (a.hasOption(Opt.LINKED))
588         {
589           // allow a default linked id for single usage
590           if (linkedId == null)
591             linkedId = DEFAULTLINKEDID;
592           // store the order of linkedIds
593           if (linkedOrder == null)
594             linkedOrder = new ArrayList<>();
595           if (!linkedOrder.contains(linkedId))
596             linkedOrder.add(linkedId);
597         }
598         // store the ArgValues
599         valuesMap.put(a, values);
600
601         // store arg in the list of args
602         if (argList == null)
603           argList = new ArrayList<>();
604         if (!argList.contains(a))
605           argList.add(a);
606       }
607     }
608   }
609
610   public boolean isSet(Arg a)
611   {
612     return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
613   }
614
615   public boolean isSet(String linkedId, Arg a)
616   {
617     Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
618     return m == null ? false : m.containsKey(a);
619   }
620
621   public boolean getBool(Arg a)
622   {
623     if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
624     {
625       Console.warn("Getting boolean from non boolean Arg '" + a.getName()
626               + "'.");
627     }
628     return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
629   }
630
631   public boolean getBool(String linkedId, Arg a)
632   {
633     Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
634     if (m == null)
635       return a.getDefaultBoolValue();
636     ArgValues v = m.get(a);
637     return v == null ? a.getDefaultBoolValue() : v.getBoolean();
638   }
639
640   public List<String> linkedIds()
641   {
642     return linkedOrder;
643   }
644
645   public HashMap<Arg, ArgValues> linkedArgs(String id)
646   {
647     return linkedArgs.get(id);
648   }
649
650   @Override
651   public String toString()
652   {
653     StringBuilder sb = new StringBuilder();
654     sb.append("UNLINKED\n");
655     sb.append(argMapToString(linkedArgs.get(null)));
656     if (linkedIds() != null)
657     {
658       sb.append("LINKED\n");
659       for (String id : linkedIds())
660       {
661         // already listed these as UNLINKED args
662         if (id == null)
663           continue;
664
665         Map<Arg, ArgValues> m = linkedArgs(id);
666         sb.append("ID: '").append(id).append("'\n");
667         sb.append(argMapToString(m));
668       }
669     }
670     return sb.toString();
671   }
672
673   private static String argMapToString(Map<Arg, ArgValues> m)
674   {
675     if (m == null)
676       return null;
677     StringBuilder sb = new StringBuilder();
678     for (Arg a : m.keySet())
679     {
680       ArgValues v = m.get(a);
681       sb.append(v.toString());
682       sb.append("\n");
683     }
684     return sb.toString();
685   }
686
687   public static SubVals getSubVals(String item)
688   {
689     return new SubVals(item);
690   }
691
692   /**
693    * A helper class to keep an index of argument position with argument values
694    */
695   public static class ArgValue
696   {
697     private int argIndex;
698
699     private String value;
700
701     protected ArgValue(String value, int argIndex)
702     {
703       this.value = value;
704       this.argIndex = argIndex;
705     }
706
707     protected String getValue()
708     {
709       return value;
710     }
711
712     protected int getArgIndex()
713     {
714       return argIndex;
715     }
716   }
717
718   /**
719    * A helper class to parse a string of the possible forms "content"
720    * "[index]content", "[keyName=keyValue]content" and return the integer index,
721    * the strings keyName and keyValue, and the content after the square brackets
722    * (if present). Values not set `will be -1 or null.
723    */
724   public static class SubVals
725   {
726     private static int NOTSET = -1;
727
728     private int index = NOTSET;
729
730     private Map<String, String> subVals = null;
731
732     private static char SEPARATOR = ';';
733
734     private String content = null;
735
736     public SubVals(String item)
737     {
738       this.parseVals(item);
739     }
740
741     public void parseVals(String item)
742     {
743       if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
744       {
745         int openBracket = item.indexOf('[');
746         int closeBracket = item.indexOf(']');
747         String subvalsString = item.substring(openBracket + 1,
748                 closeBracket);
749         this.content = item.substring(closeBracket + 1);
750         boolean setIndex = false;
751         for (String subvalString : subvalsString
752                 .split(Character.toString(SEPARATOR)))
753         {
754           int equals = subvalString.indexOf('=');
755           if (equals > -1)
756           {
757             if (subVals == null)
758               subVals = new HashMap<>();
759             subVals.put(subvalString.substring(0, equals),
760                     subvalString.substring(equals + 1));
761           }
762           else
763           {
764             try
765             {
766               this.index = Integer.parseInt(subvalString);
767               setIndex = true;
768             } catch (NumberFormatException e)
769             {
770               Console.warn("Failed to obtain subvalue or index from '"
771                       + item + "'. Setting index=0 and using content='"
772                       + content + "'.");
773             }
774           }
775         }
776         if (!setIndex)
777           this.index = NOTSET;
778       }
779       else
780       {
781         this.content = item;
782       }
783     }
784
785     public boolean notSet()
786     {
787       // notSet is true if content present but nonsensical
788       return index == NOTSET && subVals == null;
789     }
790
791     public String get(String key)
792     {
793       return subVals == null ? null : subVals.get(key);
794     }
795
796     public boolean has(String key)
797     {
798       return subVals == null ? false : subVals.containsKey(key);
799     }
800
801     public int getIndex()
802     {
803       return index;
804     }
805
806     public String getContent()
807     {
808       return content;
809     }
810   }
811
812   /**
813    * Helper class to allow easy extraction of information about specific
814    * argument values (without having to check for null etc all the time)
815    */
816   protected static class ArgValuesMap
817   {
818     protected Map<Arg, ArgValues> m;
819
820     protected ArgValuesMap(Map<Arg, ArgValues> map)
821     {
822       this.m = map;
823     }
824
825     protected ArgValues getArgValues(Arg a)
826     {
827       return m == null ? null : m.get(a);
828     }
829
830     protected List<ArgValue> getArgValueList(Arg a)
831     {
832       ArgValues av = getArgValues(a);
833       return av == null ? null : av.getArgValueList();
834     }
835
836     protected ArgValue getArgValue(Arg a)
837     {
838       List<ArgValue> vals = getArgValueList(a);
839       return (vals == null || vals.size() == 0) ? null : vals.get(0);
840     }
841
842     protected String getValue(Arg a)
843     {
844       ArgValue av = getArgValue(a);
845       return av == null ? null : av.getValue();
846     }
847
848     protected boolean hasValue(Arg a)
849     {
850       if (!m.containsKey(a))
851         return false;
852       return getArgValue(a) != null;
853     }
854
855     protected boolean getBoolean(Arg a)
856     {
857       ArgValues av = getArgValues(a);
858       return av == null ? false : av.getBoolean();
859     }
860
861     protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
862             Arg a)
863     {
864       ArgValue closestAv = null;
865       int thisArgIndex = thisAv.getArgIndex();
866       ArgValues compareAvs = this.getArgValues(a);
867       int closestPreviousIndex = -1;
868       for (ArgValue av : compareAvs.getArgValueList())
869       {
870         int argIndex = av.getArgIndex();
871         if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
872         {
873           closestPreviousIndex = argIndex;
874           closestAv = av;
875         }
876       }
877       return closestAv;
878     }
879   }
880
881   private static final Collection<Arg> bootstrapArgs = new ArrayList(
882           Arrays.asList(Arg.PROPS, Arg.DEBUG));
883
884   public static Map<Arg, String> bootstrapArgs(String[] args)
885   {
886     Map<Arg, String> bootstrapArgMap = new HashMap<>();
887     if (args == null)
888       return bootstrapArgMap;
889     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
890     while (argE.hasMoreElements())
891     {
892       String arg = argE.nextElement();
893       String argName = null;
894       String val = null;
895       if (arg.startsWith("--"))
896       {
897         int equalPos = arg.indexOf('=');
898         if (equalPos > -1)
899         {
900           argName = arg.substring(2, equalPos);
901           val = arg.substring(equalPos + 1);
902         }
903         else
904         {
905           argName = arg.substring(2);
906         }
907         Arg a = argMap.get(argName);
908         if (a != null && bootstrapArgs.contains(a))
909           bootstrapArgMap.put(a, val);
910       }
911     }
912     return bootstrapArgMap;
913   }
914 }