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