package org.jdbi.v3.sqlobject;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.WeakHashMap;
import org.jdbi.v3.core.extension.HandleSupplier;
import org.jdbi.v3.core.internal.exceptions.Unchecked;
import static java.lang.invoke.MethodHandles.Lookup.PACKAGE;
import static java.lang.invoke.MethodHandles.Lookup.PRIVATE;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
import static java.lang.invoke.MethodHandles.Lookup.PUBLIC;
import static java.util.Collections.synchronizedMap;
class DefaultMethodHandler implements Handler {
private static final int ANY_ACCESS = PUBLIC | PRIVATE | PROTECTED | PACKAGE;
private static final Method PRIVATE_LOOKUP_IN = privateLookupIn();
private static final Map<Class<?>, MethodHandles.Lookup> PRIVATE_LOOKUPS = synchronizedMap(new WeakHashMap<>());
private final MethodHandle methodHandle;
DefaultMethodHandler(Method method) {
Class<?> declaringClass = method.getDeclaringClass();
methodHandle = Unchecked.biFunction(lookupFor(declaringClass)::unreflectSpecial).apply(method, declaringClass);
}
@Override
public Object invoke(Object target, Object[] args, HandleSupplier handle) {
return Unchecked.<Object[], Object>function(methodHandle.bindTo(target)::invokeWithArguments).apply(args);
}
private static Method privateLookupIn() {
try {
return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException ignored) {
return null;
}
}
static MethodHandles.Lookup lookupFor(Class<?> clazz) {
if (PRIVATE_LOOKUP_IN != null) {
try {
return (MethodHandles.Lookup) PRIVATE_LOOKUP_IN.invoke(null, clazz, MethodHandles.lookup());
} catch (IllegalAccessException | InvocationTargetException e) {
String message = String.format(
"Error invoking MethodHandles.privateLookupIn(%s.class, MethodHandles.lookup()) in JDK 9+ runtime",
clazz);
throw new RuntimeException(message, e);
}
}
return PRIVATE_LOOKUPS.computeIfAbsent(clazz, Unchecked.function(DefaultMethodHandler::getConstructorLookup));
}
private static MethodHandles.Lookup getConstructorLookup(Class<?> type) throws ReflectiveOperationException {
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance(type, ANY_ACCESS);
}
}