ProjectAres/PGM/src/main/java/tc/oc/pgm/utils/MethodParserMap.java

157 lines
5.6 KiB
Java

package tc.oc.pgm.utils;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import com.google.api.client.util.Throwables;
import com.google.common.reflect.TypeToken;
import org.jdom2.Element;
import tc.oc.commons.core.reflect.MethodHandleUtils;
import tc.oc.commons.core.reflect.Types;
import tc.oc.commons.core.util.ExceptionUtils;
import tc.oc.commons.core.util.Optionals;
import tc.oc.pgm.xml.InvalidXMLException;
import tc.oc.pgm.xml.Node;
public class MethodParserMap<T> {
private class Record {
final Object target;
final Method method;
final MethodHandle handle;
final boolean passElement;
final boolean optional;
private Record(Object target, Method method) {
this.target = target;
this.method = method;
final TypeToken<?> returnType;
if(Optional.class.isAssignableFrom(method.getReturnType())) {
optional = true;
returnType = Optionals.elementType(method.getGenericReturnType());
} else {
optional = false;
returnType = TypeToken.of(method.getGenericReturnType());
}
if(!type.isAssignableFrom(returnType)) {
throw new IllegalStateException("Method " + method + " return type " + returnType + " is not assignable to " + type);
}
if(method.getParameterTypes().length == 0) {
passElement = false;
} else {
if(!(method.getParameterTypes().length == 1 && Element.class.isAssignableFrom(method.getParameterTypes()[0]))) {
throw new IllegalStateException("Method " + method + " should take no parameters, or a single Element parameter");
}
passElement = true;
}
try {
this.handle = MethodHandleUtils.privateLookup(method.getDeclaringClass())
.unreflect(method)
.bindTo(target);
} catch(IllegalAccessException e) {
throw Throwables.propagate(e);
}
}
Object invoke0(Element el) throws InvalidXMLException {
try {
if(passElement) {
return handle.invoke(el);
} else {
return handle.invoke();
}
} catch(InvalidXMLException e) {
e.offerNode(new Node(el));
throw e;
} catch(Throwable e) {
throw ExceptionUtils.propagate(e);
}
}
T invoke(Element el) throws InvalidXMLException {
if(optional) {
throw new IllegalStateException("Invoked optional method as required");
}
return (T) invoke0(el);
}
Optional<T> tryInvoke(Element el) throws InvalidXMLException {
return optional ? (Optional<T>) invoke0(el)
: Optional.of((T) invoke0(el));
}
}
private final TypeToken<T> type;
private final Map<String, Record> methods = new HashMap<>();
public MethodParserMap(@Nullable TypeToken<T> type) {
this.type = type != null ? type : Types.assertFullySpecified(new TypeToken<T>(getClass()){});
}
public void register(String name, Object target, Method method) {
final Record record = methods.get(name);
if(record == null || record.method.getDeclaringClass().isAssignableFrom(method.getDeclaringClass())) {
// Method is new, or overrides a superclass method
methods.put(name, new Record(target, method));
} else if(!method.getDeclaringClass().isAssignableFrom(record.method.getDeclaringClass())) {
// Method is not equal to, or overridden by, the existing one
throw new IllegalStateException("Conflicting parse method '" + name +
"' declared in " + record.method.getDeclaringClass().getName() +
" and " + method.getDeclaringClass().getName());
}
}
public void register(Object target, Method method) {
final MethodParser annot = method.getAnnotation(MethodParser.class);
if(annot != null) {
if(annot.value().length == 0) {
register(method.getName().replace('_', '-'), target, method);
} else {
for(String name : annot.value()) {
register(name, target, method);
}
}
}
}
public void register(Object target) {
for(Class<?> cls : Types.ancestors(target.getClass())) {
for(Method method : cls.getDeclaredMethods()) {
register(target, method);
}
}
}
public boolean hasMethod(String name) {
return methods.containsKey(name);
}
public boolean canParse(Element el) {
return hasMethod(el.getName());
}
private Record record(Element el) throws InvalidXMLException {
final Record record = methods.get(el.getName());
if(record == null) {
throw new InvalidXMLException("Unrecognized element", el);
}
return record;
}
public T parse(Element el) throws InvalidXMLException {
return record(el).invoke(el);
}
public Optional<T> tryParse(Element el) throws InvalidXMLException {
return record(el).tryInvoke(el);
}
}