JAL-629 Improved consistency of ArgParser classes (use of ArgValuesMap). Added settin...
[jalview.git] / src / jalview / bin / ArgParser.java
index 4d37847..cc31f5b 100644 (file)
@@ -23,6 +23,7 @@ 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;
@@ -30,6 +31,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 import jalview.util.Platform;
 
@@ -41,7 +43,7 @@ public class ArgParser
 
   private static enum Opt
   {
-    BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED
+    BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED, NODUPLICATEVALUES
   }
 
   public enum Arg
@@ -59,7 +61,7 @@ public class ArgParser
     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;
+    NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, DEBUG("d");
 
     static
     {
@@ -103,7 +105,7 @@ public class ArgParser
       TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
       TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
       TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
-      TEMPFAC_SHADING.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);
@@ -111,6 +113,7 @@ public class ArgParser
       WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
       IMAGE.setOptions(Opt.STRING, Opt.LINKED);
       QUIT.setOptions(Opt.UNARY);
+      DEBUG.setOptions(Opt.BOOLEAN);
     }
 
     private final String[] argNames;
@@ -205,6 +208,8 @@ public class ArgParser
 
   public static class ArgValues
   {
+    private static final String ID = "id";
+
     private Arg arg;
 
     private int argCount = 0;
@@ -213,12 +218,18 @@ public class ArgParser
 
     private boolean negated = false;
 
-    private List<String> argsList;
+    private int boolIndex = -1;
+
+    private List<Integer> argsIndexes;
+
+    private List<ArgValue> argValueList;
+
+    private Map<String, ArgValue> idMap = new HashMap<>();
 
     protected ArgValues(Arg a)
     {
       this.arg = a;
-      this.argsList = new ArrayList<String>();
+      this.argValueList = new ArrayList<ArgValue>();
       this.boolValue = arg.getDefaultBoolValue();
     }
 
@@ -247,9 +258,10 @@ public class ArgParser
       return this.negated;
     }
 
-    protected void setBoolean(boolean b)
+    protected void setBoolean(boolean b, int i)
     {
       this.boolValue = b;
+      this.boolIndex = i;
     }
 
     protected boolean getBoolean()
@@ -260,7 +272,7 @@ public class ArgParser
     @Override
     public String toString()
     {
-      if (argsList == null)
+      if (argValueList == null)
         return null;
       StringBuilder sb = new StringBuilder();
       sb.append(arg.toLongString());
@@ -272,8 +284,9 @@ public class ArgParser
       {
         sb.append("Values:");
         boolean first = true;
-        for (String v : argsList)
+        for (ArgValue av : argValueList)
         {
+          String v = av.getValue();
           if (!first)
             sb.append(",");
           sb.append("\n  '");
@@ -288,42 +301,59 @@ public class ArgParser
 
     protected void addValue()
     {
-      addValue(null);
+      addValue(null, -1);
     }
 
-    protected void addValue(String val)
+    protected void addValue(String val, int argIndex)
     {
-      addValue(val, false);
+      addArgValue(new ArgValue(val, argIndex));
     }
 
-    protected void addValue(String val, boolean noDuplicates)
+    protected void addArgValue(ArgValue av)
     {
-      if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
-              || (noDuplicates && argsList.contains(val)))
+      if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
+              || (arg.hasOption(Opt.NODUPLICATEVALUES)
+                      && argValueList.contains(av.getValue())))
         return;
-      if (argsList == null)
+      if (argValueList == null)
+      {
+        argValueList = new ArrayList<ArgValue>();
+      }
+      SubVals sv = ArgParser.getSubVals(av.getValue());
+      if (sv.has(ID))
       {
-        Console.warn("** inst");
-        argsList = new ArrayList<String>();
+        String id = sv.get(ID);
+        av.setId(id);
+        idMap.put(id, av);
       }
-      argsList.add(val);
+      argValueList.add(av);
     }
 
     protected boolean hasValue(String val)
     {
-      return argsList.contains(val);
+      return argValueList.contains(val);
     }
 
-    protected String getValue()
+    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;
+      return argValueList.size() > 0 ? argValueList.get(0) : null;
+    }
+
+    protected List<ArgValue> getArgValueList()
+    {
+      return argValueList;
+    }
+
+    protected boolean hasId(String id)
+    {
+      return idMap.containsKey(id);
     }
 
-    protected List<String> getValues()
+    protected ArgValue getId(String id)
     {
-      return argsList;
+      return idMap.get(id);
     }
   }
 
