/* * 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.argparser; import java.io.File; import java.io.IOException; 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 jalview.bin.Cache; import jalview.bin.Console; import jalview.bin.Jalview; import jalview.bin.argparser.Arg.Opt; import jalview.bin.argparser.Arg.Type; import jalview.util.FileUtils; import jalview.util.HttpUtils; public class ArgParser { protected static final String SINGLEDASH = "-"; protected static final String DOUBLEDASH = "--"; protected static final char EQUALS = '='; protected static final String NEGATESTRING = "no"; // the default linked id prefix used for no id (not even square braces) protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:"; // the linkedId string used to match all linkedIds seen so far protected static final String MATCHALLLINKEDIDS = "*"; // the linkedId string used to match all of the last --open'ed linkedIds protected static final String MATCHOPENEDLINKEDIDS = "open*"; // the counter added to the default linked id prefix private int defaultLinkedIdCounter = 0; // the substitution string used to use the defaultLinkedIdCounter private static final String DEFAULTLINKEDIDCOUNTER = "{}"; // the counter added to the default linked id prefix. NOW using // linkedIdAutoCounter // private int openLinkedIdCounter = 0; // the linked id prefix used for --open files. NOW the same as DEFAULT protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX; // the counter used for {n} substitutions private int linkedIdAutoCounter = 0; // the linked id substitution string used to increment the idCounter (and use // the incremented value) private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}"; // the linked id substitution string used to use the idCounter private static final String LINKEDIDAUTOCOUNTER = "{n}"; // the linked id substitution string used to use the filename extension of // --append // or --open private static final String LINKEDIDEXTENSION = "{extension}"; // the linked id substitution string used to use the base filename of --append // or --open private static final String LINKEDIDBASENAME = "{basename}"; // the linked id substitution string used to use the dir path of --append // or --open private static final String LINKEDIDDIRNAME = "{dirname}"; // the current argfile private String argFile = null; // the linked id substitution string used to use the dir path of the latest // --argfile name private static final String ARGFILEBASENAME = "{argfilebasename}"; // the linked id substitution string used to use the dir path of the latest // --argfile name private static final String ARGFILEDIRNAME = "{argfiledirname}"; // flag to say whether {n} subtitutions in output filenames should be made. // Turn on and off with --substitutions and --nosubstitutions // Start with it on private boolean substitutions = true; // flag to say whether the default linkedId is the current default linked id // or ALL linkedIds private boolean allLinkedIds = false; // flag to say whether the default linkedId is the current default linked id // or OPENED linkedIds private boolean openedLinkedIds = false; protected static final Map argMap; protected Map linkedArgs = new HashMap<>(); protected List linkedOrder = new ArrayList<>(); protected List storedLinkedIds = new ArrayList<>(); protected List argList = new ArrayList<>(); private static final char ARGFILECOMMENT = '#'; private int argIndex = 0; private BootstrapArgs bootstrapArgs = null; 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 + "'"); 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) { this(args, false, null); } public ArgParser(String[] args, boolean initsubstitutions, BootstrapArgs bsa) { // Make a mutable new ArrayList so that shell globbing parser works. // (When shell file globbing is used, there are a sequence of non-Arg // arguments (which are the expanded globbed filenames) that need to be // consumed by the --append/--argfile/etc Arg which is most easily done by // removing these filenames from the list one at a time. This can't be done // with an ArrayList made with only Arrays.asList(String[] args). ) this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false, bsa); } public ArgParser(List args, boolean initsubstitutions) { this(args, initsubstitutions, false, null); } public ArgParser(List args, boolean initsubstitutions, boolean allowPrivate, BootstrapArgs bsa) { // do nothing if there are no "--" args and (some "-" args || >0 arg is // "open") boolean d = false; boolean dd = false; for (String arg : args) { if (arg.startsWith(DOUBLEDASH)) { dd = true; break; } else if (arg.startsWith("-") || arg.equals("open")) { d = true; } } if (d && !dd) { // leave it to the old style -- parse an empty list parse(new ArrayList(), false, false); return; } if (bsa != null) this.bootstrapArgs = bsa; else this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args); parse(args, initsubstitutions, allowPrivate); } private void parse(List args, boolean initsubstitutions, boolean allowPrivate) { this.substitutions = initsubstitutions; boolean openEachInitialFilenames = true; for (int i = 0; i < args.size(); i++) { String arg = args.get(i); // If the first arguments do not start with "--" or "-" or is not "open" // and` is a filename that exists it is probably a file/list of files to // open so we fake an Arg.OPEN argument and when adding files only add the // single arg[i] and increment the defaultLinkedIdCounter so that each of // these files is opened separately. if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH) && !arg.startsWith("-") && !arg.equals("open") && (new File(arg).exists() || HttpUtils.startsWithHttpOrHttps(arg))) { arg = Arg.OPEN.argString(); } else { openEachInitialFilenames = false; } // look for double-dash, e.g. --arg if (arg.startsWith(DOUBLEDASH)) { String argName = null; String val = null; List globVals = null; // for Opt.GLOB only SubVals globSubVals = null; // also for use by Opt.GLOB only String linkedId = null; Type type = null; // look for equals e.g. --arg=value int equalPos = arg.indexOf(EQUALS); if (equalPos > -1) { argName = arg.substring(DOUBLEDASH.length(), equalPos); val = arg.substring(equalPos + 1); } else { argName = arg.substring(DOUBLEDASH.length()); } // look for linked ID e.g. --arg[linkedID] 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); } // look for type modification e.g. --help-opening int dashPos = argName.indexOf(SINGLEDASH); if (dashPos > -1) { String potentialArgName = argName.substring(0, dashPos); Arg potentialArg = argMap.get(potentialArgName); if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE)) { String typeName = argName.substring(dashPos + 1); try { type = Type.valueOf(typeName); } catch (IllegalArgumentException e) { type = Type.INVALID; } argName = argName.substring(0, dashPos); } } Arg a = argMap.get(argName); // check for boolean prepended by "no" e.g. --nowrap 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. Exiting."); Jalview.exit("Invalid argument used." + System.lineSeparator() + "Use" + System.lineSeparator() + "jalview " + Arg.HELP.argString() + System.lineSeparator() + "for a usage statement.", 13); continue; } if (a.hasOption(Opt.PRIVATE) && !allowPrivate) { Console.error( "Argument '" + a.argString() + "' is private. Ignoring."); continue; } if (!a.hasOption(Opt.BOOLEAN) && negated) { // used "no" with a non-boolean option Console.error("Argument '" + DOUBLEDASH + 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 '" + a.argString() + "' 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 '" + a.argString() + "' does not expect a linked id (given as '" + arg + "'). Ignoring."); continue; } // String value(s) if (a.hasOption(Opt.STRING)) { if (equalPos >= 0) { if (a.hasOption(Opt.GLOB)) { // strip off and save the SubVals to be added individually later globSubVals = new SubVals(val); // make substitutions before looking for files String fileGlob = makeSubstitutions(globSubVals.getContent(), linkedId); globVals = FileUtils.getFilenamesFromGlob(fileGlob); } else { // val is already set -- will be saved in the ArgValue later in // the normal way } } else { // There is no "=" so value is next arg or args (possibly shell // glob-expanded) if ((openEachInitialFilenames ? i : 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)) { // if this is the first argument with a file list at the start of // the args we add filenames from index i instead of i+1 globVals = getShellGlobbedFilenameValues(a, args, openEachInitialFilenames ? i : i + 1); } else { val = args.get(i + 1); } } } // make NOACTION adjustments // default and auto counter increments if (a == Arg.NPP) { linkedIdAutoCounter++; } else if (a == Arg.SUBSTITUTIONS) { substitutions = !negated; } else if (a == Arg.SETARGFILE) { argFile = val; } else if (a == Arg.UNSETARGFILE) { argFile = null; } else if (a == Arg.ALL) { allLinkedIds = !negated; openedLinkedIds = false; } else if (a == Arg.OPENED) { openedLinkedIds = !negated; allLinkedIds = false; } if (a.hasOption(Opt.STORED)) { // reset the lastOpenedLinkedIds list this.storedLinkedIds = new ArrayList<>(); } // this is probably only Arg.NEW and Arg.OPEN if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER)) { // use the next default prefixed OPENLINKEDID defaultLinkedId(true); } String autoCounterString = null; String defaultLinkedId = defaultLinkedId(false); boolean usingDefaultLinkedId = false; if (a.hasOption(Opt.LINKED)) { if (linkedId == null) { if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL) && val.startsWith(MATCHALLLINKEDIDS)) { // --output=*.ext is shorthand for --all --output {basename}.ext // (or --image=*.ext) allLinkedIds = true; openedLinkedIds = false; linkedId = MATCHALLLINKEDIDS; val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME + val.substring(MATCHALLLINKEDIDS.length()); } else if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL) && val.startsWith(MATCHOPENEDLINKEDIDS)) { // --output=open*.ext is shorthand for --opened --output // {basename}.ext // (or --image=open*.ext) openedLinkedIds = true; allLinkedIds = false; linkedId = MATCHOPENEDLINKEDIDS; val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME + val.substring(MATCHOPENEDLINKEDIDS.length()); } else if (allLinkedIds && a.hasOption(Opt.ALLOWALL)) { linkedId = MATCHALLLINKEDIDS; } else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL)) { linkedId = MATCHOPENEDLINKEDIDS; } else { // use default linkedId for linked arguments linkedId = defaultLinkedId; usingDefaultLinkedId = true; Console.debug("Changing linkedId to '" + linkedId + "' from " + arg); } } else if (linkedId.contains(LINKEDIDAUTOCOUNTER)) { // turn {n} to the autoCounter autoCounterString = Integer.toString(linkedIdAutoCounter); linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER, autoCounterString); Console.debug( "Changing linkedId to '" + linkedId + "' from " + arg); } else if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER)) { // turn {++n} to the incremented autoCounter autoCounterString = Integer.toString(++linkedIdAutoCounter); linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER, autoCounterString); Console.debug( "Changing linkedId to '" + linkedId + "' from " + arg); } } // do not continue in this block for NOACTION args if (a.hasOption(Opt.NOACTION)) continue; ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId); // not dealing with both NODUPLICATEVALUES and GLOB if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val)) { Console.error("Argument '" + a.argString() + "' cannot contain a duplicate value ('" + val + "'). Ignoring this and subsequent occurrences."); continue; } // check for unique id SubVals idsv = new SubVals(val); String id = idsv.get(ArgValues.ID); if (id != null && avm.hasId(a, id)) { Console.error("Argument '" + a.argString() + "' has a duplicate id ('" + id + "'). Ignoring."); continue; } /* TODO * Change all avs.addValue() avs.setBoolean avs.setNegated() avs.incrementCount calls to checkfor linkedId == "*" * DONE, need to check */ ArgValues avs = avm.getOrCreateArgValues(a); // store appropriate String value(s) if (a.hasOption(Opt.STRING)) { if (a.hasOption(Opt.GLOB) && globVals != null && globVals.size() > 0) { Enumeration gve = Collections.enumeration(globVals); while (gve.hasMoreElements()) { String v = gve.nextElement(); SubVals vsv = new SubVals(globSubVals, v); addValue(linkedId, type, avs, vsv, v, argIndex++, true); // if we're using defaultLinkedId and the arg increments the // counter: if (gve.hasMoreElements() && usingDefaultLinkedId && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER)) { // increment the default linkedId linkedId = defaultLinkedId(true); // get new avm and avs avm = linkedArgs.get(linkedId); avs = avm.getOrCreateArgValues(a); } } } else { addValue(linkedId, type, avs, val, argIndex, true); } } else if (a.hasOption(Opt.BOOLEAN)) { setBoolean(linkedId, type, avs, !negated, argIndex); setNegated(linkedId, avs, negated); } else if (a.hasOption(Opt.UNARY)) { setBoolean(linkedId, type, avs, true, argIndex); } // remove the '*' or 'open*' linkedId that should be empty if it was // created if ((MATCHALLLINKEDIDS.equals(linkedId) && linkedArgs.containsKey(linkedId)) || (MATCHOPENEDLINKEDIDS.equals(linkedId) && linkedArgs.containsKey(linkedId))) { linkedArgs.remove(linkedId); } } } } private void finaliseStoringArgValue(String linkedId, ArgValues avs) { Arg a = avs.arg(); incrementCount(linkedId, avs); argIndex++; // store in appropriate place if (a.hasOption(Opt.LINKED)) { // store the order of linkedIds if (!linkedOrder.contains(linkedId)) linkedOrder.add(linkedId); } // store arg in the list of args used if (!argList.contains(a)) argList.add(a); } private String defaultLinkedId(boolean increment) { String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX) .append(Integer.toString(defaultLinkedIdCounter)).toString(); if (increment) { while (linkedArgs.containsKey(defaultLinkedId)) { defaultLinkedIdCounter++; defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX) .append(Integer.toString(defaultLinkedIdCounter)) .toString(); } } getOrCreateLinkedArgValuesMap(defaultLinkedId); return defaultLinkedId; } public String makeSubstitutions(String val, String linkedId) { if (!this.substitutions || val == null) 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; } if (rest.contains(LINKEDIDAUTOCOUNTER)) rest = rest.replace(LINKEDIDAUTOCOUNTER, String.valueOf(linkedIdAutoCounter)); if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER)) rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER, String.valueOf(++linkedIdAutoCounter)); if (rest.contains(DEFAULTLINKEDIDCOUNTER)) rest = rest.replace(DEFAULTLINKEDIDCOUNTER, String.valueOf(defaultLinkedIdCounter)); ArgValuesMap avm = linkedArgs.get(linkedId); if (avm != null) { if (rest.contains(LINKEDIDBASENAME)) { rest = rest.replace(LINKEDIDBASENAME, avm.getBasename()); } if (rest.contains(LINKEDIDEXTENSION)) { rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension()); } if (rest.contains(LINKEDIDDIRNAME)) { rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname()); } } if (argFile != null) { if (rest.contains(ARGFILEBASENAME)) { rest = rest.replace(ARGFILEBASENAME, FileUtils.getBasename(new File(argFile))); } if (rest.contains(ARGFILEDIRNAME)) { rest = rest.replace(ARGFILEDIRNAME, FileUtils.getDirname(new File(argFile))); } } 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. */ protected static List getShellGlobbedFilenameValues(Arg a, List args, int i) { List vals = new ArrayList<>(); while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH)) { vals.add(FileUtils.substituteHomeDir(args.remove(i))); if (!a.hasOption(Opt.GLOB)) break; } return vals; } public BootstrapArgs getBootstrapArgs() { return bootstrapArgs; } public boolean isSet(Arg a) { return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a); } public boolean isSetAtAll(Arg a) { for (String linkedId : linkedOrder) { if (isSet(linkedId, a)) return true; } return false; } public boolean isSet(String linkedId, Arg a) { ArgValuesMap avm = linkedArgs.get(linkedId); return avm == null ? false : avm.containsArg(a); } public boolean getBoolean(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 getLinkedIds() { return linkedOrder; } public ArgValuesMap getLinkedArgs(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 (getLinkedIds() != null) { sb.append("LINKED\n"); for (String id : getLinkedIds()) { // already listed these as UNLINKED args if (id == null) continue; ArgValuesMap avm = getLinkedArgs(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 ArgParser parseArgFiles(List argFilenameGlobs, boolean initsubstitutions, BootstrapArgs bsa) { 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, initsubstitutions, bsa); } public static ArgParser parseArgFileList(List argFiles, boolean initsubstitutions, BootstrapArgs bsa) { List argsList = new ArrayList<>(); for (File argFile : argFiles) { if (!argFile.exists()) { String message = Arg.ARGFILE.argString() + EQUALS + "\"" + argFile.getPath() + "\": File does not exist."; Jalview.exit(message, 2); } try { String setargfile = new StringBuilder(Arg.SETARGFILE.argString()) .append(EQUALS).append(argFile.getCanonicalPath()) .toString(); argsList.add(setargfile); argsList.addAll(readArgFile(argFile)); argsList.add(Arg.UNSETARGFILE.argString()); } catch (IOException e) { String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath() + "\": File could not be read."; Jalview.exit(message, 3); } } // Third param "true" uses Opt.PRIVATE args --setargile=argfile and // --unsetargfile return new ArgParser(argsList, initsubstitutions, true, bsa); } protected static List readArgFile(File argFile) { List args = new ArrayList<>(); if (argFile != null && argFile.exists()) { try { for (String line : Files.readAllLines(Paths.get(argFile.getPath()))) { if (line != null && line.length() > 0 && line.charAt(0) != ARGFILECOMMENT) args.add(line); } } catch (IOException e) { String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath() + "\": File could not be read."; Console.debug(message, e); Jalview.exit(message, 3); } } return args; } public static enum Position { FIRST, BEFORE, AFTER } // get from following Arg of type a or subval of same name (lowercase) public static String getValueFromSubValOrArg(ArgValuesMap avm, ArgValue av, Arg a, SubVals sv) { return getFromSubValArgOrPref(avm, av, a, sv, null, null, null); } // get from following Arg of type a or subval key or preference pref or // default def public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av, Arg a, SubVals sv, String key, String pref, String def) { return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref, def); } // get from following(AFTER), first occurence of (FIRST) or previous (BEFORE) // Arg of type a or subval key or preference pref or default def public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv, String key, String pref, String def) { return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av, sv, key, pref, def); } public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv, String key, String pref, String def) { if (key == null) key = a.getName(); String value = null; if (sv != null && sv.has(key) && sv.get(key) != null) { value = ap == null ? sv.get(key) : sv.getWithSubstitutions(ap, avm.getLinkedId(), key); } else if (avm != null && avm.containsArg(a)) { if (pos == Position.FIRST && avm.getValue(a) != null) value = avm.getValue(a); else if (pos == Position.BEFORE && avm.getClosestPreviousArgValueOfArg(av, a) != null) value = avm.getClosestPreviousArgValueOfArg(av, a).getValue(); else if (pos == Position.AFTER && avm.getClosestNextArgValueOfArg(av, a) != null) value = avm.getClosestNextArgValueOfArg(av, a).getValue(); } else { value = pref != null ? Cache.getDefault(pref, def) : def; } return value; } public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a, SubVals sv) { return getFromSubValArgOrPref(avm, a, sv, null, null, false); } public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a, SubVals sv, String key, String pref, boolean def) { if ((key == null && a == null) || (sv == null && a == null)) return false; boolean usingArgKey = false; if (key == null) { key = a.getName(); usingArgKey = true; } String nokey = ArgParser.NEGATESTRING + key; // look for key or nokey in subvals first (if using Arg check options) if (sv != null) { // check for true boolean if (sv.has(key) && sv.get(key) != null) { if (usingArgKey) { if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY))) { Console.debug( "Looking for boolean in subval from non-boolean/non-unary Arg " + a.getName()); return false; } } return sv.get(key).toLowerCase(Locale.ROOT).equals("true"); } // check for negative boolean (subval "no..." will be "true") if (sv.has(nokey) && sv.get(nokey) != null) { if (usingArgKey) { if (!(a.hasOption(Opt.BOOLEAN))) { Console.debug( "Looking for negative boolean in subval from non-boolean Arg " + a.getName()); return false; } } return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true"); } } // check argvalues if (avm != null && avm.containsArg(a)) return avm.getBoolean(a); // return preference or default return pref != null ? Cache.getDefault(pref, def) : def; } // the following methods look for the "*" linkedId and add the argvalue to all // linkedId ArgValues if it does. // This version inserts the subvals sv into all created values private void addValue(String linkedId, Type type, ArgValues avs, SubVals sv, String v, int argIndex, boolean doSubs) { this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false, argIndex, doSubs); } private void addValue(String linkedId, Type type, ArgValues avs, String v, int argIndex, boolean doSubs) { this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false, argIndex, doSubs); } private void setBoolean(String linkedId, Type type, ArgValues avs, boolean b, int argIndex) { this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null, b, argIndex, false); } private void setNegated(String linkedId, ArgValues avs, boolean b) { this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null, b, 0, false); } private void incrementCount(String linkedId, ArgValues avs) { this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null, null, false, 0, false); } private enum Op { ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT } // The following operations look for the "*" and "open*" linkedIds and add the // argvalue to all appropriate linkedId ArgValues if it does. // If subvals are supplied, they are inserted into all new set values. private void argValueOperation(Op op, String linkedId, Type type, ArgValues avs, SubVals sv, String v, boolean b, int argIndex, boolean doSubs) { Arg a = avs.arg(); List wildcardLinkedIds = null; if (a.hasOption(Opt.ALLOWALL)) { switch (linkedId) { case MATCHALLLINKEDIDS: wildcardLinkedIds = getLinkedIds(); break; case MATCHOPENEDLINKEDIDS: wildcardLinkedIds = this.storedLinkedIds; break; } } // if we're not a wildcard linkedId and the arg is marked to be stored, add // to storedLinkedIds if (linkedId != null && wildcardLinkedIds == null && a.hasOption(Opt.STORED) && !storedLinkedIds.contains(linkedId)) { storedLinkedIds.add(linkedId); } // if we are a wildcard linkedId, apply the arg and value to all appropriate // linkedIds if (wildcardLinkedIds != null) { for (String id : wildcardLinkedIds) { // skip incorrectly stored wildcard ids! if (id == null || MATCHALLLINKEDIDS.equals(id) || MATCHOPENEDLINKEDIDS.equals(id)) continue; ArgValuesMap avm = linkedArgs.get(id); // don't set an output if there isn't an input if (a.hasOption(Opt.REQUIREINPUT) && !avm.hasArgWithOption(Opt.INPUT)) continue; ArgValues tavs = avm.getOrCreateArgValues(a); switch (op) { case ADDVALUE: String val = v; if (sv != null) { if (doSubs) { val = makeSubstitutions(v, id); sv = new SubVals(sv, val); } tavs.addValue(sv, type, val, argIndex, true); } else { if (doSubs) { val = makeSubstitutions(v, id); } tavs.addValue(type, val, argIndex, true); } finaliseStoringArgValue(id, tavs); break; case SETBOOLEAN: tavs.setBoolean(type, b, argIndex, true); finaliseStoringArgValue(id, tavs); break; case SETNEGATED: tavs.setNegated(b, true); break; case INCREMENTCOUNT: tavs.incrementCount(); break; default: break; } } } else // no wildcard linkedId -- do it simpler { switch (op) { case ADDVALUE: String val = v; if (sv != null) { if (doSubs) { val = makeSubstitutions(v, linkedId); sv = new SubVals(sv, val); } avs.addValue(sv, type, val, argIndex, false); } else { if (doSubs) { val = makeSubstitutions(v, linkedId); } avs.addValue(type, val, argIndex, false); } finaliseStoringArgValue(linkedId, avs); break; case SETBOOLEAN: avs.setBoolean(type, b, argIndex, false); finaliseStoringArgValue(linkedId, avs); break; case SETNEGATED: avs.setNegated(b, false); break; case INCREMENTCOUNT: avs.incrementCount(); break; default: break; } } } private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId) { if (linkedArgs.containsKey(linkedId) && linkedArgs.get(linkedId) != null) return linkedArgs.get(linkedId); linkedArgs.put(linkedId, new ArgValuesMap(linkedId)); return linkedArgs.get(linkedId); } }