Merge branch 'patch/JAL-4195_cli_eps_export_npe' into develop
[jalview.git] / src / jalview / bin / argparser / ArgParser.java
index 3ca5d3d..907b1fa 100644 (file)
@@ -38,84 +38,135 @@ import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.bin.Jalview;
 import jalview.bin.argparser.Arg.Opt;
+import jalview.bin.argparser.Arg.Type;
 import jalview.util.FileUtils;
 import jalview.util.HttpUtils;
 
 public class ArgParser
 {
+  protected static final String SINGLEDASH = "-";
+
   protected static final String DOUBLEDASH = "--";
 
-  protected static final char EQUALS = '=';
+  public static final char EQUALS = '=';
 
   protected static final String NEGATESTRING = "no";
 
-  // the default linked id prefix used for no id (not even square braces)
+  /**
+   * the default linked id prefix used for no id (ie when not even square braces
+   * are provided)
+   */
   protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
 
-  // the linkedId string used to match all linkedIds seen so far
+  /**
+   * the linkedId string used to match all linkedIds seen so far
+   */
   protected static final String MATCHALLLINKEDIDS = "*";
 
-  // the counter added to the default linked id prefix
+  /**
+   * 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;
 
-  // the substitution string used to use the defaultLinkedIdCounter
+  /**
+   * the substitution string used to use the defaultLinkedIdCounter
+   */
   private static final String DEFAULTLINKEDIDCOUNTER = "{}";
 
-  // the counter added to the default linked id prefix. NOW using
-  // linkedIdAutoCounter
-  // private int openLinkedIdCounter = 0;
-
-  // the linked id prefix used for --open files. NOW the same as DEFAULT
+  /**
+   * the linked id prefix used for --open files. NOW the same as DEFAULT
+   */
   protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
 
-  // the counter used for {n} substitutions
+  /**
+   * the counter used for {n} substitutions
+   */
   private int linkedIdAutoCounter = 0;
 
-  // the linked id substitution string used to increment the idCounter (and use
-  // the incremented value)
+  /**
+   * the linked id substitution string used to increment the idCounter (and use
+   * the incremented value)
+   */
   private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
 
-  // the linked id substitution string used to use the idCounter
+  /**
+   * 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 base filename of --append
-  // or --open
+  /**
+   * 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}";
 
-  // the linked id substitution string used to use the dir path of --append
-  // or --open
+  /**
+   * the linked id substitution string used to use the dir path of --append or
+   * --open
+   */
   private static final String LINKEDIDDIRNAME = "{dirname}";
 
-  // the current argfile
+  /**
+   * the current argfile
+   */
   private String argFile = null;
 
-  // the linked id substitution string used to use the dir path of the latest
-  // --argfile name
+  /**
+   * the linked id substitution string used to use the dir path of the latest
+   */
+  /** --argfile name */
   private static final String ARGFILEBASENAME = "{argfilebasename}";
 
-  // the linked id substitution string used to use the dir path of the latest
-  // --argfile name
+  /**
+   * the linked id substitution string used to use the dir path of the latest
+   * --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
+  /**
+   * flag to say whether {n} subtitutions in output filenames should be made.
+   * Turn on and off with --substitutions and --nosubstitutions Start with it on
+   */
   private boolean substitutions = true;
 
-  // flag to say whether the default linkedId is the current default linked id
-  // or ALL linkedIds
+  /**
+   * flag to say whether the default linkedId is the current default linked id
+   *
+   * 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;
+
+  /**
+   * flag to say whether the structure arguments should be applied to all
+   * structures with this linked id
+   */
+  private boolean allStructures = 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 = '#';
@@ -134,8 +185,7 @@ public class ArgParser
         if (argMap.containsKey(argName))
         {
           Console.warn("Trying to add argument name multiple times: '"
-                  + argName + "'"); // RESTORE THIS WHEN
-          // MERGED
+                  + argName + "'");
           if (argMap.get(argName) != a)
           {
             Console.error(
@@ -232,13 +282,17 @@ public class ArgParser
         openEachInitialFilenames = false;
       }
 
-      String argName = null;
-      String val = null;
-      List<String> globVals = null; // for Opt.GLOB only
-      SubVals globSubVals = null; // also for use by Opt.GLOB only
-      String linkedId = null;
+      // look for double-dash, e.g. --arg
       if (arg.startsWith(DOUBLEDASH))
       {
+        String argName = null;
+        String val = null;
+        List<String> globVals = null; // for Opt.GLOB only
+        SubVals globSubVals = null; // also for use by Opt.GLOB only
+        String linkedId = null;
+        Type type = null;
+
+        // look for equals e.g. --arg=value
         int equalPos = arg.indexOf(EQUALS);
         if (equalPos > -1)
         {
@@ -249,24 +303,67 @@ public class ArgParser
         {
           argName = arg.substring(DOUBLEDASH.length());
         }
+
+        // look for linked ID e.g. --arg[linkedID]
         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);
         }
 
+        // look for type modification e.g. --help-opening
+        int dashPos = argName.indexOf(SINGLEDASH);
+        if (dashPos > -1)
+        {
+          String potentialArgName = argName.substring(0, dashPos);
+          Arg potentialArg = argMap.get(potentialArgName);
+          if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
+          {
+            String typeName = argName.substring(dashPos + 1);
+            try
+            {
+              type = Type.valueOf(typeName);
+            } catch (IllegalArgumentException e)
+            {
+              type = Type.INVALID;
+            }
+            argName = argName.substring(0, dashPos);
+          }
+        }
+
         Arg a = argMap.get(argName);
-        // check for boolean prepended by "no"
+        // check for boolean prepended by "no" e.g. --nowrap
         boolean negated = false;
-        if (a == null && argName.startsWith(NEGATESTRING) && argMap
-                .containsKey(argName.substring(NEGATESTRING.length())))
+        if (a == null)
         {
-          argName = argName.substring(NEGATESTRING.length());
-          a = argMap.get(argName);
-          negated = true;
+          if (argName.startsWith(NEGATESTRING) && argMap
+                  .containsKey(argName.substring(NEGATESTRING.length())))
+          {
+            argName = argName.substring(NEGATESTRING.length());
+            a = argMap.get(argName);
+            negated = true;
+          }
+          else
+          {
+            // after all other args, look for Opt.PREFIXKEV args if still not
+            // found
+            for (Arg potentialArg : EnumSet.allOf(Arg.class))
+            {
+              if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
+                      && argName.startsWith(potentialArg.getName())
+                      && equalPos > -1)
+              {
+                val = argName.substring(potentialArg.getName().length())
+                        + EQUALS + val;
+                argName = argName.substring(0,
+                        potentialArg.getName().length());
+                a = potentialArg;
+                break;
+              }
+            }
+          }
         }
 
         // check for config errors
@@ -380,6 +477,22 @@ public class ArgParser
         else if (a == Arg.ALL)
         {
           allLinkedIds = !negated;
+          openedLinkedIds = false;
+        }
+        else if (a == Arg.OPENED)
+        {
+          openedLinkedIds = !negated;
+          allLinkedIds = false;
+        }
+        else if (a == Arg.ALLSTRUCTURES)
+        {
+          allStructures = !negated;
+        }
+
+        if (a.hasOption(Opt.STORED))
+        {
+          // reset the lastOpenedLinkedIds list
+          this.storedLinkedIds = new ArrayList<>();
         }
 
         // this is probably only Arg.NEW and Arg.OPEN
@@ -390,28 +503,44 @@ public class ArgParser
         }
 
         String autoCounterString = null;
