package org.springframework.data.projection;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.springframework.util.ReflectionUtils;
public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor {
private final MethodHandleLookup methodHandleLookup = MethodHandleLookup.getMethodHandleLookup();
private final Map<Method, MethodHandle> methodHandleCache = new ConcurrentReferenceHashMap<>(10, ReferenceType.WEAK);
public static boolean hasDefaultMethods(Class<?> interfaceClass) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(interfaceClass);
for (Method method : methods) {
if (method.isDefault()) {
return true;
}
}
return false;
}
@Nullable
@Override
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (!method.isDefault()) {
return invocation.proceed();
}
Object[] arguments = invocation.getArguments();
Object proxy = ((ProxyMethodInvocation) invocation).getProxy();
return getMethodHandle(method).bindTo(proxy).invokeWithArguments(arguments);
}
private MethodHandle getMethodHandle(Method method) throws Exception {
MethodHandle handle = methodHandleCache.get(method);
if (handle == null) {
handle = methodHandleLookup.lookup(method);
methodHandleCache.put(method, handle);
}
return handle;
}
enum MethodHandleLookup {
ENCAPSULATED {
private final @Nullable Method privateLookupIn = ReflectionUtils.findMethod(MethodHandles.class,
"privateLookupIn", Class.class, Lookup.class);
@Override
MethodHandle lookup(Method method) throws ReflectiveOperationException {
if (privateLookupIn == null) {
throw new IllegalStateException("Could not obtain MethodHandles.privateLookupIn!");
}
return doLookup(method, getLookup(method.getDeclaringClass(), privateLookupIn));
}
@Override
boolean isAvailable() {
return privateLookupIn != null;
}
private Lookup getLookup(Class<?> declaringClass, Method privateLookupIn) {
Lookup lookup = MethodHandles.lookup();
try {
return (Lookup) privateLookupIn.invoke(MethodHandles.class, declaringClass, lookup);
} catch (ReflectiveOperationException e) {
return lookup;
}
}
},
OPEN {
private final Lazy<Constructor<Lookup>> constructor = Lazy.of(MethodHandleLookup::getLookupConstructor);
@Override
MethodHandle lookup(Method method) throws ReflectiveOperationException {
if (!isAvailable()) {
throw new IllegalStateException("Could not obtain MethodHandles.lookup constructor!");
}
Constructor<Lookup> constructor = this.constructor.get();
return constructor.newInstance(method.getDeclaringClass()).unreflectSpecial(method, method.getDeclaringClass());
}
@Override
boolean isAvailable() {
return constructor.orElse(null) != null;
}
},
FALLBACK {
@Override
MethodHandle lookup(Method method) throws ReflectiveOperationException {
return doLookup(method, MethodHandles.lookup());
}
@Override
boolean isAvailable() {
return true;
}
};
private static MethodHandle doLookup(Method method, Lookup lookup)
throws NoSuchMethodException, IllegalAccessException {
MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
if (Modifier.isStatic(method.getModifiers())) {
return lookup.findStatic(method.getDeclaringClass(), method.getName(), methodType);
}
return lookup.findSpecial(method.getDeclaringClass(), method.getName(), methodType, method.getDeclaringClass());
}
abstract MethodHandle lookup(Method method) throws ReflectiveOperationException;
abstract boolean isAvailable();
public static MethodHandleLookup getMethodHandleLookup() {
for (MethodHandleLookup it : MethodHandleLookup.values()) {
if (it.isAvailable()) {
return it;
}
}
throw new IllegalStateException("No MethodHandleLookup available!");
}
@Nullable
private static Constructor<Lookup> getLookupConstructor() {
try {
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
ReflectionUtils.makeAccessible(constructor);
return constructor;
} catch (Exception ex) {
if (ex.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
return null;
}
throw new IllegalStateException(ex);
}
}
}
}