com.ontology2.centipede.parser.OptionParser Maven / Gradle / Ivy
package com.ontology2.centipede.parser;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import com.ontology2.centipede.errors.*;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.ConversionFailedException;
import javax.annotation.Resource;
public class OptionParser {
private final Class that;
@Resource public ConversionService conversionService;
public OptionParser(Class that) {
if (that.isAssignableFrom(HasOptions.class)) {
throw new IllegalArgumentException("Class " + that + " doesn't implement HasOptions");
}
this.that = that;
}
public HasOptions parse(Iterable args) throws IllegalAccessException {
HasOptions options;
try {
options = (HasOptions) that.getConstructor().newInstance();
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Class " + that + " doesn't have a zero argument constructor", ex);
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException("Class " + that + " has a non-public zero argument constructor", ex);
} catch (InstantiationException ex) {
throw new IllegalArgumentException("Class " + that + " cannot be abstract", ex);
} catch (InvocationTargetException ex) {
throw new IllegalArgumentException("Class " + that + " threw an exception during construction", ex);
}
Map lookup = getStringAnnotationMap(that);
for (RWOption o : lookup.values()) {
if (isSomeKindOfBoolean(o)) {
o.getField().setBoolean(options, false);
} else {
Object defaultValue = null;
if (!o.getDefaultValue().isEmpty()) {
try {
defaultValue = conversionService.convert(
o.getDefaultValue()
, TypeDescriptor.valueOf(String.class)
, new TypeDescriptor(o.getField())
);
} catch (ConversionFailedException x) {
throw new UnparsableDefaultException(o.getName(), o.getDefaultValue(), o.getType(), x);
}
} else {
defaultValue = defaultValueFor(o.getType());
}
o.getField().set(options, defaultValue);
}
}
Set sawOption = new HashSet<>();
Iterator p = args.iterator();
String peek=null;
while (p.hasNext()) {
peek = p.next();
if(!peek.startsWith("-"))
break;
String name = peek.substring(1);
if (!lookup.containsKey(name))
throw new InvalidOptionException("invalid option :" + name);
RWOption field = lookup.get(name);
sawOption.add(field.getField());
if (isSomeKindOfBoolean(field)) {
field.getField().setBoolean(options, true);
} else {
String value = p.next();
try {
if (field.isList()) {
String[] parts = value.split(",");
Class elementType = field.getElementType();
for (String part : parts) {
final Object innerValue = field.convertFrom(options, conversionService, part);
((List) field.getField().get(options)).add(innerValue);
}
} else {
final Object innerValue = field.convertFrom(options, conversionService, value);
field.getField().set(options,innerValue);
}
} catch (ConversionFailedException x) {
throw new UnparsableOptionException(name, value, field.getType(), x);
}
}
peek=null;
}
for (RWOption o : lookup.values()) {
if (o.isRequired() && ! sawOption.contains(o.getField()))
throw new MissingOptionException("Required option -"+o.getName()+" is missing");
}
List positional=new ArrayList();
if(peek!=null)
positional.add(peek);
while(p.hasNext())
positional.add(p.next());
Field positionalField=findPositionalParameter(that);
if(positionalField!=null)
positionalField.set(options,positional);
return options;
}
private boolean isSomeKindOfBoolean(RWOption field) {
Type t = field.getType();
return t.equals(Boolean.TYPE) || t.equals(Boolean.class);
}
//
// These are similar to the JLS default values
//
// http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
//
// with the introduction of PHP-isms such as the empty string defaulting to a String,
// a collection defaulting to an empty collection, etc.
//
static Object defaultValueFor(Type type) {
if (Byte.TYPE.equals(type)) {
return (byte) 0;
}
;
if (Short.TYPE.equals(type)) {
return (short) 0;
}
if (Integer.TYPE.equals(type)) {
return (int) 0;
}
if (Long.TYPE.equals(type)) {
return 0L;
}
if (Float.TYPE.equals(type)) {
return 0.0F;
}
if (Double.TYPE.equals(type)) {
return 0.0;
}
if (Boolean.TYPE.equals(type)) {
return false;
}
if (Character.TYPE.equals(type)) {
return '\0';
}
if (String.class.equals(type)) {
return "";
}
if (List.class.equals(type)) {
return new ArrayList();
}
if (type instanceof ParameterizedType) {
ParameterizedType generic = (ParameterizedType) type;
if (List.class.isAssignableFrom((Class) generic.getRawType())) {
return new ArrayList();
}
}
return null;
}
static Map getStringAnnotationMap(Class that) {
Map lookup = new HashMap();
for (Field f : that.getFields()) {
if(null==f.getAnnotation(Option.class))
continue;
RWOption o = new RWOption(f);
if (o != null) {
if (o.getName().isEmpty()) {
o.setName(f.getName());
}
o.setType(f.getGenericType());
lookup.put(o.getName(), o);
}
}
return lookup;
}
static Field findPositionalParameter(Class that) {
for (Field f : that.getFields())
if(f.getAnnotation(Positional.class)!=null) {
if(!List.class.isAssignableFrom(f.getType()))
throw new MisconfigurationException("The @Positional parameter must be a List");
ParameterizedType generic=(ParameterizedType) f.getGenericType();
if(!String.class.equals(generic.getActualTypeArguments()[0]))
throw new MisconfigurationException("The @Positional parameter must be a List");
return f;
}
return null;
}
}