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.
24 import java.io.IOException;
25 import java.net.URLDecoder;
26 import java.nio.file.Files;
27 import java.nio.file.Paths;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Locale;
37 import jalview.util.FileUtils;
39 public class ArgParser
41 private static final String DOUBLEDASH = "--";
43 private static final String NEGATESTRING = "no";
45 // the default linked id prefix used for no id (not even square braces)
46 private static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
48 // the counter added to the default linked id prefix
49 private int defaultLinkedIdCounter = 0;
51 // the linked id used to increment the idCounter (and use the incremented
53 private static final String INCREMENTAUTOCOUNTERLINKEDID = "{++n}";
55 // the linked id used to use the idCounter
56 private static final String AUTOCOUNTERLINKEDID = "{n}";
58 private int idCounter = 0;
60 private static enum Opt
62 BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
69 NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
70 FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
71 NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
72 OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
75 HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
76 COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
77 ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
78 USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
79 VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
80 TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
81 NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
82 ARGFILE, INCREMENT, NPP("n++");
86 HELP.setOptions(Opt.UNARY);
87 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
88 // expecting "--nocalculation"
89 MENUBAR.setOptions(true, Opt.BOOLEAN);
90 STATUS.setOptions(true, Opt.BOOLEAN);
91 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
92 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
93 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
94 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
95 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
96 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
97 HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
98 JABAWS.setOptions(Opt.STRING);
99 ANNOTATION.setOptions(true, Opt.BOOLEAN);
100 ANNOTATION2.setOptions(true, Opt.BOOLEAN);
101 DISPLAY.setOptions(true, Opt.BOOLEAN);
102 GUI.setOptions(true, Opt.BOOLEAN);
103 NEWS.setOptions(true, Opt.BOOLEAN);
104 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
105 // expects a string value
106 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
107 USAGESTATS.setOptions(true, Opt.BOOLEAN);
108 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB);
109 OPEN2.setOptions(Opt.STRING, Opt.LINKED);
110 PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
111 QUESTIONNAIRE.setOptions(Opt.STRING);
112 SETPROP.setOptions(Opt.STRING);
113 TREE.setOptions(Opt.STRING);
115 VDOC.setOptions(Opt.UNARY);
116 VSESS.setOptions(Opt.UNARY);
118 OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
119 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
121 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
122 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
123 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
124 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
125 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
126 TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
127 TITLE.setOptions(Opt.STRING, Opt.LINKED);
128 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
129 NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
130 STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
131 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
132 IMAGE.setOptions(Opt.STRING, Opt.LINKED);
133 QUIT.setOptions(Opt.UNARY);
134 CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
135 DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
136 QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
137 ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB);
138 INCREMENT.setOptions(Opt.UNARY, Opt.MULTI);
139 NPP.setOptions(Opt.UNARY, Opt.MULTI);
140 // BOOTSTRAP args are parsed before a full parse of arguments and
141 // so are accessible at an earlier stage to (e.g.) set debug log level,
142 // provide a props file (that might set log level), run headlessly, read
143 // an argfile instead of other args.
146 private final String[] argNames;
148 private Opt[] argOptions;
150 private boolean defaultBoolValue = false;
152 public String toLongString()
154 StringBuilder sb = new StringBuilder();
155 sb.append("Arg: ").append(this.name());
156 for (String name : getNames())
158 sb.append(", '").append(name).append("'");
160 sb.append("\nOptions: ");
161 boolean first = true;
162 for (Opt o : argOptions)
168 sb.append(o.toString());
172 return sb.toString();
180 private Arg(String... names)
182 int length = (names == null || names.length == 0
183 || (names.length == 1 && names[0] == null)) ? 1
185 this.argNames = new String[length];
186 this.argNames[0] = this.getName();
188 System.arraycopy(names, 0, this.argNames, 1, names.length);
191 public String[] getNames()
196 public String getName()
198 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
202 public final String toString()
207 public boolean hasOption(Opt o)
209 if (argOptions == null)
211 for (Opt option : argOptions)
219 protected void setOptions(Opt... options)
221 setOptions(false, options);
224 protected void setOptions(boolean defaultBoolValue, Opt... options)
226 this.defaultBoolValue = defaultBoolValue;
227 argOptions = options;
230 protected boolean getDefaultBoolValue()
232 return defaultBoolValue;
236 public static class ArgValues
238 private static final String ID = "id";
242 private int argCount = 0;
244 private boolean boolValue = false;
246 private boolean negated = false;
248 private int boolIndex = -1;
250 private List<Integer> argsIndexes;
252 private List<ArgValue> argValueList;
254 private Map<String, ArgValue> idMap = new HashMap<>();
256 protected ArgValues(Arg a)
259 this.argValueList = new ArrayList<ArgValue>();
260 this.boolValue = arg.getDefaultBoolValue();
268 protected int getCount()
273 protected void incrementCount()
278 protected void setNegated(boolean b)
283 protected boolean isNegated()
288 protected void setBoolean(boolean b, int i)
294 protected boolean getBoolean()
296 return this.boolValue;
300 public String toString()
302 if (argValueList == null)
304 StringBuilder sb = new StringBuilder();
305 sb.append(arg.toLongString());
306 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
307 sb.append("Boolean: ").append(boolValue).append("; Default: ")
308 .append(arg.getDefaultBoolValue()).append("; Negated: ")
309 .append(negated).append("\n");
310 if (arg.hasOption(Opt.STRING))
312 sb.append("Values:");
313 boolean first = true;
314 for (ArgValue av : argValueList)
316 String v = av.getValue();
320 sb.append(v).append("'");
325 sb.append("Count: ").append(argCount).append("\n");
326 return sb.toString();
329 protected void addValue()
334 protected void addValue(String val, int argIndex)
336 addArgValue(new ArgValue(val, argIndex));
339 protected void addArgValue(ArgValue av)
341 if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
342 || (arg.hasOption(Opt.NODUPLICATEVALUES)
343 && argValueList.contains(av.getValue())))
345 if (argValueList == null)
347 argValueList = new ArrayList<ArgValue>();
349 SubVals sv = ArgParser.getSubVals(av.getValue());
352 String id = sv.get(ID);
356 argValueList.add(av);
359 protected boolean hasValue(String val)
361 return argValueList.contains(val);
364 protected ArgValue getArgValue()
366 if (arg.hasOption(Opt.MULTI))
367 Console.warn("Requesting single value for multi value argument");
368 return argValueList.size() > 0 ? argValueList.get(0) : null;
371 protected List<ArgValue> getArgValueList()
376 protected boolean hasId(String id)
378 return idMap.containsKey(id);
381 protected ArgValue getId(String id)
383 return idMap.get(id);
388 private List<String> vargs = null;
390 private boolean isApplet;
392 // private AppletParams appletParams;
394 public boolean isApplet()
399 public String nextValue()
401 return vargs.remove(0);
409 public String getValue(String arg)
411 return getValue(arg, false);
414 public String getValue(String arg, boolean utf8decode)
416 int index = vargs.indexOf(arg);
421 ret = vargs.get(index + 1).toString();
424 if (utf8decode && ret != null)
428 dc = URLDecoder.decode(ret, "UTF-8");
430 } catch (Exception e)
432 // TODO: log failure to decode
440 private static final Map<String, Arg> argMap;
442 private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
444 private List<String> linkedOrder = null;
446 private List<Arg> argList;
450 argMap = new HashMap<>();
451 for (Arg a : EnumSet.allOf(Arg.class))
453 for (String argName : a.getNames())
455 if (argMap.containsKey(argName))
457 Console.warn("Trying to add argument name multiple times: '"
458 + argName + "'"); // RESTORE THIS WHEN MERGED
459 if (argMap.get(argName) != a)
462 "Trying to add argument name multiple times for different Args: '"
463 + argMap.get(argName).getName() + ":" + argName
464 + "' and '" + a.getName() + ":" + argName
469 argMap.put(argName, a);
474 public ArgParser(String[] args)
476 // make a mutable new ArrayList so that shell globbing parser works
477 this(new ArrayList<>(Arrays.asList(args)));
480 public ArgParser(List<String> args)
485 private void init(List<String> args)
487 // Enumeration<String> argE = Collections.enumeration(args);
489 // while (argE.hasMoreElements())
490 for (int i = 0; i < args.size(); i++)
492 // String arg = argE.nextElement();
493 String arg = args.get(i);
494 String argName = null;
496 List<String> vals = null; // for Opt.GLOB only
497 String linkedId = null;
498 if (arg.startsWith(DOUBLEDASH))
500 int equalPos = arg.indexOf('=');
503 argName = arg.substring(DOUBLEDASH.length(), equalPos);
504 val = arg.substring(equalPos + 1);
508 argName = arg.substring(DOUBLEDASH.length());
510 int idOpen = argName.indexOf('[');
511 int idClose = argName.indexOf(']');
513 if (idOpen > -1 && idClose == argName.length() - 1)
515 linkedId = argName.substring(idOpen + 1, idClose);
516 argName = argName.substring(0, idOpen);
519 Arg a = argMap.get(argName);
520 // check for boolean prepended by "no"
521 boolean negated = false;
522 if (a == null && argName.startsWith(NEGATESTRING) && argMap
523 .containsKey(argName.substring(NEGATESTRING.length())))
525 argName = argName.substring(NEGATESTRING.length());
526 a = argMap.get(argName);
530 // check for config errors
534 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
537 if (!a.hasOption(Opt.BOOLEAN) && negated)
539 // used "no" with a non-boolean option
540 Console.error("Argument '--" + NEGATESTRING + argName
541 + "' not a boolean option. Ignoring.");
544 if (!a.hasOption(Opt.STRING) && equalPos > -1)
546 // set --argname=value when arg does not accept values
547 Console.error("Argument '--" + argName
548 + "' does not expect a value (given as '" + arg
552 if (!a.hasOption(Opt.LINKED) && linkedId != null)
554 // set --argname[linkedId] when arg does not use linkedIds
555 Console.error("Argument '--" + argName
556 + "' does not expect a linked id (given as '" + arg
561 if (a.hasOption(Opt.STRING) && equalPos == -1)
563 // take next arg as value if required, and '=' was not found
564 // if (!argE.hasMoreElements())
565 if (i + 1 >= args.size())
567 // no value to take for arg, which wants a value
568 Console.error("Argument '" + a.getName()
569 + "' requires a value, none given. Ignoring.");
572 // deal with bash globs here (--arg val* is expanded before reaching
573 // the JVM). Note that SubVals cannot be used in this case.
574 // If using the --arg=val then the glob is preserved and Java globs
575 // will be used later. SubVals can be used.
576 if (a.hasOption(Opt.GLOB))
578 vals.addAll(getShellGlobbedFilenameValues(a, args, i + 1));
582 val = args.get(i + 1);
586 // default and auto counter increments
587 if (a == Arg.INCREMENT)
589 defaultLinkedIdCounter++;
592 else if (a == Arg.NPP)
598 String autoCounterString = null;
599 boolean usingAutoCounterLinkedId = false;
600 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
601 .append(Integer.toString(defaultLinkedIdCounter))
603 boolean usingDefaultLinkedId = false;
604 if (a.hasOption(Opt.LINKED))
606 if (linkedId == null)
608 // use default linkedId for linked arguments
609 linkedId = defaultLinkedId;
610 usingDefaultLinkedId = true;
612 "Changing linkedId to '" + linkedId + "' from " + arg);
614 else if (linkedId.equals(AUTOCOUNTERLINKEDID))
616 // turn {n} to the autoCounter
617 autoCounterString = Integer.toString(idCounter);
618 linkedId = autoCounterString;
619 usingAutoCounterLinkedId = true;
621 "Changing linkedId to '" + linkedId + "' from " + arg);
623 else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID))
625 // turn {++n} to the incremented autoCounter
626 autoCounterString = Integer.toString(++idCounter);
627 linkedId = autoCounterString;
628 usingAutoCounterLinkedId = true;
630 "Changing linkedId to '" + linkedId + "' from " + arg);
634 if (!linkedArgs.containsKey(linkedId))
635 linkedArgs.put(linkedId, new ArgValuesMap());
637 ArgValuesMap avm = linkedArgs.get(linkedId);
639 // not dealing with both NODUPLICATEVALUES and GLOB
640 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
642 Console.error("Argument '--" + argName
643 + "' cannot contain a duplicate value ('" + val
644 + "'). Ignoring this and subsequent occurrences.");
648 // check for unique id
649 SubVals sv = ArgParser.getSubVals(val);
650 String id = sv.get(ArgValues.ID);
651 if (id != null && avm.hasId(a, id))
653 Console.error("Argument '--" + argName + "' has a duplicate id ('"
654 + id + "'). Ignoring.");
658 ArgValues avs = avm.getOrCreateArgValues(a);
661 avs = new ArgValues(a);
663 // store appropriate value
664 if (a.hasOption(Opt.STRING))
666 if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
668 for (String v : vals)
669 avs.addValue(val, argIndex++);
673 avs.addValue(val, argIndex);
676 else if (a.hasOption(Opt.BOOLEAN))
678 avs.setBoolean(!negated, argIndex);
679 avs.setNegated(negated);
681 else if (a.hasOption(Opt.UNARY))
683 avs.setBoolean(true, argIndex);
685 avs.incrementCount();
687 // store in appropriate place
688 if (a.hasOption(Opt.LINKED))
690 // allow a default linked id for single usage
691 if (linkedId == null)
692 linkedId = defaultLinkedId;
693 // store the order of linkedIds
694 if (linkedOrder == null)
695 linkedOrder = new ArrayList<>();
696 if (!linkedOrder.contains(linkedId))
697 linkedOrder.add(linkedId);
700 // store arg in the list of args used
702 argList = new ArrayList<>();
703 if (!argList.contains(a))
710 * A helper method to take a list of String args where we're expecting
711 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
712 * and the index of the globbed arg, here 1. It returns a
713 * List<String> {"file1", "file2", "file3"}
714 * *and remove these from the original list object* so that processing
715 * can continue from where it has left off, e.g. args has become
716 * {"--previousargs", "--arg", "--otheroptionsornot"}
717 * so the next increment carries on from the next --arg if available.
719 private static List<String> getShellGlobbedFilenameValues(Arg a,
720 List<String> args, int i)
722 List<String> vals = new ArrayList<>();
723 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
725 vals.add(args.remove(i));
726 if (!a.hasOption(Opt.GLOB))
732 public boolean isSet(Arg a)
734 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
737 public boolean isSet(String linkedId, Arg a)
739 ArgValuesMap avm = linkedArgs.get(linkedId);
740 return avm == null ? false : avm.containsArg(a);
743 public boolean getBool(Arg a)
745 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
747 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
750 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
753 public boolean getBool(String linkedId, Arg a)
755 ArgValuesMap avm = linkedArgs.get(linkedId);
757 return a.getDefaultBoolValue();
758 ArgValues avs = avm.getArgValues(a);
759 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
762 public List<String> linkedIds()
767 public ArgValuesMap linkedArgs(String id)
769 return linkedArgs.get(id);
773 public String toString()
775 StringBuilder sb = new StringBuilder();
776 sb.append("UNLINKED\n");
777 sb.append(argValuesMapToString(linkedArgs.get(null)));
778 if (linkedIds() != null)
780 sb.append("LINKED\n");
781 for (String id : linkedIds())
783 // already listed these as UNLINKED args
787 ArgValuesMap avm = linkedArgs(id);
788 sb.append("ID: '").append(id).append("'\n");
789 sb.append(argValuesMapToString(avm));
792 return sb.toString();
795 private static String argValuesMapToString(ArgValuesMap avm)
799 StringBuilder sb = new StringBuilder();
800 for (Arg a : avm.getArgKeys())
802 ArgValues v = avm.getArgValues(a);
803 sb.append(v.toString());
806 return sb.toString();
809 public static SubVals getSubVals(String item)
811 return new SubVals(item);
815 * A helper class to keep an index of argument position with argument values
817 public static class ArgValue
819 private int argIndex;
821 private String value;
825 protected ArgValue(String value, int argIndex)
828 this.argIndex = argIndex;
831 protected String getValue()
836 protected int getArgIndex()
841 protected void setId(String i)
846 protected String getId()
853 * A helper class to parse a string of the possible forms "content"
854 * "[index]content", "[keyName=keyValue]content" and return the integer index,
855 * the strings keyName and keyValue, and the content after the square brackets
856 * (if present). Values not set `will be -1 or null.
858 public static class SubVals
860 private static int NOTSET = -1;
862 private int index = NOTSET;
864 private Map<String, String> subVals = null;
866 private static char SEPARATOR = ';';
868 private String content = null;
870 public SubVals(String item)
872 this.parseVals(item);
875 public void parseVals(String item)
879 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
881 int openBracket = item.indexOf('[');
882 int closeBracket = item.indexOf(']');
883 String subvalsString = item.substring(openBracket + 1,
885 this.content = item.substring(closeBracket + 1);
886 boolean setIndex = false;
887 for (String subvalString : subvalsString
888 .split(Character.toString(SEPARATOR)))
890 int equals = subvalString.indexOf('=');
894 subVals = new HashMap<>();
895 subVals.put(subvalString.substring(0, equals),
896 subvalString.substring(equals + 1));
902 this.index = Integer.parseInt(subvalString);
904 } catch (NumberFormatException e)
906 Console.warn("Failed to obtain subvalue or index from '"
907 + item + "'. Setting index=0 and using content='"
921 public boolean notSet()
923 // notSet is true if content present but nonsensical
924 return index == NOTSET && subVals == null;
927 public String get(String key)
929 return subVals == null ? null : subVals.get(key);
932 public boolean has(String key)
934 return subVals == null ? false : subVals.containsKey(key);
937 public int getIndex()
942 public String getContent()
949 * Helper class to allow easy extraction of information about specific
950 * argument values (without having to check for null etc all the time)
952 protected static class ArgValuesMap
954 protected Map<Arg, ArgValues> m;
956 protected ArgValuesMap()
961 protected ArgValuesMap(Map<Arg, ArgValues> map)
966 private Map<Arg, ArgValues> getMap()
971 private void newMap()
973 m = new HashMap<Arg, ArgValues>();
976 private void newArg(Arg a)
981 m.put(a, new ArgValues(a));
984 protected void addArgValue(Arg a, ArgValue av)
986 if (getMap() == null)
987 m = new HashMap<Arg, ArgValues>();
989 if (!m.containsKey(a))
990 m.put(a, new ArgValues(a));
991 ArgValues avs = m.get(a);
995 protected ArgValues getArgValues(Arg a)
997 return m == null ? null : m.get(a);
1000 protected ArgValues getOrCreateArgValues(Arg a)
1002 ArgValues avs = m.get(a);
1005 return getArgValues(a);
1008 protected List<ArgValue> getArgValueList(Arg a)
1010 ArgValues avs = getArgValues(a);
1011 return avs == null ? new ArrayList<>() : avs.getArgValueList();
1014 protected ArgValue getArgValue(Arg a)
1016 List<ArgValue> vals = getArgValueList(a);
1017 return (vals == null || vals.size() == 0) ? null : vals.get(0);
1020 protected String getValue(Arg a)
1022 ArgValue av = getArgValue(a);
1023 return av == null ? null : av.getValue();
1026 protected boolean containsArg(Arg a)
1028 if (m == null || !m.containsKey(a))
1030 return a.hasOption(Opt.STRING) ? getArgValue(a) != null
1031 : this.getBoolean(a);
1034 protected boolean hasValue(Arg a, String val)
1036 if (m == null || !m.containsKey(a))
1038 for (ArgValue av : getArgValueList(a))
1040 String avVal = av.getValue();
1041 if ((val == null && avVal == null)
1042 || (val != null && val.equals(avVal)))
1050 protected boolean getBoolean(Arg a)
1052 ArgValues av = getArgValues(a);
1053 return av == null ? false : av.getBoolean();
1056 protected Set<Arg> getArgKeys()
1061 protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
1064 ArgValue closestAv = null;
1065 int thisArgIndex = thisAv.getArgIndex();
1066 ArgValues compareAvs = this.getArgValues(a);
1067 int closestPreviousIndex = -1;
1068 for (ArgValue av : compareAvs.getArgValueList())
1070 int argIndex = av.getArgIndex();
1071 if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
1073 closestPreviousIndex = argIndex;
1080 protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
1082 // this looks for the *next* arg that *might* be referring back to
1083 // a thisAv. Such an arg would have no subValues (if it does it should
1084 // specify an id in the subValues so wouldn't need to be guessed).
1085 ArgValue closestAv = null;
1086 int thisArgIndex = thisAv.getArgIndex();
1087 ArgValues compareAvs = this.getArgValues(a);
1088 int closestNextIndex = Integer.MAX_VALUE;
1089 for (ArgValue av : compareAvs.getArgValueList())
1091 int argIndex = av.getArgIndex();
1092 if (argIndex > thisArgIndex && argIndex < closestNextIndex)
1094 closestNextIndex = argIndex;
1101 protected ArgValue[] getArgValuesReferringTo(String key, String value,
1104 // this looks for the *next* arg that *might* be referring back to
1105 // a thisAv. Such an arg would have no subValues (if it does it should
1106 // specify an id in the subValues so wouldn't need to be guessed).
1107 List<ArgValue> avList = new ArrayList<>();
1108 Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
1111 for (Arg keyArg : args)
1113 for (ArgValue av : this.getArgValueList(keyArg))
1118 return (ArgValue[]) avList.toArray();
1121 protected boolean hasId(Arg a, String id)
1123 ArgValues avs = this.getArgValues(a);
1124 return avs == null ? false : avs.hasId(id);
1127 protected ArgValue getId(Arg a, String id)
1129 ArgValues avs = this.getArgValues(a);
1130 return avs == null ? null : avs.getId(id);
1134 public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
1136 List<File> argFiles = new ArrayList<>();
1138 for (String pattern : argFilenameGlobs)
1140 // I don't think we want to dedup files, making life easier
1141 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1144 return parseArgFileList(argFiles);
1147 public static ArgParser parseArgFileList(List<File> argFiles)
1149 List<String> argsList = new ArrayList<>();
1150 for (File argFile : argFiles)
1152 if (!argFile.exists())
1154 System.err.println(DOUBLEDASH
1155 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1156 + argFile.getPath() + "\": File does not exist.");
1161 argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
1162 } catch (IOException e)
1164 System.err.println(DOUBLEDASH
1165 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1166 + argFile.getPath() + "\": File could not be read.");
1170 return new ArgParser(argsList);
1173 public static class BootstrapArgs
1176 private static Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
1178 public static BootstrapArgs getBootstrapArgs(String[] args)
1180 List<String> argList = new ArrayList<>(Arrays.asList(args));
1181 return new BootstrapArgs(argList);
1184 private BootstrapArgs(List<String> args)
1189 private void init(List<String> args)
1193 for (int i = 0; i < args.size(); i++)
1195 String arg = args.get(i);
1196 String argName = null;
1198 if (arg.startsWith(ArgParser.DOUBLEDASH))
1200 int equalPos = arg.indexOf('=');
1203 argName = arg.substring(ArgParser.DOUBLEDASH.length(),
1205 val = arg.substring(equalPos + 1);
1209 argName = arg.substring(ArgParser.DOUBLEDASH.length());
1213 Arg a = argMap.get(argName);
1215 if (a == null || !a.hasOption(Opt.BOOTSTRAP))
1217 // not a valid bootstrap arg
1221 if (a.hasOption(Opt.STRING))
1225 addAll(a, ArgParser.getShellGlobbedFilenameValues(a, args,
1230 if (a.hasOption(Opt.GLOB))
1231 addAll(a, FileUtils.getFilenamesFromGlob(val));
1244 public boolean contains(Arg a)
1246 return bootstrapArgMap.containsKey(a);
1249 public List<String> getList(Arg a)
1251 return bootstrapArgMap.get(a);
1254 private List<String> getOrCreateList(Arg a)
1256 List<String> l = getList(a);
1259 l = new ArrayList<>();
1265 private void putList(Arg a, List<String> l)
1267 bootstrapArgMap.put(a, l);
1271 * Creates a new list if not used before,
1272 * adds the value unless the existing list is non-empty
1273 * and the arg is not MULTI (so first expressed value is
1276 private void add(Arg a, String s)
1278 List<String> l = getOrCreateList(a);
1279 if (a.hasOption(Opt.MULTI) || l.size() == 0)
1285 private void addAll(Arg a, List<String> al)
1287 List<String> l = getOrCreateList(a);
1288 if (a.hasOption(Opt.MULTI))
1295 * Retrieves the first value even if MULTI.
1296 * A convenience for non-MULTI args.
1298 public String get(Arg a)
1300 if (!bootstrapArgMap.containsKey(a))
1302 List<String> aL = bootstrapArgMap.get(a);
1303 return (aL == null || aL.size() == 0) ? null : aL.get(0);