-        boolean usingAutoCounterLinkedId = false;
         String defaultLinkedId = defaultLinkedId(false);
         boolean usingDefaultLinkedId = false;
         if (a.hasOption(Opt.LINKED))
         {
           if (linkedId == null)
           {
-            if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
-                    && val.startsWith(OUTPUTWILDCARD))
+            if (a.hasOption(Opt.OUTPUTFILE) && 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;
-              String oldval = val;
-              val = LINKEDIDBASENAME
-                      + val.substring(OUTPUTWILDCARD.length() - 1);
+              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
+                      + val.substring(MATCHALLLINKEDIDS.length());
+            }
+            else if (a.hasOption(Opt.OUTPUTFILE)
+                    && 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
             {
               // use default linkedId for linked arguments
@@ -421,25 +550,26 @@ public class ArgParser
                       + arg);
             }
           }
-          else if (linkedId.contains(LINKEDIDAUTOCOUNTER))
-          {
-            // turn {n} to the autoCounter
-            autoCounterString = Integer.toString(linkedIdAutoCounter);
-            linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
-                    autoCounterString);
-            usingAutoCounterLinkedId = true;
-            Console.debug(
-                    "Changing linkedId to '" + linkedId + "' from " + arg);
-          }
-          else if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
+          else
           {
-            // turn {++n} to the incremented autoCounter
-            autoCounterString = Integer.toString(++linkedIdAutoCounter);
-            linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
-                    autoCounterString);
-            usingAutoCounterLinkedId = true;
-            Console.debug(
-                    "Changing linkedId to '" + linkedId + "' from " + arg);
+            if (linkedId.contains(LINKEDIDAUTOCOUNTER))
+            {
+              // turn {n} to the autoCounter
+              autoCounterString = Integer.toString(linkedIdAutoCounter);
+              linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
+                      autoCounterString);
+              Console.debug("Changing linkedId to '" + linkedId + "' from "
+                      + arg);
+            }
+            if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
+            {
+              // turn {++n} to the incremented autoCounter
+              autoCounterString = Integer.toString(++linkedIdAutoCounter);
+              linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
+                      autoCounterString);
+              Console.debug("Changing linkedId to '" + linkedId + "' from "
+                      + arg);
+            }
           }
         }
 
