package mhfc.net.common.util.parsing.valueholders;
import java.lang.invoke.MethodHandle;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import mhfc.net.common.util.parsing.Holder;
import mhfc.net.common.util.parsing.IValueHolder;
import mhfc.net.common.util.parsing.exceptions.MethodNotFoundException;
import mhfc.net.common.util.reflection.MethodHelper;
import mhfc.net.common.util.reflection.OverloadedMethod;
public class FunctionCall implements IValueHolder {
private static interface ICall {
boolean isTypeFinal();
Class<?> getType();
Holder call(Object instance, Arguments args) throws Throwable;
}
private static class MethodProxy implements ICall {
private final MethodHandle method;
private final Supplier<RuntimeException> error;
public MethodProxy(MethodHandle method) {
assert method.type().parameterArray().length == 2;
assert method.type().parameterArray()[1].isAssignableFrom(Arguments.class);
if (!Holder.class.isAssignableFrom(method.type().returnType())) {
error = () -> new IllegalArgumentException("__call__ must return Holder");
} else {
error = null;
}
this.method = method;
}
@Override
public Class<?> getType() {
return IValueHolder.EMPTY_CLASS;
}
@Override
public boolean isTypeFinal() {
return error != null;
}
@Override
public Holder call(Object instance, Arguments args) throws Throwable {
if (error != null) {
throw error.get();
}
return Holder.class.cast(method.invokeWithArguments(instance, args));
}
}
private static class NoCallFound implements ICall {
private final Class<?> clazz;
public NoCallFound(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public Holder call(Object instance, Arguments args) {
throw new MethodNotFoundException(
instance + " not callable: method 'Holder " + clazz.getName()
+ ".__call__(Arguments)' hasn't been found");
}
@Override
public Class<?> getType() {
return IValueHolder.EMPTY_CLASS;
}
@Override
public boolean isTypeFinal() {
return true;
}
}
private static LoadingCache<Class<?>, ICall> callCache;
static {
callCache = CacheBuilder.newBuilder().maximumSize(1000).build(new CacheLoader<Class<?>, ICall>() {
@Override
public ICall load(Class<?> key) {
return computeCall(key);
}
});
}
private static ICall resolveCall(Class<?> clazz) {
return callCache.getUnchecked(clazz);
}
private static ICall computeCall(Class<?> clazz) {
Optional<OverloadedMethod> specialMethod = MethodHelper.find(clazz, "__call__");
Optional<MethodHandle> call = specialMethod.flatMap(s -> s.disambiguate(clazz, Arguments.class));
if (call.isPresent()) {
return new MethodProxy(call.get());
}
return new NoCallFound(clazz);
}
private IValueHolder holder;
private Arguments args;
private FunctionCall(IValueHolder holder, Arguments args) {
this.holder = holder;
this.args = args;
}
@Override
public Holder snapshot() throws Throwable {
Holder instance = holder.snapshot();
return resolveCall(instance.getType()).call(instance.boxed(), args);
}
public static IValueHolder makeFunctionCall(IValueHolder callee, IValueHolder... arguments) {
Arguments args = new Arguments(arguments);
return new FunctionCall(callee, args);
}
@Override
public String toString() {
return holder.toString() + "(" + args.toString() + ")";
}
}