JAL-629 Ensure precedence of named linked ID values over wildcard linked ID set value...
[jalview.git] / src / jalview / bin / argparser / ArgParser.java
index dd863d1..e6bd917 100644 (file)
@@ -55,6 +55,9 @@ public class ArgParser
   // the linkedId string used to match all linkedIds seen so far
   protected static final String MATCHALLLINKEDIDS = "*";
 
+  // the linkedId string used to match all of the last --open'ed linkedIds
+  protected static final String MATCHOPENEDLINKEDIDS = "open*";
+
   // the counter added to the default linked id prefix
   private int defaultLinkedIdCounter = 0;
 
@@ -78,6 +81,11 @@ public class ArgParser
   // the linked id substitution string used to use the idCounter
   private static final String LINKEDIDAUTOCOUNTER = "{n}";
 
+  // the linked id substitution string used to use the filename extension of
+  // --append
+  // or --open
+  private static final String LINKEDIDEXTENSION = "{extension}";
+
   // the linked id substitution string used to use the base filename of --append
   // or --open
   private static final String LINKEDIDBASENAME = "{basename}";
@@ -97,6 +105,10 @@ public class ArgParser
   // --argfile name
   private static final String ARGFILEDIRNAME = "{argfiledirname}";
 
+  // an output file wildcard to signify --output=*.ext is really --all --output
+  // {basename}.ext
+  private static final String OUTPUTWILDCARD = "*.";
+
   // flag to say whether {n} subtitutions in output filenames should be made.
   // Turn on and off with --substitutions and --nosubstitutions
   // Start with it on
@@ -106,12 +118,18 @@ public class ArgParser
   // or ALL linkedIds
   private boolean allLinkedIds = false;
 
+  // flag to say whether the default linkedId is the current default linked id
+  // or OPENED linkedIds
+  private boolean openedLinkedIds = false;
+
   protected static final Map<String, Arg> argMap;
 
   protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
 
   protected List<String> linkedOrder = new ArrayList<>();
 
+  protected List<String> storedLinkedIds = new ArrayList<>();
+
   protected List<Arg> argList = new ArrayList<>();
 
   private static final char ARGFILECOMMENT = '#';
@@ -270,8 +288,10 @@ public class ArgParser
         {
           // arg not found
           Console.error("Argument '" + arg + "' not recognised.  Exiting.");
-          Jalview.exit("Unrecognised command line argument '" + arg + "'",
-                  13);
+          Jalview.exit("Invalid argument used." + System.lineSeparator()
+                  + "Use" + System.lineSeparator() + "jalview "
+                  + Arg.HELP.argString() + System.lineSeparator()
+                  + "for a usage statement.", 13);
           continue;
         }
         if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
@@ -374,6 +394,18 @@ public class ArgParser
         else if (a == Arg.ALL)
         {
           allLinkedIds = !negated;
+          openedLinkedIds = false;
+        }
+        else if (a == Arg.OPENED)
+        {
+          openedLinkedIds = !negated;
+          allLinkedIds = false;
+        }
+
+        if (a.hasOption(Opt.STORED))
+        {
+          // reset the lastOpenedLinkedIds list
+          this.storedLinkedIds = new ArrayList<>();
         }
 
         // this is probably only Arg.NEW and Arg.OPEN
@@ -391,9 +423,36 @@ public class ArgParser
         {
           if (linkedId == null)
           {
-            if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
+            if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
+                    && val.startsWith(MATCHALLLINKEDIDS))
             {
+              // --output=*.ext is shorthand for --all --output {basename}.ext
+              // (or --image=*.ext)
+              allLinkedIds = true;
+              openedLinkedIds = false;
               linkedId = MATCHALLLINKEDIDS;
+              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
+                      + val.substring(MATCHALLLINKEDIDS.length());
+            }
+            else if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
+                    && val.startsWith(MATCHOPENEDLINKEDIDS))
+            {
+              // --output=open*.ext is shorthand for --opened --output
+              // {basename}.ext
+              // (or --image=open*.ext)
+              openedLinkedIds = true;
+              allLinkedIds = false;
+              linkedId = MATCHOPENEDLINKEDIDS;
+              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
+                      + val.substring(MATCHOPENEDLINKEDIDS.length());
+            }
+            else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
+            {
+              linkedId = MATCHALLLINKEDIDS;
+            }
+            else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
+            {
+              linkedId = MATCHOPENEDLINKEDIDS;
             }
             else
             {
@@ -430,10 +489,7 @@ public class ArgParser
         if (a.hasOption(Opt.NOACTION))
           continue;
 
-        if (!linkedArgs.containsKey(linkedId))
-          linkedArgs.put(linkedId, new ArgValuesMap());
-
-        ArgValuesMap avm = linkedArgs.get(linkedId);
+        ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
 
         // not dealing with both NODUPLICATEVALUES and GLOB
         if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
@@ -500,9 +556,12 @@ public class ArgParser
           setBoolean(linkedId, avs, true, argIndex);
         }
 
