org.biojava.nbio.structure.align.util.CliTools Maven / Gradle / Ivy
Show all versions of biojava-structure Show documentation
/*
* BioJava development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public Licence. This should
* be distributed with the code. If you do not have a copy,
* see:
*
* http://www.gnu.org/copyleft/lesser.html
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
*/
/*
* BioJava development code
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
*/
package org.biojava.nbio.structure.align.util;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* Utilities for autoconfiguring javabeans based on command line arguments.
*
* @author Thomas Down
*/
public class CliTools {
private CliTools() {
}
/**
* Configure a JavaBean based on a set of command line arguments.
* For a command line construct such as "-foo 42", this method will use
* available BeanInfo
(usually obtained by introspection)
* to find a property named "foo". The argument will be interpreted
* according to the type of the "foo" property, then the appropriate
* mutator method (generally named setFoo) will be called to configure
* the property on the bean.
*
*
* Currently supported property types are int, double,
* boolean, String, File, Reader, Writer, InputStream, OutputStream, Enum
,
* plus arrays of all the above types. In the case of arrays, the option
* may appear multiple times on the command line, otherwise recurrance of
* the same option is an error.
*
*
*
* For stream types, the parameter is interpreted as a filename unless it
* is equal to "-" in which case standard input or standard output are
* used as appropriate. Each of the standard streams may only be used
* one.
*
*
*
* In the future, this method will probably be extended to handle multiple
* parameter occurances, and use Annotations to generate more useful help
* messages when something goes wrong.
*
*
* @param bean
* @param args
* @return A string array which contains any 'anonymous' arguments (may be empty)
* @throws ConfigurationException
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static String[] configureBean(Object bean, String[] args)
throws ConfigurationException
{
BeanInfo bi;
try {
bi = Introspector.getBeanInfo(bean.getClass());
} catch (Exception ex) {
throw new ConfigurationException("Couldn't get information for target bean " + ex.getMessage());
}
Map propertiesByName = new HashMap<>();
for (PropertyDescriptor pd : bi.getPropertyDescriptors() ) {
propertiesByName.put(pd.getName(), pd);
}
List anonArgs = new ArrayList<>();
Map> arrayProps = new HashMap<>();
Set usedProps = new HashSet<>();
boolean stdInUsed = false;
boolean stdOutUsed = false;
for (int i = 0; i < args.length; ++i) {
String arg = args[i];
//System.out.println("checking argument: " + arg);
if ( arg == null)
continue;
if ((arg.length() > 0 ) && arg.charAt(0) == '-') {
PropertyDescriptor pd = propertiesByName.get(arg.substring(1));
boolean arrayMode = false;
Object propVal = null;
Class propType = null;
if (pd == null) {
if (arg.startsWith("-no")) {
String altPropName = Introspector.decapitalize(arg.substring(3));
pd = propertiesByName.get(altPropName);
if (pd == null) {
throw new ConfigurationException("No property named " + arg.substring(1) + " or " + altPropName);
}
propType = pd.getPropertyType();
if (propType == Boolean.TYPE) {
propVal = Boolean.FALSE;
} else {
throw new ConfigurationException("Negatory option " + arg + " does not refer to a boolean property");
}
} else {
throw new ConfigurationException("No property named " + arg.substring(1));
}
} else {
propType = pd.getPropertyType();
if (propType.isArray()) {
arrayMode = true;
propType = propType.getComponentType();
}
if (propType == Integer.TYPE) {
try {
propVal = Integer.valueOf(args[++i]);
} catch (Exception ex) {
throw new ConfigurationException("Option " + arg + " requires an integer parameter");
}
} else if (propType == Double.TYPE || propType == Double.class ) {
try {
propVal = Double.valueOf(args[++i]);
} catch (Exception ex) {
throw new ConfigurationException("Option " + arg + " requires a numerical parameter");
}
} else if (propType == String.class) {
propVal = args[++i];
} else if (propType == Boolean.TYPE) {
String val = args[++i];
if ( val == null )
propVal = Boolean.TRUE;
else {
if ( "true".equalsIgnoreCase(val) || "t".equalsIgnoreCase(val))
propVal = Boolean.TRUE;
else if( "false".equalsIgnoreCase(val) || "f".equalsIgnoreCase(val))
propVal = Boolean.FALSE;
else
throw new ConfigurationException("Option "+arg+" requires a boolean parameter");
}
} else if (File.class.isAssignableFrom(propType)) {
// can't distinguish if the file is for reading or writing
// at the moment, so accept it without validation.
propVal = new File(args[++i]);
} else if (Reader.class.isAssignableFrom(propType)) {
String name = args[++i];
if ("-".equals(name)) {
if (stdInUsed) {
throw new ConfigurationException("Can't use standard input more than once");
}
propVal = new InputStreamReader(System.in);
stdInUsed = true;
} else {
try {
propVal = new FileReader(new File(name));
} catch (Exception ex) {
throw new ConfigurationException("Can't open " + name + " for input");
}
}
} else if (InputStream.class.isAssignableFrom(propType)) {
String name = args[++i];
if ("-".equals(name)) {
if (stdInUsed) {
throw new ConfigurationException("Can't use standard input more than once");
}
propVal = System.in;
stdInUsed = true;
} else {
try {
propVal = new FileInputStream(new File(name));
} catch (Exception ex) {
throw new ConfigurationException("Can't open " + name + " for input");
}
}
} else if (Writer.class.isAssignableFrom(propType)) {
String name = args[++i];
if ("-".equals(name)) {
if (stdOutUsed) {
throw new ConfigurationException("Can't use standard output more than once");
}
propVal = new OutputStreamWriter(System.out);
stdOutUsed = true;
} else {
try {
propVal = new FileWriter(new File(name));
} catch (Exception ex) {
throw new ConfigurationException("Can't open " + name + " for output");
}
}
} else if (OutputStream.class.isAssignableFrom(propType)) {
String name = args[++i];
if ("-".equals(name)) {
if (stdOutUsed) {
throw new ConfigurationException("Can't use standard output more than once");
}
propVal = System.out;
stdOutUsed = true;
} else {
try {
propVal = new FileOutputStream(new File(name));
} catch (Exception ex) {
throw new ConfigurationException("Can't open " + name + " for output");
}
}
} else if( propType.isEnum()) {
String name = args[++i];
try {
propVal = Enum.valueOf(propType, name);
} catch (Exception ex) {
try {
// Try with uppercase version, as common for enums
propVal = Enum.valueOf(propType, name.toUpperCase());
} catch (Exception ex2) {
//give up
StringBuilder errMsg = new StringBuilder();
errMsg.append("Option ").append(arg);
errMsg.append(" requires a ").append(propType.getSimpleName());
errMsg.append(" parameter. One of: ");
for(Object val: propType.getEnumConstants() ) {
Enum enumVal = (Enum) val;
errMsg.append(enumVal.name());
errMsg.append(" ");
}
throw new ConfigurationException(errMsg.toString());
}
}
} else {
System.err.println("Unsupported optionType for " + arg + " propType:" + propType);
System.exit(1);
}
}
//System.out.println("setting to: " + propVal + " " + propVal.getClass().getName());
if (arrayMode) {
List valList = arrayProps.get(pd);
if (valList == null) {
valList = new ArrayList();
arrayProps.put(pd, valList);
}
valList.add(propVal);
} else {
if (usedProps.contains(pd)) {
throw new ConfigurationException("Multiple values supplied for " + pd.getName());
}
try {
pd.getWriteMethod().invoke(bean, new Object[] {propVal});
} catch (InvocationTargetException ex) {
throw new ConfigurationException("Error configuring '" + pd.getName() + "'");
} catch (Exception ex) {
throw new ConfigurationException("Error configuring '" + pd.getName() + "'");
}
usedProps.add(pd);
}
} else {
anonArgs.add(arg);
}
}
for (Iterator api = arrayProps.entrySet().iterator(); api.hasNext(); ) {
Map.Entry me = (Map.Entry) api.next();
PropertyDescriptor pd = (PropertyDescriptor) me.getKey();
List vals = (List) me.getValue();
Class compType = pd.getPropertyType().getComponentType();
Object valArray;
if (compType.isPrimitive()) {
if (compType == Integer.TYPE) {
valArray = CollectionTools.toIntArray(vals);
} else if (compType == Double.TYPE) {
valArray = CollectionTools.toDoubleArray(vals);
} else {
throw new ConfigurationException("Arrays of type " + compType.getName() + " are currently unsupported");
}
} else {
valArray = vals.toArray((Object[]) Array.newInstance(compType, vals.size()));
}
try {
pd.getWriteMethod().invoke(bean, new Object[] {valArray});
} catch (InvocationTargetException ex) {
throw new ConfigurationException("Error configuring '" + pd.getName() + "'");
} catch (Exception ex) {
throw new ConfigurationException("Error configuring '" + pd.getName() + "'");
}
}
return anonArgs.toArray(new String[anonArgs.size()]);
}
/**
* Constructs a comma-separated list of values for an enum.
*
* Example:
* > getEnumValues(ScoringStrategy.class)
* "CA_SCORING, SIDE_CHAIN_SCORING, SIDE_CHAIN_ANGLE_SCORING, CA_AND_SIDE_CHAIN_ANGLE_SCORING, or SEQUENCE_CONSERVATION"
* @param enumClass
* @return
*/
public static > String getEnumValuesAsString(Class enumClass) {
//ScoringStrategy[] vals = ScoringStrategy.values();
T[] vals = enumClass.getEnumConstants();
StringBuilder str = new StringBuilder();
if(vals.length == 1) {
str.append(vals[0].name());
} else if(vals.length > 1 ) {
for(int i=0;i