JAL-4353 Add secondary Types for Args, for STRUCTUREIMAGE Type and restrict structure...
[jalview.git] / src / jalview / bin / argparser / ArgValuesMap.java
index 22d85a8..5d53641 100644 (file)
@@ -1,12 +1,39 @@
+/*
+ * 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
@@ -14,18 +41,29 @@ import jalview.bin.argparser.Arg.Opt;
  */
 public class ArgValuesMap
 {
+  private List<ArgInfo> argInfoList = new ArrayList<>();
+
   protected Map<Arg, ArgValues> m;
 
-  protected ArgValuesMap()
+  private String linkedId;
+
+  protected ArgValuesMap(String linkedId)
   {
+    this.linkedId = linkedId;
     this.newMap();
   }
 
-  protected ArgValuesMap(Map<Arg, ArgValues> map)
+  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;
@@ -44,17 +82,6 @@ public class ArgValuesMap
       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);
-  }
-
   public ArgValues getArgValues(Arg a)
   {
     return m == null ? null : m.get(a);
@@ -74,6 +101,96 @@ public class ArgValuesMap
     return avs == null ? new ArrayList<>() : avs.getArgValueList();
   }
 
+  public List<ArgValue> getArgValueListFromSubValOrArg(ArgValue av, Arg a,
+          SubVals sv)
+  {
+    return getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
+            null, a, Position.AFTER, av, sv, null, null, null, true);
+  }
+
+  public List<ArgValue> getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
+          ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
+          SubVals sv, String key, String pref, String def,
+          boolean withinTypes)
+  {
+    if (key == null)
+      key = a.getName();
+    List<ArgValue> avList = new ArrayList<>();
+    if (sv != null && sv.has(key) && sv.get(key) != null)
+    {
+      String value = ap == null ? sv.get(key)
+              : sv.getWithSubstitutions(ap, getLinkedId(), key);
+      // protected ArgValue(Arg a, SubVals sv, Type type, String content, int
+      // argIndex)
+
+      avList.add(new ArgValue(a, null, null, value, av.getArgIndex()));
+    }
+    else if (containsArg(a))
+    {
+      if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
+        avList.add(getArgValue(a));
+      else if (pos == ArgValuesMap.Position.BEFORE
+              && getClosestPreviousArgValueOfArg(av, a) != null)
+      {
+        for (ArgValue tmpAv : getArgValues(a).getArgValueList())
+        {
+          if (tmpAv.getArgIndex() >= av.getArgIndex())
+          {
+            continue;
+          }
+          avList.add(tmpAv);
+        }
+      }
+      else if (pos == ArgValuesMap.Position.AFTER
+              && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
+      {
+        for (ArgValue tmpAv : getArgValues(a).getArgValueList())
+        {
+          if (tmpAv.getArgIndex() <= av.getArgIndex())
+          {
+            continue;
+          }
+          avList.add(tmpAv);
+        }
+      }
+    }
+
+    // check if withinType the avs don't belong to the next primary arg
+    // of this type. Checking for *any* shared type.
+    if (withinTypes && !avList.isEmpty())
+    {
+      int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
+      // run through every Arg used in this ArgValuesMap
+      for (Arg tmpA : this.getArgKeys())
+      {
+        // only interested in Opt.PRIMARY args of the same type
+        if (tmpA.sharesType(a) && tmpA.hasOption(Opt.PRIMARY))
+        {
+          for (ArgValue tmpAv : getArgValueList(tmpA))
+          {
+            int tmpArgIndex = tmpAv.getArgIndex();
+            if (tmpArgIndex > av.getArgIndex()
+                    && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
+            {
+              nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
+            }
+          }
+        }
+      }
+      List<ArgValue> tmpList = List.copyOf(avList);
+      for (ArgValue tmpAv : tmpList)
+      {
+        if (nextPrimaryArgOfSameTypeIndex < tmpAv.getArgIndex())
+        {
+          // looks like this tmpAv actually belongs to a different primary Arg
+          avList.remove(tmpAv);
+        }
+      }
+    }
+
+    return avList;
+  }
+
   public ArgValue getArgValue(Arg a)
   {
     List<ArgValue> vals = getArgValueList(a);
@@ -90,8 +207,7 @@ public class ArgValuesMap
   {
     if (m == null || !m.containsKey(a))
       return false;
-    return a.hasOption(Opt.STRING) ? getArgValue(a) != null
-            : this.getBoolean(a);
+    return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
   }
 
   public boolean hasValue(Arg a, String val)
@@ -121,6 +237,33 @@ public class ArgValuesMap
     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;
@@ -139,13 +282,16 @@ public class ArgValuesMap
     return closestAv;
   }
 
-  public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
+  public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
+          boolean withinTypes)
   {
     // 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())
@@ -157,9 +303,39 @@ public class ArgValuesMap
         closestAv = av;
       }
     }
+
+    // check if withinType this closestAv doesn't belong to the next primary arg
+    // of this type. Checking for *any* shared type.
+    if (withinTypes && closestAv != null)
+    {
+      int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
+      for (Arg tmpA : this.getArgKeys())
+      {
+        // interested in Opt.PRIMARY args of the same type
+        if (tmpA.sharesType(a) && 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 like 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
@@ -190,4 +366,287 @@ public class ArgValuesMap
     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, 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, 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,
+          Position pos, ArgValue av, SubVals sv, String key, String pref,
+          String def)
+  {
+    return getFromSubValArgOrPrefWithSubstitutionsWithinTypes(ap, a, pos,
+            av, sv, key, pref, def, true);
+  }
+
+  public String getFromSubValArgOrPrefWithSubstitutionsWithinTypes(
+          ArgParser ap, Arg a, Position pos, ArgValue av, SubVals sv,
+          String key, String pref, String def, boolean withinTypes)
+  {
+    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, withinTypes) != null)
+        value = getClosestNextArgValueOfArg(av, a, withinTypes).getValue();
+
+      // look for allstructures subval for Type.STRUCTURE
+      Arg arg = av.getArg();
+      if (value == null && arg.hasOption(Opt.PRIMARY)
+              && arg.hasType(Type.STRUCTURE) && !a.hasOption(Opt.PRIMARY)
+              && (a.getFirstType() == 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
+  }
 }