-        // remove the '*' linkedId that should be empty if it was created
-        if (MATCHALLLINKEDIDS.equals(linkedId)
+        // remove the '*' or 'open*' linkedId that should be empty if it was
+        // created
+        if ((MATCHALLLINKEDIDS.equals(linkedId)
                 && linkedArgs.containsKey(linkedId))
+                || (MATCHOPENEDLINKEDIDS.equals(linkedId)
+                        && linkedArgs.containsKey(linkedId)))
         {
           linkedArgs.remove(linkedId);
         }
@@ -543,8 +602,7 @@ public class ArgParser
                 .toString();
       }
     }
-    if (!linkedArgs.containsKey(defaultLinkedId))
-      linkedArgs.put(defaultLinkedId, new ArgValuesMap());
+    getOrCreateLinkedArgValuesMap(defaultLinkedId);
     return defaultLinkedId;
   }
 
@@ -584,6 +642,10 @@ public class ArgParser
       {
         rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
       }
+      if (rest.contains(LINKEDIDEXTENSION))
+      {
+        rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
+      }
       if (rest.contains(LINKEDIDDIRNAME))
       {
         rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
@@ -817,22 +879,38 @@ public class ArgParser
           Position pos, ArgValue av, SubVals sv, String key, String pref,
           String def)
   {
+    return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
+            sv, key, pref, def);
+  }
+
+  public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
+          ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
+          String key, String pref, String def)
+  {
     if (key == null)
       key = a.getName();
+    String value = null;
     if (sv != null && sv.has(key) && sv.get(key) != null)
-      return sv.get(key);
-    if (avm != null && avm.containsArg(a))
+    {
+      value = ap == null ? sv.get(key)
+              : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
+    }
+    else if (avm != null && avm.containsArg(a))
     {
       if (pos == Position.FIRST && avm.getValue(a) != null)
-        return avm.getValue(a);
+        value = avm.getValue(a);
       else if (pos == Position.BEFORE
               && avm.getClosestPreviousArgValueOfArg(av, a) != null)
-        return avm.getClosestPreviousArgValueOfArg(av, a).getValue();
+        value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
       else if (pos == Position.AFTER
               && avm.getClosestNextArgValueOfArg(av, a) != null)
-        return avm.getClosestNextArgValueOfArg(av, a).getValue();
+        value = avm.getClosestNextArgValueOfArg(av, a).getValue();
     }
-    return pref != null ? Cache.getDefault(pref, def) : def;
+    else
+    {
+      value = pref != null ? Cache.getDefault(pref, def) : def;
+    }
+    return value;
   }
 
   public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
@@ -844,154 +922,249 @@ public class ArgParser
   public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
           SubVals sv, String key, String pref, boolean def)
   {
+    if ((key == null && a == null) || (sv == null && a == null))
+      return false;
+
+    boolean usingArgKey = false;
     if (key == null)
+    {
       key = a.getName();
-    if (sv != null && sv.has(key) && sv.get(key) != null)
-      return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
-    if (avm != null && avm.containsArg(a))
-      return avm.getBoolean(a);
-    return pref != null ? Cache.getDefault(pref, def) : def;
-  }
+      usingArgKey = true;
+    }
 
-  // the following methods look for the "*" linkedId and add the argvalue to all
-  // linkedId ArgValues if it does
-  private void addValue(String linkedId, ArgValues avs, SubVals sv,
-          String v, int argIndex, boolean doSubs)
-  {
-    Arg a = avs.arg();
-    if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
+    String nokey = ArgParser.NEGATESTRING + key;
+
+    // look for key or nokey in subvals first (if using Arg check options)
+    if (sv != null)
     {
-      for (String id : getLinkedIds())
+      // check for true boolean
+      if (sv.has(key) && sv.get(key) != null)
       {
-        if (id == null || MATCHALLLINKEDIDS.equals(id))
-          continue;
-        ArgValuesMap avm = linkedArgs.get(id);
-        if (a.hasOption(Opt.REQUIREINPUT)
-                && !avm.hasArgWithOption(Opt.INPUT))
-          continue;
-        ArgValues tavs = avm.getOrCreateArgValues(a);
-        String val = v;
-        if (doSubs)
+        if (usingArgKey)
         {
-          val = makeSubstitutions(v, id);
-          sv = new SubVals(sv, val);
+          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;
+          }
         }
-        tavs.addValue(sv, val, argIndex);
-        finaliseStoringArgValue(id, tavs);
+        return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
       }
-    }
-    else
-    {
-      String val = v;
-      if (doSubs)
+
+      // check for negative boolean (subval "no..." will be "true")
+      if (sv.has(nokey) && sv.get(nokey) != null)
       {
-        val = makeSubstitutions(v, linkedId);
-        sv = new SubVals(sv, val);
+        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");
       }
-      avs.addValue(sv, val, argIndex);
-      finaliseStoringArgValue(linkedId, avs);
     }