@@ -357,7 +387,8 @@ public class ArgParser
   public String getValue(String arg, boolean utf8decode)
   {
     int index = vargs.indexOf(arg);
-    String dc = null, ret = null;
+    String dc = null;
+    String ret = null;
     if (index != -1)
     {
       ret = vargs.get(index + 1).toString();
@@ -391,7 +422,7 @@ public class ArgParser
   // new style
   private static final Map<String, Arg> argMap;
 
-  private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
+  private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
 
   private List<String> linkedOrder = null;
 
@@ -454,6 +485,7 @@ public class ArgParser
 
     // new style
     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
+    int argIndex = 0;
     while (argE.hasMoreElements())
     {
       String arg = argE.nextElement();
@@ -541,32 +573,48 @@ public class ArgParser
           linkedId = DEFAULTLINKEDID;
 
         if (!linkedArgs.containsKey(linkedId))
-          linkedArgs.put(linkedId, new HashMap<>());
+          linkedArgs.put(linkedId, new ArgValuesMap());
+
+        ArgValuesMap avm = linkedArgs.get(linkedId);
 
-        Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
-        if (!valuesMap.containsKey(a))
-          valuesMap.put(a, new ArgValues(a));
+        if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
+        {
+          Console.error("Argument '--" + argName
+                  + "' cannot contain a duplicate value ('" + val
+                  + "'). Ignoring this and subsequent occurrences.");
+          continue;
+        }
+
+        // check for unique id
+        SubVals sv = ArgParser.getSubVals(val);
+        String id = sv.get(ArgValues.ID);
+        if (id != null && avm.hasId(a, id))
+        {
+          Console.error("Argument '--" + argName + "' has a duplicate id ('"
+                  + id + "'). Ignoring.");
+          continue;
+        }
 
-        ArgValues values = valuesMap.get(a);
-        if (values == null)
+        ArgValues avs = avm.getOrCreateArgValues(a);
+        if (avs == null)
         {
-          values = new ArgValues(a);
+          avs = new ArgValues(a);
         }
         // store appropriate value
         if (a.hasOption(Opt.STRING))
         {
-          values.addValue(val);
+          avs.addValue(val, argIndex);
         }
         else if (a.hasOption(Opt.BOOLEAN))
         {
-          values.setBoolean(!negated);
-          values.setNegated(negated);
+          avs.setBoolean(!negated, argIndex);
+          avs.setNegated(negated);
         }
         else if (a.hasOption(Opt.UNARY))
         {
-          values.setBoolean(true);
+          avs.setBoolean(true, argIndex);
         }
-        values.incrementCount();
+        avs.incrementCount();
 
         // store in appropriate place
         if (a.hasOption(Opt.LINKED))
@@ -580,10 +628,8 @@ public class ArgParser
           if (!linkedOrder.contains(linkedId))
             linkedOrder.add(linkedId);
         }
-        // store the ArgValues
-        valuesMap.put(a, values);
 
-        // store arg in the list of args
+        // store arg in the list of args used
         if (argList == null)
           argList = new ArrayList<>();
         if (!argList.contains(a))
@@ -599,13 +645,13 @@ public class ArgParser
 
   public boolean isSet(String linkedId, Arg a)
   {
-    Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
-    return m == null ? false : m.containsKey(a);
+    ArgValuesMap avm = linkedArgs.get(linkedId);
+    return avm == null ? false : avm.containsArg(a);
   }
 
   public boolean getBool(Arg a)
   {
-    if (!a.hasOption(Opt.BOOLEAN))
+    if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
     {
       Console.warn("Getting boolean from non boolean Arg '" + a.getName()
               + "'.");
@@ -615,11 +661,11 @@ public class ArgParser
 
   public boolean getBool(String linkedId, Arg a)
   {
-    Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
-    if (m == null)
+    ArgValuesMap avm = linkedArgs.get(linkedId);
+    if (avm == null)
       return a.getDefaultBoolValue();
-    ArgValues v = m.get(a);
-    return v == null ? a.getDefaultBoolValue() : v.getBoolean();
+    ArgValues avs = avm.getArgValues(a);
+    return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
   }
 
   public List<String> linkedIds()
@@ -627,7 +673,7 @@ public class ArgParser
     return linkedOrder;
   }
 
-  public HashMap<Arg, ArgValues> linkedArgs(String id)
+  public ArgValuesMap linkedArgs(String id)
   {
     return linkedArgs.get(id);
   }
@@ -637,7 +683,7 @@ public class ArgParser
   {
     StringBuilder sb = new StringBuilder();
     sb.append("UNLINKED\n");
-    sb.append(argMapToString(linkedArgs.get(null)));
+    sb.append(argValuesMapToString(linkedArgs.get(null)));
     if (linkedIds() != null)
     {
       sb.append("LINKED\n");
@@ -647,55 +693,69 @@ public class ArgParser
         if (id == null)
           continue;
 
-        Map<Arg, ArgValues> m = linkedArgs(id);
+        ArgValuesMap avm = linkedArgs(id);
         sb.append("ID: '").append(id).append("'\n");
-        sb.append(argMapToString(m));
+        sb.append(argValuesMapToString(avm));
       }
     }
     return sb.toString();
   }
 
-  private static String argMapToString(Map<Arg, ArgValues> m)
+  private static String argValuesMapToString(ArgValuesMap avm)
   {
-    if (m == null)
+    if (avm == null)
       return null;
     StringBuilder sb = new StringBuilder();
-    for (Arg a : m.keySet())
+    for (Arg a : avm.getArgKeys())
     {
-      ArgValues v = m.get(a);
+      ArgValues v = avm.getArgValues(a);
       sb.append(v.toString());
       sb.append("\n");
     }
     return sb.toString();
   }
 
-  // Helper methods with safety checks
-  protected static ArgValues getArgValues(Map<Arg, ArgValues> m, Arg a)
+  public static SubVals getSubVals(String item)
   {
-    return m == null ? null : m.get(a);
+    return new SubVals(item);
   }
 
-  public static List<String> getValues(Map<Arg, ArgValues> m, Arg a)
+  /**
+   * A helper class to keep an index of argument position with argument values
+   */
+  public static class ArgValue
   {
-    ArgValues av = getArgValues(m, a);
-    return av == null ? null : av.getValues();
-  }
+    private int argIndex;
 
-  public static String getValue(Map<Arg, ArgValues> m, Arg a)
-  {
-    List<String> vals = getValues(m, a);
-    return (vals == null || vals.size() == 0) ? null : vals.get(0);
-  }
+    private String value;
 
-  public static boolean getBoolean(Map<Arg, ArgValues> m, Arg a)
-  {
-    ArgValues av = getArgValues(m, a);
-    return av == null ? false : av.getBoolean();
-  }
+    private String id;
 
-  public static SubVal getSubVal(String item)
-  {
-    return new SubVal(item);
+    protected ArgValue(String value, int argIndex)
+    {
+      this.value = value;
+      this.argIndex = argIndex;
+    }
+
+    protected String getValue()
+    {
+      return value;
+    }
+
+    protected int getArgIndex()
+    {
+      return argIndex;
+    }
+
+    protected void setId(String i)
+    {
+      id = i;
+    }
+
+    protected String getId()
+    {
+      return id;
+    }
   }
 
   /**
@@ -704,50 +764,60 @@ public class ArgParser
    * 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 SubVal
+  public static class SubVals
   {
     private static int NOTSET = -1;
 
-    protected int index = NOTSET;
+    private int index = NOTSET;
 
-    protected String keyName = null;
+    private Map<String, String> subVals = null;
 
-    protected String keyValue = null;
+    private static char SEPARATOR = ';';
 
-    protected String content = null;
+    private String content = null;
 
-    public SubVal(String item)
+    public SubVals(String item)
     {
-      this.parseVal(item);
+      this.parseVals(item);
     }
 
-    public void parseVal(String item)
+    public void parseVals(String item)
     {
       if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
       {
         int openBracket = item.indexOf('[');
         int closeBracket = item.indexOf(']');
-        String indexString = item.substring(openBracket + 1, closeBracket);
+        String subvalsString = item.substring(openBracket + 1,
+                closeBracket);
         this.content = item.substring(closeBracket + 1);
-        int equals = indexString.indexOf('=');
-        if (equals > -1)
-        {
-          this.keyName = indexString.substring(0, equals);
-          this.keyValue = indexString.substring(equals + 1);
-          this.index = -1;
-        }
-        else
+        boolean setIndex = false;
+        for (String subvalString : subvalsString
+                .split(Character.toString(SEPARATOR)))
         {
-          try
+          int equals = subvalString.indexOf('=');
+          if (equals > -1)
           {
-            this.index = Integer.parseInt(indexString);
-          } catch (NumberFormatException e)
+            if (subVals == null)
+              subVals = new HashMap<>();
+            subVals.put(subvalString.substring(0, equals),
+                    subvalString.substring(equals + 1));
+          }
+          else
           {
-            Console.warn("Failed to obtain subvalue or index from '" + item
-                    + "'. Setting index=0 and using content='" + content
-                    + "'.");
+            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
       {
@@ -758,7 +828,246 @@ public class ArgParser
     public boolean notSet()
     {
       // notSet is true if content present but nonsensical
-      return index == NOTSET && keyName == null && keyValue == null;
+      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()
+    {
+      this.newMap();
+    }
+
+    protected ArgValuesMap(Map<Arg, ArgValues> map)
+    {
+      this.m = map;
+    }
+
+    private Map<Arg, ArgValues> getMap()
+    {
+      return m;
+    }
+
+    private void newMap()
+    {
+      m = new HashMap<Arg, ArgValues>();
+    }
+
+    private void newArg(Arg a)
+    {
+      if (m == null)
+        newMap();
+      if (!containsArg(a))
+        m.put(a, new ArgValues(a));
+    }
+
+    protected void addArgValue(Arg a, ArgValue av)
+    {
+      if (getMap() == null)
+        m = new HashMap<Arg, ArgValues>();
+
+      if (!m.containsKey(a))
+        m.put(a, new ArgValues(a));
+      ArgValues avs = m.get(a);
+      avs.addArgValue(av);
+    }
+
+    protected ArgValues getArgValues(Arg a)
+    {
+      return m == null ? null : m.get(a);
+    }
+
+    protected ArgValues getOrCreateArgValues(Arg a)
+    {
+      ArgValues avs = m.get(a);
+      if (avs == null)
+        newArg(a);
+      return getArgValues(a);
+    }
+
+    protected List<ArgValue> getArgValueList(Arg a)
+    {
+      ArgValues avs = getArgValues(a);
+      return avs == null ? new ArrayList<>() : avs.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 containsArg(Arg a)
+    {
+      if (m == null || !m.containsKey(a))
+        return false;
+      return getArgValue(a) != null;
+    }
+
+    protected boolean hasValue(Arg a, String val)
+    {
+      if (m == null || !m.containsKey(a))
+        return false;
+      for (ArgValue av : getArgValueList(a))
+      {
+        String avVal = av.getValue();
+        if ((val == null && avVal == null)
+                || (val != null && val.equals(avVal)))
+        {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    protected boolean getBoolean(Arg a)
+    {
+      ArgValues av = getArgValues(a);
+      return av == null ? false : av.getBoolean();
+    }
+
+    protected Set<Arg> getArgKeys()
+    {
+      return m.keySet();
+    }
+
+    protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
+            Arg a)
+    {
+      ArgValue closestAv = null;
+      int thisArgIndex = thisAv.getArgIndex();
+      ArgValues compareAvs = this.getArgValues(a);
+      int closestPreviousIndex = -1;
+      for (ArgValue av : compareAvs.getArgValueList())
+      {
+        int argIndex = av.getArgIndex();
+        if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
+        {
+          closestPreviousIndex = argIndex;
+          closestAv = av;
+        }
+      }
+      return closestAv;
+    }
+
+    protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
+    {
+      // this looks for the *next* arg that *might* be referring back to
+      // a thisAv. Such an arg would have no subValues (if it does it should
+      // specify an id in the subValues so wouldn't need to be guessed).
+      ArgValue closestAv = null;
+      int thisArgIndex = thisAv.getArgIndex();
+      ArgValues compareAvs = this.getArgValues(a);
+      int closestNextIndex = Integer.MAX_VALUE;
+      for (ArgValue av : compareAvs.getArgValueList())
+      {
+        int argIndex = av.getArgIndex();
+        if (argIndex > thisArgIndex && argIndex < closestNextIndex)
+        {
+          closestNextIndex = argIndex;
+          closestAv = av;
+        }
+      }
+      return closestAv;
+    }
+
+    protected ArgValue[] getArgValuesReferringTo(String key, String value,
+            Arg a)
+    {
+      // this looks for the *next* arg that *might* be referring back to
+      // a thisAv. Such an arg would have no subValues (if it does it should
+      // specify an id in the subValues so wouldn't need to be guessed).
+      List<ArgValue> avList = new ArrayList<>();
+      Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
+              : new Arg[]
+              { a };
+      for (Arg keyArg : args)
+      {
+        for (ArgValue av : this.getArgValueList(keyArg))
+        {
+
+        }
+      }
+      return (ArgValue[]) avList.toArray();
+    }
+
+    protected boolean hasId(Arg a, String id)
+    {
+      ArgValues avs = this.getArgValues(a);
+      return avs == null ? false : avs.hasId(id);
+    }
+
+    protected ArgValue getId(Arg a, String id)
+    {
+      ArgValues avs = this.getArgValues(a);
+      return avs == null ? null : avs.getId(id);
+    }
+  }
+
+  private static final Collection<Arg> bootstrapArgs = new ArrayList(
+          Arrays.asList(Arg.PROPS, Arg.DEBUG));
+
+  public static Map<Arg, String> bootstrapArgs(String[] args)
+  {
+    Map<Arg, String> bootstrapArgMap = new HashMap<>();
+    if (args == null)
+      return bootstrapArgMap;
+    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);
+        }
+        Arg a = argMap.get(argName);
+        if (a != null && bootstrapArgs.contains(a))
+          bootstrapArgMap.put(a, val);
+      }
     }
+    return bootstrapArgMap;
   }
 }
\ No newline at end of file