/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import jalview.bin.Cache;
import jalview.bin.Console;
import jalview.bin.argparser.Arg.Opt;
import jalview.bin.argparser.Arg.Type;
import jalview.util.FileUtils;
/**
* Helper class to allow easy extraction of information about specific argument
* values (without having to check for null etc all the time)
*/
public class ArgValuesMap
{
private List argInfoList = new ArrayList<>();
protected Map m;
private String linkedId;
protected ArgValuesMap(String linkedId)
{
this.linkedId = linkedId;
this.newMap();
}
protected ArgValuesMap(String linkedId, Map map)
{
this.linkedId = linkedId;
this.m = map;
}
public String getLinkedId()
{
return linkedId;
}
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, this));
}
}
public ArgValues getArgValues(Arg a)
{
return m == null ? null : m.get(a);
}
public ArgValues getOrCreateArgValues(Arg a)
{
ArgValues avs = m.get(a);
if (avs == null)
newArg(a);
return getArgValues(a);
}
public List getArgValueList(Arg a)
{
ArgValues avs = getArgValues(a);
return avs == null ? new ArrayList<>() : avs.getArgValueList();
}
public List getArgValueListFromSubValOrArg(ArgValue av, Arg a,
SubVals sv)
{
return getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
null, a, Position.AFTER, av, sv, null, null, null, true, null);
}
public List getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
SubVals sv, String key, String pref, String def,
boolean withinTypes, Type type)
{
if (key == null)
{
key = a.getName();
}
Set types = new HashSet<>();
if (type == null)
{
types.addAll(Arrays.asList(av.getArg().getTypes()));
}
else
{
types.add(type);
}
List avList = new ArrayList<>();
if (sv != null && sv.has(key) && sv.get(key) != null)
{
String value = ap == null ? sv.get(key)
: sv.getWithSubstitutions(ap, getLinkedId(), key);
// protected ArgValue(Arg a, SubVals sv, Type type, String content, int
// argIndex)
ArgValue svav = new ArgValue(a, null, null, value, av.getArgIndex(),
false, null, this.getLinkedId());
avList.add(svav);
}
else if (containsArg(a))
{
if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
avList.add(getArgValue(a));
else if (pos == ArgValuesMap.Position.BEFORE
&& getClosestPreviousArgValueOfArg(av, a) != null)
{
for (ArgValue tmpAv : getArgValues(a).getArgValueList())
{
if (tmpAv.getArgIndex() >= av.getArgIndex())
{
continue;
}
avList.add(tmpAv);
}
}
else if (pos == ArgValuesMap.Position.AFTER
&& getClosestNextArgValueOfArg(av, a, withinTypes) != null)
{
for (ArgValue tmpAv : getArgValues(a).getArgValueList())
{
if (tmpAv.getArgIndex() <= av.getArgIndex())
{
continue;
}
avList.add(tmpAv);
}
}
}
// check if withinType the avs don't belong to the next primary arg
// of this type. Checking for *any* shared type.
if (withinTypes && !avList.isEmpty())
{
int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
// run through every Arg used in this ArgValuesMap
for (Arg tmpA : this.getArgKeys())
{
// only interested in looking up to next Opt.PRIMARY args of the same
// type as av (or provided type)
if (tmpA.hasType(types) && tmpA.hasOption(Opt.PRIMARY))
{
for (ArgValue tmpAv : getArgValueList(tmpA))
{
int tmpArgIndex = tmpAv.getArgIndex();
if (tmpArgIndex > av.getArgIndex()
&& tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
{
nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
}
}
}
}
List tmpList = new ArrayList<>();
for (ArgValue tmpAv : avList)
{
int tmpAvIndex = tmpAv.getArgIndex();
if (av.getArgIndex() < tmpAvIndex
&& tmpAvIndex < nextPrimaryArgOfSameTypeIndex)
{
tmpList.add(tmpAv);
}
}
avList = tmpList;
}
return avList;
}
public ArgValue getArgValue(Arg a)
{
List vals = getArgValueList(a);
return (vals == null || vals.size() == 0) ? null : vals.get(0);
}
public String getValue(Arg a)
{
ArgValue av = getArgValue(a);
return av == null ? null : av.getValue();
}
public List getValues(Arg a)
{
return toValues(getArgValueList(a));
}
public static List toValues(List avl)
{
if (avl == null)
{
return null;
}
List vl = new ArrayList<>();
for (ArgValue av : avl)
{
vl.add(av.getValue());
}
return vl;
}
public boolean containsArg(Arg a)
{
if (m == null || !m.containsKey(a))
return false;
return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
}
public 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;
}
public boolean getBoolean(Arg a)
{
ArgValues av = getArgValues(a);
return av == null ? false : av.getBoolean();
}
public Set getArgKeys()
{
return m.keySet();
}
public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey)
{
return getArgValueOfArgWithSubValKey(a, svKey, false);
}
public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey,
boolean last)
{
ArgValues avs = this.getArgValues(a);
if (avs == null)
{
return null;
}
List compareAvs = avs.getArgValueList();
for (int i = 0; i < compareAvs.size(); i++)
{
int index = last ? compareAvs.size() - 1 - i : i;
ArgValue av = compareAvs.get(index);
SubVals sv = av.getSubVals();
if (sv.has(svKey) && !sv.get(svKey).equals("false"))
{
return av;
}
}
return null;
}
public 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;
}
public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
boolean withinTypes)
{
// 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();
if (!containsArg(a))
return null;
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;
}
}
// check if withinType this closestAv doesn't belong to the next primary arg
// of this type. Checking for *any* shared type.
if (withinTypes && closestAv != null)
{
int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
for (Arg tmpA : this.getArgKeys())
{
// interested in Opt.PRIMARY args of the same type
if (tmpA.sharesType(a) && tmpA.hasOption(Opt.PRIMARY))
{
for (ArgValue tmpAv : getArgValueList(tmpA))
{
int tmpArgIndex = tmpAv.getArgIndex();
if (tmpArgIndex > thisArgIndex
&& tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
{
nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
}
}
}
}
if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
{
// looks like closestAv actually belongs to a different primary Arg
return null;
}
}
return closestAv;
}
// TODO this is incomplete and currently unused (fortunately)
public 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();
}
public boolean hasId(Arg a, String id)
{
ArgValues avs = this.getArgValues(a);
return avs == null ? false : avs.hasId(id);
}
public ArgValue getId(Arg a, String id)
{
ArgValues avs = this.getArgValues(a);
return avs == null ? null : avs.getId(id);
}
/*
* This method returns the basename of the first --append or --open value.
* Used primarily for substitutions in output filenames.
*/
public String getBasename()
{
return getDirBasenameOrExtension(false, false, false);
}
/*
* This method returns the basename of the first --append or --open value.
* Used primarily for substitutions in output filenames.
*/
public String getExtension()
{
return getDirBasenameOrExtension(false, true, false);
}
/*
* This method returns the dirname of the first --append or --open value.
* Used primarily for substitutions in output filenames.
*/
public String getDirname()
{
return getDirBasenameOrExtension(true, false, false);
}
public String getDirBasenameOrExtension(boolean dirname,
boolean extension, boolean absoluteDirname)
{
String filename = null;
String appendVal = getValue(Arg.APPEND);
String openVal = getValue(Arg.OPEN);
if (appendVal != null)
filename = appendVal;
if (filename == null && openVal != null)
filename = openVal;
if (filename == null)
return null;
File file = new File(filename);
if (dirname)
{
return FileUtils.getDirname(file);
}
return extension ? FileUtils.getExtension(file)
: FileUtils.getBasename(file);
}
/*
* Checks if there is an Arg with Opt
*/
public boolean hasArgWithOption(Opt o)
{
for (Arg a : getArgKeys())
{
if (a.hasOption(o))
return true;
}
return false;
}
/*
* ArgInfo is a more straightforward list of arguments and their info
*/
public void addArgInfo(Arg arg, String value, SubVals subVals,
int argIndex)
{
argInfoList.add(new ArgInfo(arg, value, subVals, argIndex));
}
public List getArgInfoList()
{
Collections.sort(argInfoList);
return argInfoList;
}
/**
* get from following Arg of type a or subval of same name (lowercase)
*/
public String getValueFromSubValOrArg(ArgValue av, Arg a, SubVals sv)
{
return getFromSubValArgOrPref(av, a, sv, null, null, null);
}
/**
* get from following Arg of type a or subval key or preference pref or
* default def
*/
public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
String key, String pref, String def)
{
return getFromSubValArgOrPref(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 String getFromSubValArgOrPref(Arg a, Position pos, ArgValue av,
SubVals sv, String key, String pref, String def)
{
return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
key, pref, def);
}
public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
Position pos, ArgValue av, SubVals sv, String key, String pref,
String def)
{
return getFromSubValArgOrPrefWithSubstitutionsWithinTypes(ap, a, pos,
av, sv, key, pref, def, true);
}
public String getFromSubValArgOrPrefWithSubstitutionsWithinTypes(
ArgParser ap, Arg a, Position pos, ArgValue av, SubVals sv,
String key, String pref, String def, boolean withinTypes)
{
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, getLinkedId(), key);
}
else if (containsArg(a))
{
if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
value = getValue(a);
else if (pos == ArgValuesMap.Position.BEFORE
&& getClosestPreviousArgValueOfArg(av, a) != null)
value = getClosestPreviousArgValueOfArg(av, a).getValue();
else if (pos == ArgValuesMap.Position.AFTER
&& getClosestNextArgValueOfArg(av, a, withinTypes) != null)
value = getClosestNextArgValueOfArg(av, a, withinTypes).getValue();
// look for allstructures subval for Type.STRUCTURE
Arg arg = av.getArg();
if (value == null && arg.hasOption(Opt.PRIMARY)
&& arg.hasType(Type.STRUCTURE) && !a.hasOption(Opt.PRIMARY)
&& (a.getFirstType() == Type.STRUCTURE
// || a.getType() == Type.STRUCTUREIMAGE))
))
{
ArgValue av2 = getArgValueOfArgWithSubValKey(a,
Arg.ALLSTRUCTURES.getName());
if (av2 != null)
{
value = av2.getValue();
}
}
if (value == null)
{
// look for --all --a occurrences
for (ArgValue tmpAv : this.getArgValueList(a))
{
if (tmpAv.setByWildcardLinkedId())
{
value = tmpAv.getValue();
}
}
}
}
if (value == null)
{
value = pref != null ? Cache.getDefault(pref, def) : def;
}
return value;
}
public boolean getBoolFromSubValOrArg(Arg a, SubVals sv)
{
return getFromSubValArgOrPref(a, sv, null, null, false);
}
public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
String pref, boolean def)
{
return getFromSubValArgOrPref(a, sv, key, pref, def, false);
}
public boolean getFromSubValArgOrPref(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 (containsArg(a))
return getBoolean(a);
// return preference or default
boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
return pref != null ? (invertPref ? !prefVal : prefVal) : def;
}
public class ArgInfo implements Comparable
{
private Arg arg;
private String value;
private SubVals subVals;
private int argIndex;
public ArgInfo(Arg arg, String value, SubVals subVals, int argIndex)
{
this.arg = arg;
this.value = value;
this.subVals = subVals;
this.argIndex = argIndex;
}
public Arg arg()
{
return arg;
}
public String value()
{
return value;
}
public SubVals subVals()
{
return subVals;
}
public int argIndex()
{
return argIndex;
}
@Override
public int compareTo(ArgInfo ai2)
{
return Integer.compare(this.argIndex(), ai2.argIndex());
}
}
public static enum Position
{
FIRST, BEFORE, AFTER
}
}