*/
package jalview.bin;
+import java.io.File;
+import java.io.IOException;
import java.net.URLDecoder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.EnumSet;
-import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
-import jalview.util.Platform;
+import jalview.util.FileUtils;
public class ArgParser
{
+ private static final String DOUBLEDASH = "--";
+
private static final String NEGATESTRING = "no";
- private static final String DEFAULTLINKEDID = "";
+ // the default linked id prefix used for no id (not even square braces)
+ private static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
+
+ // the counter added to the default linked id prefix
+ private int defaultLinkedIdCounter = 0;
+
+ // the linked id used to increment the idCounter (and use the incremented
+ // value)
+ private static final String INCREMENTAUTOCOUNTERLINKEDID = "{++n}";
+
+ // the linked id used to use the idCounter
+ private static final String AUTOCOUNTERLINKEDID = "{n}";
+
+ private int idCounter = 0;
private static enum Opt
{
- BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED
+ BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
+ GLOB
}
public enum Arg
ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
- TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP;
+ TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
+ NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
+ ARGFILE, INCREMENT, NPP("n++");
static
{
FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
GROUPS.setOptions(Opt.STRING, Opt.LINKED);
- HEADLESS.setOptions(Opt.UNARY);
+ HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
JABAWS.setOptions(Opt.STRING);
ANNOTATION.setOptions(true, Opt.BOOLEAN);
ANNOTATION2.setOptions(true, Opt.BOOLEAN);
// expects a string value
SORTBYTREE.setOptions(true, Opt.BOOLEAN);
USAGESTATS.setOptions(true, Opt.BOOLEAN);
- OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+ OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB);
OPEN2.setOptions(Opt.STRING, Opt.LINKED);
- PROPS.setOptions(Opt.STRING);
+ PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
QUESTIONNAIRE.setOptions(Opt.STRING);
SETPROP.setOptions(Opt.STRING);
TREE.setOptions(Opt.STRING);
TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
- TEMPFAC_SHADING.setOptions(Opt.STRING, Opt.LINKED);
+ TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
TITLE.setOptions(Opt.STRING, Opt.LINKED);
- PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED);
+ PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+ NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
+ STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
+ IMAGE.setOptions(Opt.STRING, Opt.LINKED);
+ QUIT.setOptions(Opt.UNARY);
+ CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
+ DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
+ QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
+ ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB);
+ INCREMENT.setOptions(Opt.UNARY, Opt.MULTI);
+ NPP.setOptions(Opt.UNARY, Opt.MULTI);
+ // BOOTSTRAP args are parsed before a full parse of arguments and
+ // so are accessible at an earlier stage to (e.g.) set debug log level,
+ // provide a props file (that might set log level), run headlessly, read
+ // an argfile instead of other args.
}
private final String[] argNames;
public static class ArgValues
{
+ private static final String ID = "id";
+
private Arg arg;
private int argCount = 0;
private boolean negated = false;
- private List<String> argsList;
+ private int boolIndex = -1;
+
+ private List<Integer> argsIndexes;
+
+ private List<ArgValue> argValueList;
+
+ private Map<String, ArgValue> idMap = new HashMap<>();
protected ArgValues(Arg a)
{
this.arg = a;
- this.argsList = new ArrayList<String>();
+ this.argValueList = new ArrayList<ArgValue>();
this.boolValue = arg.getDefaultBoolValue();
}
return this.negated;
}
- protected void setBoolean(boolean b)
+ protected void setBoolean(boolean b, int i)
{
this.boolValue = b;
+ this.boolIndex = i;
}
protected boolean getBoolean()
@Override
public String toString()
{
- if (argsList == null)
+ if (argValueList == null)
return null;
StringBuilder sb = new StringBuilder();
sb.append(arg.toLongString());
{
sb.append("Values:");
boolean first = true;
- for (String v : argsList)
+ for (ArgValue av : argValueList)
{
+ String v = av.getValue();
if (!first)
sb.append(",");
sb.append("\n '");
protected void addValue()
{
- addValue(null);
+ addValue(null, -1);
}
- protected void addValue(String val)
+ protected void addValue(String val, int argIndex)
{
- addValue(val, false);
+ addArgValue(new ArgValue(val, argIndex));
}
- protected void addValue(String val, boolean noDuplicates)
+ protected void addArgValue(ArgValue av)
{
- if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
- || (noDuplicates && argsList.contains(val)))
+ if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
+ || (arg.hasOption(Opt.NODUPLICATEVALUES)
+ && argValueList.contains(av.getValue())))
return;
- if (argsList == null)
+ if (argValueList == null)
+ {
+ argValueList = new ArrayList<ArgValue>();
+ }
+ SubVals sv = ArgParser.getSubVals(av.getValue());
+ if (sv.has(ID))
{
- Console.warn("** inst");
- argsList = new ArrayList<String>();
+ String id = sv.get(ID);
+ av.setId(id);
+ idMap.put(id, av);
}
- argsList.add(val);
+ argValueList.add(av);
}
protected boolean hasValue(String val)
{
- return argsList.contains(val);
+ return argValueList.contains(val);
}
- protected String getValue()
+ protected ArgValue getArgValue()
{
if (arg.hasOption(Opt.MULTI))
Console.warn("Requesting single value for multi value argument");
- return argsList.size() > 0 ? argsList.get(0) : null;
+ return argValueList.size() > 0 ? argValueList.get(0) : null;
+ }
+
+ protected List<ArgValue> getArgValueList()
+ {
+ return argValueList;
}
- protected List<String> getValues()
+ protected boolean hasId(String id)
{
- return argsList;
+ return idMap.containsKey(id);
+ }
+
+ protected ArgValue getId(String id)
+ {
+ return idMap.get(id);
}
}
public String getValue(String arg, boolean utf8decode)
{
int index = vargs.indexOf(arg);
- String dc = null, ret = null;
+ String dc = null;
+ String ret = null;
if (index != -1)
{
ret = vargs.get(index + 1).toString();
return ret;
}
- /*
- public Object getAppletValue(String key, String def, boolean asString)
- {
- Object value;
- return (appletParams == null ? null
- : (value = appletParams.get(key.toLowerCase())) == null ? def
- : asString ? "" + value : value);
- }
- */
-
// new style
private static final Map<String, Arg> argMap;
- private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
+ private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
private List<String> linkedOrder = null;
argMap = new HashMap<>();
for (Arg a : EnumSet.allOf(Arg.class))
{
- ARGNAME: for (String argName : a.getNames())
+ for (String argName : a.getNames())
{
if (argMap.containsKey(argName))
{
+ "' and '" + a.getName() + ":" + argName
+ "'");
}
- continue ARGNAME;
+ continue;
}
argMap.put(argName, a);
}
public ArgParser(String[] args)
{
- // old style
- vargs = new ArrayList<>();
- isApplet = (args.length > 0 && args[0].startsWith("<applet"));
- if (isApplet)
- {
- // appletParams = AppletParams.getAppletParams(args, vargs);
- }
- else
- {
- if (Platform.isJS())
+ // make a mutable new ArrayList so that shell globbing parser works
+ this(new ArrayList<>(Arrays.asList(args)));
+ }
- {
- isApplet = true;
- // appletParams =
- // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
- }
- for (int i = 0; i < args.length; i++)
- {
- String arg = args[i].trim();
- if (arg.charAt(0) == '-')
- {
- arg = arg.substring(1);
- }
- vargs.add(arg);
- }
- }
+ public ArgParser(List<String> args)
+ {
+ init(args);
+ }
- // new style
- Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
- ARG: while (argE.hasMoreElements())
+ private void init(List<String> args)
+ {
+ // Enumeration<String> argE = Collections.enumeration(args);
+ int argIndex = 0;
+ // while (argE.hasMoreElements())
+ for (int i = 0; i < args.size(); i++)
{
- String arg = argE.nextElement();
+ // String arg = argE.nextElement();
+ String arg = args.get(i);
String argName = null;
String val = null;
+ List<String> vals = null; // for Opt.GLOB only
String linkedId = null;
- if (arg.startsWith("--"))
+ if (arg.startsWith(DOUBLEDASH))
{
int equalPos = arg.indexOf('=');
if (equalPos > -1)
{
- argName = arg.substring(2, equalPos);
+ argName = arg.substring(DOUBLEDASH.length(), equalPos);
val = arg.substring(equalPos + 1);
}
else
{
- argName = arg.substring(2);
+ argName = arg.substring(DOUBLEDASH.length());
}
int idOpen = argName.indexOf('[');
int idClose = argName.indexOf(']');
{
// arg not found
Console.error("Argument '" + arg + "' not recognised. Ignoring.");
- continue ARG;
+ continue;
}
if (!a.hasOption(Opt.BOOLEAN) && negated)
{
// used "no" with a non-boolean option
Console.error("Argument '--" + NEGATESTRING + argName
+ "' not a boolean option. Ignoring.");
- continue ARG;
+ continue;
}
if (!a.hasOption(Opt.STRING) && equalPos > -1)
{
Console.error("Argument '--" + argName
+ "' does not expect a value (given as '" + arg
+ "'). Ignoring.");
- continue ARG;
+ continue;
}
if (!a.hasOption(Opt.LINKED) && linkedId != null)
{
Console.error("Argument '--" + argName
+ "' does not expect a linked id (given as '" + arg
+ "'). Ignoring.");
- continue ARG;
+ continue;
}
if (a.hasOption(Opt.STRING) && equalPos == -1)
{
// take next arg as value if required, and '=' was not found
- if (!argE.hasMoreElements())
+ // if (!argE.hasMoreElements())
+ if (i + 1 >= args.size())
{
// no value to take for arg, which wants a value
Console.error("Argument '" + a.getName()
+ "' requires a value, none given. Ignoring.");
- continue ARG;
+ continue;
+ }
+ // deal with bash globs here (--arg val* is expanded before reaching
+ // the JVM). Note that SubVals cannot be used in this case.
+ // If using the --arg=val then the glob is preserved and Java globs
+ // will be used later. SubVals can be used.
+ if (a.hasOption(Opt.GLOB))
+ {
+ vals.addAll(getShellGlobbedFilenameValues(a, args, i + 1));
+ }
+ else
+ {
+ val = args.get(i + 1);
}
- val = argE.nextElement();
}
- // use default linkedId for linked arguments
- if (a.hasOption(Opt.LINKED) && linkedId == null)
- linkedId = DEFAULTLINKEDID;
+ // default and auto counter increments
+ if (a == Arg.INCREMENT)
+ {
+ defaultLinkedIdCounter++;
+ continue;
+ }
+ else if (a == Arg.NPP)
+ {
+ idCounter++;
+ continue;
+ }
+
+ String autoCounterString = null;
+ boolean usingAutoCounterLinkedId = false;
+ String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+ .append(Integer.toString(defaultLinkedIdCounter))
+ .toString();
+ boolean usingDefaultLinkedId = false;
+ if (a.hasOption(Opt.LINKED))
+ {
+ if (linkedId == null)
+ {
+ // use default linkedId for linked arguments
+ linkedId = defaultLinkedId;
+ usingDefaultLinkedId = true;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ else if (linkedId.equals(AUTOCOUNTERLINKEDID))
+ {
+ // turn {n} to the autoCounter
+ autoCounterString = Integer.toString(idCounter);
+ linkedId = autoCounterString;
+ usingAutoCounterLinkedId = true;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID))
+ {
+ // turn {++n} to the incremented autoCounter
+ autoCounterString = Integer.toString(++idCounter);
+ linkedId = autoCounterString;
+ usingAutoCounterLinkedId = true;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ }
if (!linkedArgs.containsKey(linkedId))
- linkedArgs.put(linkedId, new HashMap<>());
+ linkedArgs.put(linkedId, new ArgValuesMap());
+
+ ArgValuesMap avm = linkedArgs.get(linkedId);
- Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
- if (!valuesMap.containsKey(a))
- valuesMap.put(a, new ArgValues(a));
+ // not dealing with both NODUPLICATEVALUES and GLOB
+ if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
+ {
+ Console.error("Argument '--" + argName
+ + "' cannot contain a duplicate value ('" + val
+ + "'). Ignoring this and subsequent occurrences.");
+ continue;
+ }
- ArgValues values = valuesMap.get(a);
- if (values == null)
+ // check for unique id
+ SubVals sv = ArgParser.getSubVals(val);
+ String id = sv.get(ArgValues.ID);
+ if (id != null && avm.hasId(a, id))
{
- values = new ArgValues(a);
+ Console.error("Argument '--" + argName + "' has a duplicate id ('"
+ + id + "'). Ignoring.");
+ continue;
+ }
+
+ ArgValues avs = avm.getOrCreateArgValues(a);
+ if (avs == null)
+ {
+ avs = new ArgValues(a);
}
// store appropriate value
if (a.hasOption(Opt.STRING))
{
- values.addValue(val);
+ if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
+ {
+ for (String v : vals)
+ avs.addValue(val, argIndex++);
+ }
+ else
+ {
+ avs.addValue(val, argIndex);
+ }
}
else if (a.hasOption(Opt.BOOLEAN))
{
- values.setBoolean(!negated);
- values.setNegated(negated);
+ avs.setBoolean(!negated, argIndex);
+ avs.setNegated(negated);
}
else if (a.hasOption(Opt.UNARY))
{
- values.setBoolean(true);
+ avs.setBoolean(true, argIndex);
}
- values.incrementCount();
+ avs.incrementCount();
// store in appropriate place
if (a.hasOption(Opt.LINKED))
{
// allow a default linked id for single usage
if (linkedId == null)
- linkedId = DEFAULTLINKEDID;
+ linkedId = defaultLinkedId;
// store the order of linkedIds
if (linkedOrder == null)
linkedOrder = new ArrayList<>();
if (!linkedOrder.contains(linkedId))
linkedOrder.add(linkedId);
}
- // store the ArgValues
- valuesMap.put(a, values);
- // store arg in the list of args
+ // store arg in the list of args used
if (argList == null)
argList = new ArrayList<>();
if (!argList.contains(a))
}
}
+ /*
+ * A helper method to take a list of String args where we're expecting
+ * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
+ * and the index of the globbed arg, here 1. It returns a
+ * List<String> {"file1", "file2", "file3"}
+ * *and remove these from the original list object* so that processing
+ * can continue from where it has left off, e.g. args has become
+ * {"--previousargs", "--arg", "--otheroptionsornot"}
+ * so the next increment carries on from the next --arg if available.
+ */
+ private static List<String> getShellGlobbedFilenameValues(Arg a,
+ List<String> args, int i)
+ {
+ List<String> vals = new ArrayList<>();
+ while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
+ {
+ vals.add(args.remove(i));
+ if (!a.hasOption(Opt.GLOB))
+ break;
+ }
+ return vals;
+ }
+
public boolean isSet(Arg a)
{
return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
public boolean isSet(String linkedId, Arg a)
{
- Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
- return m == null ? false : m.containsKey(a);
+ ArgValuesMap avm = linkedArgs.get(linkedId);
+ return avm == null ? false : avm.containsArg(a);
}
public boolean getBool(Arg a)
{
- if (!a.hasOption(Opt.BOOLEAN))
+ if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
{
Console.warn("Getting boolean from non boolean Arg '" + a.getName()
+ "'.");
public boolean getBool(String linkedId, Arg a)
{
- Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
- if (m == null)
+ ArgValuesMap avm = linkedArgs.get(linkedId);
+ if (avm == null)
return a.getDefaultBoolValue();
- ArgValues v = m.get(a);
- return v == null ? a.getDefaultBoolValue() : v.getBoolean();
+ ArgValues avs = avm.getArgValues(a);
+ return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
}
public List<String> linkedIds()
return linkedOrder;
}
- public HashMap<Arg, ArgValues> linkedArgs(String id)
+ public ArgValuesMap linkedArgs(String id)
{
return linkedArgs.get(id);
}
{
StringBuilder sb = new StringBuilder();
sb.append("UNLINKED\n");
- sb.append(argMapToString(linkedArgs.get(null)));
+ sb.append(argValuesMapToString(linkedArgs.get(null)));
if (linkedIds() != null)
{
sb.append("LINKED\n");
if (id == null)
continue;
- Map<Arg, ArgValues> m = linkedArgs(id);
+ ArgValuesMap avm = linkedArgs(id);
sb.append("ID: '").append(id).append("'\n");
- sb.append(argMapToString(m));
+ sb.append(argValuesMapToString(avm));
}
}
return sb.toString();
}
- private static String argMapToString(Map<Arg, ArgValues> m)
+ private static String argValuesMapToString(ArgValuesMap avm)
{
- if (m == null)
+ if (avm == null)
return null;
StringBuilder sb = new StringBuilder();
- for (Arg a : m.keySet())
+ for (Arg a : avm.getArgKeys())
{
- ArgValues v = m.get(a);
+ ArgValues v = avm.getArgValues(a);
sb.append(v.toString());
sb.append("\n");
}
return sb.toString();
}
+
+ public static SubVals getSubVals(String item)
+ {
+ return new SubVals(item);
+ }
+
+ /**
+ * A helper class to keep an index of argument position with argument values
+ */
+ public static class ArgValue
+ {
+ private int argIndex;
+
+ private String value;
+
+ private String id;
+
+ protected ArgValue(String value, int argIndex)
+ {
+ this.value = value;
+ this.argIndex = argIndex;
+ }
+
+ protected String getValue()
+ {
+ return value;
+ }
+
+ protected int getArgIndex()
+ {
+ return argIndex;
+ }
+
+ protected void setId(String i)
+ {
+ id = i;
+ }
+
+ protected String getId()
+ {
+ return id;
+ }
+ }
+
+ /**
+ * A helper class to parse a string of the possible forms "content"
+ * "[index]content", "[keyName=keyValue]content" and return the integer index,
+ * the strings keyName and keyValue, and the content after the square brackets
+ * (if present). Values not set `will be -1 or null.
+ */
+ public static class SubVals
+ {
+ private static int NOTSET = -1;
+
+ private int index = NOTSET;
+
+ private Map<String, String> subVals = null;
+
+ private static char SEPARATOR = ';';
+
+ private String content = null;
+
+ public SubVals(String item)
+ {
+ this.parseVals(item);
+ }
+
+ public void parseVals(String item)
+ {
+ if (item == null)
+ return;
+ if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
+ {
+ int openBracket = item.indexOf('[');
+ int closeBracket = item.indexOf(']');
+ String subvalsString = item.substring(openBracket + 1,
+ closeBracket);
+ this.content = item.substring(closeBracket + 1);
+ boolean setIndex = false;
+ for (String subvalString : subvalsString
+ .split(Character.toString(SEPARATOR)))
+ {
+ int equals = subvalString.indexOf('=');
+ if (equals > -1)
+ {
+ if (subVals == null)
+ subVals = new HashMap<>();
+ subVals.put(subvalString.substring(0, equals),
+ subvalString.substring(equals + 1));
+ }
+ else
+ {
+ try
+ {
+ this.index = Integer.parseInt(subvalString);
+ setIndex = true;
+ } catch (NumberFormatException e)
+ {
+ Console.warn("Failed to obtain subvalue or index from '"
+ + item + "'. Setting index=0 and using content='"
+ + content + "'.");
+ }
+ }
+ }
+ if (!setIndex)
+ this.index = NOTSET;
+ }
+ else
+ {
+ this.content = item;
+ }
+ }
+
+ public boolean notSet()
+ {
+ // notSet is true if content present but nonsensical
+ 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);
+ }
+ }
}
\ No newline at end of file