/*
* 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.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
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 java.util.stream.Collectors;
import jalview.bin.argparser.Arg.Opt;
import jalview.bin.argparser.Arg.Type;
import jalview.util.FileUtils;
public class BootstrapArgs
{
// only need one
private Map>> bootstrapArgMap = new HashMap<>();
private Set argFiles = new HashSet<>();
private Set argsOptions = new HashSet<>();
private Set argsTypes = new HashSet<>();
private boolean outputToStdout = false;
public static BootstrapArgs getBootstrapArgs(String[] args)
{
List argList = new ArrayList<>(Arrays.asList(args));
return new BootstrapArgs(argList);
}
public static BootstrapArgs getBootstrapArgs(List args)
{
return new BootstrapArgs(args);
}
private BootstrapArgs(List args)
{
parse(args, null);
}
private void parse(List args, File inArgFile)
{
if (args == null)
return;
// avoid looping argFiles
if (inArgFile != null)
{
if (argFiles.contains(inArgFile))
{
jalview.bin.Console.errPrintln(
"Looped argfiles detected: '" + inArgFile.getPath() + "'");
return;
}
argFiles.add(inArgFile);
}
for (int i = 0; i < args.size(); i++)
{
String arg = args.get(i);
// look for double-dash, e.g. --arg
if (arg.startsWith(ArgParser.DOUBLEDASH))
{
String argName = null;
String val = null;
Type type = null;
// remove "--"
argName = arg.substring(ArgParser.DOUBLEDASH.length());
// look for equals e.g. --arg=value
int equalPos = argName.indexOf(ArgParser.EQUALS);
if (equalPos > -1)
{
val = argName.substring(equalPos + 1);
argName = argName.substring(0, equalPos);
}
// check for boolean prepended by "no"
if (argName.startsWith(ArgParser.NEGATESTRING)
&& ArgParser.argMap.containsKey(
argName.substring(ArgParser.NEGATESTRING.length())))
{
val = "false";
argName = argName.substring(ArgParser.NEGATESTRING.length());
}
// look for type modification e.g. --help-opening
int dashPos = argName.indexOf(ArgParser.SINGLEDASH);
if (dashPos > -1)
{
String potentialArgName = argName.substring(0, dashPos);
Arg potentialArg = ArgParser.argMap.get(potentialArgName);
if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
{
String typeName = argName.substring(dashPos + 1);
try
{
type = Type.valueOf(typeName.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e)
{
type = Type.INVALID;
}
argName = argName.substring(0, dashPos);
}
}
// after all other args, look for Opt.PREFIX args if still not found
if (!ArgParser.argMap.containsKey(argName))
{
for (Arg potentialArg : EnumSet.allOf(Arg.class))
{
if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
&& argName.startsWith(potentialArg.getName())
&& val != null)
{
val = argName.substring(potentialArg.getName().length())
+ ArgParser.EQUALS + val;
argName = argName.substring(0,
potentialArg.getName().length());
break;
}
}
}
Arg a = ArgParser.argMap.get(argName);
if (a != null)
{
for (Opt opt : a.getOptions())
{
if (!argsOptions.contains(opt))
{
argsOptions.add(opt);
}
}
Type t = a.getType();
if (!argsTypes.contains(t))
{
argsTypes.add(t);
}
}
if (a == null || !a.hasOption(Opt.BOOTSTRAP))
{
// not a bootstrap arg
// make a check for an output going to stdout
if (a != null && a.hasOption(Opt.OUTPUTFILE)
&& a.hasOption(Opt.STDOUT))
{
if (val == null && i + 1 < args.size())
{
val = args.get(i + 1);
}
if (val.startsWith("[") && val.indexOf(']') > 0)
{
val = val.substring(val.indexOf(']') + 1);
}
if (ArgParser.STDOUTFILENAME.equals(val))
{
this.outputToStdout = true;
}
}
continue;
}
if (a.hasOption(Opt.STRING))
{
List vals = null;
if (equalPos == -1)
{
vals = ArgParser.getShellGlobbedFilenameValues(a, args, i + 1);
}
else
{
if (a.hasOption(Opt.GLOB))
{
vals = FileUtils.getFilenamesFromGlob(val);
}
else
{
vals = new ArrayList<>();
vals.add(val);
}
}
addAll(a, type, vals);
if (a == Arg.ARGFILE)
{
for (String filename : vals)
{
File argFile = new File(filename);
parse(ArgParser.readArgFile(argFile), argFile);
}
}
}
else
{
if (val == null)
{
val = "true";
}
add(a, type, val);
}
}
}
// if in an argfile, remove it from the hashset so it can be re-used in
// another argfile
if (inArgFile != null)
{
argFiles.remove(inArgFile);
}
}
public boolean contains(Arg a)
{
return bootstrapArgMap.containsKey(a);
}
public boolean containsType(Type t)
{
for (List> l : bootstrapArgMap.values())
{
for (Map.Entry e : l)
{
if (e.getKey() == t)
return true;
}
}
return false;
}
public List getArgsOfType(Type t)
{
return getArgsOfType(t, new Opt[] {});
}
public List getArgsOfType(Type t, Opt... opts)
{
List args = new ArrayList<>();
for (Arg a : bootstrapArgMap.keySet())
{
if (!a.hasAllOptions(opts))
continue;
List> l = bootstrapArgMap.get(a);
if (l.stream().anyMatch(e -> e.getKey() == t))
{
args.add(a);
}
}
return args;
}
public List> getList(Arg a)
{
return bootstrapArgMap.get(a);
}
public List getValueList(Arg a)
{
return bootstrapArgMap.get(a).stream().map(e -> e.getValue())
.collect(Collectors.toList());
}
private List> getOrCreateList(Arg a)
{
List> l = getList(a);
if (l == null)
{
l = new ArrayList<>();
putList(a, l);
}
return l;
}
private void putList(Arg a, List> l)
{
bootstrapArgMap.put(a, l);
}
/*
* Creates a new list if not used before,
* adds the value unless the existing list is non-empty
* and the arg is not MULTI (so first expressed value is
* retained).
*/
private void add(Arg a, Type t, String s)
{
List> l = getOrCreateList(a);
if (a.hasOption(Opt.MULTIVALUE) || l.size() == 0)
{
l.add(entry(t, s));
}
}
private void addAll(Arg a, Type t, List al)
{
List> l = getOrCreateList(a);
if (a.hasOption(Opt.MULTIVALUE))
{
for (String s : al)
{
l.add(entry(t, s));
}
}
else if (l.size() == 0 && al.size() > 0)
{
l.add(entry(t, al.get(0)));
}
}
private static Map.Entry entry(Type t, String s)
{
return new AbstractMap.SimpleEntry(t, s);
}
/*
* Retrieves the first value even if MULTI.
* A convenience for non-MULTI args.
*/
public String getValue(Arg a)
{
if (!bootstrapArgMap.containsKey(a))
return null;
List> aL = bootstrapArgMap.get(a);
return (aL == null || aL.size() == 0) ? null : aL.get(0).getValue();
}
public boolean getBoolean(Arg a, boolean d)
{
if (!bootstrapArgMap.containsKey(a))
return d;
return Boolean.parseBoolean(getValue(a));
}
public boolean getBoolean(Arg a)
{
if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
{
return false;
}
if (bootstrapArgMap.containsKey(a))
{
return Boolean.parseBoolean(getValue(a));
}
else
{
return a.getDefaultBoolValue();
}
}
public boolean argsHaveOption(Opt opt)
{
return argsOptions.contains(opt);
}
public boolean argsHaveType(Type type)
{
return argsTypes.contains(type);
}
public boolean isHeadless()
{
boolean isHeadless = false;
if (this.argsHaveType(Type.HELP))
{
// --help, --help-all, ... specified => headless
isHeadless = true;
}
else if (this.contains(Arg.VERSION))
{
// --version specified => headless
isHeadless = true;
}
else if (this.contains(Arg.GUI))
{
// --gui specified => forced NOT headless
isHeadless = !this.getBoolean(Arg.GUI);
}
else if (this.contains(Arg.HEADLESS))
{
// --headless has been specified on the command line => headless
isHeadless = this.getBoolean(Arg.HEADLESS);
}
else if (this.argsHaveOption(Opt.OUTPUTFILE))
{
// --output file.fa, --image pic.png, --structureimage struct.png =>
// assume headless unless above has been already specified
isHeadless = true;
}
return isHeadless;
}
public boolean outputToStdout()
{
return this.outputToStdout;
}
}