/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
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.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import jalview.util.FileUtils;
public class ArgParser
{
private static final String DOUBLEDASH = "--";
private static final String NEGATESTRING = "no";
// 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;
// flag to say whether {n} subtitutions in output filenames should be made.
// Turn on and off with --subs and --nosubs
private boolean substitutions = false;
private static enum Opt
{
BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
GLOB, NOACTION, ALLOWSUBSTITUTIONS
}
public enum Arg
{
/*
NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
VSESS;
*/
HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
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,
NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
ARGFILE, INCREMENT, NPP("n++"), SUBSTITUTIONS, NIL;
static
{
HELP.setOptions(Opt.UNARY);
CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
// expecting "--nocalculation"
MENUBAR.setOptions(true, Opt.BOOLEAN);
STATUS.setOptions(true, Opt.BOOLEAN);
SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
COLOUR.setOptions(Opt.STRING, Opt.LINKED);
FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
Opt.ALLOWSUBSTITUTIONS);
GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
Opt.ALLOWSUBSTITUTIONS);
GROUPS.setOptions(Opt.STRING, Opt.LINKED);
HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
JABAWS.setOptions(Opt.STRING);
ANNOTATION.setOptions(true, Opt.BOOLEAN, Opt.LINKED);
ANNOTATION2.setOptions(true, Opt.BOOLEAN, Opt.LINKED);
DISPLAY.setOptions(true, Opt.BOOLEAN);
GUI.setOptions(true, Opt.BOOLEAN);
NEWS.setOptions(true, Opt.BOOLEAN);
NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
// expects a string value
SORTBYTREE.setOptions(true, Opt.BOOLEAN);
USAGESTATS.setOptions(true, Opt.BOOLEAN);
OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
Opt.ALLOWSUBSTITUTIONS);
OPEN2.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
QUESTIONNAIRE.setOptions(Opt.STRING);
SETPROP.setOptions(Opt.STRING);
TREE.setOptions(Opt.STRING);
VDOC.setOptions(Opt.UNARY);
VSESS.setOptions(Opt.UNARY);
OUTPUT.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
TITLE.setOptions(Opt.STRING, Opt.LINKED);
PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
Opt.ALLOWSUBSTITUTIONS);
NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
Opt.ALLOWSUBSTITUTIONS);
WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
IMAGE.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
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,
Opt.ALLOWSUBSTITUTIONS);
INCREMENT.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
NPP.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
SUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION);
NIL.setOptions(Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION);
// 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;
private Opt[] argOptions;
private boolean defaultBoolValue = false;
public String toLongString()
{
StringBuilder sb = new StringBuilder();
sb.append("Arg: ").append(this.name());
for (String name : getNames())
{
sb.append(", '").append(name).append("'");
}
sb.append("\nOptions: ");
boolean first = true;
for (Opt o : argOptions)
{
if (!first)
{
sb.append(", ");
}
sb.append(o.toString());
first = false;
}
sb.append("\n");
return sb.toString();
}
private Arg()
{
this(new String[0]);
}
private Arg(String... names)
{
int length = (names == null || names.length == 0
|| (names.length == 1 && names[0] == null)) ? 1
: names.length + 1;
this.argNames = new String[length];
this.argNames[0] = this.getName();
if (length > 1)
System.arraycopy(names, 0, this.argNames, 1, names.length);
}
public String[] getNames()
{
return argNames;
}
public String getName()
{
return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
}
@Override
public final String toString()
{
return getName();
}
public boolean hasOption(Opt o)
{
if (argOptions == null)
return false;
for (Opt option : argOptions)
{
if (o == option)
return true;
}
return false;
}
protected void setOptions(Opt... options)
{
setOptions(false, options);
}
protected void setOptions(boolean defaultBoolValue, Opt... options)
{
this.defaultBoolValue = defaultBoolValue;
argOptions = options;
}
protected boolean getDefaultBoolValue()
{
return defaultBoolValue;
}
}
public static class ArgValues
{
private static final String ID = "id";
private Arg arg;
private int argCount = 0;
private boolean boolValue = false;
private boolean negated = false;
private int boolIndex = -1;
private List argsIndexes;
private List argValueList;
private Map idMap = new HashMap<>();
protected ArgValues(Arg a)
{
this.arg = a;
this.argValueList = new ArrayList();
this.boolValue = arg.getDefaultBoolValue();
}
public Arg arg()
{
return arg;
}
protected int getCount()
{
return argCount;
}
protected void incrementCount()
{
argCount++;
}
protected void setNegated(boolean b)
{
this.negated = b;
}
protected boolean isNegated()
{
return this.negated;
}
protected void setBoolean(boolean b, int i)
{
this.boolValue = b;
this.boolIndex = i;
}
protected boolean getBoolean()
{
return this.boolValue;
}
@Override
public String toString()
{
if (argValueList == null)
return null;
StringBuilder sb = new StringBuilder();
sb.append(arg.toLongString());
if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
sb.append("Boolean: ").append(boolValue).append("; Default: ")
.append(arg.getDefaultBoolValue()).append("; Negated: ")
.append(negated).append("\n");
if (arg.hasOption(Opt.STRING))
{
sb.append("Values:");
boolean first = true;
for (ArgValue av : argValueList)
{
String v = av.getValue();
if (!first)
sb.append(",");
sb.append("\n '");
sb.append(v).append("'");
first = false;
}
sb.append("\n");
}
sb.append("Count: ").append(argCount).append("\n");
return sb.toString();
}
protected void addValue()
{
addValue(null, -1);
}
protected void addValue(String val, int argIndex)
{
addArgValue(new ArgValue(val, argIndex));
}
protected void addArgValue(ArgValue av)
{
if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
|| (arg.hasOption(Opt.NODUPLICATEVALUES)
&& argValueList.contains(av.getValue())))
return;
if (argValueList == null)
{
argValueList = new ArrayList();
}
SubVals sv = ArgParser.getSubVals(av.getValue());
if (sv.has(ID))
{
String id = sv.get(ID);
av.setId(id);
idMap.put(id, av);
}
argValueList.add(av);
}
protected boolean hasValue(String val)
{
return argValueList.contains(val);
}
protected ArgValue getArgValue()
{
if (arg.hasOption(Opt.MULTI))
Console.warn("Requesting single value for multi value argument");
return argValueList.size() > 0 ? argValueList.get(0) : null;
}
protected List getArgValueList()
{
return argValueList;
}
protected boolean hasId(String id)
{
return idMap.containsKey(id);
}
protected ArgValue getId(String id)
{
return idMap.get(id);
}
}
// old style
private List vargs = null;
private boolean isApplet;
// private AppletParams appletParams;
public boolean isApplet()
{
return isApplet;
}
public String nextValue()
{
return vargs.remove(0);
}
public int getSize()
{
return vargs.size();
}
public String getValue(String arg)
{
return getValue(arg, false);
}
public String getValue(String arg, boolean utf8decode)
{
int index = vargs.indexOf(arg);
String dc = null;
String ret = null;
if (index != -1)
{
ret = vargs.get(index + 1).toString();
vargs.remove(index);
vargs.remove(index);
if (utf8decode && ret != null)
{
try
{
dc = URLDecoder.decode(ret, "UTF-8");
ret = dc;
} catch (Exception e)
{
// TODO: log failure to decode
}
}
}
return ret;
}
// new style
private static final Map argMap;
private Map linkedArgs = new HashMap<>();
private List linkedOrder = null;
private List argList;
static
{
argMap = new HashMap<>();
for (Arg a : EnumSet.allOf(Arg.class))
{
for (String argName : a.getNames())
{
if (argMap.containsKey(argName))
{
Console.warn("Trying to add argument name multiple times: '"
+ argName + "'"); // RESTORE THIS WHEN MERGED
if (argMap.get(argName) != a)
{
Console.error(
"Trying to add argument name multiple times for different Args: '"
+ argMap.get(argName).getName() + ":" + argName
+ "' and '" + a.getName() + ":" + argName
+ "'");
}
continue;
}
argMap.put(argName, a);
}
}
}
public ArgParser(String[] args)
{
// make a mutable new ArrayList so that shell globbing parser works
this(new ArrayList<>(Arrays.asList(args)));
}
public ArgParser(List args)
{
init(args);
}
private void init(List args)
{
// Enumeration argE = Collections.enumeration(args);
int argIndex = 0;
// while (argE.hasMoreElements())
for (int i = 0; i < args.size(); i++)
{
// String arg = argE.nextElement();
String arg = args.get(i);
String argName = null;
String val = null;
List vals = null; // for Opt.GLOB only
String linkedId = null;
if (arg.startsWith(DOUBLEDASH))
{
int equalPos = arg.indexOf('=');
if (equalPos > -1)
{
argName = arg.substring(DOUBLEDASH.length(), equalPos);
val = arg.substring(equalPos + 1);
}
else
{
argName = arg.substring(DOUBLEDASH.length());
}
int idOpen = argName.indexOf('[');
int idClose = argName.indexOf(']');
if (idOpen > -1 && idClose == argName.length() - 1)
{
linkedId = argName.substring(idOpen + 1, idClose);
argName = argName.substring(0, idOpen);
}
Arg a = argMap.get(argName);
// check for boolean prepended by "no"
boolean negated = false;
if (a == null && argName.startsWith(NEGATESTRING) && argMap
.containsKey(argName.substring(NEGATESTRING.length())))
{
argName = argName.substring(NEGATESTRING.length());
a = argMap.get(argName);
negated = true;
}
// check for config errors
if (a == null)
{
// arg not found
Console.error("Argument '" + arg + "' not recognised. Ignoring.");
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;
}
if (!a.hasOption(Opt.STRING) && equalPos > -1)
{
// set --argname=value when arg does not accept values
Console.error("Argument '--" + argName
+ "' does not expect a value (given as '" + arg
+ "'). Ignoring.");
continue;
}
if (!a.hasOption(Opt.LINKED) && linkedId != null)
{
// set --argname[linkedId] when arg does not use linkedIds
Console.error("Argument '--" + argName
+ "' does not expect a linked id (given as '" + arg
+ "'). Ignoring.");
continue;
}
if (a.hasOption(Opt.STRING) && equalPos == -1)
{
// take next arg as value if required, and '=' was not found
// 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;
}
// 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);
}
}
// make NOACTION adjustments
// default and auto counter increments
if (a == Arg.INCREMENT)
{
defaultLinkedIdCounter++;
}
else if (a == Arg.NPP)
{
idCounter++;
}
else if (a == Arg.SUBSTITUTIONS)
{
substitutions = !negated;
}
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 ArgValuesMap());
// do not continue for NOACTION args
if (a.hasOption(Opt.NOACTION))
continue;
ArgValuesMap avm = linkedArgs.get(linkedId);
// 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;
}
// check for unique id
SubVals sv = ArgParser.getSubVals(val);
String id = sv.get(ArgValues.ID);
if (id != null && avm.hasId(a, id))
{
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))
{
if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
{
for (String v : vals)
{
avs.addValue(makeSubstitutions(v), argIndex++);
}
}
else
{
avs.addValue(makeSubstitutions(val), argIndex);
}
}
else if (a.hasOption(Opt.BOOLEAN))
{
avs.setBoolean(!negated, argIndex);
avs.setNegated(negated);
}
else if (a.hasOption(Opt.UNARY))
{
avs.setBoolean(true, argIndex);
}
avs.incrementCount();
// store in appropriate place
if (a.hasOption(Opt.LINKED))
{
// allow a default linked id for single usage
if (linkedId == null)
linkedId = defaultLinkedId;
// store the order of linkedIds
if (linkedOrder == null)
linkedOrder = new ArrayList<>();
if (!linkedOrder.contains(linkedId))
linkedOrder.add(linkedId);
}
// store arg in the list of args used
if (argList == null)
argList = new ArrayList<>();
if (!argList.contains(a))
argList.add(a);
}
}
}
private String makeSubstitutions(String val)
{
if (!this.substitutions)
return val;
String subvals;
String rest;
if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
{
int closeBracket = val.indexOf(']');
if (val.length() == closeBracket)
return val;
subvals = val.substring(0, closeBracket + 1);
rest = val.substring(closeBracket + 1);
}
else
{
subvals = "";
rest = val;
}
rest.replace(AUTOCOUNTERLINKEDID, String.valueOf(idCounter));
rest.replace(INCREMENTAUTOCOUNTERLINKEDID, String.valueOf(++idCounter));
rest.replace("{}", String.valueOf(defaultLinkedIdCounter));
return new StringBuilder(subvals).append(rest).toString();
}
/*
* 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 {"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 getShellGlobbedFilenameValues(Arg a,
List args, int i)
{
List 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)
{
ArgValuesMap avm = linkedArgs.get(linkedId);
return avm == null ? false : avm.containsArg(a);
}
public boolean getBool(Arg a)
{
if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
{
Console.warn("Getting boolean from non boolean Arg '" + a.getName()
+ "'.");
}
return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
}
public boolean getBool(String linkedId, Arg a)
{
ArgValuesMap avm = linkedArgs.get(linkedId);
if (avm == null)
return a.getDefaultBoolValue();
ArgValues avs = avm.getArgValues(a);
return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
}
public List linkedIds()
{
return linkedOrder;
}
public ArgValuesMap linkedArgs(String id)
{
return linkedArgs.get(id);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("UNLINKED\n");
sb.append(argValuesMapToString(linkedArgs.get(null)));
if (linkedIds() != null)
{
sb.append("LINKED\n");
for (String id : linkedIds())
{
// already listed these as UNLINKED args
if (id == null)
continue;
ArgValuesMap avm = linkedArgs(id);
sb.append("ID: '").append(id).append("'\n");
sb.append(argValuesMapToString(avm));
}
}
return sb.toString();
}
private static String argValuesMapToString(ArgValuesMap avm)
{
if (avm == null)
return null;
StringBuilder sb = new StringBuilder();
for (Arg a : avm.getArgKeys())
{
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 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 = 0;
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 m;
protected ArgValuesMap()
{
this.newMap();
}
protected ArgValuesMap(Map map)
{
this.m = map;
}
private Map getMap()
{
return m;
}
private void newMap()
{
m = new HashMap();
}
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();
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 getArgValueList(Arg a)
{
ArgValues avs = getArgValues(a);
return avs == null ? new ArrayList<>() : avs.getArgValueList();
}
protected ArgValue getArgValue(Arg a)
{
List 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 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 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 argFilenameGlobs)
{
List 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 argFiles)
{
List 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> bootstrapArgMap = new HashMap<>();
public static BootstrapArgs getBootstrapArgs(String[] args)
{
List argList = new ArrayList<>(Arrays.asList(args));
return new BootstrapArgs(argList);
}
private BootstrapArgs(List args)
{
init(args);
}
private void init(List 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 getList(Arg a)
{
return bootstrapArgMap.get(a);
}
private List getOrCreateList(Arg a)
{
List l = getList(a);
if (l == null)
{
l = new ArrayList<>();
putList(a, l);
}
return l;
}
private void putList(Arg a, List 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 l = getOrCreateList(a);
if (a.hasOption(Opt.MULTI) || l.size() == 0)
{
l.add(s);
}
}
private void addAll(Arg a, List al)
{
List 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 aL = bootstrapArgMap.get(a);
return (aL == null || aL.size() == 0) ? null : aL.get(0);
}
}
}