/*
* 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;
// flag to say whether the structure arguments should be applied to all
// structures with this linked id
private boolean allStructures = 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;
}
else if (a == Arg.ALLSTRUCTURES)
{
allStructures = !negated;
}
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);
}
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 subvals = new SubVals(val);
boolean addNewSubVals = false;
String id = subvals.get(ArgValues.ID);
if (id != null && avm.hasId(a, id))
{
Console.error("Argument '" + a.argString()
+ "' has a duplicate id ('" + id + "'). Ignoring.");
continue;
}
// set allstructures to all non-primary structure options in this linked
// id if --allstructures has been set
if (allStructures
&& (a.getType() == Type.STRUCTURE
|| a.getType() == Type.STRUCTUREIMAGE)
&& !a.hasOption(Opt.PRIMARY))
{
if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
// && !subvals.has("structureid"))
{
subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
addNewSubVals = true;
}
}
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);
addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
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();
// look for allstructures subval for Type.STRUCTURE*
Arg arg = av.getArg();
if (value == null && arg.hasOption(Opt.PRIMARY)
&& arg.getType() == Type.STRUCTURE
&& !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
|| a.getType() == Type.STRUCTUREIMAGE))
{
ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
Arg.ALLSTRUCTURES.getName());
if (av2 != null)
{
value = av2.getValue();
}
}
}
if (value == null)
{
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)
{
return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
}
public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
SubVals sv, String key, String pref, boolean def,
boolean invertPref)
{
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
boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
return pref != null ? (invertPref ? !prefVal : prefVal) : 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
}
private void argValueOperation(Op op, String linkedId, Type type,
ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
boolean doSubs)
{
// default to merge subvals if subvals are provided
argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
doSubs);
}
/*
* 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.
*
* @param op The ArgParser.Op operation
* @param linkedId The String linkedId from the ArgValuesMap
* @param type The Arg.Type to attach to this ArgValue
* @param avs The ArgValues for this linkedId
* @param sv Use these SubVals on the ArgValue
* @param merge Merge the SubVals with any existing on the value. False will replace unless sv is null
* @param v The value of the ArgValue (may contain subvals).
* @param b The boolean value of the ArgValue.
* @param argIndex The argIndex for the ArgValue.
* @param doSubs Whether to perform substitutions on the subvals and value.
*/
private void argValueOperation(Op op, String linkedId, Type type,
ArgValues avs, SubVals sv, boolean merge, 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)
{
sv = new SubVals(sv, val, merge);
val = makeSubstitutions(sv.getContent(), id);
}
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);
}
}