Merge branch 'features/JAL-3858_PAEsInProjects' into develop
[jalview.git] / src / jalview / bin / ArgParser.java
diff --git a/src/jalview/bin/ArgParser.java b/src/jalview/bin/ArgParser.java
new file mode 100644 (file)
index 0000000..2290182
--- /dev/null
@@ -0,0 +1,906 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.bin;
+
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import jalview.util.Platform;
+
+public class ArgParser
+{
+  private static final String NEGATESTRING = "no";
+
+  private static final String DEFAULTLINKEDID = "";
+
+  private static enum Opt
+  {
+    BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED
+  }
+
+  public enum Arg
+  {
+    /*
+    NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
+    FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
+    NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
+    OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
+    VSESS;
+    */
+    HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
+    COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
+    ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
+    USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
+    VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
+    TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
+    NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, DEBUG("d");
+
+    static
+    {
+      HELP.setOptions(Opt.UNARY);
+      CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
+                                                 // expecting "--nocalculation"
+      MENUBAR.setOptions(true, Opt.BOOLEAN);
+      STATUS.setOptions(true, Opt.BOOLEAN);
+      SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
+      ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
+      COLOUR.setOptions(Opt.STRING, Opt.LINKED);
+      FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+      GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+      GROUPS.setOptions(Opt.STRING, Opt.LINKED);
+      HEADLESS.setOptions(Opt.UNARY);
+      JABAWS.setOptions(Opt.STRING);
+      ANNOTATION.setOptions(true, Opt.BOOLEAN);
+      ANNOTATION2.setOptions(true, Opt.BOOLEAN);
+      DISPLAY.setOptions(true, Opt.BOOLEAN);
+      GUI.setOptions(true, Opt.BOOLEAN);
+      NEWS.setOptions(true, Opt.BOOLEAN);
+      NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
+                                             // expects a string value
+      SORTBYTREE.setOptions(true, Opt.BOOLEAN);
+      USAGESTATS.setOptions(true, Opt.BOOLEAN);
+      OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+      OPEN2.setOptions(Opt.STRING, Opt.LINKED);
+      PROPS.setOptions(Opt.STRING);
+      QUESTIONNAIRE.setOptions(Opt.STRING);
+      SETPROP.setOptions(Opt.STRING);
+      TREE.setOptions(Opt.STRING);
+
+      VDOC.setOptions(Opt.UNARY);
+      VSESS.setOptions(Opt.UNARY);
+
+      OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
+      OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+
+      SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
+      NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
+      TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
+      TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
+      TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
+      TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
+      TITLE.setOptions(Opt.STRING, Opt.LINKED);
+      PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+      NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
+      STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+      WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
+      IMAGE.setOptions(Opt.STRING, Opt.LINKED);
+      QUIT.setOptions(Opt.UNARY);
+      DEBUG.setOptions(Opt.BOOLEAN);
+    }
+
+    private final String[] argNames;
+
+    private Opt[] argOptions;
+
+    private boolean defaultBoolValue = false;
+
+    private int argIndex = -1;
+
+    public String toLongString()
+    {
+      StringBuilder sb = new StringBuilder();
+      sb.append("Arg: ").append(this.name());
+      for (String name : getNames())
+      {
+        sb.append(", '").append(name).append("'");
+      }
+      sb.append("\nOptions: ");
+      boolean first = true;
+      for (Opt o : argOptions)
+      {
+        if (!first)
+        {
+          sb.append(", ");
+        }
+        sb.append(o.toString());
+        first = false;
+      }
+      sb.append("\n");
+      return sb.toString();
+    }
+
+    private Arg()
+    {
+      this(new String[0]);
+    }
+
+    private Arg(String... names)
+    {
+      int length = (names == null || names.length == 0
+              || (names.length == 1 && names[0] == null)) ? 1
+                      : names.length + 1;
+      this.argNames = new String[length];
+      this.argNames[0] = this.getName();
+      if (length > 1)
+        System.arraycopy(names, 0, this.argNames, 1, names.length);
+    }
+
+    public String[] getNames()
+    {
+      return argNames;
+    }
+
+    public String getName()
+    {
+      return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
+    }
+
+    @Override
+    public final String toString()
+    {
+      return getName();
+    }
+
+    public boolean hasOption(Opt o)
+    {
+      if (argOptions == null)
+        return false;
+      for (Opt option : argOptions)
+      {
+        if (o == option)
+          return true;
+      }
+      return false;
+    }
+
+    protected void setOptions(Opt... options)
+    {
+      setOptions(false, options);
+    }
+
+    protected void setOptions(boolean defaultBoolValue, Opt... options)
+    {
+      this.defaultBoolValue = defaultBoolValue;
+      argOptions = options;
+    }
+
+    protected boolean getDefaultBoolValue()
+    {
+      return defaultBoolValue;
+    }
+
+    private void setArgIndex(int i)
+    {
+      this.argIndex = i;
+    }
+
+    protected int getArgIndex()
+    {
+      return this.argIndex;
+    }
+  }
+
+  public static class ArgValues
+  {
+    private Arg arg;
+
+    private int argCount = 0;
+
+    private boolean boolValue = false;
+
+    private boolean negated = false;
+
+    private int singleArgIndex = -1;
+
+    private List<Integer> argsIndexes;
+
+    private List<ArgValue> argsList;
+
+    protected ArgValues(Arg a)
+    {
+      this.arg = a;
+      this.argsList = new ArrayList<ArgValue>();
+      this.boolValue = arg.getDefaultBoolValue();
+    }
+
+    public Arg arg()
+    {
+      return arg;
+    }
+
+    protected int getCount()
+    {
+      return argCount;
+    }
+
+    protected void incrementCount()
+    {
+      argCount++;
+    }
+
+    protected void setNegated(boolean b)
+    {
+      this.negated = b;
+    }
+
+    protected boolean isNegated()
+    {
+      return this.negated;
+    }
+
+    protected void setBoolean(boolean b)
+    {
+      this.boolValue = b;
+    }
+
+    protected boolean getBoolean()
+    {
+      return this.boolValue;
+    }
+
+    @Override
+    public String toString()
+    {
+      if (argsList == null)
+        return null;
+      StringBuilder sb = new StringBuilder();
+      sb.append(arg.toLongString());
+      if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
+        sb.append("Boolean: ").append(boolValue).append("; Default: ")
+                .append(arg.getDefaultBoolValue()).append("; Negated: ")
+                .append(negated).append("\n");
+      if (arg.hasOption(Opt.STRING))
+      {
+        sb.append("Values:");
+        boolean first = true;
+        for (ArgValue av : argsList)
+        {
+          String v = av.getValue();
+          if (!first)
+            sb.append(",");
+          sb.append("\n  '");
+          sb.append(v).append("'");
+          first = false;
+        }
+        sb.append("\n");
+      }
+      sb.append("Count: ").append(argCount).append("\n");
+      return sb.toString();
+    }
+
+    protected void addValue()
+    {
+      addValue(null, -1);
+    }
+
+    protected void addValue(String val, int argIndex)
+    {
+      addValue(val, argIndex, false);
+    }
+
+    protected void addValue(String val, int argIndex, boolean noDuplicates)
+    {
+      if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
+              || (noDuplicates && argsList.contains(val)))
+        return;
+      if (argsList == null)
+      {
+        argsList = new ArrayList<ArgValue>();
+      }
+      argsList.add(new ArgValue(val, argIndex));
+    }
+
+    protected boolean hasValue(String val)
+    {
+      return argsList.contains(val);
+    }
+
+    protected ArgValue getArgValue()
+    {
+      if (arg.hasOption(Opt.MULTI))
+        Console.warn("Requesting single value for multi value argument");
+      return argsList.size() > 0 ? argsList.get(0) : null;
+    }
+
+    /*
+    protected String getValue()
+    {
+    ArgValue av = getArgValue();
+    return av == null ? null : av.getValue();
+    }
+    */
+
+    protected List<ArgValue> getArgValueList()
+    {
+      return argsList;
+    }
+  }
+
+  // old style
+  private List<String> vargs = null;
+
+  private boolean isApplet;
+
+  // private AppletParams appletParams;
+
+  public boolean isApplet()
+  {
+    return isApplet;
+  }
+
+  public String nextValue()
+  {
+    return vargs.remove(0);
+  }
+
+  public int getSize()
+  {
+    return vargs.size();
+  }
+
+  public String getValue(String arg)
+  {
+    return getValue(arg, false);
+  }
+
+  public String getValue(String arg, boolean utf8decode)
+  {
+    int index = vargs.indexOf(arg);
+    String dc = null, ret = null;
+    if (index != -1)
+    {
+      ret = vargs.get(index + 1).toString();
+      vargs.remove(index);
+      vargs.remove(index);
+      if (utf8decode && ret != null)
+      {
+        try
+        {
+          dc = URLDecoder.decode(ret, "UTF-8");
+          ret = dc;
+        } catch (Exception e)
+        {
+          // TODO: log failure to decode
+        }
+      }
+    }
+    return ret;
+  }
+
+  /*
+  public Object getAppletValue(String key, String def, boolean asString)
+  {
+    Object value;
+    return (appletParams == null ? null
+            : (value = appletParams.get(key.toLowerCase())) == null ? def
+                    : asString ? "" + value : value);
+  }
+  */
+
+  // new style
+  private static final Map<String, Arg> argMap;
+
+  private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
+
+  private List<String> linkedOrder = null;
+
+  private List<Arg> argList;
+
+  static
+  {
+    argMap = new HashMap<>();
+    for (Arg a : EnumSet.allOf(Arg.class))
+    {
+      ARGNAME: for (String argName : a.getNames())
+      {
+        if (argMap.containsKey(argName))
+        {
+          Console.warn("Trying to add argument name multiple times: '"
+                  + argName + "'"); // RESTORE THIS WHEN MERGED
+          if (argMap.get(argName) != a)
+          {
+            Console.error(
+                    "Trying to add argument name multiple times for different Args: '"
+                            + argMap.get(argName).getName() + ":" + argName
+                            + "' and '" + a.getName() + ":" + argName
+                            + "'");
+          }
+          continue ARGNAME;
+        }
+        argMap.put(argName, a);
+      }
+    }
+  }
+
+  public ArgParser(String[] args)
+  {
+    // old style
+    vargs = new ArrayList<>();
+    isApplet = (args.length > 0 && args[0].startsWith("<applet"));
+    if (isApplet)
+    {
+      // appletParams = AppletParams.getAppletParams(args, vargs);
+    }
+    else
+    {
+      if (Platform.isJS())
+
+      {
+        isApplet = true;
+        // appletParams =
+        // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
+      }
+      for (int i = 0; i < args.length; i++)
+      {
+        String arg = args[i].trim();
+        if (arg.charAt(0) == '-')
+        {
+          arg = arg.substring(1);
+        }
+        vargs.add(arg);
+      }
+    }
+
+    // new style
+    Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
+    int argIndex = 0;
+    while (argE.hasMoreElements())
+    {
+      String arg = argE.nextElement();
+      String argName = null;
+      String val = null;
+      String linkedId = null;
+      if (arg.startsWith("--"))
+      {
+        int equalPos = arg.indexOf('=');
+        if (equalPos > -1)
+        {
+          argName = arg.substring(2, equalPos);
+          val = arg.substring(equalPos + 1);
+        }
+        else
+        {
+          argName = arg.substring(2);
+        }
+        int idOpen = argName.indexOf('[');
+        int idClose = argName.indexOf(']');
+
+        if (idOpen > -1 && idClose == argName.length() - 1)
+        {
+          linkedId = argName.substring(idOpen + 1, idClose);
+          argName = argName.substring(0, idOpen);
+        }
+
+        Arg a = argMap.get(argName);
+        // check for boolean prepended by "no"
+        boolean negated = false;
+        if (a == null && argName.startsWith(NEGATESTRING) && argMap
+                .containsKey(argName.substring(NEGATESTRING.length())))
+        {
+          argName = argName.substring(NEGATESTRING.length());
+          a = argMap.get(argName);
+          negated = true;
+        }
+
+        // check for config errors
+        if (a == null)
+        {
+          // arg not found
+          Console.error("Argument '" + arg + "' not recognised. Ignoring.");
+          continue;
+        }
+        if (!a.hasOption(Opt.BOOLEAN) && negated)
+        {
+          // used "no" with a non-boolean option
+          Console.error("Argument '--" + NEGATESTRING + argName
+                  + "' not a boolean option. Ignoring.");
+          continue;
+        }
+        if (!a.hasOption(Opt.STRING) && equalPos > -1)
+        {
+          // set --argname=value when arg does not accept values
+          Console.error("Argument '--" + argName
+                  + "' does not expect a value (given as '" + arg
+                  + "').  Ignoring.");
+          continue;
+        }
+        if (!a.hasOption(Opt.LINKED) && linkedId != null)
+        {
+          // set --argname[linkedId] when arg does not use linkedIds
+          Console.error("Argument '--" + argName
+                  + "' does not expect a linked id (given as '" + arg
+                  + "'). Ignoring.");
+          continue;
+        }
+
+        if (a.hasOption(Opt.STRING) && equalPos == -1)
+        {
+          // take next arg as value if required, and '=' was not found
+          if (!argE.hasMoreElements())
+          {
+            // no value to take for arg, which wants a value
+            Console.error("Argument '" + a.getName()
+                    + "' requires a value, none given. Ignoring.");
+            continue;
+          }
+          val = argE.nextElement();
+        }
+
+        // use default linkedId for linked arguments
+        if (a.hasOption(Opt.LINKED) && linkedId == null)
+          linkedId = DEFAULTLINKEDID;
+
+        if (!linkedArgs.containsKey(linkedId))
+          linkedArgs.put(linkedId, new HashMap<>());
+
+        Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
+        if (!valuesMap.containsKey(a))
+          valuesMap.put(a, new ArgValues(a));
+
+        ArgValues values = valuesMap.get(a);
+        if (values == null)
+        {
+          values = new ArgValues(a);
+        }
+        // store appropriate value
+        if (a.hasOption(Opt.STRING))
+        {
+          values.addValue(val, argIndex);
+        }
+        else if (a.hasOption(Opt.BOOLEAN))
+        {
+          values.setBoolean(!negated);
+          values.setNegated(negated);
+        }
+        else if (a.hasOption(Opt.UNARY))
+        {
+          values.setBoolean(true);
+        }
+        values.incrementCount();
+
+        // store in appropriate place
+        if (a.hasOption(Opt.LINKED))
+        {
+          // allow a default linked id for single usage
+          if (linkedId == null)
+            linkedId = DEFAULTLINKEDID;
+          // store the order of linkedIds
+          if (linkedOrder == null)
+            linkedOrder = new ArrayList<>();
+          if (!linkedOrder.contains(linkedId))
+            linkedOrder.add(linkedId);
+        }
+        // store the ArgValues
+        valuesMap.put(a, values);
+
+        // store arg in the list of args
+        if (argList == null)
+          argList = new ArrayList<>();
+        if (!argList.contains(a))
+          argList.add(a);
+      }
+    }
+  }
+
+  public boolean isSet(Arg a)
+  {
+    return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
+  }
+
+  public boolean isSet(String linkedId, Arg a)
+  {
+    Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
+    return m == null ? false : m.containsKey(a);
+  }
+
+  public boolean getBool(Arg a)
+  {
+    if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
+    {
+      Console.warn("Getting boolean from non boolean Arg '" + a.getName()
+              + "'.");
+    }
+    return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
+  }
+
+  public boolean getBool(String linkedId, Arg a)
+  {
+    Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
+    if (m == null)
+      return a.getDefaultBoolValue();
+    ArgValues v = m.get(a);
+    return v == null ? a.getDefaultBoolValue() : v.getBoolean();
+  }
+
+  public List<String> linkedIds()
+  {
+    return linkedOrder;
+  }
+
+  public HashMap<Arg, ArgValues> linkedArgs(String id)
+  {
+    return linkedArgs.get(id);
+  }
+
+  @Override
+  public String toString()
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append("UNLINKED\n");
+    sb.append(argMapToString(linkedArgs.get(null)));
+    if (linkedIds() != null)
+    {
+      sb.append("LINKED\n");
+      for (String id : linkedIds())
+      {
+        // already listed these as UNLINKED args
+        if (id == null)
+          continue;
+
+        Map<Arg, ArgValues> m = linkedArgs(id);
+        sb.append("ID: '").append(id).append("'\n");
+        sb.append(argMapToString(m));
+      }
+    }
+    return sb.toString();
+  }
+
+  private static String argMapToString(Map<Arg, ArgValues> m)
+  {
+    if (m == null)
+      return null;
+    StringBuilder sb = new StringBuilder();
+    for (Arg a : m.keySet())
+    {
+      ArgValues v = m.get(a);
+      sb.append(v.toString());
+      sb.append("\n");
+    }
+    return sb.toString();
+  }
+
+  public static SubVals getSubVals(String item)
+  {
+    return new SubVals(item);
+  }
+
+  /**
+   * A helper class to keep an index of argument position with argument values
+   */
+  public static class ArgValue
+  {
+    private int argIndex;
+
+    private String value;
+
+    protected ArgValue(String value, int argIndex)
+    {
+      this.value = value;
+      this.argIndex = argIndex;
+    }
+
+    protected String getValue()
+    {
+      return value;
+    }
+
+    protected int getArgIndex()
+    {
+      return argIndex;
+    }
+  }
+
+  /**
+   * A helper class to parse a string of the possible forms "content"
+   * "[index]content", "[keyName=keyValue]content" and return the integer index,
+   * the strings keyName and keyValue, and the content after the square brackets
+   * (if present). Values not set `will be -1 or null.
+   */
+  public static class SubVals
+  {
+    private static int NOTSET = -1;
+
+    private int index = NOTSET;
+
+    private Map<String, String> subVals = null;
+
+    private static char SEPARATOR = ';';
+
+    private String content = null;
+
+    public SubVals(String item)
+    {
+      this.parseVals(item);
+    }
+
+    public void parseVals(String item)
+    {
+      if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
+      {
+        int openBracket = item.indexOf('[');
+        int closeBracket = item.indexOf(']');
+        String subvalsString = item.substring(openBracket + 1,
+                closeBracket);
+        this.content = item.substring(closeBracket + 1);
+        boolean setIndex = false;
+        for (String subvalString : subvalsString
+                .split(Character.toString(SEPARATOR)))
+        {
+          int equals = subvalString.indexOf('=');
+          if (equals > -1)
+          {
+            if (subVals == null)
+              subVals = new HashMap<>();
+            subVals.put(subvalString.substring(0, equals),
+                    subvalString.substring(equals + 1));
+          }
+          else
+          {
+            try
+            {
+              this.index = Integer.parseInt(subvalString);
+              setIndex = true;
+            } catch (NumberFormatException e)
+            {
+              Console.warn("Failed to obtain subvalue or index from '"
+                      + item + "'. Setting index=0 and using content='"
+                      + content + "'.");
+            }
+          }
+        }
+        if (!setIndex)
+          this.index = NOTSET;
+      }
+      else
+      {
+        this.content = item;
+      }
+    }
+
+    public boolean notSet()
+    {
+      // notSet is true if content present but nonsensical
+      return index == NOTSET && subVals == null;
+    }
+
+    public String get(String key)
+    {
+      return subVals == null ? null : subVals.get(key);
+    }
+
+    public boolean has(String key)
+    {
+      return subVals == null ? false : subVals.containsKey(key);
+    }
+
+    public int getIndex()
+    {
+      return index;
+    }
+
+    public String getContent()
+    {
+      return content;
+    }
+  }
+
+  /**
+   * Helper class to allow easy extraction of information about specific
+   * argument values (without having to check for null etc all the time)
+   */
+  protected static class ArgValuesMap
+  {
+    protected Map<Arg, ArgValues> m;
+
+    protected ArgValuesMap(Map<Arg, ArgValues> map)
+    {
+      this.m = map;
+    }
+
+    protected ArgValues getArgValues(Arg a)
+    {
+      return m == null ? null : m.get(a);
+    }
+
+    protected List<ArgValue> getArgValueList(Arg a)
+    {
+      ArgValues av = getArgValues(a);
+      return av == null ? null : av.getArgValueList();
+    }
+
+    protected ArgValue getArgValue(Arg a)
+    {
+      List<ArgValue> vals = getArgValueList(a);
+      return (vals == null || vals.size() == 0) ? null : vals.get(0);
+    }
+
+    protected String getValue(Arg a)
+    {
+      ArgValue av = getArgValue(a);
+      return av == null ? null : av.getValue();
+    }
+
+    protected boolean hasValue(Arg a)
+    {
+      if (!m.containsKey(a))
+        return false;
+      return getArgValue(a) != null;
+    }
+
+    protected boolean getBoolean(Arg a)
+    {
+      ArgValues av = getArgValues(a);
+      return av == null ? false : av.getBoolean();
+    }
+  }
+
+  private static final Collection<String> bootstrapArgs = new ArrayList(
+          Arrays.asList("props", "debug"));
+
+  public static Map<String, String> bootstrapArgs(String[] args)
+  {
+    Map<String, String> argMap = new HashMap<>();
+    if (args == null)
+      return argMap;
+    Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
+    while (argE.hasMoreElements())
+    {
+      String arg = argE.nextElement();
+      String argName = null;
+      String val = null;
+      if (arg.startsWith("--"))
+      {
+        int equalPos = arg.indexOf('=');
+        if (equalPos > -1)
+        {
+          argName = arg.substring(2, equalPos);
+          val = arg.substring(equalPos + 1);
+        }
+        else
+        {
+          argName = arg.substring(2);
+        }
+        if (bootstrapArgs.contains(argName))
+          argMap.put(argName, val);
+      }
+    }
+    return argMap;
+  }
+}
\ No newline at end of file