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