+
+    // check argvalues
+    if (avm != null && avm.containsArg(a))
+      return avm.getBoolean(a);
+
+    // return preference or default
+    return pref != null ? Cache.getDefault(pref, def) : def;
+  }
+
+  // the following methods look for the "*" linkedId and add the argvalue to all
+  // linkedId ArgValues if it does.
+  // This version inserts the subvals sv into all created values
+  private void addValue(String linkedId, ArgValues avs, SubVals sv,
+          String v, int argIndex, boolean doSubs)
+  {
+    this.argValueOperation(Op.ADDVALUE, linkedId, avs, sv, v, false,
+            argIndex, doSubs);
   }
 
   private void addValue(String linkedId, ArgValues avs, String v,
           int argIndex, boolean doSubs)
   {
-    Arg a = avs.arg();
-    if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
-    {
-      for (String id : getLinkedIds())
-      {
-        if (id == null || MATCHALLLINKEDIDS.equals(id))
-          continue;
-        ArgValuesMap avm = linkedArgs.get(id);
-        // don't set an output if there isn't an input
-        if (a.hasOption(Opt.REQUIREINPUT)
-                && !avm.hasArgWithOption(Opt.INPUT))
-          continue;
-        ArgValues tavs = avm.getOrCreateArgValues(a);
-        String val = doSubs ? makeSubstitutions(v, id) : v;
-        tavs.addValue(val, argIndex);
-        finaliseStoringArgValue(id, tavs);
-      }
-    }
-    else
-    {
-      String val = doSubs ? makeSubstitutions(v, linkedId) : v;
-      avs.addValue(val, argIndex);
-      finaliseStoringArgValue(linkedId, avs);
-    }
+    this.argValueOperation(Op.ADDVALUE, linkedId, avs, null, v, false,
+            argIndex, doSubs);
   }
 
   private void setBoolean(String linkedId, ArgValues avs, boolean b,
           int argIndex)
   {
-    Arg a = avs.arg();
-    if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
-    {
-      for (String id : getLinkedIds())
-      {
-        if (id == null || MATCHALLLINKEDIDS.equals(id))
-          continue;
-        ArgValuesMap avm = linkedArgs.get(id);
-        if (a.hasOption(Opt.REQUIREINPUT)
-                && !avm.hasArgWithOption(Opt.INPUT))
-          continue;
-        ArgValues tavs = avm.getOrCreateArgValues(a);
-        tavs.setBoolean(b, argIndex);
-        finaliseStoringArgValue(id, tavs);
-      }
-    }
-    else
-    {
-      avs.setBoolean(b, argIndex);
-      finaliseStoringArgValue(linkedId, avs);
-    }
+    this.argValueOperation(Op.SETBOOLEAN, linkedId, avs, null, null, b,
+            argIndex, false);
   }
 
   private void setNegated(String linkedId, ArgValues avs, boolean b)
   {
+    this.argValueOperation(Op.SETNEGATED, linkedId, avs, null, null, b, 0,
+            false);
+  }
+
+  private void incrementCount(String linkedId, ArgValues avs)
+  {
+    this.argValueOperation(Op.INCREMENTCOUNT, linkedId, avs, null, null,
+            false, 0, false);
+  }
+
+  private enum Op
+  {
+    ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
+  }
+
+  // The following operations look for the "*" and "open*" linkedIds and add the
+  // argvalue to all appropriate linkedId ArgValues if it does.
+  // If subvals are supplied, they are inserted into all new set values.
+  private void argValueOperation(Op op, String linkedId, ArgValues avs,
+          SubVals sv, String v, boolean b, int argIndex, boolean doSubs)
+  {
     Arg a = avs.arg();
-    if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
+
+    List<String> wildcardLinkedIds = null;
+    if (a.hasOption(Opt.ALLOWALL))
     {
-      for (String id : getLinkedIds())
+      switch (linkedId)
       {
-        if (id == null || MATCHALLLINKEDIDS.equals(id))
-          continue;
-        ArgValuesMap avm = linkedArgs.get(id);
-        if (a.hasOption(Opt.REQUIREINPUT)
-                && !avm.hasArgWithOption(Opt.INPUT))
-          continue;
-        ArgValues tavs = avm.getOrCreateArgValues(a);
-        tavs.setNegated(b);
+      case MATCHALLLINKEDIDS:
+        wildcardLinkedIds = getLinkedIds();
+        break;
+      case MATCHOPENEDLINKEDIDS:
+        wildcardLinkedIds = this.storedLinkedIds;
+        break;
       }
     }
-    else
+
+    // if we're not a wildcard linkedId and the arg is marked to be stored, add
+    // to storedLinkedIds
+    if (linkedId != null && wildcardLinkedIds == null
+            && a.hasOption(Opt.STORED)
+            && !storedLinkedIds.contains(linkedId))
     {
-      avs.setNegated(b);
+      storedLinkedIds.add(linkedId);
     }
-  }
 