@@ -459,8 +589,9 @@ public class ArgParser
         }
 
         // check for unique id
-        SubVals idsv = new SubVals(val);
-        String id = idsv.get(ArgValues.ID);
+        SubVals subvals = new SubVals(val);
+        boolean addNewSubVals = false;
+        String id = subvals.get(ArgValues.ID);
         if (id != null && avm.hasId(a, id))
         {
           Console.error("Argument '" + a.argString()
@@ -468,10 +599,21 @@ public class ArgParser
           continue;
         }
 
-        /* TODO
-         * Change all avs.addValue() avs.setBoolean avs.setNegated() avs.incrementCount calls to checkfor linkedId == "*"
-         * DONE, need to check
-         */
+        // set allstructures to all non-primary structure options in this linked
+        // id if --allstructures has been set
+        if (allStructures
+                && (a.getType() == Type.STRUCTURE
+                        || a.getType() == Type.STRUCTUREIMAGE)
+                && !a.hasOption(Opt.PRIMARY))
+        {
+          if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
+          // && !subvals.has("structureid"))
+          {
+            subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
+            addNewSubVals = true;
+          }
+        }
+
         ArgValues avs = avm.getOrCreateArgValues(a);
 
         // store appropriate String value(s)
@@ -485,7 +627,7 @@ public class ArgParser
             {
               String v = gve.nextElement();
               SubVals vsv = new SubVals(globSubVals, v);
-              addValue(linkedId, avs, vsv, v, argIndex++, true);
+              addValue(linkedId, type, avs, vsv, v, argIndex++, true);
               // if we're using defaultLinkedId and the arg increments the
               // counter:
               if (gve.hasMoreElements() && usingDefaultLinkedId
@@ -501,22 +643,27 @@ public class ArgParser
           }
           else
           {
-            addValue(linkedId, avs, val, argIndex, true);
+            // addValue(linkedId, type, avs, val, argIndex, true);
+            addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
+                    val, argIndex, true);
           }
         }
         else if (a.hasOption(Opt.BOOLEAN))
         {
-          setBoolean(linkedId, avs, !negated, argIndex);
+          setBoolean(linkedId, type, avs, !negated, argIndex);
           setNegated(linkedId, avs, negated);
         }
         else if (a.hasOption(Opt.UNARY))
         {
-          setBoolean(linkedId, avs, true, argIndex);
+          setBoolean(linkedId, type, 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);
         }
@@ -597,6 +744,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());
@@ -808,15 +959,19 @@ public class ArgParser
     FIRST, BEFORE, AFTER
   }
 
-  // get from following Arg of type a or subval of same name (lowercase)
+  /**
+   * get from following Arg of type a or subval of same name (lowercase)
+   */
   public static String getValueFromSubValOrArg(ArgValuesMap avm,
           ArgValue av, Arg a, SubVals sv)
   {
     return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
   }
 
