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 // flag to say whether {n} subtitutions in output filenames should be made.
61 // Turn on and off with --subs and --nosubs
62 private boolean substitutions = false;
64 private static enum Opt
66 BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
67 GLOB, NOACTION, ALLOWSUBSTITUTIONS
73 NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
74 FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
75 NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
76 OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
79 HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
80 COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
81 ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
82 USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
83 VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
84 TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
85 NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
86 ARGFILE, INCREMENT, NPP("n++"), SUBSTITUTIONS, NIL;
90 HELP.setOptions(Opt.UNARY);
91 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
92 // expecting "--nocalculation"
93 MENUBAR.setOptions(true, Opt.BOOLEAN);
94 STATUS.setOptions(true, Opt.BOOLEAN);
95 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
96 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
97 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
98 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
99 Opt.ALLOWSUBSTITUTIONS);
100 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
101 Opt.ALLOWSUBSTITUTIONS);
102 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
103 HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
104 JABAWS.setOptions(Opt.STRING);
105 ANNOTATION.setOptions(true, Opt.BOOLEAN, Opt.LINKED);
106 ANNOTATION2.setOptions(true, Opt.BOOLEAN, Opt.LINKED);
107 DISPLAY.setOptions(true, Opt.BOOLEAN);
108 GUI.setOptions(true, Opt.BOOLEAN);
109 NEWS.setOptions(true, Opt.BOOLEAN);
110 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
111 // expects a string value
112 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
113 USAGESTATS.setOptions(true, Opt.BOOLEAN);
114 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
115 Opt.ALLOWSUBSTITUTIONS);
116 OPEN2.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
117 PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
118 QUESTIONNAIRE.setOptions(Opt.STRING);
119 SETPROP.setOptions(Opt.STRING);
120 TREE.setOptions(Opt.STRING);
122 VDOC.setOptions(Opt.UNARY);
123 VSESS.setOptions(Opt.UNARY);
125 OUTPUT.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
126 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
128 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
129 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
130 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
131 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
132 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
133 TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
134 TITLE.setOptions(Opt.STRING, Opt.LINKED);
135 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
136 Opt.ALLOWSUBSTITUTIONS);
137 NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
138 STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
139 Opt.ALLOWSUBSTITUTIONS);
140 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
141 IMAGE.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
142 QUIT.setOptions(Opt.UNARY);
143 CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
144 DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
145 QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
146 ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
147 Opt.ALLOWSUBSTITUTIONS);
148 INCREMENT.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
149 NPP.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
150 SUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION);
151 NIL.setOptions(Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION);
152 // BOOTSTRAP args are parsed before a full parse of arguments and
153 // so are accessible at an earlier stage to (e.g.) set debug log level,
154 // provide a props file (that might set log level), run headlessly, read
155 // an argfile instead of other args.
158 private final String[] argNames;
160 private Opt[] argOptions;
162 private boolean defaultBoolValue = false;
164 public String toLongString()
166 StringBuilder sb = new StringBuilder();
167 sb.append("Arg: ").append(this.name());
168 for (String name : getNames())
170 sb.append(", '").append(name).append("'");
172 sb.append("\nOptions: ");
173 boolean first = true;
174 for (Opt o : argOptions)
180 sb.append(o.toString());
184 return sb.toString();
192 private Arg(String... names)
194 int length = (names == null || names.length == 0
195 || (names.length == 1 && names[0] == null)) ? 1
197 this.argNames = new String[length];
198 this.argNames[0] = this.getName();
200 System.arraycopy(names, 0, this.argNames, 1, names.length);
203 public String[] getNames()
208 public String getName()
210 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
214 public final String toString()
219 public boolean hasOption(Opt o)
221 if (argOptions == null)
223 for (Opt option : argOptions)
231 protected void setOptions(Opt... options)
233 setOptions(false, options);
236 protected void setOptions(boolean defaultBoolValue, Opt... options)
238 this.defaultBoolValue = defaultBoolValue;
239 argOptions = options;
242 protected boolean getDefaultBoolValue()
244 return defaultBoolValue;
248 public static class ArgValues
250 private static final String ID = "id";
254 private int argCount = 0;
256 private boolean boolValue = false;
258 private boolean negated = false;
260 private int boolIndex = -1;
262 private List<Integer> argsIndexes;
264 private List<ArgValue> argValueList;
266 private Map<String, ArgValue> idMap = new HashMap<>();
268 protected ArgValues(Arg a)
271 this.argValueList = new ArrayList<ArgValue>();
272 this.boolValue = arg.getDefaultBoolValue();
280 protected int getCount()
285 protected void incrementCount()
290 protected void setNegated(boolean b)
295 protected boolean isNegated()
300 protected void setBoolean(boolean b, int i)
306 protected boolean getBoolean()
308 return this.boolValue;
312 public String toString()
314 if (argValueList == null)
316 StringBuilder sb = new StringBuilder();
317 sb.append(arg.toLongString());
318 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
319 sb.append("Boolean: ").append(boolValue).append("; Default: ")
320 .append(arg.getDefaultBoolValue()).append("; Negated: ")
321 .append(negated).append("\n");
322 if (arg.hasOption(Opt.STRING))
324 sb.append("Values:");
325 boolean first = true;
326 for (ArgValue av : argValueList)
328 String v = av.getValue();
332 sb.append(v).append("'");
337 sb.append("Count: ").append(argCount).append("\n");
338 return sb.toString();
341 protected void addValue()
346 protected void addValue(String val, int argIndex)
348 addArgValue(new ArgValue(val, argIndex));
351 protected void addArgValue(ArgValue av)
353 if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
354 || (arg.hasOption(Opt.NODUPLICATEVALUES)
355 && argValueList.contains(av.getValue())))
357 if (argValueList == null)
359 argValueList = new ArrayList<ArgValue>();
361 SubVals sv = ArgParser.getSubVals(av.getValue());
364 String id = sv.get(ID);
368 argValueList.add(av);
371 protected boolean hasValue(String val)
373 return argValueList.contains(val);
376 protected ArgValue getArgValue()
378 if (arg.hasOption(Opt.MULTI))
379 Console.warn("Requesting single value for multi value argument");
380 return argValueList.size() > 0 ? argValueList.get(0) : null;
383 protected List<ArgValue> getArgValueList()
388 protected boolean hasId(String id)
390 return idMap.containsKey(id);
393 protected ArgValue getId(String id)
395 return idMap.get(id);
400 private List<String> vargs = null;
402 private boolean isApplet;
404 // private AppletParams appletParams;
406 public boolean isApplet()
411 public String nextValue()
413 return vargs.remove(0);
421 public String getValue(String arg)
423 return getValue(arg, false);
426 public String getValue(String arg, boolean utf8decode)
428 int index = vargs.indexOf(arg);
433 ret = vargs.get(index + 1).toString();
436 if (utf8decode && ret != null)
440 dc = URLDecoder.decode(ret, "UTF-8");
442 } catch (Exception e)
444 // TODO: log failure to decode
452 private static final Map<String, Arg> argMap;
454 private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
456 private List<String> linkedOrder = null;
458 private List<Arg> argList;
462 argMap = new HashMap<>();
463 for (Arg a : EnumSet.allOf(Arg.class))
465 for (String argName : a.getNames())
467 if (argMap.containsKey(argName))
469 Console.warn("Trying to add argument name multiple times: '"
470 + argName + "'"); // RESTORE THIS WHEN MERGED
471 if (argMap.get(argName) != a)
474 "Trying to add argument name multiple times for different Args: '"
475 + argMap.get(argName).getName() + ":" + argName
476 + "' and '" + a.getName() + ":" + argName
481 argMap.put(argName, a);
486 public ArgParser(String[] args)
488 // make a mutable new ArrayList so that shell globbing parser works
489 this(new ArrayList<>(Arrays.asList(args)));
492 public ArgParser(List<String> args)
497 private void init(List<String> args)
499 // Enumeration<String> argE = Collections.enumeration(args);
501 // while (argE.hasMoreElements())
502 for (int i = 0; i < args.size(); i++)
504 // String arg = argE.nextElement();
505 String arg = args.get(i);
506 String argName = null;
508 List<String> vals = null; // for Opt.GLOB only
509 String linkedId = null;
510 if (arg.startsWith(DOUBLEDASH))
512 int equalPos = arg.indexOf('=');
515 argName = arg.substring(DOUBLEDASH.length(), equalPos);
516 val = arg.substring(equalPos + 1);
520 argName = arg.substring(DOUBLEDASH.length());
522 int idOpen = argName.indexOf('[');
523 int idClose = argName.indexOf(']');
525 if (idOpen > -1 && idClose == argName.length() - 1)
527 linkedId = argName.substring(idOpen + 1, idClose);
528 argName = argName.substring(0, idOpen);
531 Arg a = argMap.get(argName);
532 // check for boolean prepended by "no"
533 boolean negated = false;
534 if (a == null && argName.startsWith(NEGATESTRING) && argMap
535 .containsKey(argName.substring(NEGATESTRING.length())))
537 argName = argName.substring(NEGATESTRING.length());
538 a = argMap.get(argName);
542 // check for config errors
546 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
549 if (!a.hasOption(Opt.BOOLEAN) && negated)
551 // used "no" with a non-boolean option
552 Console.error("Argument '--" + NEGATESTRING + argName
553 + "' not a boolean option. Ignoring.");
556 if (!a.hasOption(Opt.STRING) && equalPos > -1)
558 // set --argname=value when arg does not accept values
559 Console.error("Argument '--" + argName
560 + "' does not expect a value (given as '" + arg
564 if (!a.hasOption(Opt.LINKED) && linkedId != null)
566 // set --argname[linkedId] when arg does not use linkedIds
567 Console.error("Argument '--" + argName
568 + "' does not expect a linked id (given as '" + arg
573 if (a.hasOption(Opt.STRING) && equalPos == -1)
575 // take next arg as value if required, and '=' was not found
576 // if (!argE.hasMoreElements())
577 if (i + 1 >= args.size())
579 // no value to take for arg, which wants a value
580 Console.error("Argument '" + a.getName()
581 + "' requires a value, none given. Ignoring.");
584 // deal with bash globs here (--arg val* is expanded before reaching
585 // the JVM). Note that SubVals cannot be used in this case.
586 // If using the --arg=val then the glob is preserved and Java globs
587 // will be used later. SubVals can be used.
588 if (a.hasOption(Opt.GLOB))
590 vals.addAll(getShellGlobbedFilenameValues(a, args, i + 1));
594 val = args.get(i + 1);
598 // make NOACTION adjustments
599 // default and auto counter increments
600 if (a == Arg.INCREMENT)
602 defaultLinkedIdCounter++;
604 else if (a == Arg.NPP)
608 else if (a == Arg.SUBSTITUTIONS)
610 substitutions = !negated;
613 String autoCounterString = null;
614 boolean usingAutoCounterLinkedId = false;
615 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
616 .append(Integer.toString(defaultLinkedIdCounter))
618 boolean usingDefaultLinkedId = false;
619 if (a.hasOption(Opt.LINKED))
621 if (linkedId == null)
623 // use default linkedId for linked arguments
624 linkedId = defaultLinkedId;
625 usingDefaultLinkedId = true;
627 "Changing linkedId to '" + linkedId + "' from " + arg);
629 else if (linkedId.equals(AUTOCOUNTERLINKEDID))
631 // turn {n} to the autoCounter
632 autoCounterString = Integer.toString(idCounter);
633 linkedId = autoCounterString;
634 usingAutoCounterLinkedId = true;
636 "Changing linkedId to '" + linkedId + "' from " + arg);
638 else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID))
640 // turn {++n} to the incremented autoCounter
641 autoCounterString = Integer.toString(++idCounter);
642 linkedId = autoCounterString;
643 usingAutoCounterLinkedId = true;
645 "Changing linkedId to '" + linkedId + "' from " + arg);
649 if (!linkedArgs.containsKey(linkedId))
650 linkedArgs.put(linkedId, new ArgValuesMap());
652 // do not continue for NOACTION args
653 if (a.hasOption(Opt.NOACTION))
656 ArgValuesMap avm = linkedArgs.get(linkedId);
658 // not dealing with both NODUPLICATEVALUES and GLOB
659 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
661 Console.error("Argument '--" + argName
662 + "' cannot contain a duplicate value ('" + val
663 + "'). Ignoring this and subsequent occurrences.");
667 // check for unique id
668 SubVals sv = ArgParser.getSubVals(val);
669 String id = sv.get(ArgValues.ID);
670 if (id != null && avm.hasId(a, id))
672 Console.error("Argument '--" + argName + "' has a duplicate id ('"
673 + id + "'). Ignoring.");
677 ArgValues avs = avm.getOrCreateArgValues(a);
680 avs = new ArgValues(a);
682 // store appropriate value
683 if (a.hasOption(Opt.STRING))
685 if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
687 for (String v : vals)
689 avs.addValue(makeSubstitutions(v), argIndex++);
694 avs.addValue(makeSubstitutions(val), argIndex);
697 else if (a.hasOption(Opt.BOOLEAN))
699 avs.setBoolean(!negated, argIndex);
700 avs.setNegated(negated);
702 else if (a.hasOption(Opt.UNARY))
704 avs.setBoolean(true, argIndex);
706 avs.incrementCount();
708 // store in appropriate place
709 if (a.hasOption(Opt.LINKED))
711 // allow a default linked id for single usage
712 if (linkedId == null)
713 linkedId = defaultLinkedId;
714 // store the order of linkedIds
715 if (linkedOrder == null)
716 linkedOrder = new ArrayList<>();
717 if (!linkedOrder.contains(linkedId))
718 linkedOrder.add(linkedId);
721 // store arg in the list of args used
723 argList = new ArrayList<>();
724 if (!argList.contains(a))
730 private String makeSubstitutions(String val)
732 if (!this.substitutions)
737 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
739 int closeBracket = val.indexOf(']');
740 if (val.length() == closeBracket)
742 subvals = val.substring(0, closeBracket + 1);
743 rest = val.substring(closeBracket + 1);
750 rest.replace(AUTOCOUNTERLINKEDID, String.valueOf(idCounter));
751 rest.replace(INCREMENTAUTOCOUNTERLINKEDID, String.valueOf(++idCounter));
752 rest.replace("{}", String.valueOf(defaultLinkedIdCounter));
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
761 * List<String> {"file1", "file2", "file3"}
762 * *and remove these from the original list object* so that processing
763 * can continue from where it has left off, e.g. args has become
764 * {"--previousargs", "--arg", "--otheroptionsornot"}
765 * so the next increment carries on from the next --arg if available.
767 private static List<String> getShellGlobbedFilenameValues(Arg a,
768 List<String> args, int i)
770 List<String> vals = new ArrayList<>();
771 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
773 vals.add(args.remove(i));
774 if (!a.hasOption(Opt.GLOB))
780 public boolean isSet(Arg a)
782 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
785 public boolean isSet(String linkedId, Arg a)
787 ArgValuesMap avm = linkedArgs.get(linkedId);
788 return avm == null ? false : avm.containsArg(a);
791 public boolean getBool(Arg a)
793 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
795 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
798 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
801 public boolean getBool(String linkedId, Arg a)
803 ArgValuesMap avm = linkedArgs.get(linkedId);
805 return a.getDefaultBoolValue();
806 ArgValues avs = avm.getArgValues(a);
807 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
810 public List<String> linkedIds()
815 public ArgValuesMap linkedArgs(String id)
817 return linkedArgs.get(id);
821 public String toString()
823 StringBuilder sb = new StringBuilder();
824 sb.append("UNLINKED\n");
825 sb.append(argValuesMapToString(linkedArgs.get(null)));
826 if (linkedIds() != null)
828 sb.append("LINKED\n");
829 for (String id : linkedIds())
831 // already listed these as UNLINKED args
835 ArgValuesMap avm = linkedArgs(id);
836 sb.append("ID: '").append(id).append("'\n");
837 sb.append(argValuesMapToString(avm));
840 return sb.toString();
843 private static String argValuesMapToString(ArgValuesMap avm)
847 StringBuilder sb = new StringBuilder();
848 for (Arg a : avm.getArgKeys())
850 ArgValues v = avm.getArgValues(a);
851 sb.append(v.toString());
854 return sb.toString();
857 public static SubVals getSubVals(String item)
859 return new SubVals(item);
863 * A helper class to keep an index of argument position with argument values
865 public static class ArgValue
867 private int argIndex;
869 private String value;
873 protected ArgValue(String value, int argIndex)
876 this.argIndex = argIndex;
879 protected String getValue()
884 protected int getArgIndex()
889 protected void setId(String i)
894 protected String getId()
901 * A helper class to parse a string of the possible forms "content"
902 * "[index]content", "[keyName=keyValue]content" and return the integer index,
903 * the strings keyName and keyValue, and the content after the square brackets
904 * (if present). Values not set `will be -1 or null.
906 public static class SubVals
908 private static int NOTSET = -1;
910 private int index = NOTSET;
912 private Map<String, String> subVals = null;
914 private static char SEPARATOR = ';';
916 private String content = null;
918 public SubVals(String item)
920 this.parseVals(item);
923 public void parseVals(String item)
927 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
930 int closeBracket = item.indexOf(']');
931 String subvalsString = item.substring(openBracket + 1,
933 this.content = item.substring(closeBracket + 1);
934 boolean setIndex = false;
935 for (String subvalString : subvalsString
936 .split(Character.toString(SEPARATOR)))
938 int equals = subvalString.indexOf('=');
942 subVals = new HashMap<>();
943 subVals.put(subvalString.substring(0, equals),
944 subvalString.substring(equals + 1));
950 this.index = Integer.parseInt(subvalString);
952 } catch (NumberFormatException e)
954 Console.warn("Failed to obtain subvalue or index from '"
955 + item + "'. Setting index=0 and using content='"
969 public boolean notSet()
971 // notSet is true if content present but nonsensical
972 return index == NOTSET && subVals == null;
975 public String get(String key)
977 return subVals == null ? null : subVals.get(key);
980 public boolean has(String key)
982 return subVals == null ? false : subVals.containsKey(key);
985 public int getIndex()
990 public String getContent()
997 * Helper class to allow easy extraction of information about specific
998 * argument values (without having to check for null etc all the time)
1000 protected static class ArgValuesMap
1002 protected Map<Arg, ArgValues> m;
1004 protected ArgValuesMap()
1009 protected ArgValuesMap(Map<Arg, ArgValues> map)
1014 private Map<Arg, ArgValues> getMap()
1019 private void newMap()
1021 m = new HashMap<Arg, ArgValues>();
1024 private void newArg(Arg a)
1028 if (!containsArg(a))
1029 m.put(a, new ArgValues(a));
1032 protected void addArgValue(Arg a, ArgValue av)
1034 if (getMap() == null)
1035 m = new HashMap<Arg, ArgValues>();
1037 if (!m.containsKey(a))
1038 m.put(a, new ArgValues(a));
1039 ArgValues avs = m.get(a);
1040 avs.addArgValue(av);
1043 protected ArgValues getArgValues(Arg a)
1045 return m == null ? null : m.get(a);
1048 protected ArgValues getOrCreateArgValues(Arg a)
1050 ArgValues avs = m.get(a);
1053 return getArgValues(a);
1056 protected List<ArgValue> getArgValueList(Arg a)
1058 ArgValues avs = getArgValues(a);
1059 return avs == null ? new ArrayList<>() : avs.getArgValueList();
1062 protected ArgValue getArgValue(Arg a)
1064 List<ArgValue> vals = getArgValueList(a);
1065 return (vals == null || vals.size() == 0) ? null : vals.get(0);
1068 protected String getValue(Arg a)
1070 ArgValue av = getArgValue(a);
1071 return av == null ? null : av.getValue();
1074 protected boolean containsArg(Arg a)
1076 if (m == null || !m.containsKey(a))
1078 return a.hasOption(Opt.STRING) ? getArgValue(a) != null
1079 : this.getBoolean(a);
1082 protected boolean hasValue(Arg a, String val)
1084 if (m == null || !m.containsKey(a))
1086 for (ArgValue av : getArgValueList(a))
1088 String avVal = av.getValue();
1089 if ((val == null && avVal == null)
1090 || (val != null && val.equals(avVal)))
1098 protected boolean getBoolean(Arg a)
1100 ArgValues av = getArgValues(a);
1101 return av == null ? false : av.getBoolean();
1104 protected Set<Arg> getArgKeys()
1109 protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
1112 ArgValue closestAv = null;
1113 int thisArgIndex = thisAv.getArgIndex();
1114 ArgValues compareAvs = this.getArgValues(a);
1115 int closestPreviousIndex = -1;
1116 for (ArgValue av : compareAvs.getArgValueList())
1118 int argIndex = av.getArgIndex();
1119 if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
1121 closestPreviousIndex = argIndex;
1128 protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
1130 // this looks for the *next* arg that *might* be referring back to
1131 // a thisAv. Such an arg would have no subValues (if it does it should
1132 // specify an id in the subValues so wouldn't need to be guessed).
1133 ArgValue closestAv = null;
1134 int thisArgIndex = thisAv.getArgIndex();
1135 ArgValues compareAvs = this.getArgValues(a);
1136 int closestNextIndex = Integer.MAX_VALUE;
1137 for (ArgValue av : compareAvs.getArgValueList())
1139 int argIndex = av.getArgIndex();
1140 if (argIndex > thisArgIndex && argIndex < closestNextIndex)
1142 closestNextIndex = argIndex;
1149 protected ArgValue[] getArgValuesReferringTo(String key, String value,
1152 // this looks for the *next* arg that *might* be referring back to
1153 // a thisAv. Such an arg would have no subValues (if it does it should
1154 // specify an id in the subValues so wouldn't need to be guessed).
1155 List<ArgValue> avList = new ArrayList<>();
1156 Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
1159 for (Arg keyArg : args)
1161 for (ArgValue av : this.getArgValueList(keyArg))
1166 return (ArgValue[]) avList.toArray();
1169 protected boolean hasId(Arg a, String id)
1171 ArgValues avs = this.getArgValues(a);
1172 return avs == null ? false : avs.hasId(id);
1175 protected ArgValue getId(Arg a, String id)
1177 ArgValues avs = this.getArgValues(a);
1178 return avs == null ? null : avs.getId(id);
1182 public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
1184 List<File> argFiles = new ArrayList<>();
1186 for (String pattern : argFilenameGlobs)
1188 // I don't think we want to dedup files, making life easier
1189 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1192 return parseArgFileList(argFiles);
1195 public static ArgParser parseArgFileList(List<File> argFiles)
1197 List<String> argsList = new ArrayList<>();
1198 for (File argFile : argFiles)
1200 if (!argFile.exists())
1202 System.err.println(DOUBLEDASH
1203 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1204 + argFile.getPath() + "\": File does not exist.");
1209 argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
1210 } catch (IOException e)
1212 System.err.println(DOUBLEDASH
1213 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1214 + argFile.getPath() + "\": File could not be read.");
1218 return new ArgParser(argsList);
1221 public static class BootstrapArgs
1224 private static Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
1226 public static BootstrapArgs getBootstrapArgs(String[] args)
1228 List<String> argList = new ArrayList<>(Arrays.asList(args));
1229 return new BootstrapArgs(argList);
1232 private BootstrapArgs(List<String> args)
1237 private void init(List<String> args)
1241 for (int i = 0; i < args.size(); i++)
1243 String arg = args.get(i);
1244 String argName = null;
1246 if (arg.startsWith(ArgParser.DOUBLEDASH))
1248 int equalPos = arg.indexOf('=');
1251 argName = arg.substring(ArgParser.DOUBLEDASH.length(),
1253 val = arg.substring(equalPos + 1);
1257 argName = arg.substring(ArgParser.DOUBLEDASH.length());
1261 Arg a = argMap.get(argName);
1263 if (a == null || !a.hasOption(Opt.BOOTSTRAP))
1265 // not a valid bootstrap arg
1269 if (a.hasOption(Opt.STRING))
1273 addAll(a, ArgParser.getShellGlobbedFilenameValues(a, args,
1278 if (a.hasOption(Opt.GLOB))
1279 addAll(a, FileUtils.getFilenamesFromGlob(val));
1292 public boolean contains(Arg a)
1294 return bootstrapArgMap.containsKey(a);
1297 public List<String> getList(Arg a)
1299 return bootstrapArgMap.get(a);
1302 private List<String> getOrCreateList(Arg a)
1304 List<String> l = getList(a);
1307 l = new ArrayList<>();
1313 private void putList(Arg a, List<String> l)
1315 bootstrapArgMap.put(a, l);
1319 * Creates a new list if not used before,
1320 * adds the value unless the existing list is non-empty
1321 * and the arg is not MULTI (so first expressed value is
1324 private void add(Arg a, String s)
1326 List<String> l = getOrCreateList(a);
1327 if (a.hasOption(Opt.MULTI) || l.size() == 0)
1333 private void addAll(Arg a, List<String> al)
1335 List<String> l = getOrCreateList(a);
1336 if (a.hasOption(Opt.MULTI))
1343 * Retrieves the first value even if MULTI.
1344 * A convenience for non-MULTI args.
1346 public String get(Arg a)
1348 if (!bootstrapArgMap.containsKey(a))
1350 List<String> aL = bootstrapArgMap.get(a);
1351 return (aL == null || aL.size() == 0) ? null : aL.get(0);