-  private void incrementCount(String linkedId, ArgValues avs)
-  {
-    Arg a = avs.arg();
-    if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
+    // if we are a wildcard linkedId, apply the arg and value to all appropriate
+    // linkedIds
+    if (wildcardLinkedIds != null)
     {
-      for (String id : getLinkedIds())
+      for (String id : wildcardLinkedIds)
       {
-        if (id == null || MATCHALLLINKEDIDS.equals(id))
+        // skip incorrectly stored wildcard ids!
+        if (id == null || MATCHALLLINKEDIDS.equals(id)
+                || MATCHOPENEDLINKEDIDS.equals(id))
           continue;
         ArgValuesMap avm = linkedArgs.get(id);
+        // don't set an output if there isn't an input
         if (a.hasOption(Opt.REQUIREINPUT)
                 && !avm.hasArgWithOption(Opt.INPUT))
           continue;
+
         ArgValues tavs = avm.getOrCreateArgValues(a);
-        tavs.incrementCount();
+        switch (op)
+        {
+
+        case ADDVALUE:
+          String val = v;
+          if (sv != null)
+          {
+            if (doSubs)
+            {
+              val = makeSubstitutions(v, id);
+              sv = new SubVals(sv, val);
+            }
+            tavs.addValue(sv, val, argIndex, true);
+          }
+          else
+          {
+            if (doSubs)
+            {
+              val = makeSubstitutions(v, id);
+            }
+            tavs.addValue(val, argIndex, true);
+          }
+          finaliseStoringArgValue(id, tavs);
+          break;
+
+        case SETBOOLEAN:
+          tavs.setBoolean(b, argIndex, true);
+          finaliseStoringArgValue(id, tavs);
+          break;
+
+        case SETNEGATED:
+          tavs.setNegated(b, true);
+          break;
+
+        case INCREMENTCOUNT:
+          tavs.incrementCount();
+          break;
+
+        default:
+          break;
+
+        }
+
       }
     }
-    else
+    else // no wildcard linkedId -- do it simpler
     {
-      avs.incrementCount();
+      switch (op)
+      {
+      case ADDVALUE:
+        String val = v;
+        if (sv != null)
+        {
+          if (doSubs)
+          {
+            val = makeSubstitutions(v, linkedId);
+            sv = new SubVals(sv, val);
+          }
+          avs.addValue(sv, val, argIndex, false);
+        }
+        else
+        {
+          if (doSubs)
+          {
+            val = makeSubstitutions(v, linkedId);
+          }
+          avs.addValue(val, argIndex, false);
+        }
+        finaliseStoringArgValue(linkedId, avs);
+        break;
+
+      case SETBOOLEAN:
+        avs.setBoolean(b, argIndex, false);
+        finaliseStoringArgValue(linkedId, avs);
+        break;
+
+      case SETNEGATED:
+        avs.setNegated(b, false);
+        break;
+
+      case INCREMENTCOUNT:
+        avs.incrementCount();
+        break;
+
+      default:
+        break;
+      }
     }
   }
 
+  private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
+  {
+    if (linkedArgs.containsKey(linkedId)
+            && linkedArgs.get(linkedId) != null)
+      return linkedArgs.get(linkedId);
+
+    linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
+    return linkedArgs.get(linkedId);
+  }
+
 }
\ No newline at end of file