|
|
|
@@ -11,37 +11,31 @@ import com.google.common.primitives.Primitives;
|
|
|
|
|
import com.google.common.reflect.TypeToken;
|
|
|
|
|
import dan200.computercraft.api.lua.*;
|
|
|
|
|
import dan200.computercraft.core.methods.LuaMethod;
|
|
|
|
|
import org.objectweb.asm.*;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
|
import java.lang.constant.ConstantDescs;
|
|
|
|
|
import java.lang.invoke.MethodHandle;
|
|
|
|
|
import java.lang.invoke.MethodHandles;
|
|
|
|
|
import java.lang.invoke.MethodType;
|
|
|
|
|
import java.lang.reflect.Member;
|
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
|
import java.lang.reflect.Modifier;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.lang.reflect.Type;
|
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
|
|
|
|
|
import static org.objectweb.asm.Opcodes.*;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The underlying generator for {@link LuaFunction}-annotated methods.
|
|
|
|
|
* <p>
|
|
|
|
|
* The constructor {@link Generator#Generator(Class, List, Function)} takes in the type of interface to generate (i.e.
|
|
|
|
|
* {@link LuaMethod}), the context arguments for this function (in the case of {@link LuaMethod}, this will just be
|
|
|
|
|
* {@link ILuaContext}) and a "wrapper" function to lift a function to execute on the main thread.
|
|
|
|
|
* The constructor {@link Generator#Generator(List, Function, Function)} takes in the type of interface to generate
|
|
|
|
|
* (i.e. {@link LuaMethod}), the context arguments for this function (in the case of {@link LuaMethod}, this will just
|
|
|
|
|
* be {@link ILuaContext}), a factory function (which invokes a method handle), and a "wrapper" function to lift a
|
|
|
|
|
* function to execute on the main thread.
|
|
|
|
|
* <p>
|
|
|
|
|
* The generated class then implements this interface - the {@code apply} method calls the appropriate methods on
|
|
|
|
|
* {@link IArguments} to extract the arguments, and then calls the original method.
|
|
|
|
|
* <p>
|
|
|
|
|
* As the method is not guaranteed to come from the same classloader, we cannot call the method directly, as that may
|
|
|
|
|
* result in linkage errors. We instead inject a {@link MethodHandle} into the class as a dynamic constant, and then
|
|
|
|
|
* call the method with {@link MethodHandle#invokeExact(Object...)}. The method handle is constant, and so this has
|
|
|
|
|
* equivalent performance to the direct call.
|
|
|
|
|
* For each input function, the generator then fabricates a {@link MethodHandle} which performs the argument validation,
|
|
|
|
|
* and then calls the factory function to convert it to the desired interface.
|
|
|
|
|
*
|
|
|
|
|
* @param <T> The type of the interface the generated classes implement.
|
|
|
|
|
*/
|
|
|
|
@@ -49,47 +43,87 @@ final class Generator<T> {
|
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(Generator.class);
|
|
|
|
|
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
|
|
|
|
|
|
|
|
|
private static final String METHOD_NAME = "apply";
|
|
|
|
|
private static final String[] EXCEPTIONS = new String[]{ Type.getInternalName(LuaException.class) };
|
|
|
|
|
private static final MethodHandle METHOD_RESULT_OF_VOID, METHOD_RESULT_OF_ONE, METHOD_RESULT_OF_MANY;
|
|
|
|
|
|
|
|
|
|
private static final String INTERNAL_METHOD_RESULT = Type.getInternalName(MethodResult.class);
|
|
|
|
|
private static final String DESC_METHOD_RESULT = Type.getDescriptor(MethodResult.class);
|
|
|
|
|
private static final Map<Class<?>, ArgMethods> argMethods;
|
|
|
|
|
private static final ArgMethods ARG_TABLE_UNSAFE;
|
|
|
|
|
private static final MethodHandle ARG_GET_OBJECT, ARG_GET_ENUM, ARG_OPT_ENUM, ARG_GET_STRING_COERCED;
|
|
|
|
|
|
|
|
|
|
private static final String INTERNAL_ARGUMENTS = Type.getInternalName(IArguments.class);
|
|
|
|
|
private static final String DESC_ARGUMENTS = Type.getDescriptor(IArguments.class);
|
|
|
|
|
private record ArgMethods(MethodHandle get, MethodHandle opt) {
|
|
|
|
|
public static ArgMethods of(Class<?> type, String name) throws ReflectiveOperationException {
|
|
|
|
|
return new ArgMethods(
|
|
|
|
|
LOOKUP.findVirtual(IArguments.class, "get" + name, MethodType.methodType(type, int.class)),
|
|
|
|
|
LOOKUP.findVirtual(IArguments.class, "opt" + name, MethodType.methodType(Optional.class, int.class))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static final String INTERNAL_COERCED = Type.getInternalName(Coerced.class);
|
|
|
|
|
static void addArgType(Map<Class<?>, ArgMethods> types, Class<?> type, String name) throws ReflectiveOperationException {
|
|
|
|
|
types.put(type, ArgMethods.of(type, name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static final ConstantDynamic METHOD_CONSTANT = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), new Handle(
|
|
|
|
|
H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData",
|
|
|
|
|
MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class).descriptorString(), false
|
|
|
|
|
));
|
|
|
|
|
static {
|
|
|
|
|
try {
|
|
|
|
|
METHOD_RESULT_OF_VOID = LOOKUP.findStatic(MethodResult.class, "of", MethodType.methodType(MethodResult.class));
|
|
|
|
|
METHOD_RESULT_OF_ONE = LOOKUP.findStatic(MethodResult.class, "of", MethodType.methodType(MethodResult.class, Object.class));
|
|
|
|
|
METHOD_RESULT_OF_MANY = LOOKUP.findStatic(MethodResult.class, "of", MethodType.methodType(MethodResult.class, Object[].class));
|
|
|
|
|
|
|
|
|
|
Map<Class<?>, ArgMethods> argMethodMap = new HashMap<>();
|
|
|
|
|
addArgType(argMethodMap, int.class, "Int");
|
|
|
|
|
addArgType(argMethodMap, boolean.class, "Boolean");
|
|
|
|
|
addArgType(argMethodMap, double.class, "Double");
|
|
|
|
|
addArgType(argMethodMap, long.class, "Long");
|
|
|
|
|
addArgType(argMethodMap, Map.class, "Table");
|
|
|
|
|
addArgType(argMethodMap, String.class, "String");
|
|
|
|
|
addArgType(argMethodMap, ByteBuffer.class, "Bytes");
|
|
|
|
|
argMethods = Map.copyOf(argMethodMap);
|
|
|
|
|
|
|
|
|
|
ARG_TABLE_UNSAFE = ArgMethods.of(LuaTable.class, "TableUnsafe");
|
|
|
|
|
ARG_GET_OBJECT = LOOKUP.findVirtual(IArguments.class, "get", MethodType.methodType(Object.class, int.class));
|
|
|
|
|
ARG_GET_ENUM = LOOKUP.findVirtual(IArguments.class, "getEnum", MethodType.methodType(Enum.class, int.class, Class.class));
|
|
|
|
|
ARG_OPT_ENUM = LOOKUP.findVirtual(IArguments.class, "optEnum", MethodType.methodType(Optional.class, int.class, Class.class));
|
|
|
|
|
|
|
|
|
|
// Create a new Coerced<>(args.getStringCoerced(_)) function.
|
|
|
|
|
ARG_GET_STRING_COERCED = MethodHandles.filterReturnValue(
|
|
|
|
|
setReturn(LOOKUP.findVirtual(IArguments.class, "getStringCoerced", MethodType.methodType(String.class, int.class)), Object.class),
|
|
|
|
|
LOOKUP.findConstructor(Coerced.class, MethodType.methodType(void.class, Object.class))
|
|
|
|
|
);
|
|
|
|
|
} catch (ReflectiveOperationException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final Class<T> base;
|
|
|
|
|
private final List<Class<?>> context;
|
|
|
|
|
private final List<Class<?>> contextWithArguments;
|
|
|
|
|
private final MethodHandle argumentGetter;
|
|
|
|
|
private final List<MethodHandle> contextGetters;
|
|
|
|
|
|
|
|
|
|
private final String[] interfaces;
|
|
|
|
|
private final String methodDesc;
|
|
|
|
|
private final String classPrefix;
|
|
|
|
|
|
|
|
|
|
private final Function<MethodHandle, T> factory;
|
|
|
|
|
private final Function<T, T> wrap;
|
|
|
|
|
|
|
|
|
|
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder
|
|
|
|
|
.newBuilder()
|
|
|
|
|
.build(CacheLoader.from(catching(this::build, Optional.empty())));
|
|
|
|
|
|
|
|
|
|
Generator(Class<T> base, List<Class<?>> context, Function<T, T> wrap) {
|
|
|
|
|
this.base = base;
|
|
|
|
|
Generator(List<Class<?>> context, Function<MethodHandle, T> factory, Function<T, T> wrap) {
|
|
|
|
|
this.context = context;
|
|
|
|
|
interfaces = new String[]{ Type.getInternalName(base) };
|
|
|
|
|
this.factory = factory;
|
|
|
|
|
this.wrap = wrap;
|
|
|
|
|
|
|
|
|
|
var methodDesc = new StringBuilder().append("(Ljava/lang/Object;");
|
|
|
|
|
for (var klass : context) methodDesc.append(Type.getDescriptor(klass));
|
|
|
|
|
methodDesc.append(DESC_ARGUMENTS).append(")").append(DESC_METHOD_RESULT);
|
|
|
|
|
this.methodDesc = methodDesc.toString();
|
|
|
|
|
var contextWithArguments = this.contextWithArguments = new ArrayList<>(context.size() + 1);
|
|
|
|
|
contextWithArguments.addAll(context);
|
|
|
|
|
contextWithArguments.add(IArguments.class);
|
|
|
|
|
|
|
|
|
|
classPrefix = Generator.class.getPackageName() + "." + base.getSimpleName() + "$";
|
|
|
|
|
// Prepare a series of getters of the type (context..., IArguments) -> _ (or some prefix of this), for
|
|
|
|
|
// extracting a single context value.
|
|
|
|
|
argumentGetter = MethodHandles.dropArguments(MethodHandles.identity(IArguments.class), 0, context);
|
|
|
|
|
|
|
|
|
|
var contextGetters = this.contextGetters = new ArrayList<>(context.size());
|
|
|
|
|
for (var i = 0; i < context.size(); i++) {
|
|
|
|
|
var getter = MethodHandles.identity(context.get(i));
|
|
|
|
|
if (i > 0) getter = MethodHandles.dropArguments(getter, 0, contextWithArguments.subList(0, i));
|
|
|
|
|
contextGetters.add(getter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<T> getMethod(Method method) {
|
|
|
|
@@ -131,184 +165,144 @@ final class Generator<T> {
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We have some rather ugly handling of static methods in both here and the main generate function. Static methods
|
|
|
|
|
// only come from generic sources, so this should be safe.
|
|
|
|
|
var target = Modifier.isStatic(modifiers) ? method.getParameterTypes()[0] : method.getDeclaringClass();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var handle = LOOKUP.unreflect(method);
|
|
|
|
|
var originalHandle = LOOKUP.unreflect(method);
|
|
|
|
|
|
|
|
|
|
// Convert the handle from one of the form (target, ...) -> ret type to (Object, ...) -> Object. This both
|
|
|
|
|
// handles the boxing of primitives for us, and ensures our bytecode does not reference any external types.
|
|
|
|
|
// We could handle the conversion to MethodResult here too, but it doesn't feel worth it.
|
|
|
|
|
var widenedHandle = handle.asType(widenMethodType(handle.type(), target));
|
|
|
|
|
List<Type> parameters;
|
|
|
|
|
if (Modifier.isStatic(modifiers)) {
|
|
|
|
|
var allParameters = method.getGenericParameterTypes();
|
|
|
|
|
parameters = Arrays.asList(allParameters).subList(1, allParameters.length);
|
|
|
|
|
} else {
|
|
|
|
|
parameters = Arrays.asList(method.getGenericParameterTypes());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bytes = generate(classPrefix + method.getName(), target, method, widenedHandle.type().descriptorString(), annotation.unsafe());
|
|
|
|
|
if (bytes == null) return Optional.empty();
|
|
|
|
|
var handle = buildMethodHandle(method, originalHandle, parameters, annotation.unsafe());
|
|
|
|
|
if (handle == null) return Optional.empty();
|
|
|
|
|
|
|
|
|
|
var klass = LOOKUP.defineHiddenClassWithClassData(bytes, widenedHandle, true).lookupClass();
|
|
|
|
|
|
|
|
|
|
var instance = klass.asSubclass(base).getDeclaredConstructor().newInstance();
|
|
|
|
|
var instance = factory.apply(handle);
|
|
|
|
|
return Optional.of(annotation.mainThread() ? wrap.apply(instance) : instance);
|
|
|
|
|
} catch (ReflectiveOperationException | ClassFormatError | RuntimeException e) {
|
|
|
|
|
} catch (ReflectiveOperationException | RuntimeException e) {
|
|
|
|
|
LOG.error("Error generating wrapper for {}.", name, e);
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static MethodType widenMethodType(MethodType source, Class<?> target) {
|
|
|
|
|
// Treat the target argument as just Object - we'll do the cast in the method handle.
|
|
|
|
|
var args = source.parameterArray();
|
|
|
|
|
for (var i = 0; i < args.length; i++) {
|
|
|
|
|
if (args[i] == target) args[i] = Object.class;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And convert the return value to Object if needed.
|
|
|
|
|
var ret = source.returnType();
|
|
|
|
|
return ret == void.class || ret == MethodResult.class || ret == Object[].class
|
|
|
|
|
? MethodType.methodType(ret, args)
|
|
|
|
|
: MethodType.methodType(Object.class, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert the given handle from type {@code (target, args...) -> ret} to {@code (Object, context..., IArguments) -> MethodResult},
|
|
|
|
|
* inserting calls to {@link IArguments}'s getters, and wrapping the result with {@link MethodResult#of()}.
|
|
|
|
|
*
|
|
|
|
|
* @param method The original method, for error reporting.
|
|
|
|
|
* @param handle The method handle to wrap.
|
|
|
|
|
* @param parameterTypes The generic parameter types to this method. This should have the same type as the {@code handle}.
|
|
|
|
|
* @param unsafe Whether to allow unsafe argument getters.
|
|
|
|
|
* @return The wrapped method handle.
|
|
|
|
|
*/
|
|
|
|
|
@Nullable
|
|
|
|
|
private byte[] generate(String className, Class<?> target, Method targetMethod, String targetDescriptor, boolean unsafe) {
|
|
|
|
|
var internalName = className.replace(".", "/");
|
|
|
|
|
|
|
|
|
|
// Construct a public final class which extends Object and implements MethodInstance.Delegate
|
|
|
|
|
var cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
|
|
|
|
cw.visit(V17, ACC_PUBLIC | ACC_FINAL, internalName, null, "java/lang/Object", interfaces);
|
|
|
|
|
cw.visitSource("CC generated method", null);
|
|
|
|
|
|
|
|
|
|
{ // Constructor just invokes super.
|
|
|
|
|
var mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
|
|
|
|
mw.visitCode();
|
|
|
|
|
mw.visitVarInsn(ALOAD, 0);
|
|
|
|
|
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
|
|
|
|
mw.visitInsn(RETURN);
|
|
|
|
|
mw.visitMaxs(0, 0);
|
|
|
|
|
mw.visitEnd();
|
|
|
|
|
private MethodHandle buildMethodHandle(Member method, MethodHandle handle, List<Type> parameterTypes, boolean unsafe) {
|
|
|
|
|
if (handle.type().parameterCount() != parameterTypes.size() + 1) {
|
|
|
|
|
throw new IllegalArgumentException("Argument lists are mismatched");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
var mw = cw.visitMethod(ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS);
|
|
|
|
|
mw.visitCode();
|
|
|
|
|
// We start off with a method handle of type (target, args...) -> _. We then append the context and IArguments
|
|
|
|
|
// to the end, leaving a handle with type (target, args..., context..., IArguments) -> _.
|
|
|
|
|
handle = MethodHandles.dropArguments(handle, handle.type().parameterCount(), contextWithArguments);
|
|
|
|
|
|
|
|
|
|
mw.visitLdcInsn(METHOD_CONSTANT);
|
|
|
|
|
// Then for each argument, generate a method handle of type (context..., IArguments) -> _, which is used to
|
|
|
|
|
// extract this argument.
|
|
|
|
|
var argCount = 0;
|
|
|
|
|
List<MethodHandle> argSelectors = new ArrayList<>(parameterTypes.size());
|
|
|
|
|
for (var paramType : parameterTypes) {
|
|
|
|
|
var paramClass = Reflect.getRawType(method, paramType, true);
|
|
|
|
|
if (paramClass == null) return null;
|
|
|
|
|
|
|
|
|
|
// If we're an instance method, load the target as the first argument.
|
|
|
|
|
if (!Modifier.isStatic(targetMethod.getModifiers())) mw.visitVarInsn(ALOAD, 1);
|
|
|
|
|
|
|
|
|
|
var argIndex = 0;
|
|
|
|
|
for (var genericArg : targetMethod.getGenericParameterTypes()) {
|
|
|
|
|
var loadedArg = loadArg(mw, target, targetMethod, unsafe, genericArg, argIndex);
|
|
|
|
|
if (loadedArg == null) return null;
|
|
|
|
|
if (loadedArg) argIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", targetDescriptor, false);
|
|
|
|
|
|
|
|
|
|
// We allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
|
|
|
|
|
// we convert basic types into an immediate result.
|
|
|
|
|
var ret = targetMethod.getReturnType();
|
|
|
|
|
if (ret != MethodResult.class) {
|
|
|
|
|
if (ret == void.class) {
|
|
|
|
|
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "()" + DESC_METHOD_RESULT, false);
|
|
|
|
|
} else if (ret == Object[].class) {
|
|
|
|
|
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "([Ljava/lang/Object;)" + DESC_METHOD_RESULT, false);
|
|
|
|
|
// We first generate a method handle of type (context..., IArguments) -> _, which is used to extract this
|
|
|
|
|
// argument.
|
|
|
|
|
MethodHandle argSelector;
|
|
|
|
|
if (paramClass == IArguments.class) {
|
|
|
|
|
argSelector = argumentGetter;
|
|
|
|
|
} else {
|
|
|
|
|
var idx = context.indexOf(paramClass);
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
|
argSelector = contextGetters.get(idx);
|
|
|
|
|
} else {
|
|
|
|
|
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false);
|
|
|
|
|
var selector = loadArg(method, unsafe, paramClass, paramType, argCount++);
|
|
|
|
|
if (selector == null) return null;
|
|
|
|
|
argSelector = MethodHandles.filterReturnValue(argumentGetter, selector);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mw.visitInsn(ARETURN);
|
|
|
|
|
|
|
|
|
|
mw.visitMaxs(0, 0);
|
|
|
|
|
mw.visitEnd();
|
|
|
|
|
argSelectors.add(argSelector);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cw.visitEnd();
|
|
|
|
|
// Fold over the original method's arguments, excluding the target in reverse. For each argument, we reduce
|
|
|
|
|
// a method of type type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
|
|
|
|
|
// until eventually we've flattened the whole list.
|
|
|
|
|
for (var i = parameterTypes.size() - 1; i >= 0; i--) {
|
|
|
|
|
handle = MethodHandles.foldArguments(handle, i + 1, argSelectors.get(i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cw.toByteArray();
|
|
|
|
|
// Then cast the target to Object, so it's compatible with the desired type.
|
|
|
|
|
handle = handle.asType(handle.type().changeParameterType(0, Object.class));
|
|
|
|
|
|
|
|
|
|
// Finally wrap the returned value into a MethodResult.
|
|
|
|
|
var type = handle.type();
|
|
|
|
|
var ret = type.returnType();
|
|
|
|
|
if (ret == MethodResult.class) {
|
|
|
|
|
return handle;
|
|
|
|
|
} else if (ret == void.class) {
|
|
|
|
|
return MethodHandles.filterReturnValue(handle, METHOD_RESULT_OF_VOID);
|
|
|
|
|
} else if (ret == Object[].class) {
|
|
|
|
|
return MethodHandles.filterReturnValue(handle, METHOD_RESULT_OF_MANY);
|
|
|
|
|
} else {
|
|
|
|
|
return MethodHandles.filterReturnValue(handle.asType(type.changeReturnType(Object.class)), METHOD_RESULT_OF_ONE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
|
private Boolean loadArg(MethodVisitor mw, Class<?> target, Method method, boolean unsafe, java.lang.reflect.Type genericArg, int argIndex) {
|
|
|
|
|
if (genericArg == target) {
|
|
|
|
|
mw.visitVarInsn(ALOAD, 1);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var arg = Reflect.getRawType(method, genericArg, true);
|
|
|
|
|
if (arg == null) return null;
|
|
|
|
|
|
|
|
|
|
if (arg == IArguments.class) {
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + context.size());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var idx = context.indexOf(arg);
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + idx);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (arg == Coerced.class) {
|
|
|
|
|
private static MethodHandle loadArg(Member method, boolean unsafe, Class<?> argType, Type genericArg, int argIndex) {
|
|
|
|
|
if (argType == Coerced.class) {
|
|
|
|
|
var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.COERCED_IN).getType(), false);
|
|
|
|
|
if (klass == null) return null;
|
|
|
|
|
|
|
|
|
|
if (klass == String.class) {
|
|
|
|
|
mw.visitTypeInsn(NEW, INTERNAL_COERCED);
|
|
|
|
|
mw.visitInsn(DUP);
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + context.size());
|
|
|
|
|
Reflect.loadInt(mw, argIndex);
|
|
|
|
|
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getStringCoerced", "(I)Ljava/lang/String;", true);
|
|
|
|
|
mw.visitMethodInsn(INVOKESPECIAL, INTERNAL_COERCED, "<init>", "(Ljava/lang/Object;)V", false);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (klass == String.class) return MethodHandles.insertArguments(ARG_GET_STRING_COERCED, 1, argIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (arg == Optional.class) {
|
|
|
|
|
var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.OPTIONAL_IN).getType(), false);
|
|
|
|
|
if (klass == null) return null;
|
|
|
|
|
if (argType == Optional.class) {
|
|
|
|
|
var optType = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.OPTIONAL_IN).getType(), false);
|
|
|
|
|
if (optType == null) return null;
|
|
|
|
|
|
|
|
|
|
if (Enum.class.isAssignableFrom(klass) && klass != Enum.class) {
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + context.size());
|
|
|
|
|
Reflect.loadInt(mw, argIndex);
|
|
|
|
|
mw.visitLdcInsn(Type.getType(klass));
|
|
|
|
|
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "optEnum", "(ILjava/lang/Class;)Ljava/util/Optional;", true);
|
|
|
|
|
return true;
|
|
|
|
|
if (Enum.class.isAssignableFrom(optType) && optType != Enum.class) {
|
|
|
|
|
return MethodHandles.insertArguments(ARG_OPT_ENUM, 1, argIndex, optType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var name = Reflect.getLuaName(Primitives.unwrap(klass), unsafe);
|
|
|
|
|
if (name != null) {
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + context.size());
|
|
|
|
|
Reflect.loadInt(mw, argIndex);
|
|
|
|
|
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "opt" + name, "(I)Ljava/util/Optional;", true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
var getter = getArgMethods(Primitives.unwrap(optType), unsafe);
|
|
|
|
|
if (getter != null) return MethodHandles.insertArguments(getter.opt(), 1, argIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Enum.class.isAssignableFrom(arg) && arg != Enum.class) {
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + context.size());
|
|
|
|
|
Reflect.loadInt(mw, argIndex);
|
|
|
|
|
mw.visitLdcInsn(Type.getType(arg));
|
|
|
|
|
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getEnum", "(ILjava/lang/Class;)Ljava/lang/Enum;", true);
|
|
|
|
|
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(arg));
|
|
|
|
|
return true;
|
|
|
|
|
if (Enum.class.isAssignableFrom(argType) && argType != Enum.class) {
|
|
|
|
|
return setReturn(MethodHandles.insertArguments(ARG_GET_ENUM, 1, argIndex, argType), argType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var name = arg == Object.class ? "" : Reflect.getLuaName(arg, unsafe);
|
|
|
|
|
if (name != null) {
|
|
|
|
|
if (Reflect.getRawType(method, genericArg, false) == null) return null;
|
|
|
|
|
if (argType == Object.class) return MethodHandles.insertArguments(ARG_GET_OBJECT, 1, argIndex);
|
|
|
|
|
|
|
|
|
|
mw.visitVarInsn(ALOAD, 2 + context.size());
|
|
|
|
|
Reflect.loadInt(mw, argIndex);
|
|
|
|
|
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "get" + name, "(I)" + Type.getDescriptor(arg), true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Check we don't have a non-wildcard generic.
|
|
|
|
|
if (Reflect.getRawType(method, genericArg, false) == null) return null;
|
|
|
|
|
|
|
|
|
|
LOG.error("Unknown parameter type {} for method {}.{}.",
|
|
|
|
|
arg.getName(), method.getDeclaringClass().getName(), method.getName());
|
|
|
|
|
var getter = getArgMethods(argType, unsafe);
|
|
|
|
|
if (getter != null) return MethodHandles.insertArguments(getter.get(), 1, argIndex);
|
|
|
|
|
|
|
|
|
|
LOG.error("Unknown parameter type {} for method {}.{}.", argType.getName(), method.getDeclaringClass().getName(), method.getName());
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static MethodHandle setReturn(MethodHandle handle, Class<?> retTy) {
|
|
|
|
|
return handle.asType(handle.type().changeReturnType(retTy));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static @Nullable ArgMethods getArgMethods(Class<?> type, boolean unsafe) {
|
|
|
|
|
var getter = argMethods.get(type);
|
|
|
|
|
if (getter != null) return getter;
|
|
|
|
|
if (type == LuaTable.class && unsafe) return ARG_TABLE_UNSAFE;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|