2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.bin.argparser;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.EnumSet;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
37 import jalview.bin.Cache;
38 import jalview.bin.Console;
39 import jalview.bin.Jalview;
40 import jalview.bin.argparser.Arg.Opt;
41 import jalview.bin.argparser.Arg.Type;
42 import jalview.util.FileUtils;
43 import jalview.util.HttpUtils;
45 public class ArgParser
47 protected static final String SINGLEDASH = "-";
49 protected static final String DOUBLEDASH = "--";
51 public static final char EQUALS = '=';
53 public static final String STDOUTFILENAME = "-";
55 protected static final String NEGATESTRING = "no";
58 * the default linked id prefix used for no id (ie when not even square braces
61 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
64 * the linkedId string used to match all linkedIds seen so far
66 protected static final String MATCHALLLINKEDIDS = "*";
69 * the linkedId string used to match all of the last --open'ed linkedIds
71 protected static final String MATCHOPENEDLINKEDIDS = "open*";
74 * the counter added to the default linked id prefix
76 private int defaultLinkedIdCounter = 0;
79 * the substitution string used to use the defaultLinkedIdCounter
81 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
84 * the linked id prefix used for --open files. NOW the same as DEFAULT
86 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
89 * the counter used for {n} substitutions
91 private int linkedIdAutoCounter = 0;
94 * the linked id substitution string used to increment the idCounter (and use
95 * the incremented value)
97 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
100 * the linked id substitution string used to use the idCounter
102 private static final String LINKEDIDAUTOCOUNTER = "{n}";
105 * the linked id substitution string used to use the filename extension of
108 private static final String LINKEDIDEXTENSION = "{extension}";
111 * the linked id substitution string used to use the base filename of --append
114 private static final String LINKEDIDBASENAME = "{basename}";
117 * the linked id substitution string used to use the dir path of --append or
120 private static final String LINKEDIDDIRNAME = "{dirname}";
123 * the current argfile
125 private String argFile = null;
128 * the linked id substitution string used to use the dir path of the latest
130 /** --argfile name */
131 private static final String ARGFILEBASENAME = "{argfilebasename}";
134 * the linked id substitution string used to use the dir path of the latest
137 private static final String ARGFILEDIRNAME = "{argfiledirname}";
140 * flag to say whether {n} subtitutions in output filenames should be made.
141 * Turn on and off with --substitutions and --nosubstitutions Start with it on
143 private boolean substitutions = true;
146 * flag to say whether the default linkedId is the current default linked id
150 private boolean allLinkedIds = false;
153 * flag to say whether the structure arguments should be applied to all
154 * structures with this linked id
156 private boolean allStructures = false;
158 protected static final Map<String, Arg> argMap;
160 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
162 protected List<String> linkedOrder = new ArrayList<>();
164 protected List<String> storedLinkedIds = new ArrayList<>();
166 protected List<Arg> argList = new ArrayList<>();
168 private static final char ARGFILECOMMENT = '#';
170 private int argIndex = 0;
172 private BootstrapArgs bootstrapArgs = null;
176 argMap = new HashMap<>();
177 for (Arg a : EnumSet.allOf(Arg.class))
179 for (String argName : a.getNames())
181 if (argMap.containsKey(argName))
183 Console.warn("Trying to add argument name multiple times: '"
185 if (argMap.get(argName) != a)
188 "Trying to add argument name multiple times for different Args: '"
189 + argMap.get(argName).getName() + ":" + argName
190 + "' and '" + a.getName() + ":" + argName
195 argMap.put(argName, a);
200 public ArgParser(String[] args)
202 this(args, false, null);
205 public ArgParser(String[] args, boolean initsubstitutions,
209 * Make a mutable new ArrayList so that shell globbing parser works.
210 * (When shell file globbing is used, there are a sequence of non-Arg
211 * arguments (which are the expanded globbed filenames) that need to be
212 * consumed by the --append/--argfile/etc Arg which is most easily done
213 * by removing these filenames from the list one at a time. This can't be
214 * done with an ArrayList made with only Arrays.asList(String[] args) as
215 * that is not mutable. )
217 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
221 public ArgParser(List<String> args, boolean initsubstitutions)
223 this(args, initsubstitutions, false, null);
226 public ArgParser(List<String> args, boolean initsubstitutions,
227 boolean allowPrivate, BootstrapArgs bsa)
229 // do nothing if there are no "--" args and (some "-" args || >0 arg is
233 for (String arg : args)
235 if (arg.startsWith(DOUBLEDASH))
240 else if (arg.startsWith("-") || arg.equals("open"))
247 // leave it to the old style -- parse an empty list
248 parse(new ArrayList<String>(), false, false);
252 this.bootstrapArgs = bsa;
254 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
255 parse(args, initsubstitutions, allowPrivate);
258 private void parse(List<String> args, boolean initsubstitutions,
259 boolean allowPrivate)
261 this.substitutions = initsubstitutions;
264 * If the first argument does not start with "--" or "-" or is not "open",
265 * and is a filename that exists or a URL, it is probably a file/list of
266 * files to open so we insert an Arg.OPEN argument before it. This will
267 * mean the list of files at the start of the arguments are all opened
272 String arg0 = args.get(0);
274 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
275 && !arg0.equals("open") && (new File(arg0).exists()
276 || HttpUtils.startsWithHttpOrHttps(arg0))))
278 // insert "--open" at the start
279 args.add(0, Arg.OPEN.argString());
283 for (int i = 0; i < args.size(); i++)
285 String arg = args.get(i);
287 // look for double-dash, e.g. --arg
288 if (arg.startsWith(DOUBLEDASH))
290 String argName = null;
292 List<String> globVals = null; // for Opt.GLOB only
293 SubVals globSubVals = null; // also for use by Opt.GLOB only
294 String linkedId = null;
297 // look for equals e.g. --arg=value
298 int equalPos = arg.indexOf(EQUALS);
301 argName = arg.substring(DOUBLEDASH.length(), equalPos);
302 val = arg.substring(equalPos + 1);
306 argName = arg.substring(DOUBLEDASH.length());
309 // look for linked ID e.g. --arg[linkedID]
310 int idOpen = argName.indexOf('[');
311 int idClose = argName.indexOf(']');
312 if (idOpen > -1 && idClose == argName.length() - 1)
314 linkedId = argName.substring(idOpen + 1, idClose);
315 argName = argName.substring(0, idOpen);
318 // look for type modification e.g. --help-opening
319 int dashPos = argName.indexOf(SINGLEDASH);
322 String potentialArgName = argName.substring(0, dashPos);
323 Arg potentialArg = argMap.get(potentialArgName);
324 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
326 String typeName = argName.substring(dashPos + 1);
329 type = Type.valueOf(typeName);
330 } catch (IllegalArgumentException e)
334 argName = argName.substring(0, dashPos);
338 Arg a = argMap.get(argName);
339 // check for boolean prepended by "no" e.g. --nowrap
340 boolean negated = false;
343 if (argName.startsWith(NEGATESTRING) && argMap
344 .containsKey(argName.substring(NEGATESTRING.length())))
346 argName = argName.substring(NEGATESTRING.length());
347 a = argMap.get(argName);
352 // after all other args, look for Opt.PREFIXKEV args if still not
354 for (Arg potentialArg : EnumSet.allOf(Arg.class))
356 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
357 && argName.startsWith(potentialArg.getName())
360 val = argName.substring(potentialArg.getName().length())
362 argName = argName.substring(0,
363 potentialArg.getName().length());
371 // check for config errors
375 Console.error("Argument '" + arg + "' not recognised. Exiting.");
376 Jalview.exit("Invalid argument used." + System.lineSeparator()
377 + "Use" + System.lineSeparator() + "jalview "
378 + Arg.HELP.argString() + System.lineSeparator()
379 + "for a usage statement.", 13);
382 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
385 "Argument '" + a.argString() + "' is private. Ignoring.");
388 if (!a.hasOption(Opt.BOOLEAN) && negated)
390 // used "no" with a non-boolean option
391 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
392 + "' not a boolean option. Ignoring.");
395 if (!a.hasOption(Opt.STRING) && equalPos > -1)
397 // set --argname=value when arg does not accept values
398 Console.error("Argument '" + a.argString()
399 + "' does not expect a value (given as '" + arg
403 if (!a.hasOption(Opt.LINKED) && linkedId != null)
405 // set --argname[linkedId] when arg does not use linkedIds
406 Console.error("Argument '" + a.argString()
407 + "' does not expect a linked id (given as '" + arg
413 if (a.hasOption(Opt.STRING))
417 if (a.hasOption(Opt.GLOB))
419 // strip off and save the SubVals to be added individually later
420 globSubVals = new SubVals(val);
421 // make substitutions before looking for files
422 String fileGlob = makeSubstitutions(globSubVals.getContent(),
424 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
428 // val is already set -- will be saved in the ArgValue later in
434 // There is no "=" so value is next arg or args (possibly shell
436 if (i + 1 >= args.size())
438 // no value to take for arg, which wants a value
439 Console.error("Argument '" + a.getName()
440 + "' requires a value, none given. Ignoring.");
443 // deal with bash globs here (--arg val* is expanded before reaching
444 // the JVM). Note that SubVals cannot be used in this case.
445 // If using the --arg=val then the glob is preserved and Java globs
446 // will be used later. SubVals can be used.
447 if (a.hasOption(Opt.GLOB))
449 // if this is the first argument with a file list at the start of
450 // the args we add filenames from index i instead of i+1
451 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
455 val = args.get(i + 1);
460 // make NOACTION adjustments
461 // default and auto counter increments
464 linkedIdAutoCounter++;
466 else if (a == Arg.SUBSTITUTIONS)
468 substitutions = !negated;
470 else if (a == Arg.SETARGFILE)
474 else if (a == Arg.UNSETARGFILE)
478 else if (a == Arg.ALL)
480 allLinkedIds = !negated;
482 else if (a == Arg.ALLSTRUCTURES)
484 allStructures = !negated;
487 if (a.hasOption(Opt.STORED))
489 // reset the lastOpenedLinkedIds list
490 this.storedLinkedIds = new ArrayList<>();
493 // this is probably only Arg.NEW and Arg.OPEN
494 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
496 // use the next default prefixed OPENLINKEDID
497 defaultLinkedId(true);
500 String autoCounterString = null;
501 String defaultLinkedId = defaultLinkedId(false);
502 boolean usingDefaultLinkedId = false;
503 if (a.hasOption(Opt.LINKED))
505 if (linkedId == null)
507 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
508 && val.contains(MATCHALLLINKEDIDS))
510 // --output=*.ext is shorthand for --output {basename}.ext
511 // --output=*/*.ext is shorthand for
512 // --output {dirname}/{basename}.ext
513 // (or --image=*.ext)
514 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
515 : MATCHOPENEDLINKEDIDS;
516 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
517 LINKEDIDDIRNAME, LINKEDIDBASENAME);
519 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
521 linkedId = MATCHALLLINKEDIDS;
523 else if (a.hasOption(Opt.ALLOWMULTIID)
524 && this.storedLinkedIds != null
525 && this.storedLinkedIds.size() > 0)
527 linkedId = MATCHOPENEDLINKEDIDS;
531 // use default linkedId for linked arguments
532 linkedId = defaultLinkedId;
533 usingDefaultLinkedId = true;
534 Console.debug("Changing linkedId to '" + linkedId + "' from "
540 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
542 // turn {n} to the autoCounter
543 autoCounterString = Integer.toString(linkedIdAutoCounter);
544 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
546 Console.debug("Changing linkedId to '" + linkedId + "' from "
549 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
551 // turn {++n} to the incremented autoCounter
552 autoCounterString = Integer.toString(++linkedIdAutoCounter);
553 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
555 Console.debug("Changing linkedId to '" + linkedId + "' from "
561 // do not continue in this block for NOACTION args
562 if (a.hasOption(Opt.NOACTION))
565 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
567 // not dealing with both NODUPLICATEVALUES and GLOB
568 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
570 Console.error("Argument '" + a.argString()
571 + "' cannot contain a duplicate value ('" + val
572 + "'). Ignoring this and subsequent occurrences.");
576 // check for unique id
577 SubVals subvals = new SubVals(val);
578 boolean addNewSubVals = false;
579 String id = subvals.get(ArgValues.ID);
580 if (id != null && avm.hasId(a, id))
582 Console.error("Argument '" + a.argString()
583 + "' has a duplicate id ('" + id + "'). Ignoring.");
587 // set allstructures to all non-primary structure options in this linked
588 // id if --allstructures has been set
590 && (a.getType() == Type.STRUCTURE
591 || a.getType() == Type.STRUCTUREIMAGE)
592 && !a.hasOption(Opt.PRIMARY))
594 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
595 // && !subvals.has("structureid"))
597 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
598 addNewSubVals = true;
602 ArgValues avs = avm.getOrCreateArgValues(a);
604 // store appropriate String value(s)
605 if (a.hasOption(Opt.STRING))
607 if (a.hasOption(Opt.GLOB) && globVals != null
608 && globVals.size() > 0)
610 Enumeration<String> gve = Collections.enumeration(globVals);
611 while (gve.hasMoreElements())
613 String v = gve.nextElement();
614 SubVals vsv = new SubVals(globSubVals, v);
615 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
616 // if we're using defaultLinkedId and the arg increments the
618 if (gve.hasMoreElements() && usingDefaultLinkedId
619 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
621 // increment the default linkedId
622 linkedId = defaultLinkedId(true);
623 // get new avm and avs
624 avm = linkedArgs.get(linkedId);
625 avs = avm.getOrCreateArgValues(a);
631 // addValue(linkedId, type, avs, val, argIndex, true);
632 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
633 val, argIndex, true);
636 else if (a.hasOption(Opt.BOOLEAN))
638 setBoolean(linkedId, type, avs, !negated, argIndex);
639 setNegated(linkedId, avs, negated);
641 else if (a.hasOption(Opt.UNARY))
643 setBoolean(linkedId, type, avs, true, argIndex);
646 // remove the '*' or 'open*' linkedId that should be empty if it was
648 if ((MATCHALLLINKEDIDS.equals(linkedId)
649 || MATCHOPENEDLINKEDIDS.equals(linkedId))
650 && linkedArgs.containsKey(linkedId))
652 linkedArgs.remove(linkedId);
658 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
661 incrementCount(linkedId, avs);
664 // store in appropriate place
665 if (a.hasOption(Opt.LINKED))
667 // store the order of linkedIds
668 if (!linkedOrder.contains(linkedId))
669 linkedOrder.add(linkedId);
672 // store arg in the list of args used
673 if (!argList.contains(a))
677 private String defaultLinkedId(boolean increment)
679 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
680 .append(Integer.toString(defaultLinkedIdCounter)).toString();
683 while (linkedArgs.containsKey(defaultLinkedId))
685 defaultLinkedIdCounter++;
686 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
687 .append(Integer.toString(defaultLinkedIdCounter))
691 getOrCreateLinkedArgValuesMap(defaultLinkedId);
692 return defaultLinkedId;
695 public String makeSubstitutions(String val, String linkedId)
697 if (!this.substitutions || val == null)
702 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
704 int closeBracket = val.indexOf(']');
705 if (val.length() == closeBracket)
707 subvals = val.substring(0, closeBracket + 1);
708 rest = val.substring(closeBracket + 1);
715 if (rest.contains(LINKEDIDAUTOCOUNTER))
716 rest = rest.replace(LINKEDIDAUTOCOUNTER,
717 String.valueOf(linkedIdAutoCounter));
718 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
719 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
720 String.valueOf(++linkedIdAutoCounter));
721 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
722 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
723 String.valueOf(defaultLinkedIdCounter));
724 ArgValuesMap avm = linkedArgs.get(linkedId);
727 if (rest.contains(LINKEDIDBASENAME))
729 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
731 if (rest.contains(LINKEDIDEXTENSION))
733 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
735 if (rest.contains(LINKEDIDDIRNAME))
737 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
742 if (rest.contains(ARGFILEBASENAME))
744 rest = rest.replace(ARGFILEBASENAME,
745 FileUtils.getBasename(new File(argFile)));
747 if (rest.contains(ARGFILEDIRNAME))
749 rest = rest.replace(ARGFILEDIRNAME,
750 FileUtils.getDirname(new File(argFile)));
754 return new StringBuilder(subvals).append(rest).toString();
758 * A helper method to take a list of String args where we're expecting
759 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
760 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
761 * "file2", "file3"} *and remove these from the original list object* so that
762 * processing can continue from where it has left off, e.g. args has become
763 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
764 * carries on from the next --arg if available.
766 protected static List<String> getShellGlobbedFilenameValues(Arg a,
767 List<String> args, int i)
769 List<String> vals = new ArrayList<>();
770 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
772 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
773 if (!a.hasOption(Opt.GLOB))
779 public BootstrapArgs getBootstrapArgs()
781 return bootstrapArgs;
784 public boolean isSet(Arg a)
786 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
789 public boolean isSetAtAll(Arg a)
791 for (String linkedId : linkedOrder)
793 if (isSet(linkedId, a))
799 public boolean isSet(String linkedId, Arg a)
801 ArgValuesMap avm = linkedArgs.get(linkedId);
802 return avm == null ? false : avm.containsArg(a);
805 public boolean getBoolean(Arg a)
807 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
809 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
812 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
815 public boolean getBool(String linkedId, Arg a)
817 ArgValuesMap avm = linkedArgs.get(linkedId);
819 return a.getDefaultBoolValue();
820 ArgValues avs = avm.getArgValues(a);
821 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
824 public List<String> getLinkedIds()
829 public ArgValuesMap getLinkedArgs(String id)
831 return linkedArgs.get(id);
835 public String toString()
837 StringBuilder sb = new StringBuilder();
838 sb.append("UNLINKED\n");
839 sb.append(argValuesMapToString(linkedArgs.get(null)));
840 if (getLinkedIds() != null)
842 sb.append("LINKED\n");
843 for (String id : getLinkedIds())
845 // already listed these as UNLINKED args
849 ArgValuesMap avm = getLinkedArgs(id);
850 sb.append("ID: '").append(id).append("'\n");
851 sb.append(argValuesMapToString(avm));
854 return sb.toString();
857 private static String argValuesMapToString(ArgValuesMap avm)
861 StringBuilder sb = new StringBuilder();
862 for (Arg a : avm.getArgKeys())
864 ArgValues v = avm.getArgValues(a);
865 sb.append(v.toString());
868 return sb.toString();
871 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
872 boolean initsubstitutions, BootstrapArgs bsa)
874 List<File> argFiles = new ArrayList<>();
876 for (String pattern : argFilenameGlobs)
878 // I don't think we want to dedup files, making life easier
879 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
882 return parseArgFileList(argFiles, initsubstitutions, bsa);
885 public static ArgParser parseArgFileList(List<File> argFiles,
886 boolean initsubstitutions, BootstrapArgs bsa)
888 List<String> argsList = new ArrayList<>();
889 for (File argFile : argFiles)
891 if (!argFile.exists())
893 String message = Arg.ARGFILE.argString() + EQUALS + "\""
894 + argFile.getPath() + "\": File does not exist.";
895 Jalview.exit(message, 2);
899 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
900 .append(EQUALS).append(argFile.getCanonicalPath())
902 argsList.add(setargfile);
903 argsList.addAll(readArgFile(argFile));
904 argsList.add(Arg.UNSETARGFILE.argString());
905 } catch (IOException e)
907 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
908 + "\": File could not be read.";
909 Jalview.exit(message, 3);
912 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
914 return new ArgParser(argsList, initsubstitutions, true, bsa);
917 protected static List<String> readArgFile(File argFile)
919 List<String> args = new ArrayList<>();
920 if (argFile != null && argFile.exists())
924 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
926 if (line != null && line.length() > 0
927 && line.charAt(0) != ARGFILECOMMENT)
930 } catch (IOException e)
932 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
933 + "\": File could not be read.";
934 Console.debug(message, e);
935 Jalview.exit(message, 3);
941 public static enum Position
947 * get from following Arg of type a or subval of same name (lowercase)
949 public static String getValueFromSubValOrArg(ArgValuesMap avm,
950 ArgValue av, Arg a, SubVals sv)
952 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
956 * get from following Arg of type a or subval key or preference pref or
959 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
960 Arg a, SubVals sv, String key, String pref, String def)
962 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
967 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
968 * Arg of type a or subval key or preference pref or default def
970 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
971 Position pos, ArgValue av, SubVals sv, String key, String pref,
974 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
978 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
979 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
980 String key, String pref, String def)
985 if (sv != null && sv.has(key) && sv.get(key) != null)
987 value = ap == null ? sv.get(key)
988 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
990 else if (avm != null && avm.containsArg(a))
992 if (pos == Position.FIRST && avm.getValue(a) != null)
993 value = avm.getValue(a);
994 else if (pos == Position.BEFORE
995 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
996 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
997 else if (pos == Position.AFTER
998 && avm.getClosestNextArgValueOfArg(av, a) != null)
999 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1001 // look for allstructures subval for Type.STRUCTURE*
1002 Arg arg = av.getArg();
1003 if (value == null && arg.hasOption(Opt.PRIMARY)
1004 && arg.getType() == Type.STRUCTURE
1005 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1006 || a.getType() == Type.STRUCTUREIMAGE))
1008 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1009 Arg.ALLSTRUCTURES.getName());
1012 value = av2.getValue();
1018 value = pref != null ? Cache.getDefault(pref, def) : def;
1023 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1026 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1029 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1030 SubVals sv, String key, String pref, boolean def)
1032 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1035 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1036 SubVals sv, String key, String pref, boolean def,
1039 if ((key == null && a == null) || (sv == null && a == null))
1042 boolean usingArgKey = false;
1049 String nokey = ArgParser.NEGATESTRING + key;
1051 // look for key or nokey in subvals first (if using Arg check options)
1054 // check for true boolean
1055 if (sv.has(key) && sv.get(key) != null)
1059 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1062 "Looking for boolean in subval from non-boolean/non-unary Arg "
1067 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1070 // check for negative boolean (subval "no..." will be "true")
1071 if (sv.has(nokey) && sv.get(nokey) != null)
1075 if (!(a.hasOption(Opt.BOOLEAN)))
1078 "Looking for negative boolean in subval from non-boolean Arg "
1083 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1088 if (avm != null && avm.containsArg(a))
1089 return avm.getBoolean(a);
1091 // return preference or default
1092 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1093 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1096 // the following methods look for the "*" linkedId and add the argvalue to all
1097 // linkedId ArgValues if it does.
1099 * This version inserts the subvals sv into all created values
1101 private void addValue(String linkedId, Type type, ArgValues avs,
1102 SubVals sv, String v, int argIndex, boolean doSubs)
1104 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1108 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1109 int argIndex, boolean doSubs)
1111 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1115 private void setBoolean(String linkedId, Type type, ArgValues avs,
1116 boolean b, int argIndex)
1118 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1119 b, argIndex, false);
1122 private void setNegated(String linkedId, ArgValues avs, boolean b)
1124 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1128 private void incrementCount(String linkedId, ArgValues avs)
1130 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1131 null, false, 0, false);
1136 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1139 private void argValueOperation(Op op, String linkedId, Type type,
1140 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1143 // default to merge subvals if subvals are provided
1144 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1149 * The following operations look for the "*" and "open*" linkedIds and add the
1150 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1151 * supplied, they are inserted into all new set values.
1154 * The ArgParser.Op operation
1156 * The String linkedId from the ArgValuesMap
1158 * The Arg.Type to attach to this ArgValue
1160 * The ArgValues for this linkedId
1162 * Use these SubVals on the ArgValue
1164 * Merge the SubVals with any existing on the value. False will
1165 * replace unless sv is null
1167 * The value of the ArgValue (may contain subvals).
1169 * The boolean value of the ArgValue.
1171 * The argIndex for the ArgValue.
1173 * Whether to perform substitutions on the subvals and value.
1175 private void argValueOperation(Op op, String linkedId, Type type,
1176 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1177 int argIndex, boolean doSubs)
1181 List<String> wildcardLinkedIds = null;
1182 if (a.hasOption(Opt.ALLOWMULTIID))
1186 case MATCHALLLINKEDIDS:
1187 wildcardLinkedIds = getLinkedIds();
1189 case MATCHOPENEDLINKEDIDS:
1190 wildcardLinkedIds = this.storedLinkedIds;
1195 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1196 // to storedLinkedIds
1197 if (linkedId != null && wildcardLinkedIds == null
1198 && a.hasOption(Opt.STORED)
1199 && !storedLinkedIds.contains(linkedId))
1201 storedLinkedIds.add(linkedId);
1204 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1206 if (wildcardLinkedIds != null)
1208 for (String id : wildcardLinkedIds)
1210 // skip incorrectly stored wildcard ids!
1211 if (id == null || MATCHALLLINKEDIDS.equals(id)
1212 || MATCHOPENEDLINKEDIDS.equals(id))
1216 ArgValuesMap avm = linkedArgs.get(id);
1217 // don't set an output if there isn't an input
1218 if (a.hasOption(Opt.REQUIREINPUT)
1219 && !avm.hasArgWithOption(Opt.INPUT))
1222 ArgValues tavs = avm.getOrCreateArgValues(a);
1232 sv = new SubVals(sv, val, merge);
1233 val = makeSubstitutions(sv.getContent(), id);
1235 tavs.addValue(sv, type, val, argIndex, true);
1241 val = makeSubstitutions(v, id);
1243 tavs.addValue(type, val, argIndex, true);
1245 finaliseStoringArgValue(id, tavs);
1249 tavs.setBoolean(type, b, argIndex, true);
1250 finaliseStoringArgValue(id, tavs);
1254 tavs.setNegated(b, true);
1257 case INCREMENTCOUNT:
1258 tavs.incrementCount();
1268 else // no wildcard linkedId -- do it simpler
1278 val = makeSubstitutions(v, linkedId);
1279 sv = new SubVals(sv, val);
1281 avs.addValue(sv, type, val, argIndex, false);
1287 val = makeSubstitutions(v, linkedId);
1289 avs.addValue(type, val, argIndex, false);
1291 finaliseStoringArgValue(linkedId, avs);
1295 avs.setBoolean(type, b, argIndex, false);
1296 finaliseStoringArgValue(linkedId, avs);
1300 avs.setNegated(b, false);
1303 case INCREMENTCOUNT:
1304 avs.incrementCount();
1313 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1315 if (linkedArgs.containsKey(linkedId)
1316 && linkedArgs.get(linkedId) != null)
1317 return linkedArgs.get(linkedId);
1319 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1320 return linkedArgs.get(linkedId);