+ return index == NOTSET && subVals == null;
+ }
+
+ public String get(String key)
+ {
+ return subVals == null ? null : subVals.get(key);
+ }
+
+ public boolean has(String key)
+ {
+ return subVals == null ? false : subVals.containsKey(key);
+ }
+
+ public int getIndex()
+ {
+ return index;
+ }
+
+ public String getContent()
+ {
+ return content;
+ }
+ }
+
+ /**
+ * Helper class to allow easy extraction of information about specific
+ * argument values (without having to check for null etc all the time)
+ */
+ protected static class ArgValuesMap
+ {
+ protected Map<Arg, ArgValues> m;
+
+ protected ArgValuesMap()
+ {
+ this.newMap();
+ }
+
+ protected ArgValuesMap(Map<Arg, ArgValues> map)
+ {
+ this.m = map;
+ }
+
+ private Map<Arg, ArgValues> getMap()
+ {
+ return m;
+ }
+
+ private void newMap()
+ {
+ m = new HashMap<Arg, ArgValues>();
+ }
+
+ private void newArg(Arg a)
+ {
+ if (m == null)
+ newMap();
+ if (!containsArg(a))
+ m.put(a, new ArgValues(a));
+ }
+
+ protected void addArgValue(Arg a, ArgValue av)
+ {
+ if (getMap() == null)
+ m = new HashMap<Arg, ArgValues>();
+
+ if (!m.containsKey(a))
+ m.put(a, new ArgValues(a));
+ ArgValues avs = m.get(a);
+ avs.addArgValue(av);
+ }
+
+ protected ArgValues getArgValues(Arg a)
+ {
+ return m == null ? null : m.get(a);
+ }
+
+ protected ArgValues getOrCreateArgValues(Arg a)
+ {
+ ArgValues avs = m.get(a);
+ if (avs == null)
+ newArg(a);
+ return getArgValues(a);
+ }
+
+ protected List<ArgValue> getArgValueList(Arg a)
+ {
+ ArgValues avs = getArgValues(a);
+ return avs == null ? new ArrayList<>() : avs.getArgValueList();
+ }
+
+ protected ArgValue getArgValue(Arg a)
+ {
+ List<ArgValue> vals = getArgValueList(a);
+ return (vals == null || vals.size() == 0) ? null : vals.get(0);
+ }
+
+ protected String getValue(Arg a)
+ {
+ ArgValue av = getArgValue(a);
+ return av == null ? null : av.getValue();
+ }
+
+ protected boolean containsArg(Arg a)
+ {
+ if (m == null || !m.containsKey(a))
+ return false;
+ return a.hasOption(Opt.STRING) ? getArgValue(a) != null
+ : this.getBoolean(a);
+ }
+
+ protected boolean hasValue(Arg a, String val)
+ {
+ if (m == null || !m.containsKey(a))
+ return false;
+ for (ArgValue av : getArgValueList(a))
+ {
+ String avVal = av.getValue();
+ if ((val == null && avVal == null)
+ || (val != null && val.equals(avVal)))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean getBoolean(Arg a)
+ {
+ ArgValues av = getArgValues(a);
+ return av == null ? false : av.getBoolean();
+ }
+
+ protected Set<Arg> getArgKeys()
+ {
+ return m.keySet();
+ }
+
+ protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
+ Arg a)
+ {
+ ArgValue closestAv = null;
+ int thisArgIndex = thisAv.getArgIndex();
+ ArgValues compareAvs = this.getArgValues(a);
+ int closestPreviousIndex = -1;
+ for (ArgValue av : compareAvs.getArgValueList())
+ {
+ int argIndex = av.getArgIndex();
+ if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
+ {
+ closestPreviousIndex = argIndex;
+ closestAv = av;
+ }
+ }
+ return closestAv;
+ }
+
+ protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
+ {
+ // this looks for the *next* arg that *might* be referring back to
+ // a thisAv. Such an arg would have no subValues (if it does it should
+ // specify an id in the subValues so wouldn't need to be guessed).
+ ArgValue closestAv = null;
+ int thisArgIndex = thisAv.getArgIndex();
+ ArgValues compareAvs = this.getArgValues(a);
+ int closestNextIndex = Integer.MAX_VALUE;
+ for (ArgValue av : compareAvs.getArgValueList())
+ {
+ int argIndex = av.getArgIndex();
+ if (argIndex > thisArgIndex && argIndex < closestNextIndex)
+ {
+ closestNextIndex = argIndex;
+ closestAv = av;
+ }
+ }
+ return closestAv;
+ }
+
+ protected ArgValue[] getArgValuesReferringTo(String key, String value,
+ Arg a)
+ {
+ // this looks for the *next* arg that *might* be referring back to
+ // a thisAv. Such an arg would have no subValues (if it does it should
+ // specify an id in the subValues so wouldn't need to be guessed).
+ List<ArgValue> avList = new ArrayList<>();
+ Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
+ : new Arg[]
+ { a };
+ for (Arg keyArg : args)
+ {
+ for (ArgValue av : this.getArgValueList(keyArg))
+ {
+
+ }
+ }
+ return (ArgValue[]) avList.toArray();
+ }
+
+ protected boolean hasId(Arg a, String id)
+ {
+ ArgValues avs = this.getArgValues(a);
+ return avs == null ? false : avs.hasId(id);
+ }
+
+ protected ArgValue getId(Arg a, String id)
+ {
+ ArgValues avs = this.getArgValues(a);
+ return avs == null ? null : avs.getId(id);
+ }
+ }
+
+ public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
+ {
+ List<File> argFiles = new ArrayList<>();
+
+ for (String pattern : argFilenameGlobs)
+ {
+ // I don't think we want to dedup files, making life easier
+ argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
+ }
+
+ return parseArgFileList(argFiles);
+ }
+
+ public static ArgParser parseArgFileList(List<File> argFiles)
+ {
+ List<String> argsList = new ArrayList<>();
+ for (File argFile : argFiles)
+ {
+ if (!argFile.exists())
+ {
+ System.err.println(DOUBLEDASH
+ + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
+ + argFile.getPath() + "\": File does not exist.");
+ System.exit(2);
+ }
+ try
+ {
+ argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
+ } catch (IOException e)
+ {
+ System.err.println(DOUBLEDASH
+ + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
+ + argFile.getPath() + "\": File could not be read.");
+ System.exit(3);
+ }
+ }
+ return new ArgParser(argsList);
+ }
+
+ public static class BootstrapArgs
+ {
+ // only need one
+ private static Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
+
+ public static BootstrapArgs getBootstrapArgs(String[] args)
+ {
+ List<String> argList = new ArrayList<>(Arrays.asList(args));
+ return new BootstrapArgs(argList);
+ }
+
+ private BootstrapArgs(List<String> args)
+ {
+ init(args);
+ }
+
+ private void init(List<String> args)
+ {
+ if (args == null)
+ return;
+ for (int i = 0; i < args.size(); i++)
+ {
+ String arg = args.get(i);
+ String argName = null;
+ String val = null;
+ if (arg.startsWith(ArgParser.DOUBLEDASH))
+ {
+ int equalPos = arg.indexOf('=');
+ if (equalPos > -1)
+ {
+ argName = arg.substring(ArgParser.DOUBLEDASH.length(),
+ equalPos);
+ val = arg.substring(equalPos + 1);
+ }
+ else
+ {
+ argName = arg.substring(ArgParser.DOUBLEDASH.length());
+ val = "true";
+ }
+
+ Arg a = argMap.get(argName);
+
+ if (a == null || !a.hasOption(Opt.BOOTSTRAP))
+ {
+ // not a valid bootstrap arg
+ continue;
+ }
+
+ if (a.hasOption(Opt.STRING))
+ {
+ if (equalPos == -1)
+ {
+ addAll(a, ArgParser.getShellGlobbedFilenameValues(a, args,
+ i + 1));
+ }
+ else
+ {
+ if (a.hasOption(Opt.GLOB))
+ addAll(a, FileUtils.getFilenamesFromGlob(val));
+ else
+ add(a, val);
+ }
+ }
+ else
+ {
+ add(a, val);
+ }
+ }
+ }
+ }
+
+ public boolean contains(Arg a)
+ {
+ return bootstrapArgMap.containsKey(a);
+ }
+
+ public List<String> getList(Arg a)
+ {
+ return bootstrapArgMap.get(a);
+ }
+
+ private List<String> getOrCreateList(Arg a)
+ {
+ List<String> l = getList(a);
+ if (l == null)
+ {
+ l = new ArrayList<>();
+ putList(a, l);
+ }
+ return l;
+ }
+
+ private void putList(Arg a, List<String> l)
+ {
+ bootstrapArgMap.put(a, l);
+ }
+
+ /*
+ * Creates a new list if not used before,
+ * adds the value unless the existing list is non-empty
+ * and the arg is not MULTI (so first expressed value is
+ * retained).
+ */
+ private void add(Arg a, String s)
+ {
+ List<String> l = getOrCreateList(a);
+ if (a.hasOption(Opt.MULTI) || l.size() == 0)
+ {
+ l.add(s);
+ }
+ }
+
+ private void addAll(Arg a, List<String> al)
+ {
+ List<String> l = getOrCreateList(a);
+ if (a.hasOption(Opt.MULTI))
+ {
+ l.addAll(al);
+ }
+ }
+
+ /*
+ * Retrieves the first value even if MULTI.
+ * A convenience for non-MULTI args.
+ */
+ public String get(Arg a)
+ {
+ if (!bootstrapArgMap.containsKey(a))
+ return null;
+ List<String> aL = bootstrapArgMap.get(a);
+ return (aL == null || aL.size() == 0) ? null : aL.get(0);