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