JAL-629 Add --renderer arg/subval for vector output and fixed annotation renderer...
[jalview.git] / src / jalview / bin / argparser / ArgParser.java
index c2568bb..f1781fc 100644 (file)
@@ -26,16 +26,20 @@ import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.EnumSet;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.bin.Jalview;
 import jalview.bin.argparser.Arg.Opt;
 import jalview.util.FileUtils;
+import jalview.util.HttpUtils;
 
 public class ArgParser
 {
@@ -48,28 +52,38 @@ public class ArgParser
   // the default linked id prefix used for no id (not even square braces)
   protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
 
+  // 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
   private int defaultLinkedIdCounter = 0;
 
-  // the linked id prefix used for --opennew files
-  protected static final String OPENNEWLINKEDIDPREFIX = "OPENNEW:";
+  // the substitution string used to use the defaultLinkedIdCounter
+  private static final String DEFAULTLINKEDIDCOUNTER = "{}";
 
-  // the counter added to the default linked id prefix
-  private int opennewLinkedIdCounter = 0;
+  // 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
+  protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
+
+  // 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)
-  private static final String INCREMENTAUTOCOUNTERLINKEDID = "{++n}";
+  private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
 
   // the linked id substitution string used to use the idCounter
-  private static final String AUTOCOUNTERLINKEDID = "{n}";
+  private static final String LINKEDIDAUTOCOUNTER = "{n}";
 
-  // the linked id substitution string used to use the base filename of --open
-  // or --opennew
+  // 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 --open
-  // or --opennew
+  // the linked id substitution string used to use the dir path of --append
+  // or --open
   private static final String LINKEDIDDIRNAME = "{dirname}";
 
   // the current argfile
@@ -83,19 +97,25 @@ public class ArgParser
   // --argfile name
   private static final String ARGFILEDIRNAME = "{argfiledirname}";
 
-  private int linkedIdAutoCounter = 0;
-
   // flag to say whether {n} subtitutions in output filenames should be made.
   // Turn on and off with --subs and --nosubs
   private boolean substitutions = false;
 
+  // flag to say whether the default linkedId is the current default linked id
+  // or ALL linkedIds
+  private boolean allLinkedIds = false;
+
   protected static final Map<String, Arg> argMap;
 
   protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
 
-  protected List<String> linkedOrder = null;
+  protected List<String> linkedOrder = new ArrayList<>();
+
+  protected List<Arg> argList = new ArrayList<>();
 
-  protected List<Arg> argList;
+  private static final char ARGFILECOMMENT = '#';
+
+  private int argIndex = 0;
 
   static
   {
@@ -107,7 +127,8 @@ public class ArgParser
         if (argMap.containsKey(argName))
         {
           Console.warn("Trying to add argument name multiple times: '"
-                  + argName + "'"); // RESTORE THIS WHEN MERGED
+                  + argName + "'"); // RESTORE THIS WHEN
+          // MERGED
           if (argMap.get(argName) != a)
           {
             Console.error(
@@ -125,23 +146,30 @@ public class ArgParser
 
   public ArgParser(String[] args)
   {
+    this(args, false);
+  }
+
+  public ArgParser(String[] args, boolean initsubstitutions)
+  {
     // Make a mutable new ArrayList so that shell globbing parser works.
     // (When shell file globbing is used, there are a sequence of non-Arg
     // arguments (which are the expanded globbed filenames) that need to be
-    // consumed by the --open/--argfile/etc Arg which is most easily done by
+    // consumed by the --append/--argfile/etc Arg which is most easily done by
     // removing these filenames from the list one at a time. This can't be done
     // with an ArrayList made with only Arrays.asList(String[] args). )
-    this(new ArrayList<>(Arrays.asList(args)));
+    this(new ArrayList<>(Arrays.asList(args)), initsubstitutions);
   }
 
-  public ArgParser(List<String> args)
+  public ArgParser(List<String> args, boolean initsubstitutions)
   {
-    this(args, false);
+    this(args, initsubstitutions, false);
   }
 
-  public ArgParser(List<String> args, boolean allowPrivate)
+  public ArgParser(List<String> args, boolean initsubstitutions,
+          boolean allowPrivate)
   {
-    // do nothing if there are no "--" args and some "-" args
+    // do nothing if there are no "--" args and (some "-" args || >0 arg is
+    // "open")
     boolean d = false;
     boolean dd = false;
     for (String arg : args)
@@ -151,7 +179,7 @@ public class ArgParser
         dd = true;
         break;
       }
-      else if (arg.startsWith("-"))
+      else if (arg.startsWith("-") || arg.equals("open"))
       {
         d = true;
       }
@@ -159,29 +187,32 @@ public class ArgParser
     if (d && !dd)
     {
       // leave it to the old style -- parse an empty list
-      parse(new ArrayList<String>(), allowPrivate);
+      parse(new ArrayList<String>(), false, false);
       return;
     }
-    parse(args, allowPrivate);
+    parse(args, initsubstitutions, allowPrivate);
   }
 
-  private void parse(List<String> args, boolean allowPrivate)
+  private void parse(List<String> args, boolean initsubstitutions,
+          boolean allowPrivate)
   {
-    int argIndex = 0;
+    this.substitutions = initsubstitutions;
     boolean openEachInitialFilenames = true;
     for (int i = 0; i < args.size(); i++)
     {
       String arg = args.get(i);
 
-      // If the first arguments do not start with "--" or "-" or is "open" and
-      // is a filename that exists it is probably a file/list of files to open
-      // so we fake an Arg.OPEN argument and when adding files only add the
+      // If the first arguments do not start with "--" or "-" or is not "open"
+      // and` is a filename that exists it is probably a file/list of files to
+      // open so we fake an Arg.OPEN argument and when adding files only add the
       // single arg[i] and increment the defaultLinkedIdCounter so that each of
       // these files is opened separately.
       if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
-              && !arg.startsWith("-") && new File(arg).exists())
+              && !arg.startsWith("-") && !arg.equals("open")
+              && (new File(arg).exists()
+                      || HttpUtils.startsWithHttpOrHttps(arg)))
       {
-        arg = DOUBLEDASH + Arg.OPENNEW.getName();
+        arg = Arg.OPEN.argString();
       }
       else
       {
@@ -229,13 +260,15 @@ public class ArgParser
         if (a == null)
         {
           // arg not found
-          Console.error("Argument '" + arg + "' not recognised. Ignoring.");
+          Console.error("Argument '" + arg + "' not recognised.  Exiting.");
+          Jalview.exit("Unrecognised command line argument '" + arg + "'",
+                  13);
           continue;
         }
         if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
         {
-          Console.error("Argument '" + DOUBLEDASH + argName
-                  + "' is private. Ignoring.");
+          Console.error(
+                  "Argument '" + a.argString() + "' is private. Ignoring.");
           continue;
         }
         if (!a.hasOption(Opt.BOOLEAN) && negated)
@@ -248,7 +281,7 @@ public class ArgParser
         if (!a.hasOption(Opt.STRING) && equalPos > -1)
         {
           // set --argname=value when arg does not accept values
-          Console.error("Argument '" + DOUBLEDASH + argName
+          Console.error("Argument '" + a.argString()
                   + "' does not expect a value (given as '" + arg
                   + "').  Ignoring.");
           continue;
@@ -256,7 +289,7 @@ public class ArgParser
         if (!a.hasOption(Opt.LINKED) && linkedId != null)
         {
           // set --argname[linkedId] when arg does not use linkedIds
-          Console.error("Argument '" + DOUBLEDASH + argName
+          Console.error("Argument '" + a.argString()
                   + "' does not expect a linked id (given as '" + arg
                   + "'). Ignoring.");
           continue;
@@ -270,7 +303,7 @@ public class ArgParser
             if (a.hasOption(Opt.GLOB))
             {
               // strip off and save the SubVals to be added individually later
-              globSubVals = ArgParser.getSubVals(val);
+              globSubVals = new SubVals(val);
               // make substitutions before looking for files
               String fileGlob = makeSubstitutions(globSubVals.getContent(),
                       linkedId);
@@ -313,7 +346,7 @@ public class ArgParser
 
         // make NOACTION adjustments
         // default and auto counter increments
-        if (a == Arg.INCREMENT)
+        if (a == Arg.NEWFRAME)
         {
           defaultLinkedIdCounter++;
         }
@@ -333,24 +366,27 @@ public class ArgParser
         {
           argFile = null;
         }
+        else if (a == Arg.ALLFRAMES)
+        {
+          allLinkedIds = !negated;
+        }
 
         String autoCounterString = null;
         boolean usingAutoCounterLinkedId = false;
-        String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
-                .append(Integer.toString(defaultLinkedIdCounter))
-                .toString();
+        String defaultLinkedId = defaultLinkedId(false);
         boolean usingDefaultLinkedId = false;
         if (a.hasOption(Opt.LINKED))
         {
           if (linkedId == null)
           {
-            if (a == Arg.OPENNEW)
+            if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
+            {
+              // use the next default prefixed OPENLINKEDID
+              defaultLinkedId = defaultLinkedId(true);
+            }
+            if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
             {
-              // use the next default prefixed OPENNEWLINKEDID
-              linkedId = new StringBuilder(OPENNEWLINKEDIDPREFIX)
-                      .append(Integer.toString(opennewLinkedIdCounter))
-                      .toString();
-              opennewLinkedIdCounter++;
+              linkedId = MATCHALLLINKEDIDS;
             }
             else
             {
@@ -361,21 +397,21 @@ public class ArgParser
                       + arg);
             }
           }
-          else if (linkedId.contains(AUTOCOUNTERLINKEDID))
+          else if (linkedId.contains(LINKEDIDAUTOCOUNTER))
           {
             // turn {n} to the autoCounter
             autoCounterString = Integer.toString(linkedIdAutoCounter);
-            linkedId = linkedId.replace(AUTOCOUNTERLINKEDID,
+            linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
                     autoCounterString);
             usingAutoCounterLinkedId = true;
             Console.debug(
                     "Changing linkedId to '" + linkedId + "' from " + arg);
           }
-          else if (linkedId.contains(INCREMENTAUTOCOUNTERLINKEDID))
+          else if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
           {
             // turn {++n} to the incremented autoCounter
             autoCounterString = Integer.toString(++linkedIdAutoCounter);
-            linkedId = linkedId.replace(INCREMENTAUTOCOUNTERLINKEDID,
+            linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
                     autoCounterString);
             usingAutoCounterLinkedId = true;
             Console.debug(
@@ -383,35 +419,38 @@ public class ArgParser
           }
         }
 
-        if (!linkedArgs.containsKey(linkedId))
-          linkedArgs.put(linkedId, new ArgValuesMap());
-
         // do not continue for NOACTION args
         if (a.hasOption(Opt.NOACTION))
           continue;
 
+        if (!linkedArgs.containsKey(linkedId))
+          linkedArgs.put(linkedId, new ArgValuesMap());
+
         ArgValuesMap avm = linkedArgs.get(linkedId);
 
         // not dealing with both NODUPLICATEVALUES and GLOB
         if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
         {
-          Console.error("Argument '" + DOUBLEDASH + argName
+          Console.error("Argument '" + a.argString()
                   + "' cannot contain a duplicate value ('" + val
                   + "'). Ignoring this and subsequent occurrences.");
           continue;
         }
 
         // check for unique id
-        SubVals idsv = ArgParser.getSubVals(val);
+        SubVals idsv = new SubVals(val);
         String id = idsv.get(ArgValues.ID);
         if (id != null && avm.hasId(a, id))
         {
-          Console.error("Argument '" + DOUBLEDASH + argName
+          Console.error("Argument '" + a.argString()
                   + "' has a duplicate id ('" + id + "'). Ignoring.");
           continue;
         }
 
-        boolean argIndexIncremented = false;
+        /* TODO
+         * Change all avs.addValue() avs.setBoolean avs.setNegated() avs.incrementCount calls to checkfor linkedId == "*"
+         * DONE, need to check
+         */
         ArgValues avs = avm.getOrCreateArgValues(a);
 
         // store appropriate String value(s)
@@ -420,55 +459,91 @@ public class ArgParser
           if (a.hasOption(Opt.GLOB) && globVals != null
                   && globVals.size() > 0)
           {
-            for (String v : globVals)
+            Enumeration<String> gve = Collections.enumeration(globVals);
+            while (gve.hasMoreElements())
             {
-              v = makeSubstitutions(v, linkedId);
+              String v = gve.nextElement();
               SubVals vsv = new SubVals(globSubVals, v);
-              avs.addValue(vsv, v, argIndex++);
-              argIndexIncremented = true;
+              addValue(linkedId, avs, vsv, v, argIndex++, true);
+              // if we're using defaultLinkedId and the arg increments the
+              // counter:
+              if (gve.hasMoreElements() && usingDefaultLinkedId
+                      && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
+              {
+                // increment the default linkedId
+                linkedId = defaultLinkedId(true);
+                // get new avm and avs
+                avm = linkedArgs.get(linkedId);
+                avs = avm.getOrCreateArgValues(a);
+              }
             }
           }
           else
           {
-            avs.addValue(makeSubstitutions(val, linkedId), argIndex);
+            addValue(linkedId, avs, val, argIndex, true);
           }
         }
         else if (a.hasOption(Opt.BOOLEAN))
         {
-          avs.setBoolean(!negated, argIndex);
-          avs.setNegated(negated);
+          setBoolean(linkedId, avs, !negated, argIndex);
+          setNegated(linkedId, avs, negated);
         }
         else if (a.hasOption(Opt.UNARY))
         {
-          avs.setBoolean(true, argIndex);
+          setBoolean(linkedId, avs, true, argIndex);
         }
-        avs.incrementCount();
-        if (!argIndexIncremented)
-          argIndex++;
 
-        // store in appropriate place
-        if (a.hasOption(Opt.LINKED))
+        // remove the '*' linkedId that should be empty if it was created
+        if (MATCHALLLINKEDIDS.equals(linkedId)
+                && linkedArgs.containsKey(linkedId))
         {
-          // store the order of linkedIds
-          if (linkedOrder == null)
-            linkedOrder = new ArrayList<>();
-          if (!linkedOrder.contains(linkedId))
-            linkedOrder.add(linkedId);
+          linkedArgs.remove(linkedId);
         }
+      }
+    }
+  }
+
+  private void finaliseStoringArgValue(String linkedId, ArgValues avs)
+  {
+    Arg a = avs.arg();
+    incrementCount(linkedId, avs);
+    argIndex++;
+
+    // store in appropriate place
+    if (a.hasOption(Opt.LINKED))
+    {
+      // store the order of linkedIds
+      if (!linkedOrder.contains(linkedId))
+        linkedOrder.add(linkedId);
+    }
 
-        // store arg in the list of args used
-        if (argList == null)
-          argList = new ArrayList<>();
-        if (!argList.contains(a))
-          argList.add(a);
+    // store arg in the list of args used
+    if (!argList.contains(a))
+      argList.add(a);
+  }
 
+  private String defaultLinkedId(boolean increment)
+  {
+    String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+            .append(Integer.toString(defaultLinkedIdCounter)).toString();
+    if (increment)
+    {
+      while (linkedArgs.containsKey(defaultLinkedId))
+      {
+        defaultLinkedIdCounter++;
+        defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+                .append(Integer.toString(defaultLinkedIdCounter))
+                .toString();
       }
     }
+    if (!linkedArgs.containsKey(defaultLinkedId))
+      linkedArgs.put(defaultLinkedId, new ArgValuesMap());
+    return defaultLinkedId;
   }
 
-  private String makeSubstitutions(String val, String linkedId)
+  public String makeSubstitutions(String val, String linkedId)
   {
-    if (!this.substitutions)
+    if (!this.substitutions || val == null)
       return val;
 
     String subvals;
@@ -486,14 +561,15 @@ public class ArgParser
       subvals = "";
       rest = val;
     }
-    if (rest.contains(AUTOCOUNTERLINKEDID))
-      rest = rest.replace(AUTOCOUNTERLINKEDID,
+    if (rest.contains(LINKEDIDAUTOCOUNTER))
+      rest = rest.replace(LINKEDIDAUTOCOUNTER,
               String.valueOf(linkedIdAutoCounter));
-    if (rest.contains(INCREMENTAUTOCOUNTERLINKEDID))
-      rest = rest.replace(INCREMENTAUTOCOUNTERLINKEDID,
+    if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
+      rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
               String.valueOf(++linkedIdAutoCounter));
-    if (rest.contains("{}"))
-      rest = rest.replace("{}", String.valueOf(defaultLinkedIdCounter));
+    if (rest.contains(DEFAULTLINKEDIDCOUNTER))
+      rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
+              String.valueOf(defaultLinkedIdCounter));
     ArgValuesMap avm = linkedArgs.get(linkedId);
     if (avm != null)
     {
@@ -526,12 +602,11 @@ public class ArgParser
   /*
    * A helper method to take a list of String args where we're expecting
    * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
-   * and the index of the globbed arg, here 1.  It returns a
-   * List<String> {"file1", "file2", "file3"}
-   * *and remove these from the original list object* so that processing
-   * can continue from where it has left off, e.g. args has become
-   * {"--previousargs", "--arg", "--otheroptionsornot"}
-   * so the next increment carries on from the next --arg if available.
+   * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
+   * "file2", "file3"} *and remove these from the original list object* so that
+   * processing can continue from where it has left off, e.g. args has become
+   * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
+   * carries on from the next --arg if available.
    */
   protected static List<String> getShellGlobbedFilenameValues(Arg a,
           List<String> args, int i)
@@ -576,12 +651,12 @@ public class ArgParser
     return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
   }
 
-  public List<String> linkedIds()
+  public List<String> getLinkedIds()
   {
     return linkedOrder;
   }
 
-  public ArgValuesMap linkedArgs(String id)
+  public ArgValuesMap getLinkedArgs(String id)
   {
     return linkedArgs.get(id);
   }
@@ -592,16 +667,16 @@ public class ArgParser
     StringBuilder sb = new StringBuilder();
     sb.append("UNLINKED\n");
     sb.append(argValuesMapToString(linkedArgs.get(null)));
-    if (linkedIds() != null)
+    if (getLinkedIds() != null)
     {
       sb.append("LINKED\n");
-      for (String id : linkedIds())
+      for (String id : getLinkedIds())
       {
         // already listed these as UNLINKED args
         if (id == null)
           continue;
 
-        ArgValuesMap avm = linkedArgs(id);
+        ArgValuesMap avm = getLinkedArgs(id);
         sb.append("ID: '").append(id).append("'\n");
         sb.append(argValuesMapToString(avm));
       }
@@ -623,12 +698,8 @@ public class ArgParser
     return sb.toString();
   }
 
-  public static SubVals getSubVals(String item)
-  {
-    return new SubVals(item);
-  }
-
-  public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
+  public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
+          boolean initsubstitutions)
   {
     List<File> argFiles = new ArrayList<>();
 
@@ -638,41 +709,267 @@ public class ArgParser
       argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
     }
 
-    return parseArgFileList(argFiles);
+    return parseArgFileList(argFiles, initsubstitutions);
   }
 
-  public static ArgParser parseArgFileList(List<File> argFiles)
+  public static ArgParser parseArgFileList(List<File> argFiles,
+          boolean initsubstitutions)
   {
     List<String> argsList = new ArrayList<>();
     for (File argFile : argFiles)
     {
       if (!argFile.exists())
       {
-        String message = DOUBLEDASH
-                + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + EQUALS
-                + "\"" + argFile.getPath() + "\": File does not exist.";
+        String message = Arg.ARGFILE.argString() + EQUALS + "\""
+                + argFile.getPath() + "\": File does not exist.";
         Jalview.exit(message, 2);
       }
       try
       {
-        String setargfile = new StringBuilder(ArgParser.DOUBLEDASH)
-                .append(Arg.SETARGFILE.getName()).append(EQUALS)
-                .append(argFile.getCanonicalPath()).toString();
+        String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
+                .append(EQUALS).append(argFile.getCanonicalPath())
+                .toString();
         argsList.add(setargfile);
-        argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
-        argsList.add(new StringBuilder(ArgParser.DOUBLEDASH)
-                .append(Arg.UNSETARGFILE.getName()).toString());
+        argsList.addAll(readArgFile(argFile));
+        argsList.add(Arg.UNSETARGFILE.argString());
       } catch (IOException e)
       {
-        String message = DOUBLEDASH
-                + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
-                + argFile.getPath() + "\": File could not be read.";
+        String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
+                + "\": File could not be read.";
         Jalview.exit(message, 3);
       }
     }
-    // Second param "true" uses Opt.PRIVATE args --setargile=argfile and
+    // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
     // --unsetargfile
-    return new ArgParser(argsList, true);
+    return new ArgParser(argsList, initsubstitutions, true);
+  }
+
+  protected static List<String> readArgFile(File argFile)
+  {
+    List<String> args = new ArrayList<>();
+    if (argFile != null && argFile.exists())
+    {
+      try
+      {
+        for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
+        {
+          if (line != null && line.length() > 0
+                  && line.charAt(0) != ARGFILECOMMENT)
+            args.add(line);
+        }
+      } catch (IOException e)
+      {
+        String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
+                + "\": File could not be read.";
+        Console.debug(message, e);
+        Jalview.exit(message, 3);
+      }
+    }
+    return args;
+  }
+
+  public static enum Position
+  {
+    FIRST, BEFORE, AFTER
+  }
+
+  // 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
+  public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
+          Arg a, SubVals sv, String key, String pref, String def)
+  {
+    return getFromSubValArgOrPref(avm, 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 static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
+          Position pos, ArgValue av, SubVals sv, String key, String pref,
+          String def)
+  {
+    if (key == null)
+      key = a.getName();
+    if (sv != null && sv.has(key) && sv.get(key) != null)
+      return sv.get(key);
+    if (avm != null && avm.containsArg(a))
+    {
+      if (pos == Position.FIRST && avm.getValue(a) != null)
+        return avm.getValue(a);
+      else if (pos == Position.BEFORE
+              && avm.getClosestPreviousArgValueOfArg(av, a) != null)
+        return avm.getClosestPreviousArgValueOfArg(av, a).getValue();
+      else if (pos == Position.AFTER
+              && avm.getClosestNextArgValueOfArg(av, a) != null)
+        return avm.getClosestNextArgValueOfArg(av, a).getValue();
+    }
+    return pref != null ? Cache.getDefault(pref, def) : def;
+  }
+
+  public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
+          SubVals sv)
+  {
+    return getFromSubValArgOrPref(avm, a, sv, null, null, false);
+  }
+
+  public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
+          SubVals sv, String key, String pref, boolean def)
+  {
+    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;
+  }
+
+  // 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))
+    {
+      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);
+    }
+  }
+
+  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);
+    }
+  }
+
+  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);
+    }
+  }
+
+  private void setNegated(String linkedId, ArgValues avs, boolean b)
+  {
+    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.setNegated(b);
+      }
+    }
+    else
+    {
+      avs.setNegated(b);
+    }
+  }
+
+  private void incrementCount(String linkedId, ArgValues avs)
+  {
+    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.incrementCount();
+      }
+    }
+    else
+    {
+      avs.incrementCount();
+    }
   }
 
 }
\ No newline at end of file