-  // get from following Arg of type a or subval key or preference pref or
-  // default def
+  /**
+   * get from following Arg of type a or subval key or preference pref or
+   * default def
+   */
   public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
           Arg a, SubVals sv, String key, String pref, String def)
   {
@@ -824,8 +979,10 @@ public class ArgParser
             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
+  /**
+   * 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 static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
           Position pos, ArgValue av, SubVals sv, String key, String pref,
           String def)
@@ -856,8 +1013,23 @@ public class ArgParser
       else if (pos == Position.AFTER
               && avm.getClosestNextArgValueOfArg(av, a) != null)
         value = avm.getClosestNextArgValueOfArg(av, a).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 = avm.getArgValueOfArgWithSubValKey(a,
+                Arg.ALLSTRUCTURES.getName());
+        if (av2 != null)
+        {
+          value = av2.getValue();
+        }
+      }
     }
-    else
+    if (value == null)
     {
       value = pref != null ? Cache.getDefault(pref, def) : def;
     }
@@ -873,6 +1045,13 @@ public class ArgParser
   public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
           SubVals sv, String key, String pref, boolean def)
   {
+    return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
+  }
+
+  public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
+          SubVals sv, String key, String pref, boolean def,
+          boolean invertPref)
+  {
     if ((key == null && a == null) || (sv == null && a == null))
       return false;
 
@@ -926,147 +1105,222 @@ public class ArgParser
       return avm.getBoolean(a);
 
     // return preference or default
-    return pref != null ? Cache.getDefault(pref, def) : def;
+    boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
+    return pref != null ? (invertPref ? !prefVal : prefVal) : def;
   }
 
   // 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)
+  // linkedId ArgValues if it does.
+  /**
+   * This version inserts the subvals sv into all created values
+   */
+  private void addValue(String linkedId, Type type, ArgValues avs,
+          SubVals sv, 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);
-        if (a.hasOption(Opt.REQUIREINPUT)
-                && !avm.hasArgWithOption(Opt.INPUT))
-          continue;
-        ArgValues tavs = avm.getOrCreateArgValues(a);
-        String val = v;
-        if (doSubs)
-        {
-          val = makeSubstitutions(v, id);
-          sv = new SubVals(sv, val);
-        }
-        tavs.addValue(sv, val, argIndex);
-        finaliseStoringArgValue(id, tavs);
-      }
-    }
-    else
-    {
-      String val = v;
-      if (doSubs)
-      {
-        val = makeSubstitutions(v, linkedId);
-        sv = new SubVals(sv, val);
-      }
-      avs.addValue(sv, val, argIndex);
-      finaliseStoringArgValue(linkedId, avs);
-    }
+    this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
+            argIndex, doSubs);
   }
 
-  private void addValue(String linkedId, ArgValues avs, String v,
+  private void addValue(String linkedId, Type type, 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, type, avs, null, v, false,
+            argIndex, doSubs);
   }
 
-  private void setBoolean(String linkedId, ArgValues avs, boolean b,
-          int argIndex)
+  private void setBoolean(String linkedId, Type type, 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, type, avs, null, null,
+            b, argIndex, false);
   }
 
   private void setNegated(String linkedId, ArgValues avs, boolean b)
   {
+    this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
+            b, 0, false);
+  }
+
+  private void incrementCount(String linkedId, ArgValues avs)
+  {
+    this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
+            null, false, 0, false);
+  }
+
+  private enum Op
+  {
+    ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
+  }
+
+  private void argValueOperation(Op op, String linkedId, Type type,
+          ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
+          boolean doSubs)
+  {
+    // default to merge subvals if subvals are provided
+    argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
+            doSubs);
+  }
+
+  /**
+   * 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.
+   * 
+   * @param op
+   *          The ArgParser.Op operation
+   * @param linkedId
+   *          The String linkedId from the ArgValuesMap
+   * @param type
+   *          The Arg.Type to attach to this ArgValue
+   * @param avs
+   *          The ArgValues for this linkedId
+   * @param sv
+   *          Use these SubVals on the ArgValue
+   * @param merge
+   *          Merge the SubVals with any existing on the value. False will
+   *          replace unless sv is null
+   * @param v
+   *          The value of the ArgValue (may contain subvals).
+   * @param b
+   *          The boolean value of the ArgValue.
+   * @param argIndex
+   *          The argIndex for the ArgValue.
+   * @param doSubs
+   *          Whether to perform substitutions on the subvals and value.
+   */
+  private void argValueOperation(Op op, String linkedId, Type type,
+          ArgValues avs, SubVals sv, boolean merge, 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)
+            {
+              sv = new SubVals(sv, val, merge);
+              val = makeSubstitutions(sv.getContent(), id);
+            }
+            tavs.addValue(sv, type, val, argIndex, true);
+          }
+          else
+          {
+            if (doSubs)
+            {
+              val = makeSubstitutions(v, id);
+            }
+            tavs.addValue(type, val, argIndex, true);
+          }
+          finaliseStoringArgValue(id, tavs);
+          break;
+
+        case SETBOOLEAN:
+          tavs.setBoolean(type, 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, type, val, argIndex, false);
+        }
+        else
+        {
+          if (doSubs)
+          {
+            val = makeSubstitutions(v, linkedId);
+          }
+          avs.addValue(type, val, argIndex, false);
+        }
+        finaliseStoringArgValue(linkedId, avs);
+        break;
+
+      case SETBOOLEAN:
+        avs.setBoolean(type, b, argIndex, false);
+        finaliseStoringArgValue(linkedId, avs);
+        break;
+
+      case SETNEGATED:
+        avs.setNegated(b, false);
+        break;
+
+      case INCREMENTCOUNT:
+        avs.incrementCount();
+        break;
+
+      default:
+        break;
+      }
     }
   }