Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / bin / argparser / ArgValuesMap.java
diff --git a/src/jalview/bin/argparser/ArgValuesMap.java b/src/jalview/bin/argparser/ArgValuesMap.java
new file mode 100644 (file)
index 0000000..fc1e090
--- /dev/null
@@ -0,0 +1,563 @@
+/*
+ * 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.argparser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.bin.argparser.Arg.Opt;
+import jalview.bin.argparser.Arg.Type;
+import jalview.util.FileUtils;
+
+/**
+ * Helper class to allow easy extraction of information about specific argument
+ * values (without having to check for null etc all the time)
+ */
+public class ArgValuesMap
+{
+  private List<ArgInfo> argInfoList = new ArrayList<>();
+
+  protected Map<Arg, ArgValues> m;
+
+  private String linkedId;
+
+  protected ArgValuesMap(String linkedId)
+  {
+    this.linkedId = linkedId;
+    this.newMap();
+  }
+
+  protected ArgValuesMap(String linkedId, Map<Arg, ArgValues> map)
+  {
+    this.linkedId = linkedId;
+    this.m = map;
+  }
+
+  public String getLinkedId()
+  {
+    return linkedId;
+  }
+
+  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));
+  }
+
+  public ArgValues getArgValues(Arg a)
+  {
+    return m == null ? null : m.get(a);
+  }
+
+  public ArgValues getOrCreateArgValues(Arg a)
+  {
+    ArgValues avs = m.get(a);
+    if (avs == null)
+      newArg(a);
+    return getArgValues(a);
+  }
+
+  public List<ArgValue> getArgValueList(Arg a)
+  {
+    ArgValues avs = getArgValues(a);
+    return avs == null ? new ArrayList<>() : avs.getArgValueList();
+  }
+
+  public ArgValue getArgValue(Arg a)
+  {
+    List<ArgValue> vals = getArgValueList(a);
+    return (vals == null || vals.size() == 0) ? null : vals.get(0);
+  }
+
+  public String getValue(Arg a)
+  {
+    ArgValue av = getArgValue(a);
+    return av == null ? null : av.getValue();
+  }
+
+  public boolean containsArg(Arg a)
+  {
+    if (m == null || !m.containsKey(a))
+      return false;
+    return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
+  }
+
+  public 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;
+  }
+
+  public boolean getBoolean(Arg a)
+  {
+    ArgValues av = getArgValues(a);
+    return av == null ? false : av.getBoolean();
+  }
+
+  public Set<Arg> getArgKeys()
+  {
+    return m.keySet();
+  }
+
+  public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey)
+  {
+    return getArgValueOfArgWithSubValKey(a, svKey, false);
+  }
+
+  public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey,
+          boolean last)
+  {
+    ArgValues avs = this.getArgValues(a);
+    if (avs == null)
+    {
+      return null;
+    }
+    List<ArgValue> compareAvs = avs.getArgValueList();
+    for (int i = 0; i < compareAvs.size(); i++)
+    {
+      int index = last ? compareAvs.size() - 1 - i : i;
+      ArgValue av = compareAvs.get(index);
+      SubVals sv = av.getSubVals();
+      if (sv.has(svKey) && !sv.get(svKey).equals("false"))
+      {
+        return av;
+      }
+    }
+    return null;
+  }
+
+  public 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;
+  }
+
+  public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
+          boolean withinType)
+  {
+    // 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();
+    if (!containsArg(a))
+      return null;
+    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;
+      }
+    }
+
+    // check if withinType this closestAv doesn't belong to the next primary arg
+    // of this type
+    if (withinType && closestAv != null)
+    {
+      int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
+      for (Arg tmpA : this.getArgKeys())
+      {
+        // interested in Opt.PRIMARY args of the same type
+        if (tmpA.getType() == a.getType() && tmpA.hasOption(Opt.PRIMARY))
+        {
+          for (ArgValue tmpAv : getArgValueList(tmpA))
+          {
+            int tmpArgIndex = tmpAv.getArgIndex();
+            if (tmpArgIndex > thisArgIndex
+                    && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
+            {
+              nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
+            }
+          }
+        }
+      }
+      if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
+      {
+        // looks licke closestAv actually belongs to a different primary Arg
+        return null;
+      }
+    }
+
+    return closestAv;
+  }
+
+  // TODO this is incomplete and currently unused (fortunately)
+  public 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();
+  }
+
+  public boolean hasId(Arg a, String id)
+  {
+    ArgValues avs = this.getArgValues(a);
+    return avs == null ? false : avs.hasId(id);
+  }
+
+  public ArgValue getId(Arg a, String id)
+  {
+    ArgValues avs = this.getArgValues(a);
+    return avs == null ? null : avs.getId(id);
+  }
+
+  /*
+   * This method returns the basename of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getBasename()
+  {
+    return getDirBasenameOrExtension(false, false, false);
+  }
+
+  /*
+   * This method returns the basename of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getExtension()
+  {
+    return getDirBasenameOrExtension(false, true, false);
+  }
+
+  /*
+   * This method returns the dirname of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getDirname()
+  {
+    return getDirBasenameOrExtension(true, false, false);
+  }
+
+  public String getDirBasenameOrExtension(boolean dirname,
+          boolean extension, boolean absoluteDirname)
+  {
+    String filename = null;
+    String appendVal = getValue(Arg.APPEND);
+    String openVal = getValue(Arg.OPEN);
+    if (appendVal != null)
+      filename = appendVal;
+    if (filename == null && openVal != null)
+      filename = openVal;
+    if (filename == null)
+      return null;
+
+    File file = new File(filename);
+    if (dirname)
+    {
+      return FileUtils.getDirname(file);
+    }
+    return extension ? FileUtils.getExtension(file)
+            : FileUtils.getBasename(file);
+  }
+
+  /*
+   * Checks if there is an Arg with Opt
+   */
+  public boolean hasArgWithOption(Opt o)
+  {
+    for (Arg a : getArgKeys())
+    {
+      if (a.hasOption(o))
+        return true;
+    }
+    return false;
+  }
+
+  /*
+   * ArgInfo is a more straightforward list of arguments and their info
+   */
+
+  public void addArgInfo(Arg arg, String value, SubVals subVals,
+          int argIndex)
+  {
+    argInfoList.add(new ArgInfo(arg, value, subVals, argIndex));
+  }
+
+  public List<ArgInfo> getArgInfoList()
+  {
+    Collections.sort(argInfoList);
+    return argInfoList;
+  }
+
+  /**
+   * get from following Arg of type a or subval of same name (lowercase)
+   */
+  public String getValueFromSubValOrArg(ArgValue av, Arg a, SubVals sv)
+  {
+    return getFromSubValArgOrPref(av, a, sv, null, null, null);
+  }
+
+  /**
+   * get from following Arg of type a or subval key or preference pref or
+   * default def
+   */
+  public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
+          String key, String pref, String def)
+  {
+    return getFromSubValArgOrPref(a, ArgValuesMap.Position.AFTER, av, sv,
+            key, pref, def);
+  }
+
+  /**
+   * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
+   * Arg of type a or subval key or preference pref or default def
+   */
+  public String getFromSubValArgOrPref(Arg a, ArgValuesMap.Position pos,
+          ArgValue av, SubVals sv, String key, String pref, String def)
+  {
+    return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
+            key, pref, def);
+  }
+
+  public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
+          ArgValuesMap.Position pos, ArgValue av, SubVals sv, String key,
+          String pref, String def)
+  {
+    return getFromSubValArgOrPrefWithSubstitutionsWithinType(ap, a, pos, av,
+            sv, key, pref, def, true);
+  }
+
+  public String getFromSubValArgOrPrefWithSubstitutionsWithinType(
+          ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
+          SubVals sv, String key, String pref, String def,
+          boolean withinType)
+  {
+    if (key == null)
+      key = a.getName();
+    String value = null;
+    if (sv != null && sv.has(key) && sv.get(key) != null)
+    {
+      value = ap == null ? sv.get(key)
+              : sv.getWithSubstitutions(ap, getLinkedId(), key);
+    }
+    else if (containsArg(a))
+    {
+      if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
+        value = getValue(a);
+      else if (pos == ArgValuesMap.Position.BEFORE
+              && getClosestPreviousArgValueOfArg(av, a) != null)
+        value = getClosestPreviousArgValueOfArg(av, a).getValue();
+      else if (pos == ArgValuesMap.Position.AFTER
+              && getClosestNextArgValueOfArg(av, a, withinType) != null)
+        value = getClosestNextArgValueOfArg(av, a, withinType).getValue();
+
+      // look for allstructures subval for Type.STRUCTURE
+      Arg arg = av.getArg();
+      if (value == null && arg.hasOption(Opt.PRIMARY)
+              && arg.getType() == Type.STRUCTURE
+              && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
+              // || a.getType() == Type.STRUCTUREIMAGE))
+              ))
+      {
+        ArgValue av2 = getArgValueOfArgWithSubValKey(a,
+                Arg.ALLSTRUCTURES.getName());
+        if (av2 != null)
+        {
+          value = av2.getValue();
+        }
+      }
+    }
+    if (value == null)
+    {
+      value = pref != null ? Cache.getDefault(pref, def) : def;
+    }
+    return value;
+  }
+
+  public boolean getBoolFromSubValOrArg(Arg a, SubVals sv)
+  {
+    return getFromSubValArgOrPref(a, sv, null, null, false);
+  }
+
+  public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
+          String pref, boolean def)
+  {
+    return getFromSubValArgOrPref(a, sv, key, pref, def, false);
+  }
+
+  public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
+          String pref, boolean def, boolean invertPref)
+  {
+    if ((key == null && a == null) || (sv == null && a == null))
+      return false;
+
+    boolean usingArgKey = false;
+    if (key == null)
+    {
+      key = a.getName();
+      usingArgKey = true;
+    }
+
+    String nokey = ArgParser.NEGATESTRING + key;
+
+    // look for key or nokey in subvals first (if using Arg check options)
+    if (sv != null)
+    {
+      // check for true boolean
+      if (sv.has(key) && sv.get(key) != null)
+      {
+        if (usingArgKey)
+        {
+          if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
+          {
+            Console.debug(
+                    "Looking for boolean in subval from non-boolean/non-unary Arg "
+                            + a.getName());
+            return false;
+          }
+        }
+        return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
+      }
+
+      // check for negative boolean (subval "no..." will be "true")
+      if (sv.has(nokey) && sv.get(nokey) != null)
+      {
+        if (usingArgKey)
+        {
+          if (!(a.hasOption(Opt.BOOLEAN)))
+          {
+            Console.debug(
+                    "Looking for negative boolean in subval from non-boolean Arg "
+                            + a.getName());
+            return false;
+          }
+        }
+        return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
+      }
+    }
+
+    // check argvalues
+    if (containsArg(a))
+      return getBoolean(a);
+
+    // return preference or default
+    boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
+    return pref != null ? (invertPref ? !prefVal : prefVal) : def;
+  }
+
+  public class ArgInfo implements Comparable<ArgInfo>
+  {
+    private Arg arg;
+
+    private String value;
+
+    private SubVals subVals;
+
+    private int argIndex;
+
+    public ArgInfo(Arg arg, String value, SubVals subVals, int argIndex)
+    {
+      this.arg = arg;
+      this.value = value;
+      this.subVals = subVals;
+      this.argIndex = argIndex;
+    }
+
+    public Arg arg()
+    {
+      return arg;
+    }
+
+    public String value()
+    {
+      return value;
+    }
+
+    public SubVals subVals()
+    {
+      return subVals;
+    }
+
+    public int argIndex()
+    {
+      return argIndex;
+    }
+
+    @Override
+    public int compareTo(ArgInfo ai2)
+    {
+      return Integer.compare(this.argIndex(), ai2.argIndex());
+    }
+  }
+
+  public static enum Position
+  {
+    FIRST, BEFORE, AFTER
+  }
+}