X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fbin%2FArgParser.java;h=14785ea867c29112d7e15a1e5c49d54e72e4a2a0;hb=a5928e2c1b9e2cea8d9c43f3d03f9cce2604f3b2;hp=338d13064e6a3eb31619c1f7651dc6b7e83e0233;hpb=01f8ff469a507604ed961418f7c80f9b245e60ca;p=jalview.git diff --git a/src/jalview/bin/ArgParser.java b/src/jalview/bin/ArgParser.java index 338d130..14785ea 100644 --- a/src/jalview/bin/ArgParser.java +++ b/src/jalview/bin/ArgParser.java @@ -20,28 +20,51 @@ */ 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; + + // 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, ORDERED + BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP, + GLOB, NOACTION, ALLOWSUBSTITUTIONS } public enum Arg @@ -59,7 +82,8 @@ public class ArgParser 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; + NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"), + ARGFILE, INCREMENT, NPP("n++"), SUBSTITUTIONS, NIL; static { @@ -71,13 +95,15 @@ public class ArgParser 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); - GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI); + 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); + HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP); JABAWS.setOptions(Opt.STRING); - ANNOTATION.setOptions(true, Opt.BOOLEAN); - ANNOTATION2.setOptions(true, Opt.BOOLEAN); + 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); @@ -85,9 +111,10 @@ public class ArgParser // expects a string value SORTBYTREE.setOptions(true, Opt.BOOLEAN); USAGESTATS.setOptions(true, Opt.BOOLEAN); - OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI); - OPEN2.setOptions(Opt.STRING, Opt.LINKED); - PROPS.setOptions(Opt.STRING); + 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); @@ -95,7 +122,7 @@ public class ArgParser VDOC.setOptions(Opt.UNARY); VSESS.setOptions(Opt.UNARY); - OUTPUT.setOptions(Opt.STRING, Opt.LINKED); + OUTPUT.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS); OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI); SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED); @@ -105,12 +132,27 @@ public class ArgParser 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); + PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, + Opt.ALLOWSUBSTITUTIONS); NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED); - STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI); + STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, + Opt.ALLOWSUBSTITUTIONS); WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED); - IMAGE.setOptions(Opt.STRING, 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; @@ -205,6 +247,8 @@ public class ArgParser public static class ArgValues { + private static final String ID = "id"; + private Arg arg; private int argCount = 0; @@ -213,12 +257,18 @@ public class ArgParser private boolean negated = false; - private List argsList; + private int boolIndex = -1; + + private List argsIndexes; + + private List argValueList; + + private Map idMap = new HashMap<>(); protected ArgValues(Arg a) { this.arg = a; - this.argsList = new ArrayList(); + this.argValueList = new ArrayList(); this.boolValue = arg.getDefaultBoolValue(); } @@ -247,9 +297,10 @@ public class ArgParser return this.negated; } - protected void setBoolean(boolean b) + protected void setBoolean(boolean b, int i) { this.boolValue = b; + this.boolIndex = i; } protected boolean getBoolean() @@ -260,7 +311,7 @@ public class ArgParser @Override public String toString() { - if (argsList == null) + if (argValueList == null) return null; StringBuilder sb = new StringBuilder(); sb.append(arg.toLongString()); @@ -272,8 +323,9 @@ public class ArgParser { 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 '"); @@ -288,42 +340,59 @@ public class ArgParser 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) { - Console.warn("** inst"); - argsList = new ArrayList(); + argValueList = new ArrayList(); } - argsList.add(val); + 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 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 getArgValueList() + { + return argValueList; + } + + protected boolean hasId(String id) + { + return idMap.containsKey(id); } - protected List getValues() + protected ArgValue getId(String id) { - return argsList; + return idMap.get(id); } } @@ -357,7 +426,8 @@ public class ArgParser 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(); @@ -378,20 +448,10 @@ public class ArgParser 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 argMap; - private Map> linkedArgs = new HashMap<>(); + private Map linkedArgs = new HashMap<>(); private List linkedOrder = null; @@ -402,7 +462,7 @@ public class ArgParser 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)) { @@ -416,7 +476,7 @@ public class ArgParser + "' and '" + a.getName() + ":" + argName + "'"); } - continue ARGNAME; + continue; } argMap.put(argName, a); } @@ -425,52 +485,39 @@ public class ArgParser public ArgParser(String[] args) { - // old style - vargs = new ArrayList<>(); - isApplet = (args.length > 0 && args[0].startsWith("(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 args) + { + init(args); + } - // new style - Enumeration argE = Collections.enumeration(Arrays.asList(args)); - while (argE.hasMoreElements()) + 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 = 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("--")) + 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(']'); @@ -526,64 +573,152 @@ public class ArgParser 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; } - val = argE.nextElement(); + // 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); + } } - // use default linkedId for linked arguments - if (a.hasOption(Opt.LINKED) && linkedId == null) - linkedId = DEFAULTLINKEDID; + // 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 HashMap<>()); + 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; + } - Map valuesMap = linkedArgs.get(linkedId); - if (!valuesMap.containsKey(a)) - valuesMap.put(a, new ArgValues(a)); + // 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 values = valuesMap.get(a); - if (values == null) + ArgValues avs = avm.getOrCreateArgValues(a); + if (avs == null) { - values = new ArgValues(a); + 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(makeSubstitutions(v), argIndex++); + } + } + else + { + avs.addValue(makeSubstitutions(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)) @@ -592,6 +727,56 @@ public class ArgParser } } + 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); @@ -599,8 +784,8 @@ public class ArgParser public boolean isSet(String linkedId, Arg a) { - Map 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) @@ -615,11 +800,11 @@ public class ArgParser public boolean getBool(String linkedId, Arg a) { - Map 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 linkedIds() @@ -627,7 +812,7 @@ public class ArgParser return linkedOrder; } - public HashMap linkedArgs(String id) + public ArgValuesMap linkedArgs(String id) { return linkedArgs.get(id); } @@ -637,7 +822,7 @@ public class ArgParser { 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"); @@ -647,62 +832,69 @@ public class ArgParser if (id == null) continue; - Map 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 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(); } - // Helper methods with safety checks - protected static ArgValues getArgValues(Map m, Arg a) + public static SubVals getSubVals(String item) { - return m == null ? null : m.get(a); + return new SubVals(item); } - public static List getValues(Map m, Arg a) + /** + * A helper class to keep an index of argument position with argument values + */ + public static class ArgValue { - ArgValues av = getArgValues(m, a); - return av == null ? null : av.getValues(); - } + private int argIndex; - public static String getValue(Map m, Arg a) - { - List vals = getValues(m, a); - return (vals == null || vals.size() == 0) ? null : vals.get(0); - } + private String value; - public static boolean hasValue(Map m, Arg a) - { - if (!m.containsKey(a)) - return false; - return getValue(m, a) != null; - } + private String id; - public static boolean getBoolean(Map m, Arg a) - { - ArgValues av = getArgValues(m, a); - return av == null ? false : av.getBoolean(); - } + protected ArgValue(String value, int argIndex) + { + this.value = value; + this.argIndex = argIndex; + } - public static SubVal getSubVal(String item) - { - return new SubVal(item); + protected String getValue() + { + return value; + } + + protected int getArgIndex() + { + return argIndex; + } + + protected void setId(String i) + { + id = i; + } + + protected String getId() + { + return id; + } } /** @@ -711,50 +903,62 @@ public class ArgParser * 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 SubVal + public static class SubVals { private static int NOTSET = -1; - protected int index = NOTSET; + private int index = NOTSET; - protected String keyName = null; + private Map subVals = null; - protected String keyValue = null; + private static char SEPARATOR = ';'; - protected String content = null; + private String content = null; - public SubVal(String item) + public SubVals(String item) { - this.parseVal(item); + this.parseVals(item); } - public void parseVal(String item) + public void parseVals(String item) { + if (item == null) + return; if (item.indexOf('[') == 0 && item.indexOf(']') > 1) { - int openBracket = item.indexOf('['); + int openBracket = 0; int closeBracket = item.indexOf(']'); - String indexString = item.substring(openBracket + 1, closeBracket); + String subvalsString = item.substring(openBracket + 1, + closeBracket); this.content = item.substring(closeBracket + 1); - int equals = indexString.indexOf('='); - if (equals > -1) + boolean setIndex = false; + for (String subvalString : subvalsString + .split(Character.toString(SEPARATOR))) { - this.keyName = indexString.substring(0, equals); - this.keyValue = indexString.substring(equals + 1); - this.index = -1; - } - else - { - try + int equals = subvalString.indexOf('='); + if (equals > -1) { - this.index = Integer.parseInt(indexString); - } catch (NumberFormatException e) + if (subVals == null) + subVals = new HashMap<>(); + subVals.put(subvalString.substring(0, equals), + subvalString.substring(equals + 1)); + } + else { - Console.warn("Failed to obtain subvalue or index from '" + item - + "'. Setting index=0 and using content='" + content - + "'."); + 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 { @@ -765,7 +969,386 @@ public class ArgParser public boolean notSet() { // notSet is true if content present but nonsensical - return index == NOTSET && keyName == null && keyValue == null; + 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); } } } \ No newline at end of file