JAL-629 Added --all arg, [*] linkedId and functionality to apply some args to all...
[jalview.git] / src / jalview / bin / argparser / ArgParser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin.argparser;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.EnumSet;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34
35 import jalview.bin.Cache;
36 import jalview.bin.Console;
37 import jalview.bin.Jalview;
38 import jalview.bin.argparser.Arg.Opt;
39 import jalview.util.FileUtils;
40 import jalview.util.HttpUtils;
41
42 public class ArgParser
43 {
44   protected static final String DOUBLEDASH = "--";
45
46   protected static final char EQUALS = '=';
47
48   protected static final String NEGATESTRING = "no";
49
50   // the default linked id prefix used for no id (not even square braces)
51   protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
52
53   // the linkedId string used to match all linkedIds seen so far
54   protected static final String MATCHALLLINKEDIDS = "*";
55
56   // the counter added to the default linked id prefix
57   private int defaultLinkedIdCounter = 0;
58
59   // the substitution string used to use the defaultLinkedIdCounter
60   private static final String DEFAULTLINKEDIDCOUNTER = "{}";
61
62   // the counter added to the default linked id prefix. NOW using
63   // linkedIdAutoCounter
64   // private int openLinkedIdCounter = 0;
65
66   // the linked id prefix used for --open files. NOW the same as DEFAULT
67   protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
68
69   // the counter used for {n} substitutions
70   private int linkedIdAutoCounter = 0;
71
72   // the linked id substitution string used to increment the idCounter (and use
73   // the incremented value)
74   private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
75
76   // the linked id substitution string used to use the idCounter
77   private static final String LINKEDIDAUTOCOUNTER = "{n}";
78
79   // the linked id substitution string used to use the base filename of --append
80   // or --open
81   private static final String LINKEDIDBASENAME = "{basename}";
82
83   // the linked id substitution string used to use the dir path of --append
84   // or --open
85   private static final String LINKEDIDDIRNAME = "{dirname}";
86
87   // the current argfile
88   private String argFile = null;
89
90   // the linked id substitution string used to use the dir path of the latest
91   // --argfile name
92   private static final String ARGFILEBASENAME = "{argfilebasename}";
93
94   // the linked id substitution string used to use the dir path of the latest
95   // --argfile name
96   private static final String ARGFILEDIRNAME = "{argfiledirname}";
97
98   // flag to say whether {n} subtitutions in output filenames should be made.
99   // Turn on and off with --subs and --nosubs
100   private boolean substitutions = false;
101
102   // flag to say whether the default linkedId is the current default linked id
103   // or ALL linkedIds
104   private boolean allLinkedIds = false;
105
106   protected static final Map<String, Arg> argMap;
107
108   protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
109
110   protected List<String> linkedOrder = null;
111
112   protected List<Arg> argList;
113
114   private static final char ARGFILECOMMENT = '#';
115
116   static
117   {
118     argMap = new HashMap<>();
119     for (Arg a : EnumSet.allOf(Arg.class))
120     {
121       for (String argName : a.getNames())
122       {
123         if (argMap.containsKey(argName))
124         {
125           Console.warn("Trying to add argument name multiple times: '"
126                   + argName + "'"); // RESTORE THIS WHEN
127           // MERGED
128           if (argMap.get(argName) != a)
129           {
130             Console.error(
131                     "Trying to add argument name multiple times for different Args: '"
132                             + argMap.get(argName).getName() + ":" + argName
133                             + "' and '" + a.getName() + ":" + argName
134                             + "'");
135           }
136           continue;
137         }
138         argMap.put(argName, a);
139       }
140     }
141   }
142
143   public ArgParser(String[] args)
144   {
145     this(args, false);
146   }
147
148   public ArgParser(String[] args, boolean initsubstitutions)
149   {
150     // Make a mutable new ArrayList so that shell globbing parser works.
151     // (When shell file globbing is used, there are a sequence of non-Arg
152     // arguments (which are the expanded globbed filenames) that need to be
153     // consumed by the --append/--argfile/etc Arg which is most easily done by
154     // removing these filenames from the list one at a time. This can't be done
155     // with an ArrayList made with only Arrays.asList(String[] args). )
156     this(new ArrayList<>(Arrays.asList(args)), initsubstitutions);
157   }
158
159   public ArgParser(List<String> args, boolean initsubstitutions)
160   {
161     this(args, initsubstitutions, false);
162   }
163
164   public ArgParser(List<String> args, boolean initsubstitutions,
165           boolean allowPrivate)
166   {
167     // do nothing if there are no "--" args and (some "-" args || >0 arg is
168     // "open")
169     boolean d = false;
170     boolean dd = false;
171     for (String arg : args)
172     {
173       if (arg.startsWith(DOUBLEDASH))
174       {
175         dd = true;
176         break;
177       }
178       else if (arg.startsWith("-") || arg.equals("open"))
179       {
180         d = true;
181       }
182     }
183     if (d && !dd)
184     {
185       // leave it to the old style -- parse an empty list
186       parse(new ArrayList<String>(), false, false);
187       return;
188     }
189     parse(args, initsubstitutions, allowPrivate);
190   }
191
192   private void parse(List<String> args, boolean initsubstitutions,
193           boolean allowPrivate)
194   {
195     this.substitutions = initsubstitutions;
196     int argIndex = 0;
197     boolean openEachInitialFilenames = true;
198     for (int i = 0; i < args.size(); i++)
199     {
200       String arg = args.get(i);
201
202       // If the first arguments do not start with "--" or "-" or is "open" and
203       // is a filename that exists it is probably a file/list of files to open
204       // so we fake an Arg.OPEN argument and when adding files only add the
205       // single arg[i] and increment the defaultLinkedIdCounter so that each of
206       // these files is opened separately.
207       if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
208               && !arg.startsWith("-") && (new File(arg).exists()
209                       || HttpUtils.startsWithHttpOrHttps(arg)))
210       {
211         arg = Arg.OPEN.argString();
212       }
213       else
214       {
215         openEachInitialFilenames = false;
216       }
217
218       String argName = null;
219       String val = null;
220       List<String> globVals = null; // for Opt.GLOB only
221       SubVals globSubVals = null; // also for use by Opt.GLOB only
222       String linkedId = null;
223       if (arg.startsWith(DOUBLEDASH))
224       {
225         int equalPos = arg.indexOf(EQUALS);
226         if (equalPos > -1)
227         {
228           argName = arg.substring(DOUBLEDASH.length(), equalPos);
229           val = arg.substring(equalPos + 1);
230         }
231         else
232         {
233           argName = arg.substring(DOUBLEDASH.length());
234         }
235         int idOpen = argName.indexOf('[');
236         int idClose = argName.indexOf(']');
237
238         if (idOpen > -1 && idClose == argName.length() - 1)
239         {
240           linkedId = argName.substring(idOpen + 1, idClose);
241           argName = argName.substring(0, idOpen);
242         }
243
244         Arg a = argMap.get(argName);
245         // check for boolean prepended by "no"
246         boolean negated = false;
247         if (a == null && argName.startsWith(NEGATESTRING) && argMap
248                 .containsKey(argName.substring(NEGATESTRING.length())))
249         {
250           argName = argName.substring(NEGATESTRING.length());
251           a = argMap.get(argName);
252           negated = true;
253         }
254
255         // check for config errors
256         if (a == null)
257         {
258           // arg not found
259           Console.error("Argument '" + arg + "' not recognised. Ignoring.");
260           continue;
261         }
262         if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
263         {
264           Console.error(
265                   "Argument '" + a.argString() + "' is private. Ignoring.");
266           continue;
267         }
268         if (!a.hasOption(Opt.BOOLEAN) && negated)
269         {
270           // used "no" with a non-boolean option
271           Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
272                   + "' not a boolean option. Ignoring.");
273           continue;
274         }
275         if (!a.hasOption(Opt.STRING) && equalPos > -1)
276         {
277           // set --argname=value when arg does not accept values
278           Console.error("Argument '" + a.argString()
279                   + "' does not expect a value (given as '" + arg
280                   + "').  Ignoring.");
281           continue;
282         }
283         if (!a.hasOption(Opt.LINKED) && linkedId != null)
284         {
285           // set --argname[linkedId] when arg does not use linkedIds
286           Console.error("Argument '" + a.argString()
287                   + "' does not expect a linked id (given as '" + arg
288                   + "'). Ignoring.");
289           continue;
290         }
291
292         // String value(s)
293         if (a.hasOption(Opt.STRING))
294         {
295           if (equalPos >= 0)
296           {
297             if (a.hasOption(Opt.GLOB))
298             {
299               // strip off and save the SubVals to be added individually later
300               globSubVals = new SubVals(val);
301               // make substitutions before looking for files
302               String fileGlob = makeSubstitutions(globSubVals.getContent(),
303                       linkedId);
304               globVals = FileUtils.getFilenamesFromGlob(fileGlob);
305             }
306             else
307             {
308               // val is already set -- will be saved in the ArgValue later in
309               // the normal way
310             }
311           }
312           else
313           {
314             // There is no "=" so value is next arg or args (possibly shell
315             // glob-expanded)
316             if ((openEachInitialFilenames ? i : i + 1) >= args.size())
317             {
318               // no value to take for arg, which wants a value
319               Console.error("Argument '" + a.getName()
320                       + "' requires a value, none given. Ignoring.");
321               continue;
322             }
323             // deal with bash globs here (--arg val* is expanded before reaching
324             // the JVM). Note that SubVals cannot be used in this case.
325             // If using the --arg=val then the glob is preserved and Java globs
326             // will be used later. SubVals can be used.
327             if (a.hasOption(Opt.GLOB))
328             {
329               // if this is the first argument with a file list at the start of
330               // the args we add filenames from index i instead of i+1
331               globVals = getShellGlobbedFilenameValues(a, args,
332                       openEachInitialFilenames ? i : i + 1);
333             }
334             else
335             {
336               val = args.get(i + 1);
337             }
338           }
339         }
340
341         // make NOACTION adjustments
342         // default and auto counter increments
343         if (a == Arg.NEWFRAME)
344         {
345           defaultLinkedIdCounter++;
346         }
347         else if (a == Arg.NPP)
348         {
349           linkedIdAutoCounter++;
350         }
351         else if (a == Arg.SUBSTITUTIONS)
352         {
353           substitutions = !negated;
354         }
355         else if (a == Arg.SETARGFILE)
356         {
357           argFile = val;
358         }
359         else if (a == Arg.UNSETARGFILE)
360         {
361           argFile = null;
362         }
363         else if (a == Arg.ALLFRAMES)
364         {
365           allLinkedIds = !negated;
366         }
367
368         String autoCounterString = null;
369         boolean usingAutoCounterLinkedId = false;
370         String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
371                 .append(Integer.toString(defaultLinkedIdCounter))
372                 .toString();
373         boolean usingDefaultLinkedId = false;
374         if (a.hasOption(Opt.LINKED))
375         {
376           if (linkedId == null)
377           {
378             if (a == Arg.OPEN)
379             {
380               // use the next default prefixed OPENLINKEDID
381               // NOW using the linkedIdAutoCounter
382               defaultLinkedIdCounter++;
383               linkedId = new StringBuilder(OPENLINKEDIDPREFIX)
384                       .append(Integer.toString(defaultLinkedIdCounter))
385                       .toString();
386             }
387             else
388             {
389               if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
390               {
391                 linkedId = this.MATCHALLLINKEDIDS;
392               }
393               else
394               {
395                 // use default linkedId for linked arguments
396                 linkedId = defaultLinkedId;
397                 usingDefaultLinkedId = true;
398                 Console.debug("Changing linkedId to '" + linkedId
399                         + "' from " + arg);
400               }
401             }
402           }
403           else if (linkedId.contains(LINKEDIDAUTOCOUNTER))
404           {
405             // turn {n} to the autoCounter
406             autoCounterString = Integer.toString(linkedIdAutoCounter);
407             linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
408                     autoCounterString);
409             usingAutoCounterLinkedId = true;
410             Console.debug(
411                     "Changing linkedId to '" + linkedId + "' from " + arg);
412           }
413           else if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
414           {
415             // turn {++n} to the incremented autoCounter
416             autoCounterString = Integer.toString(++linkedIdAutoCounter);
417             linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
418                     autoCounterString);
419             usingAutoCounterLinkedId = true;
420             Console.debug(
421                     "Changing linkedId to '" + linkedId + "' from " + arg);
422           }
423         }
424
425         if (!linkedArgs.containsKey(linkedId))
426           linkedArgs.put(linkedId, new ArgValuesMap());
427
428         // do not continue for NOACTION args
429         if (a.hasOption(Opt.NOACTION))
430           continue;
431
432         ArgValuesMap avm = linkedArgs.get(linkedId);
433
434         // not dealing with both NODUPLICATEVALUES and GLOB
435         if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
436         {
437           Console.error("Argument '" + a.argString()
438                   + "' cannot contain a duplicate value ('" + val
439                   + "'). Ignoring this and subsequent occurrences.");
440           continue;
441         }
442
443         // check for unique id
444         SubVals idsv = new SubVals(val);
445         String id = idsv.get(ArgValues.ID);
446         if (id != null && avm.hasId(a, id))
447         {
448           Console.error("Argument '" + a.argString()
449                   + "' has a duplicate id ('" + id + "'). Ignoring.");
450           continue;
451         }
452
453         boolean argIndexIncremented = false;
454         /* TODO
455          * Change all avs.addValue() avs.setBoolean avs.setNegated() avs.incrementCount calls to checkfor linkedId == "*"
456          * DONE, need to check
457          */
458         ArgValues avs = avm.getOrCreateArgValues(a);
459
460         // store appropriate String value(s)
461         if (a.hasOption(Opt.STRING))
462         {
463           if (a.hasOption(Opt.GLOB) && globVals != null
464                   && globVals.size() > 0)
465           {
466             for (String v : globVals)
467             {
468               SubVals vsv = new SubVals(globSubVals, v);
469               addValue(linkedId, avs, vsv, v, argIndex++, true);
470               argIndexIncremented = true;
471             }
472           }
473           else
474           {
475             addValue(linkedId, avs, val, argIndex, true);
476           }
477         }
478         else if (a.hasOption(Opt.BOOLEAN))
479         {
480           setBoolean(linkedId, avs, !negated, argIndex);
481           setNegated(linkedId, avs, negated);
482         }
483         else if (a.hasOption(Opt.UNARY))
484         {
485           setBoolean(linkedId, avs, true, argIndex);
486         }
487         incrementCount(linkedId, avs);
488         if (!argIndexIncremented)
489           argIndex++;
490
491         // store in appropriate place
492         if (a.hasOption(Opt.LINKED))
493         {
494           // store the order of linkedIds
495           if (linkedOrder == null)
496             linkedOrder = new ArrayList<>();
497           if (!linkedOrder.contains(linkedId))
498             linkedOrder.add(linkedId);
499         }
500
501         // store arg in the list of args used
502         if (argList == null)
503           argList = new ArrayList<>();
504         if (!argList.contains(a))
505           argList.add(a);
506
507       }
508     }
509   }
510
511   public String makeSubstitutions(String val, String linkedId)
512   {
513     if (!this.substitutions || val == null)
514       return val;
515
516     String subvals;
517     String rest;
518     if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
519     {
520       int closeBracket = val.indexOf(']');
521       if (val.length() == closeBracket)
522         return val;
523       subvals = val.substring(0, closeBracket + 1);
524       rest = val.substring(closeBracket + 1);
525     }
526     else
527     {
528       subvals = "";
529       rest = val;
530     }
531     if (rest.contains(LINKEDIDAUTOCOUNTER))
532       rest = rest.replace(LINKEDIDAUTOCOUNTER,
533               String.valueOf(linkedIdAutoCounter));
534     if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
535       rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
536               String.valueOf(++linkedIdAutoCounter));
537     if (rest.contains(DEFAULTLINKEDIDCOUNTER))
538       rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
539               String.valueOf(defaultLinkedIdCounter));
540     ArgValuesMap avm = linkedArgs.get(linkedId);
541     if (avm != null)
542     {
543       if (rest.contains(LINKEDIDBASENAME))
544       {
545         rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
546       }
547       if (rest.contains(LINKEDIDDIRNAME))
548       {
549         rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
550       }
551     }
552     if (argFile != null)
553     {
554       if (rest.contains(ARGFILEBASENAME))
555       {
556         rest = rest.replace(ARGFILEBASENAME,
557                 FileUtils.getBasename(new File(argFile)));
558       }
559       if (rest.contains(ARGFILEDIRNAME))
560       {
561         rest = rest.replace(ARGFILEDIRNAME,
562                 FileUtils.getDirname(new File(argFile)));
563       }
564     }
565
566     return new StringBuilder(subvals).append(rest).toString();
567   }
568
569   /*
570    * A helper method to take a list of String args where we're expecting
571    * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
572    * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
573    * "file2", "file3"} *and remove these from the original list object* so that
574    * processing can continue from where it has left off, e.g. args has become
575    * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
576    * carries on from the next --arg if available.
577    */
578   protected static List<String> getShellGlobbedFilenameValues(Arg a,
579           List<String> args, int i)
580   {
581     List<String> vals = new ArrayList<>();
582     while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
583     {
584       vals.add(FileUtils.substituteHomeDir(args.remove(i)));
585       if (!a.hasOption(Opt.GLOB))
586         break;
587     }
588     return vals;
589   }
590
591   public boolean isSet(Arg a)
592   {
593     return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
594   }
595
596   public boolean isSet(String linkedId, Arg a)
597   {
598     ArgValuesMap avm = linkedArgs.get(linkedId);
599     return avm == null ? false : avm.containsArg(a);
600   }
601
602   public boolean getBool(Arg a)
603   {
604     if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
605     {
606       Console.warn("Getting boolean from non boolean Arg '" + a.getName()
607               + "'.");
608     }
609     return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
610   }
611
612   public boolean getBool(String linkedId, Arg a)
613   {
614     ArgValuesMap avm = linkedArgs.get(linkedId);
615     if (avm == null)
616       return a.getDefaultBoolValue();
617     ArgValues avs = avm.getArgValues(a);
618     return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
619   }
620
621   public List<String> linkedIds()
622   {
623     return linkedOrder;
624   }
625
626   public ArgValuesMap linkedArgs(String id)
627   {
628     return linkedArgs.get(id);
629   }
630
631   @Override
632   public String toString()
633   {
634     StringBuilder sb = new StringBuilder();
635     sb.append("UNLINKED\n");
636     sb.append(argValuesMapToString(linkedArgs.get(null)));
637     if (linkedIds() != null)
638     {
639       sb.append("LINKED\n");
640       for (String id : linkedIds())
641       {
642         // already listed these as UNLINKED args
643         if (id == null)
644           continue;
645
646         ArgValuesMap avm = linkedArgs(id);
647         sb.append("ID: '").append(id).append("'\n");
648         sb.append(argValuesMapToString(avm));
649       }
650     }
651     return sb.toString();
652   }
653
654   private static String argValuesMapToString(ArgValuesMap avm)
655   {
656     if (avm == null)
657       return null;
658     StringBuilder sb = new StringBuilder();
659     for (Arg a : avm.getArgKeys())
660     {
661       ArgValues v = avm.getArgValues(a);
662       sb.append(v.toString());
663       sb.append("\n");
664     }
665     return sb.toString();
666   }
667
668   public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
669           boolean initsubstitutions)
670   {
671     List<File> argFiles = new ArrayList<>();
672
673     for (String pattern : argFilenameGlobs)
674     {
675       // I don't think we want to dedup files, making life easier
676       argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
677     }
678
679     return parseArgFileList(argFiles, initsubstitutions);
680   }
681
682   public static ArgParser parseArgFileList(List<File> argFiles,
683           boolean initsubstitutions)
684   {
685     List<String> argsList = new ArrayList<>();
686     for (File argFile : argFiles)
687     {
688       if (!argFile.exists())
689       {
690         String message = Arg.ARGFILE.argString() + EQUALS + "\""
691                 + argFile.getPath() + "\": File does not exist.";
692         Jalview.exit(message, 2);
693       }
694       try
695       {
696         String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
697                 .append(EQUALS).append(argFile.getCanonicalPath())
698                 .toString();
699         argsList.add(setargfile);
700         argsList.addAll(readArgFile(argFile));
701         argsList.add(Arg.UNSETARGFILE.argString());
702       } catch (IOException e)
703       {
704         String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
705                 + "\": File could not be read.";
706         Jalview.exit(message, 3);
707       }
708     }
709     // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
710     // --unsetargfile
711     return new ArgParser(argsList, initsubstitutions, true);
712   }
713
714   protected static List<String> readArgFile(File argFile)
715   {
716     List<String> args = new ArrayList<>();
717     if (argFile != null && argFile.exists())
718     {
719       try
720       {
721         for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
722         {
723           if (line != null && line.length() > 0
724                   && line.charAt(0) != ARGFILECOMMENT)
725             args.add(line);
726         }
727       } catch (IOException e)
728       {
729         String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
730                 + "\": File could not be read.";
731         Console.debug(message, e);
732         Jalview.exit(message, 3);
733       }
734     }
735     return args;
736   }
737
738   public static enum Position
739   {
740     FIRST, BEFORE, AFTER
741   }
742
743   public static String getValueFromSubValOrArg(ArgValuesMap avm, Arg a,
744           SubVals sv)
745   {
746     return getFromSubValArgOrPref(avm, a, sv, null, null, null);
747   }
748
749   public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
750           SubVals sv, String key, String pref, String def)
751   {
752     return getFromSubValArgOrPref(avm, a, Position.FIRST, null, sv, key,
753             pref, def);
754   }
755
756   public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
757           Position pos, ArgValue av, SubVals sv, String key, String pref,
758           String def)
759   {
760     if (key == null)
761       key = a.getName();
762     if (sv != null && sv.has(key) && sv.get(key) != null)
763       return sv.get(key);
764     if (avm != null && avm.containsArg(a))
765     {
766       String val = null;
767       if (pos == Position.FIRST && avm.getValue(a) != null)
768         return avm.getValue(a);
769       else if (pos == Position.BEFORE
770               && avm.getClosestPreviousArgValueOfArg(av, a) != null)
771         return avm.getClosestPreviousArgValueOfArg(av, a).getValue();
772       else if (pos == Position.AFTER
773               && avm.getClosestNextArgValueOfArg(av, a) != null)
774         return avm.getClosestNextArgValueOfArg(av, a).getValue();
775     }
776     return pref != null ? Cache.getDefault(pref, def) : def;
777   }
778
779   public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
780           SubVals sv)
781   {
782     return getFromSubValArgOrPref(avm, a, sv, null, null, false);
783   }
784
785   public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
786           SubVals sv, String key, String pref, boolean def)
787   {
788     if (key == null)
789       key = a.getName();
790     if (sv != null && sv.has(key) && sv.get(key) != null)
791       return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
792     if (avm != null && avm.containsArg(a))
793       return avm.getBoolean(a);
794     return pref != null ? Cache.getDefault(pref, def) : def;
795   }
796
797   // the following methods look for the "*" linkedId and add the argvalue to all
798   // linkedId ArgValues if it does
799   private void addValue(String linkedId, ArgValues avs, SubVals sv,
800           String v, int argIndex, boolean doSubs)
801   {
802     Arg a = avs.arg();
803     if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
804     {
805       for (String id : linkedOrder)
806       {
807         ArgValuesMap avm = linkedArgs.get(id);
808         ArgValues tavs = avm.getArgValues(a);
809         String val = v;
810         if (doSubs)
811         {
812           val = makeSubstitutions(v, linkedId);
813           sv = new SubVals(sv, val);
814         }
815         tavs.addValue(sv, val, argIndex);
816       }
817     }
818     else
819     {
820       String val = v;
821       if (doSubs)
822       {
823         val = makeSubstitutions(v, linkedId);
824         sv = new SubVals(sv, val);
825       }
826       avs.addValue(sv, val, argIndex);
827     }
828   }
829
830   private void addValue(String linkedId, ArgValues avs, String v,
831           int argIndex, boolean doSubs)
832   {
833     Arg a = avs.arg();
834     if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
835     {
836       for (String id : linkedOrder)
837       {
838         ArgValuesMap avm = linkedArgs.get(id);
839         ArgValues tavs = avm.getArgValues(a);
840         String val = doSubs ? makeSubstitutions(v, linkedId) : v;
841         tavs.addValue(val, argIndex);
842       }
843     }
844     else
845     {
846       String val = doSubs ? makeSubstitutions(v, linkedId) : v;
847       avs.addValue(val, argIndex);
848     }
849   }
850
851   private void setBoolean(String linkedId, ArgValues avs, boolean b,
852           int argIndex)
853   {
854     Arg a = avs.arg();
855     if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
856     {
857       for (String id : linkedOrder)
858       {
859         ArgValuesMap avm = linkedArgs.get(id);
860         ArgValues tavs = avm.getArgValues(a);
861         tavs.setBoolean(b, argIndex);
862       }
863     }
864     else
865     {
866       avs.setBoolean(b, argIndex);
867     }
868   }
869
870   private void setNegated(String linkedId, ArgValues avs, boolean b)
871   {
872     Arg a = avs.arg();
873     if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
874     {
875       for (String id : linkedOrder)
876       {
877         ArgValuesMap avm = linkedArgs.get(id);
878         ArgValues tavs = avm.getArgValues(a);
879         tavs.setNegated(b);
880       }
881     }
882     else
883     {
884       avs.setNegated(b);
885     }
886   }
887
888   private void incrementCount(String linkedId, ArgValues avs)
889   {
890     Arg a = avs.arg();
891     if (MATCHALLLINKEDIDS.equals(linkedId) && a.hasOption(Opt.ALLOWALL))
892     {
893       for (String id : linkedOrder)
894       {
895         ArgValuesMap avm = linkedArgs.get(id);
896         ArgValues tavs = avm.getArgValues(a);
897         tavs.incrementCount();
898       }
899     }
900     else
901     {
902       avs.incrementCount();
903     }
904   }
905
906 }