/* * 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.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; 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 jalview.util.Platform; public class ArgParser { private static final String NEGATESTRING = "no"; private static final String DEFAULTLINKEDID = ""; private static enum Opt { BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED } 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, DEBUG("d"); 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); GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI); GROUPS.setOptions(Opt.STRING, Opt.LINKED); HEADLESS.setOptions(Opt.UNARY); JABAWS.setOptions(Opt.STRING); ANNOTATION.setOptions(true, Opt.BOOLEAN); ANNOTATION2.setOptions(true, Opt.BOOLEAN); 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); OPEN2.setOptions(Opt.STRING, Opt.LINKED); PROPS.setOptions(Opt.STRING); 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); 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); 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); DEBUG.setOptions(Opt.BOOLEAN); } private final String[] argNames; private Opt[] argOptions; private boolean defaultBoolValue = false; private int argIndex = -1; 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; } private void setArgIndex(int i) { this.argIndex = i; } protected int getArgIndex() { return this.argIndex; } } public static class ArgValues { private Arg arg; private int argCount = 0; private boolean boolValue = false; private boolean negated = false; private int singleArgIndex = -1; private List argsIndexes; private List argsList; protected ArgValues(Arg a) { this.arg = a; this.argsList = 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) { this.boolValue = b; } protected boolean getBoolean() { return this.boolValue; } @Override public String toString() { if (argsList == 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 : argsList) { 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) { addValue(val, argIndex, false); } protected void addValue(String val, int argIndex, boolean noDuplicates) { if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0) || (noDuplicates && argsList.contains(val))) return; if (argsList == null) { argsList = new ArrayList(); } argsList.add(new ArgValue(val, argIndex)); } protected boolean hasValue(String val) { return argsList.contains(val); } 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; } /* protected String getValue() { ArgValue av = getArgValue(); return av == null ? null : av.getValue(); } */ protected List getArgValueList() { return argsList; } } // 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, 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; } /* 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 List linkedOrder = null; private List argList; static { argMap = new HashMap<>(); for (Arg a : EnumSet.allOf(Arg.class)) { ARGNAME: 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 ARGNAME; } argMap.put(argName, a); } } } public ArgParser(String[] args) { // old style vargs = new ArrayList<>(); isApplet = (args.length > 0 && args[0].startsWith(" argE = Collections.enumeration(Arrays.asList(args)); int argIndex = 0; while (argE.hasMoreElements()) { String arg = argE.nextElement(); String argName = null; String val = null; String linkedId = null; if (arg.startsWith("--")) { int equalPos = arg.indexOf('='); if (equalPos > -1) { argName = arg.substring(2, equalPos); val = arg.substring(equalPos + 1); } else { argName = arg.substring(2); } 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()) { // 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(); } // use default linkedId for linked arguments if (a.hasOption(Opt.LINKED) && linkedId == null) linkedId = DEFAULTLINKEDID; if (!linkedArgs.containsKey(linkedId)) linkedArgs.put(linkedId, new HashMap<>()); Map valuesMap = linkedArgs.get(linkedId); if (!valuesMap.containsKey(a)) valuesMap.put(a, new ArgValues(a)); ArgValues values = valuesMap.get(a); if (values == null) { values = new ArgValues(a); } // store appropriate value if (a.hasOption(Opt.STRING)) { values.addValue(val, argIndex); } else if (a.hasOption(Opt.BOOLEAN)) { values.setBoolean(!negated); values.setNegated(negated); } else if (a.hasOption(Opt.UNARY)) { values.setBoolean(true); } values.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 the ArgValues valuesMap.put(a, values); // store arg in the list of args if (argList == null) argList = new ArrayList<>(); if (!argList.contains(a)) argList.add(a); } } } public boolean isSet(Arg a) { return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a); } public boolean isSet(String linkedId, Arg a) { Map m = linkedArgs.get(linkedId); return m == null ? false : m.containsKey(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) { Map m = linkedArgs.get(linkedId); if (m == null) return a.getDefaultBoolValue(); ArgValues v = m.get(a); return v == null ? a.getDefaultBoolValue() : v.getBoolean(); } public List linkedIds() { return linkedOrder; } public HashMap linkedArgs(String id) { return linkedArgs.get(id); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("UNLINKED\n"); sb.append(argMapToString(linkedArgs.get(null))); if (linkedIds() != null) { sb.append("LINKED\n"); for (String id : linkedIds()) { // already listed these as UNLINKED args if (id == null) continue; Map m = linkedArgs(id); sb.append("ID: '").append(id).append("'\n"); sb.append(argMapToString(m)); } } return sb.toString(); } private static String argMapToString(Map m) { if (m == null) return null; StringBuilder sb = new StringBuilder(); for (Arg a : m.keySet()) { ArgValues v = m.get(a); sb.append(v.toString()); sb.append("\n"); } return sb.toString(); } // Helper methods with safety checks protected static ArgValues getArgValues(Map m, Arg a) { return m == null ? null : m.get(a); } public static List getArgValueList(Map m, Arg a) { ArgValues av = getArgValues(m, a); return av == null ? null : av.getArgValueList(); } public static ArgValue getArgValue(Map m, Arg a) { List vals = getArgValueList(m, a); return (vals == null || vals.size() == 0) ? null : vals.get(0); } public static String getValue(Map m, Arg a) { ArgValue av = getArgValue(m, a); return av == null ? null : av.getValue(); } public static boolean hasValue(Map m, Arg a) { if (!m.containsKey(a)) return false; return getArgValue(m, a) != null; } public static boolean getBoolean(Map m, Arg a) { ArgValues av = getArgValues(m, a); return av == null ? false : av.getBoolean(); } 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; protected ArgValue(String value, int argIndex) { this.value = value; this.argIndex = argIndex; } protected String getValue() { return value; } protected int getArgIndex() { return argIndex; } } /** * 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.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; } } private static final Collection bootstrapArgs = new ArrayList( Arrays.asList("props", "debug")); public static Map bootstrapArgs(String[] args) { Map argMap = new HashMap<>(); if (args == null) return argMap; Enumeration argE = Collections.enumeration(Arrays.asList(args)); while (argE.hasMoreElements()) { String arg = argE.nextElement(); String argName = null; String val = null; if (arg.startsWith("--")) { int equalPos = arg.indexOf('='); if (equalPos > -1) { argName = arg.substring(2, equalPos); val = arg.substring(equalPos + 1); } else { argName = arg.substring(2); } if (bootstrapArgs.contains(argName)) argMap.put(argName, val); } } return argMap; } }