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