JAL-629 Added --opened arg which is like limiting --all to only the previously -...
[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.Collections;
30 import java.util.EnumSet;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36
37 import jalview.bin.Cache;
38 import jalview.bin.Console;
39 import jalview.bin.Jalview;
40 import jalview.bin.argparser.Arg.Opt;
41 import jalview.util.FileUtils;
42 import jalview.util.HttpUtils;
43
44 public class ArgParser
45 {
46   protected static final String DOUBLEDASH = "--";
47
48   protected static final char EQUALS = '=';
49
50   protected static final String NEGATESTRING = "no";
51
52   // the default linked id prefix used for no id (not even square braces)
53   protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
54
55   // the linkedId string used to match all linkedIds seen so far
56   protected static final String MATCHALLLINKEDIDS = "*";
57
58   // the linkedId string used to match all of the last --open'ed linkedIds
59   protected static final String MATCHOPENEDLINKEDIDS = "open*";
60
61   // the counter added to the default linked id prefix
62   private int defaultLinkedIdCounter = 0;
63
64   // the substitution string used to use the defaultLinkedIdCounter
65   private static final String DEFAULTLINKEDIDCOUNTER = "{}";
66
67   // the counter added to the default linked id prefix. NOW using
68   // linkedIdAutoCounter
69   // private int openLinkedIdCounter = 0;
70
71   // the linked id prefix used for --open files. NOW the same as DEFAULT
72   protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
73
74   // the counter used for {n} substitutions
75   private int linkedIdAutoCounter = 0;
76
77   // the linked id substitution string used to increment the idCounter (and use
78   // the incremented value)
79   private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
80
81   // the linked id substitution string used to use the idCounter
82   private static final String LINKEDIDAUTOCOUNTER = "{n}";
83
84   // the linked id substitution string used to use the base filename of --append
85   // or --open
86   private static final String LINKEDIDBASENAME = "{basename}";
87
88   // the linked id substitution string used to use the dir path of --append
89   // or --open
90   private static final String LINKEDIDDIRNAME = "{dirname}";
91
92   // the current argfile
93   private String argFile = null;
94
95   // the linked id substitution string used to use the dir path of the latest
96   // --argfile name
97   private static final String ARGFILEBASENAME = "{argfilebasename}";
98
99   // the linked id substitution string used to use the dir path of the latest
100   // --argfile name
101   private static final String ARGFILEDIRNAME = "{argfiledirname}";
102
103   // an output file wildcard to signify --output=*.ext is really --all --output
104   // {basename}.ext
105   private static final String OUTPUTWILDCARD = "*.";
106
107   // flag to say whether {n} subtitutions in output filenames should be made.
108   // Turn on and off with --substitutions and --nosubstitutions
109   // Start with it on
110   private boolean substitutions = true;
111
112   // flag to say whether the default linkedId is the current default linked id
113   // or ALL linkedIds
114   private boolean allLinkedIds = false;
115
116   // flag to say whether the default linkedId is the current default linked id
117   // or OPENED linkedIds
118   private boolean openedLinkedIds = false;
119
120   protected static final Map<String, Arg> argMap;
121
122   protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
123
124   protected List<String> linkedOrder = new ArrayList<>();
125
126   protected List<String> storedLinkedIds = new ArrayList<>();
127
128   protected List<Arg> argList = new ArrayList<>();
129
130   private static final char ARGFILECOMMENT = '#';
131
132   private int argIndex = 0;
133
134   private BootstrapArgs bootstrapArgs = null;
135
136   static
137   {
138     argMap = new HashMap<>();
139     for (Arg a : EnumSet.allOf(Arg.class))
140     {
141       for (String argName : a.getNames())
142       {
143         if (argMap.containsKey(argName))
144         {
145           Console.warn("Trying to add argument name multiple times: '"
146                   + argName + "'"); // RESTORE THIS WHEN
147           // MERGED
148           if (argMap.get(argName) != a)
149           {
150             Console.error(
151                     "Trying to add argument name multiple times for different Args: '"
152                             + argMap.get(argName).getName() + ":" + argName
153                             + "' and '" + a.getName() + ":" + argName
154                             + "'");
155           }
156           continue;
157         }
158         argMap.put(argName, a);
159       }
160     }
161   }
162
163   public ArgParser(String[] args)
164   {
165     this(args, false, null);
166   }
167
168   public ArgParser(String[] args, boolean initsubstitutions,
169           BootstrapArgs bsa)
170   {
171     // Make a mutable new ArrayList so that shell globbing parser works.
172     // (When shell file globbing is used, there are a sequence of non-Arg
173     // arguments (which are the expanded globbed filenames) that need to be
174     // consumed by the --append/--argfile/etc Arg which is most easily done by
175     // removing these filenames from the list one at a time. This can't be done
176     // with an ArrayList made with only Arrays.asList(String[] args). )
177     this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
178             bsa);
179   }
180
181   public ArgParser(List<String> args, boolean initsubstitutions)
182   {
183     this(args, initsubstitutions, false, null);
184   }
185
186   public ArgParser(List<String> args, boolean initsubstitutions,
187           boolean allowPrivate, BootstrapArgs bsa)
188   {
189     // do nothing if there are no "--" args and (some "-" args || >0 arg is
190     // "open")
191     boolean d = false;
192     boolean dd = false;
193     for (String arg : args)
194     {
195       if (arg.startsWith(DOUBLEDASH))
196       {
197         dd = true;
198         break;
199       }
200       else if (arg.startsWith("-") || arg.equals("open"))
201       {
202         d = true;
203       }
204     }
205     if (d && !dd)
206     {
207       // leave it to the old style -- parse an empty list
208       parse(new ArrayList<String>(), false, false);
209       return;
210     }
211     if (bsa != null)
212       this.bootstrapArgs = bsa;
213     else
214       this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
215     parse(args, initsubstitutions, allowPrivate);
216   }
217
218   private void parse(List<String> args, boolean initsubstitutions,
219           boolean allowPrivate)
220   {
221     this.substitutions = initsubstitutions;
222     boolean openEachInitialFilenames = true;
223     for (int i = 0; i < args.size(); i++)
224     {
225       String arg = args.get(i);
226
227       // If the first arguments do not start with "--" or "-" or is not "open"
228       // and` is a filename that exists it is probably a file/list of files to
229       // open so we fake an Arg.OPEN argument and when adding files only add the
230       // single arg[i] and increment the defaultLinkedIdCounter so that each of
231       // these files is opened separately.
232       if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
233               && !arg.startsWith("-") && !arg.equals("open")
234               && (new File(arg).exists()
235                       || HttpUtils.startsWithHttpOrHttps(arg)))
236       {
237         arg = Arg.OPEN.argString();
238       }
239       else
240       {
241         openEachInitialFilenames = false;
242       }
243
244       String argName = null;
245       String val = null;
246       List<String> globVals = null; // for Opt.GLOB only
247       SubVals globSubVals = null; // also for use by Opt.GLOB only
248       String linkedId = null;
249       if (arg.startsWith(DOUBLEDASH))
250       {
251         int equalPos = arg.indexOf(EQUALS);
252         if (equalPos > -1)
253         {
254           argName = arg.substring(DOUBLEDASH.length(), equalPos);
255           val = arg.substring(equalPos + 1);
256         }
257         else
258         {
259           argName = arg.substring(DOUBLEDASH.length());
260         }
261         int idOpen = argName.indexOf('[');
262         int idClose = argName.indexOf(']');
263
264         if (idOpen > -1 && idClose == argName.length() - 1)
265         {
266           linkedId = argName.substring(idOpen + 1, idClose);
267           argName = argName.substring(0, idOpen);
268         }
269
270         Arg a = argMap.get(argName);
271         // check for boolean prepended by "no"
272         boolean negated = false;
273         if (a == null && argName.startsWith(NEGATESTRING) && argMap
274                 .containsKey(argName.substring(NEGATESTRING.length())))
275         {
276           argName = argName.substring(NEGATESTRING.length());
277           a = argMap.get(argName);
278           negated = true;
279         }
280
281         // check for config errors
282         if (a == null)
283         {
284           // arg not found
285           Console.error("Argument '" + arg + "' not recognised.  Exiting.");
286           Jalview.exit("Invalid argument used." + System.lineSeparator()
287                   + "Use" + System.lineSeparator() + "jalview "
288                   + Arg.HELP.argString() + System.lineSeparator()
289                   + "for a usage statement.", 13);
290           continue;
291         }
292         if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
293         {
294           Console.error(
295                   "Argument '" + a.argString() + "' is private. Ignoring.");
296           continue;
297         }
298         if (!a.hasOption(Opt.BOOLEAN) && negated)
299         {
300           // used "no" with a non-boolean option
301           Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
302                   + "' not a boolean option. Ignoring.");
303           continue;
304         }
305         if (!a.hasOption(Opt.STRING) && equalPos > -1)
306         {
307           // set --argname=value when arg does not accept values
308           Console.error("Argument '" + a.argString()
309                   + "' does not expect a value (given as '" + arg
310                   + "').  Ignoring.");
311           continue;
312         }
313         if (!a.hasOption(Opt.LINKED) && linkedId != null)
314         {
315           // set --argname[linkedId] when arg does not use linkedIds
316           Console.error("Argument '" + a.argString()
317                   + "' does not expect a linked id (given as '" + arg
318                   + "'). Ignoring.");
319           continue;
320         }
321
322         // String value(s)
323         if (a.hasOption(Opt.STRING))
324         {
325           if (equalPos >= 0)
326           {
327             if (a.hasOption(Opt.GLOB))
328             {
329               // strip off and save the SubVals to be added individually later
330               globSubVals = new SubVals(val);
331               // make substitutions before looking for files
332               String fileGlob = makeSubstitutions(globSubVals.getContent(),
333                       linkedId);
334               globVals = FileUtils.getFilenamesFromGlob(fileGlob);
335             }
336             else
337             {
338               // val is already set -- will be saved in the ArgValue later in
339               // the normal way
340             }
341           }
342           else
343           {
344             // There is no "=" so value is next arg or args (possibly shell
345             // glob-expanded)
346             if ((openEachInitialFilenames ? i : i + 1) >= args.size())
347             {
348               // no value to take for arg, which wants a value
349               Console.error("Argument '" + a.getName()
350                       + "' requires a value, none given. Ignoring.");
351               continue;
352             }
353             // deal with bash globs here (--arg val* is expanded before reaching
354             // the JVM). Note that SubVals cannot be used in this case.
355             // If using the --arg=val then the glob is preserved and Java globs
356             // will be used later. SubVals can be used.
357             if (a.hasOption(Opt.GLOB))
358             {
359               // if this is the first argument with a file list at the start of
360               // the args we add filenames from index i instead of i+1
361               globVals = getShellGlobbedFilenameValues(a, args,
362                       openEachInitialFilenames ? i : i + 1);
363             }
364             else
365             {
366               val = args.get(i + 1);
367             }
368           }
369         }
370
371         // make NOACTION adjustments
372         // default and auto counter increments
373         if (a == Arg.NPP)
374         {
375           linkedIdAutoCounter++;
376         }
377         else if (a == Arg.SUBSTITUTIONS)
378         {
379           substitutions = !negated;
380         }
381         else if (a == Arg.SETARGFILE)
382         {
383           argFile = val;
384         }
385         else if (a == Arg.UNSETARGFILE)
386         {
387           argFile = null;
388         }
389         else if (a == Arg.ALL)
390         {
391           allLinkedIds = !negated;
392           openedLinkedIds = false;
393         }
394         else if (a == Arg.OPENED)
395         {
396           openedLinkedIds = !negated;
397           allLinkedIds = false;
398         }
399
400         if (a.hasOption(Opt.STORED))
401         {
402           // reset the lastOpenedLinkedIds list
403           this.storedLinkedIds = new ArrayList<>();
404         }
405
406         // this is probably only Arg.NEW and Arg.OPEN
407         if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
408         {
409           // use the next default prefixed OPENLINKEDID
410           defaultLinkedId(true);
411         }
412
413         String autoCounterString = null;
414         boolean usingAutoCounterLinkedId = false;
415         String defaultLinkedId = defaultLinkedId(false);
416         boolean usingDefaultLinkedId = false;
417         if (a.hasOption(Opt.LINKED))
418         {
419           if (linkedId == null)
420           {
421             if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
422                     && val.startsWith(MATCHALLLINKEDIDS))
423             {
424               // --output=*.ext is shorthand for --all --output {basename}.ext
425               // (or --image=*.ext)
426               allLinkedIds = true;
427               openedLinkedIds = false;
428               linkedId = MATCHALLLINKEDIDS;
429               val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
430                       + val.substring(MATCHALLLINKEDIDS.length());
431             }
432             else if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
433                     && val.startsWith(MATCHOPENEDLINKEDIDS))
434             {
435               // --output=open*.ext is shorthand for --opened --output
436               // {basename}.ext
437               // (or --image=open*.ext)
438               openedLinkedIds = true;
439               allLinkedIds = false;
440               linkedId = MATCHOPENEDLINKEDIDS;
441               val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
442                       + val.substring(MATCHOPENEDLINKEDIDS.length());
443             }
444             else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
445             {
446               linkedId = MATCHALLLINKEDIDS;
447             }
448             else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
449             {
450               linkedId = MATCHOPENEDLINKEDIDS;
451             }
452             else
453             {
454               // use default linkedId for linked arguments
455               linkedId = defaultLinkedId;
456               usingDefaultLinkedId = true;
457               Console.debug("Changing linkedId to '" + linkedId + "' from "
458                       + arg);
459             }
460           }
461           else if (linkedId.contains(LINKEDIDAUTOCOUNTER))
462           {
463             // turn {n} to the autoCounter
464             autoCounterString = Integer.toString(linkedIdAutoCounter);
465             linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
466                     autoCounterString);
467             usingAutoCounterLinkedId = true;
468             Console.debug(
469                     "Changing linkedId to '" + linkedId + "' from " + arg);
470           }
471           else if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
472           {
473             // turn {++n} to the incremented autoCounter
474             autoCounterString = Integer.toString(++linkedIdAutoCounter);
475             linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
476                     autoCounterString);
477             usingAutoCounterLinkedId = true;
478             Console.debug(
479                     "Changing linkedId to '" + linkedId + "' from " + arg);
480           }
481         }
482
483         // do not continue in this block for NOACTION args
484         if (a.hasOption(Opt.NOACTION))
485           continue;
486
487         ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
488
489         // not dealing with both NODUPLICATEVALUES and GLOB
490         if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
491         {
492           Console.error("Argument '" + a.argString()
493                   + "' cannot contain a duplicate value ('" + val
494                   + "'). Ignoring this and subsequent occurrences.");
495           continue;
496         }
497
498         // check for unique id
499         SubVals idsv = new SubVals(val);
500         String id = idsv.get(ArgValues.ID);
501         if (id != null && avm.hasId(a, id))
502         {
503           Console.error("Argument '" + a.argString()
504                   + "' has a duplicate id ('" + id + "'). Ignoring.");
505           continue;
506         }
507
508         /* TODO
509          * Change all avs.addValue() avs.setBoolean avs.setNegated() avs.incrementCount calls to checkfor linkedId == "*"
510          * DONE, need to check
511          */
512         ArgValues avs = avm.getOrCreateArgValues(a);
513
514         // store appropriate String value(s)
515         if (a.hasOption(Opt.STRING))
516         {
517           if (a.hasOption(Opt.GLOB) && globVals != null
518                   && globVals.size() > 0)
519           {
520             Enumeration<String> gve = Collections.enumeration(globVals);
521             while (gve.hasMoreElements())
522             {
523               String v = gve.nextElement();
524               SubVals vsv = new SubVals(globSubVals, v);
525               addValue(linkedId, avs, vsv, v, argIndex++, true);
526               // if we're using defaultLinkedId and the arg increments the
527               // counter:
528               if (gve.hasMoreElements() && usingDefaultLinkedId
529                       && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
530               {
531                 // increment the default linkedId
532                 linkedId = defaultLinkedId(true);
533                 // get new avm and avs
534                 avm = linkedArgs.get(linkedId);
535                 avs = avm.getOrCreateArgValues(a);
536               }
537             }
538           }
539           else
540           {
541             addValue(linkedId, avs, val, argIndex, true);
542           }
543         }
544         else if (a.hasOption(Opt.BOOLEAN))
545         {
546           setBoolean(linkedId, avs, !negated, argIndex);
547           setNegated(linkedId, avs, negated);
548         }
549         else if (a.hasOption(Opt.UNARY))
550         {
551           setBoolean(linkedId, avs, true, argIndex);
552         }
553
554         // remove the '*' or 'open*' linkedId that should be empty if it was
555         // created
556         if ((MATCHALLLINKEDIDS.equals(linkedId)
557                 && linkedArgs.containsKey(linkedId))
558                 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
559                         && linkedArgs.containsKey(linkedId)))
560         {
561           linkedArgs.remove(linkedId);
562         }
563       }
564     }
565   }
566
567   private void finaliseStoringArgValue(String linkedId, ArgValues avs)
568   {
569     Arg a = avs.arg();
570     incrementCount(linkedId, avs);
571     argIndex++;
572
573     // store in appropriate place
574     if (a.hasOption(Opt.LINKED))
575     {
576       // store the order of linkedIds
577       if (!linkedOrder.contains(linkedId))
578         linkedOrder.add(linkedId);
579     }
580
581     // store arg in the list of args used
582     if (!argList.contains(a))
583       argList.add(a);
584   }
585
586   private String defaultLinkedId(boolean increment)
587   {
588     String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
589             .append(Integer.toString(defaultLinkedIdCounter)).toString();
590     if (increment)
591     {
592       while (linkedArgs.containsKey(defaultLinkedId))
593       {
594         defaultLinkedIdCounter++;
595         defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
596                 .append(Integer.toString(defaultLinkedIdCounter))
597                 .toString();
598       }
599     }
600     getOrCreateLinkedArgValuesMap(defaultLinkedId);
601     return defaultLinkedId;
602   }
603
604   public String makeSubstitutions(String val, String linkedId)
605   {
606     if (!this.substitutions || val == null)
607       return val;
608
609     String subvals;
610     String rest;
611     if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
612     {
613       int closeBracket = val.indexOf(']');
614       if (val.length() == closeBracket)
615         return val;
616       subvals = val.substring(0, closeBracket + 1);
617       rest = val.substring(closeBracket + 1);
618     }
619     else
620     {
621       subvals = "";
622       rest = val;
623     }
624     if (rest.contains(LINKEDIDAUTOCOUNTER))
625       rest = rest.replace(LINKEDIDAUTOCOUNTER,
626               String.valueOf(linkedIdAutoCounter));
627     if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
628       rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
629               String.valueOf(++linkedIdAutoCounter));
630     if (rest.contains(DEFAULTLINKEDIDCOUNTER))
631       rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
632               String.valueOf(defaultLinkedIdCounter));
633     ArgValuesMap avm = linkedArgs.get(linkedId);
634     if (avm != null)
635     {
636       if (rest.contains(LINKEDIDBASENAME))
637       {
638         rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
639       }
640       if (rest.contains(LINKEDIDDIRNAME))
641       {
642         rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
643       }
644     }
645     if (argFile != null)
646     {
647       if (rest.contains(ARGFILEBASENAME))
648       {
649         rest = rest.replace(ARGFILEBASENAME,
650                 FileUtils.getBasename(new File(argFile)));
651       }
652       if (rest.contains(ARGFILEDIRNAME))
653       {
654         rest = rest.replace(ARGFILEDIRNAME,
655                 FileUtils.getDirname(new File(argFile)));
656       }
657     }
658
659     return new StringBuilder(subvals).append(rest).toString();
660   }
661
662   /*
663    * A helper method to take a list of String args where we're expecting
664    * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
665    * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
666    * "file2", "file3"} *and remove these from the original list object* so that
667    * processing can continue from where it has left off, e.g. args has become
668    * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
669    * carries on from the next --arg if available.
670    */
671   protected static List<String> getShellGlobbedFilenameValues(Arg a,
672           List<String> args, int i)
673   {
674     List<String> vals = new ArrayList<>();
675     while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
676     {
677       vals.add(FileUtils.substituteHomeDir(args.remove(i)));
678       if (!a.hasOption(Opt.GLOB))
679         break;
680     }
681     return vals;
682   }
683
684   public BootstrapArgs getBootstrapArgs()
685   {
686     return bootstrapArgs;
687   }
688
689   public boolean isSet(Arg a)
690   {
691     return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
692   }
693
694   public boolean isSetAtAll(Arg a)
695   {
696     for (String linkedId : linkedOrder)
697     {
698       if (isSet(linkedId, a))
699         return true;
700     }
701     return false;
702   }
703
704   public boolean isSet(String linkedId, Arg a)
705   {
706     ArgValuesMap avm = linkedArgs.get(linkedId);
707     return avm == null ? false : avm.containsArg(a);
708   }
709
710   public boolean getBoolean(Arg a)
711   {
712     if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
713     {
714       Console.warn("Getting boolean from non boolean Arg '" + a.getName()
715               + "'.");
716     }
717     return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
718   }
719
720   public boolean getBool(String linkedId, Arg a)
721   {
722     ArgValuesMap avm = linkedArgs.get(linkedId);
723     if (avm == null)
724       return a.getDefaultBoolValue();
725     ArgValues avs = avm.getArgValues(a);
726     return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
727   }
728
729   public List<String> getLinkedIds()
730   {
731     return linkedOrder;
732   }
733
734   public ArgValuesMap getLinkedArgs(String id)
735   {
736     return linkedArgs.get(id);
737   }
738
739   @Override
740   public String toString()
741   {
742     StringBuilder sb = new StringBuilder();
743     sb.append("UNLINKED\n");
744     sb.append(argValuesMapToString(linkedArgs.get(null)));
745     if (getLinkedIds() != null)
746     {
747       sb.append("LINKED\n");
748       for (String id : getLinkedIds())
749       {
750         // already listed these as UNLINKED args
751         if (id == null)
752           continue;
753
754         ArgValuesMap avm = getLinkedArgs(id);
755         sb.append("ID: '").append(id).append("'\n");
756         sb.append(argValuesMapToString(avm));
757       }
758     }
759     return sb.toString();
760   }
761
762   private static String argValuesMapToString(ArgValuesMap avm)
763   {
764     if (avm == null)
765       return null;
766     StringBuilder sb = new StringBuilder();
767     for (Arg a : avm.getArgKeys())
768     {
769       ArgValues v = avm.getArgValues(a);
770       sb.append(v.toString());
771       sb.append("\n");
772     }
773     return sb.toString();
774   }
775
776   public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
777           boolean initsubstitutions, BootstrapArgs bsa)
778   {
779     List<File> argFiles = new ArrayList<>();
780
781     for (String pattern : argFilenameGlobs)
782     {
783       // I don't think we want to dedup files, making life easier
784       argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
785     }
786
787     return parseArgFileList(argFiles, initsubstitutions, bsa);
788   }
789
790   public static ArgParser parseArgFileList(List<File> argFiles,
791           boolean initsubstitutions, BootstrapArgs bsa)
792   {
793     List<String> argsList = new ArrayList<>();
794     for (File argFile : argFiles)
795     {
796       if (!argFile.exists())
797       {
798         String message = Arg.ARGFILE.argString() + EQUALS + "\""
799                 + argFile.getPath() + "\": File does not exist.";
800         Jalview.exit(message, 2);
801       }
802       try
803       {
804         String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
805                 .append(EQUALS).append(argFile.getCanonicalPath())
806                 .toString();
807         argsList.add(setargfile);
808         argsList.addAll(readArgFile(argFile));
809         argsList.add(Arg.UNSETARGFILE.argString());
810       } catch (IOException e)
811       {
812         String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
813                 + "\": File could not be read.";
814         Jalview.exit(message, 3);
815       }
816     }
817     // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
818     // --unsetargfile
819     return new ArgParser(argsList, initsubstitutions, true, bsa);
820   }
821
822   protected static List<String> readArgFile(File argFile)
823   {
824     List<String> args = new ArrayList<>();
825     if (argFile != null && argFile.exists())
826     {
827       try
828       {
829         for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
830         {
831           if (line != null && line.length() > 0
832                   && line.charAt(0) != ARGFILECOMMENT)
833             args.add(line);
834         }
835       } catch (IOException e)
836       {
837         String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
838                 + "\": File could not be read.";
839         Console.debug(message, e);
840         Jalview.exit(message, 3);
841       }
842     }
843     return args;
844   }
845
846   public static enum Position
847   {
848     FIRST, BEFORE, AFTER
849   }
850
851   // get from following Arg of type a or subval of same name (lowercase)
852   public static String getValueFromSubValOrArg(ArgValuesMap avm,
853           ArgValue av, Arg a, SubVals sv)
854   {
855     return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
856   }
857
858   // get from following Arg of type a or subval key or preference pref or
859   // default def
860   public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
861           Arg a, SubVals sv, String key, String pref, String def)
862   {
863     return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
864             def);
865   }
866
867   // get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
868   // Arg of type a or subval key or preference pref or default def
869   public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
870           Position pos, ArgValue av, SubVals sv, String key, String pref,
871           String def)
872   {
873     return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
874             sv, key, pref, def);
875   }
876
877   public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
878           ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
879           String key, String pref, String def)
880   {
881     if (key == null)
882       key = a.getName();
883     String value = null;
884     if (sv != null && sv.has(key) && sv.get(key) != null)
885     {
886       value = ap == null ? sv.get(key)
887               : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
888     }
889     else if (avm != null && avm.containsArg(a))
890     {
891       if (pos == Position.FIRST && avm.getValue(a) != null)
892         value = avm.getValue(a);
893       else if (pos == Position.BEFORE
894               && avm.getClosestPreviousArgValueOfArg(av, a) != null)
895         value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
896       else if (pos == Position.AFTER
897               && avm.getClosestNextArgValueOfArg(av, a) != null)
898         value = avm.getClosestNextArgValueOfArg(av, a).getValue();
899     }
900     else
901     {
902       value = pref != null ? Cache.getDefault(pref, def) : def;
903     }
904     return value;
905   }
906
907   public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
908           SubVals sv)
909   {
910     return getFromSubValArgOrPref(avm, a, sv, null, null, false);
911   }
912
913   public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
914           SubVals sv, String key, String pref, boolean def)
915   {
916     if ((key == null && a == null) || (sv == null && a == null))
917       return false;
918
919     boolean usingArgKey = false;
920     if (key == null)
921     {
922       key = a.getName();
923       usingArgKey = true;
924     }
925
926     String nokey = ArgParser.NEGATESTRING + key;
927
928     // look for key or nokey in subvals first (if using Arg check options)
929     if (sv != null)
930     {
931       // check for true boolean
932       if (sv.has(key) && sv.get(key) != null)
933       {
934         if (usingArgKey)
935         {
936           if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
937           {
938             Console.debug(
939                     "Looking for boolean in subval from non-boolean/non-unary Arg "
940                             + a.getName());
941             return false;
942           }
943         }
944         return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
945       }
946
947       // check for negative boolean (subval "no..." will be "true")
948       if (sv.has(nokey) && sv.get(nokey) != null)
949       {
950         if (usingArgKey)
951         {
952           if (!(a.hasOption(Opt.BOOLEAN)))
953           {
954             Console.debug(
955                     "Looking for negative boolean in subval from non-boolean Arg "
956                             + a.getName());
957             return false;
958           }
959         }
960         return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
961       }
962     }
963
964     // check argvalues
965     if (avm != null && avm.containsArg(a))
966       return avm.getBoolean(a);
967
968     // return preference or default
969     return pref != null ? Cache.getDefault(pref, def) : def;
970   }
971
972   // the following methods look for the "*" linkedId and add the argvalue to all
973   // linkedId ArgValues if it does.
974   // This version inserts the subvals sv into all created values
975   private void addValue(String linkedId, ArgValues avs, SubVals sv,
976           String v, int argIndex, boolean doSubs)
977   {
978     this.argValueOperation(Op.ADDVALUE, linkedId, avs, sv, v, false,
979             argIndex, doSubs);
980   }
981
982   private void NOTaddValue(String linkedId, ArgValues avs, SubVals sv,
983           String v, int argIndex, boolean doSubs)
984   {
985     Arg a = avs.arg();
986
987     List<String> wildcardLinkedIds = null;
988     if (a.hasOption(Opt.ALLOWALL))
989     {
990       switch (linkedId)
991       {
992       case MATCHALLLINKEDIDS:
993         wildcardLinkedIds = getLinkedIds();
994         break;
995       case MATCHOPENEDLINKEDIDS:
996         wildcardLinkedIds = this.storedLinkedIds;
997         break;
998       }
999     }
1000
1001     // if we're not a wildcard linkedId and the arg is marked to be stored, add
1002     // to storedLinkedIds
1003     if (linkedId != null && wildcardLinkedIds == null
1004             && a.hasOption(Opt.STORED)
1005             && !storedLinkedIds.contains(linkedId))
1006     {
1007       storedLinkedIds.add(linkedId);
1008     }
1009
1010     // if we are a wildcard linkedId, apply the arg and value to all appropriate
1011     // linkedIds
1012     if (wildcardLinkedIds != null)
1013     {
1014       for (String id : wildcardLinkedIds)
1015       {
1016         // skip incorrectly stored wildcard ids!
1017         if (id == null || MATCHALLLINKEDIDS.equals(id)
1018                 || MATCHOPENEDLINKEDIDS.equals(id))
1019           continue;
1020         ArgValuesMap avm = linkedArgs.get(id);
1021         if (a.hasOption(Opt.REQUIREINPUT)
1022                 && !avm.hasArgWithOption(Opt.INPUT))
1023           continue;
1024         ArgValues tavs = avm.getOrCreateArgValues(a);
1025         String val = v;
1026         if (doSubs)
1027         {
1028           val = makeSubstitutions(v, id);
1029           sv = new SubVals(sv, val);
1030         }
1031         tavs.addValue(sv, val, argIndex);
1032         finaliseStoringArgValue(id, tavs);
1033       }
1034     }
1035     else
1036     {
1037       String val = v;
1038       if (doSubs)
1039       {
1040         val = makeSubstitutions(v, linkedId);
1041         sv = new SubVals(sv, val);
1042       }
1043       avs.addValue(sv, val, argIndex);
1044       finaliseStoringArgValue(linkedId, avs);
1045     }
1046   }
1047
1048   private void addValue(String linkedId, ArgValues avs, String v,
1049           int argIndex, boolean doSubs)
1050   {
1051     this.argValueOperation(Op.ADDVALUE, linkedId, avs, null, v, false,
1052             argIndex, doSubs);
1053   }
1054
1055   // the following methods look for the "*" linkedId and add the argvalue to all
1056   // linkedId ArgValues if it does.
1057   private void NOTaddValue(String linkedId, ArgValues avs, String v,
1058           int argIndex, boolean doSubs)
1059   {
1060     Arg a = avs.arg();
1061     if (linkedId != null && a.hasOption(Opt.STORED)
1062             && !storedLinkedIds.contains(linkedId))
1063     {
1064       storedLinkedIds.add(linkedId);
1065     }
1066
1067     List<String> wildcardLinkedIds = null;
1068     if (a.hasOption(Opt.ALLOWALL))
1069     {
1070       switch (linkedId)
1071       {
1072       case MATCHALLLINKEDIDS:
1073         wildcardLinkedIds = getLinkedIds();
1074         break;
1075       case MATCHOPENEDLINKEDIDS:
1076         wildcardLinkedIds = this.storedLinkedIds;
1077         break;
1078       }
1079     }
1080
1081     // if we're not a wildcard linkedId and the arg is marked to be stored, add
1082     // to storedLinkedIds
1083     if (linkedId != null && wildcardLinkedIds == null
1084             && a.hasOption(Opt.STORED)
1085             && !storedLinkedIds.contains(linkedId))
1086     {
1087       storedLinkedIds.add(linkedId);
1088     }
1089
1090     // if we are a wildcard linkedId, apply the arg and value to all appropriate
1091     // linkedIds
1092     if (wildcardLinkedIds != null)
1093     {
1094       for (String id : wildcardLinkedIds)
1095       {
1096         // skip incorrectly stored wildcard ids!
1097         if (id == null || MATCHALLLINKEDIDS.equals(id)
1098                 || MATCHOPENEDLINKEDIDS.equals(id))
1099           continue;
1100         ArgValuesMap avm = linkedArgs.get(id);
1101         // don't set an output if there isn't an input
1102         if (a.hasOption(Opt.REQUIREINPUT)
1103                 && !avm.hasArgWithOption(Opt.INPUT))
1104           continue;
1105         ArgValues tavs = avm.getOrCreateArgValues(a);
1106         String val = doSubs ? makeSubstitutions(v, id) : v;
1107         tavs.addValue(val, argIndex);
1108         finaliseStoringArgValue(id, tavs);
1109       }
1110     }
1111     else
1112     {
1113       String val = doSubs ? makeSubstitutions(v, linkedId) : v;
1114       avs.addValue(val, argIndex);
1115       finaliseStoringArgValue(linkedId, avs);
1116     }
1117   }
1118
1119   private void setBoolean(String linkedId, ArgValues avs, boolean b,
1120           int argIndex)
1121   {
1122     this.argValueOperation(Op.SETBOOLEAN, linkedId, avs, null, null, b,
1123             argIndex, false);
1124   }
1125
1126   private void NOTsetBoolean(String linkedId, ArgValues avs, boolean b,
1127           int argIndex)
1128   {
1129     Arg a = avs.arg();
1130     if (linkedId != null && a.hasOption(Opt.STORED)
1131             && !storedLinkedIds.contains(linkedId))
1132     {
1133       storedLinkedIds.add(linkedId);
1134     }
1135
1136     List<String> wildcardLinkedIds = null;
1137     if (a.hasOption(Opt.ALLOWALL))
1138     {
1139       switch (linkedId)
1140       {
1141       case MATCHALLLINKEDIDS:
1142         wildcardLinkedIds = getLinkedIds();
1143         break;
1144       case MATCHOPENEDLINKEDIDS:
1145         wildcardLinkedIds = this.storedLinkedIds;
1146         break;
1147       }
1148     }
1149
1150     // if we're not a wildcard linkedId and the arg is marked to be stored, add
1151     // to storedLinkedIds
1152     if (linkedId != null && wildcardLinkedIds == null
1153             && a.hasOption(Opt.STORED)
1154             && !storedLinkedIds.contains(linkedId))
1155     {
1156       storedLinkedIds.add(linkedId);
1157     }
1158
1159     // if we are a wildcard linkedId, apply the arg and value to all appropriate
1160     // linkedIds
1161     if (wildcardLinkedIds != null)
1162     {
1163       for (String id : wildcardLinkedIds)
1164       {
1165         // skip incorrectly stored wildcard ids!
1166         if (id == null || MATCHALLLINKEDIDS.equals(id)
1167                 || MATCHOPENEDLINKEDIDS.equals(id))
1168           continue;
1169         ArgValuesMap avm = linkedArgs.get(id);
1170         if (a.hasOption(Opt.REQUIREINPUT)
1171                 && !avm.hasArgWithOption(Opt.INPUT))
1172           continue;
1173         ArgValues tavs = avm.getOrCreateArgValues(a);
1174         tavs.setBoolean(b, argIndex);
1175         finaliseStoringArgValue(id, tavs);
1176       }
1177     }
1178     else
1179     {
1180       avs.setBoolean(b, argIndex);
1181       finaliseStoringArgValue(linkedId, avs);
1182     }
1183   }
1184
1185   private void setNegated(String linkedId, ArgValues avs, boolean b)
1186   {
1187     this.argValueOperation(Op.SETNEGATED, linkedId, avs, null, null, b, 0,
1188             false);
1189   }
1190
1191   private void NOTsetNegated(String linkedId, ArgValues avs, boolean b)
1192   {
1193     Arg a = avs.arg();
1194     if (linkedId != null && a.hasOption(Opt.STORED)
1195             && !storedLinkedIds.contains(linkedId))
1196     {
1197       storedLinkedIds.add(linkedId);
1198     }
1199
1200     List<String> wildcardLinkedIds = null;
1201     if (a.hasOption(Opt.ALLOWALL))
1202     {
1203       switch (linkedId)
1204       {
1205       case MATCHALLLINKEDIDS:
1206         wildcardLinkedIds = getLinkedIds();
1207         break;
1208       case MATCHOPENEDLINKEDIDS:
1209         wildcardLinkedIds = this.storedLinkedIds;
1210         break;
1211       }
1212     }
1213
1214     // if we're not a wildcard linkedId and the arg is marked to be stored, add
1215     // to storedLinkedIds
1216     if (linkedId != null && wildcardLinkedIds == null
1217             && a.hasOption(Opt.STORED)
1218             && !storedLinkedIds.contains(linkedId))
1219     {
1220       storedLinkedIds.add(linkedId);
1221     }
1222
1223     // if we are a wildcard linkedId, apply the arg and value to all appropriate
1224     // linkedIds
1225     if (wildcardLinkedIds != null)
1226     {
1227       for (String id : wildcardLinkedIds)
1228       {
1229         // skip incorrectly stored wildcard ids!
1230         if (id == null || MATCHALLLINKEDIDS.equals(id)
1231                 || MATCHOPENEDLINKEDIDS.equals(id))
1232           continue;
1233         ArgValuesMap avm = linkedArgs.get(id);
1234         if (a.hasOption(Opt.REQUIREINPUT)
1235                 && !avm.hasArgWithOption(Opt.INPUT))
1236           continue;
1237         ArgValues tavs = avm.getOrCreateArgValues(a);
1238         tavs.setNegated(b);
1239       }
1240     }
1241     else
1242     {
1243       avs.setNegated(b);
1244     }
1245   }
1246
1247   private void incrementCount(String linkedId, ArgValues avs)
1248   {
1249     this.argValueOperation(Op.INCREMENTCOUNT, linkedId, avs, null, null,
1250             false, 0, false);
1251   }
1252
1253   private void NOTincrementCount(String linkedId, ArgValues avs)
1254   {
1255     Arg a = avs.arg();
1256
1257     List<String> wildcardLinkedIds = null;
1258     if (a.hasOption(Opt.ALLOWALL))
1259     {
1260       switch (linkedId)
1261       {
1262       case MATCHALLLINKEDIDS:
1263         wildcardLinkedIds = getLinkedIds();
1264         break;
1265       case MATCHOPENEDLINKEDIDS:
1266         wildcardLinkedIds = this.storedLinkedIds;
1267         break;
1268       }
1269     }
1270
1271     // if we're not a wildcard linkedId and the arg is marked to be stored, add
1272     // to storedLinkedIds
1273     if (linkedId != null && wildcardLinkedIds == null
1274             && a.hasOption(Opt.STORED)
1275             && !storedLinkedIds.contains(linkedId))
1276     {
1277       storedLinkedIds.add(linkedId);
1278     }
1279
1280     // if we are a wildcard linkedId, apply the arg and value to all appropriate
1281     // linkedIds
1282     if (wildcardLinkedIds != null)
1283     {
1284       for (String id : wildcardLinkedIds)
1285       {
1286         // skip incorrectly stored wildcard ids!
1287         if (id == null || MATCHALLLINKEDIDS.equals(id)
1288                 || MATCHOPENEDLINKEDIDS.equals(id))
1289           continue;
1290         ArgValuesMap avm = linkedArgs.get(id);
1291         if (a.hasOption(Opt.REQUIREINPUT)
1292                 && !avm.hasArgWithOption(Opt.INPUT))
1293           continue;
1294         ArgValues tavs = avm.getOrCreateArgValues(a);
1295         tavs.incrementCount();
1296       }
1297     }
1298     else
1299     {
1300       avs.incrementCount();
1301     }
1302   }
1303
1304   private enum Op
1305   {
1306     ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1307   }
1308
1309   // The following operations look for the "*" and "open*" linkedIds and add the
1310   // argvalue to all appropriate linkedId ArgValues if it does.
1311   // If subvals are supplied, they are inserted into all new set values.
1312   private void argValueOperation(Op op, String linkedId, ArgValues avs,
1313           SubVals sv, String v, boolean b, int argIndex, boolean doSubs)
1314   {
1315     Arg a = avs.arg();
1316
1317     List<String> wildcardLinkedIds = null;
1318     if (a.hasOption(Opt.ALLOWALL))
1319     {
1320       switch (linkedId)
1321       {
1322       case MATCHALLLINKEDIDS:
1323         wildcardLinkedIds = getLinkedIds();
1324         break;
1325       case MATCHOPENEDLINKEDIDS:
1326         wildcardLinkedIds = this.storedLinkedIds;
1327         break;
1328       }
1329     }
1330
1331     // if we're not a wildcard linkedId and the arg is marked to be stored, add
1332     // to storedLinkedIds
1333     if (linkedId != null && wildcardLinkedIds == null
1334             && a.hasOption(Opt.STORED)
1335             && !storedLinkedIds.contains(linkedId))
1336     {
1337       storedLinkedIds.add(linkedId);
1338     }
1339
1340     // if we are a wildcard linkedId, apply the arg and value to all appropriate
1341     // linkedIds
1342     if (wildcardLinkedIds != null)
1343     {
1344       for (String id : wildcardLinkedIds)
1345       {
1346         // skip incorrectly stored wildcard ids!
1347         if (id == null || MATCHALLLINKEDIDS.equals(id)
1348                 || MATCHOPENEDLINKEDIDS.equals(id))
1349           continue;
1350         ArgValuesMap avm = linkedArgs.get(id);
1351         // don't set an output if there isn't an input
1352         if (a.hasOption(Opt.REQUIREINPUT)
1353                 && !avm.hasArgWithOption(Opt.INPUT))
1354           continue;
1355
1356         ArgValues tavs = avm.getOrCreateArgValues(a);
1357         switch (op)
1358         {
1359
1360         case ADDVALUE:
1361           String val = v;
1362           if (sv != null)
1363           {
1364             if (doSubs)
1365             {
1366               val = makeSubstitutions(v, id);
1367               sv = new SubVals(sv, val);
1368             }
1369             tavs.addValue(sv, val, argIndex);
1370           }
1371           else
1372           {
1373             if (doSubs)
1374             {
1375               val = makeSubstitutions(v, id);
1376             }
1377             tavs.addValue(val, argIndex);
1378           }
1379           finaliseStoringArgValue(id, tavs);
1380           break;
1381
1382         case SETBOOLEAN:
1383           tavs.setBoolean(b, argIndex);
1384           finaliseStoringArgValue(id, tavs);
1385           break;
1386
1387         case SETNEGATED:
1388           tavs.setNegated(b);
1389           break;
1390
1391         case INCREMENTCOUNT:
1392           tavs.incrementCount();
1393           break;
1394
1395         default:
1396           break;
1397
1398         }
1399
1400       }
1401     }
1402     else // no wildcard linkedId -- do it simpler
1403     {
1404       switch (op)
1405       {
1406       case ADDVALUE:
1407         String val = v;
1408         if (sv != null)
1409         {
1410           if (doSubs)
1411           {
1412             val = makeSubstitutions(v, linkedId);
1413             sv = new SubVals(sv, val);
1414           }
1415           avs.addValue(sv, val, argIndex);
1416         }
1417         else
1418         {
1419           if (doSubs)
1420           {
1421             val = makeSubstitutions(v, linkedId);
1422           }
1423           avs.addValue(val, argIndex);
1424         }
1425         finaliseStoringArgValue(linkedId, avs);
1426         break;
1427
1428       case SETBOOLEAN:
1429         avs.setBoolean(b, argIndex);
1430         finaliseStoringArgValue(linkedId, avs);
1431         break;
1432
1433       case SETNEGATED:
1434         avs.setNegated(b);
1435         break;
1436
1437       case INCREMENTCOUNT:
1438         avs.incrementCount();
1439         break;
1440
1441       default:
1442         break;
1443       }
1444     }
1445   }
1446
1447   private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1448   {
1449     if (linkedArgs.containsKey(linkedId)
1450             && linkedArgs.get(linkedId) != null)
1451       return linkedArgs.get(linkedId);
1452
1453     linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1454     return linkedArgs.get(linkedId);
1455   }
1456
1457 }