com.sun.jna.internal.ReflectionUtils Maven / Gradle / Ivy
/* Copyright (c) 2019 Matthias Bläsing, All Rights Reserved
*
* The contents of this file is dual-licensed under 2
* alternative Open Source/Free licenses: LGPL 2.1 or later and
* Apache License 2.0. (starting with JNA version 4.0.0).
*
* You can freely decide which license you want to apply to
* the project.
*
* You may obtain a copy of the LGPL License at:
*
* http://www.gnu.org/licenses/licenses.html
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "LGPL2.1".
*
* You may obtain a copy of the Apache License at:
*
* http://www.apache.org/licenses/
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "AL2.0".
*/
package com.sun.jna.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Helper class to invoke default method reflectively.
*
* This class is intented to be used only be JNA itself.
*
* This implementation is inspired by:
*
* Correct Reflective Access to Interface Default Methods in Java 8, 9, 10
*
*/
public class ReflectionUtils {
private static final Logger LOG = Logger.getLogger(ReflectionUtils.class.getName());
private static final Method METHOD_IS_DEFAULT;
private static final Method METHOD_HANDLES_LOOKUP;
private static final Method METHOD_HANDLES_LOOKUP_IN;
private static final Method METHOD_HANDLES_PRIVATE_LOOKUP_IN;
private static final Method METHOD_HANDLES_LOOKUP_UNREFLECT_SPECIAL;
private static final Method METHOD_HANDLES_LOOKUP_FIND_SPECIAL;
private static final Method METHOD_HANDLES_BIND_TO;
private static final Method METHOD_HANDLES_INVOKE_WITH_ARGUMENTS;
private static final Method METHOD_TYPE;
private static Constructor CONSTRUCTOR_LOOKUP_CLASS;
private static Constructor getConstructorLookupClass() {
if (CONSTRUCTOR_LOOKUP_CLASS == null) {
Class lookup = lookupClass("java.lang.invoke.MethodHandles$Lookup");
CONSTRUCTOR_LOOKUP_CLASS = lookupDeclaredConstructor(lookup, Class.class);
}
return CONSTRUCTOR_LOOKUP_CLASS;
}
static {
Class methodHandles = lookupClass("java.lang.invoke.MethodHandles");
Class methodHandle = lookupClass("java.lang.invoke.MethodHandle");
Class lookup = lookupClass("java.lang.invoke.MethodHandles$Lookup");
Class methodType = lookupClass("java.lang.invoke.MethodType");
METHOD_IS_DEFAULT = lookupMethod(Method.class, "isDefault");
METHOD_HANDLES_LOOKUP = lookupMethod(methodHandles, "lookup");
METHOD_HANDLES_LOOKUP_IN = lookupMethod(lookup, "in", Class.class);
METHOD_HANDLES_LOOKUP_UNREFLECT_SPECIAL = lookupMethod(lookup, "unreflectSpecial", Method.class, Class.class);
METHOD_HANDLES_LOOKUP_FIND_SPECIAL = lookupMethod(lookup, "findSpecial", Class.class, String.class, methodType, Class.class);
METHOD_HANDLES_BIND_TO = lookupMethod(methodHandle, "bindTo", Object.class);
METHOD_HANDLES_INVOKE_WITH_ARGUMENTS = lookupMethod(methodHandle, "invokeWithArguments", Object[].class);
METHOD_HANDLES_PRIVATE_LOOKUP_IN = lookupMethod(methodHandles, "privateLookupIn", Class.class, lookup);
METHOD_TYPE = lookupMethod(methodType, "methodType", Class.class, Class[].class);
}
private static Constructor lookupDeclaredConstructor(Class clazz, Class... arguments) {
if(clazz == null) {
LOG.log(Level.FINE, "Failed to lookup method: #{1}({2})",
new Object[]{clazz, Arrays.toString(arguments)});
return null;
}
try {
Constructor init = clazz.getDeclaredConstructor(arguments);
init.setAccessible(true);
return init;
} catch (Exception ex) {
LOG.log(Level.FINE, "Failed to lookup method: #{1}({2})",
new Object[]{clazz, Arrays.toString(arguments)});
return null;
}
}
private static Method lookupMethod(Class clazz, String methodName, Class... arguments) {
if(clazz == null) {
LOG.log(Level.FINE, "Failed to lookup method: {0}#{1}({2})",
new Object[]{clazz, methodName, Arrays.toString(arguments)});
return null;
}
try {
return clazz.getMethod(methodName, arguments);
} catch (Exception ex) {
LOG.log(Level.FINE, "Failed to lookup method: {0}#{1}({2})",
new Object[]{clazz, methodName, Arrays.toString(arguments)});
return null;
}
}
private static Class lookupClass(String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException ex) {
LOG.log(Level.FINE, "Failed to lookup class: " + name, ex);
return null;
}
}
/**
* Check if the supplied method object represents a default method.
*
* This is the reflective equivalent of {@code method.isDefault()}.
*
* @param method
* @return true if JVM supports default methods and {@code method} is a
* default method
*/
public static boolean isDefault(Method method) {
if (METHOD_IS_DEFAULT == null) {
return false;
}
try {
return (boolean) (Boolean) METHOD_IS_DEFAULT.invoke(method);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
throw new RuntimeException(cause);
}
}
}
/**
* Retrieve the method handle, that can be used to invoke the provided
* method. It is only intended to be used to call default methods on
* interfaces.
*
* @param method
* @return method handle that can be used to invoke the supplied method
* @throws Exception
*/
public static Object getMethodHandle(Method method) throws Exception {
assert isDefault(method);
Object baseLookup = createLookup();
try {
Object lookup = createPrivateLookupIn(method.getDeclaringClass(), baseLookup);
Object mh = mhViaFindSpecial(lookup, method);
return mh;
} catch (Exception ex) {
Object lookup = getConstructorLookupClass().newInstance(method.getDeclaringClass());
Object mh = mhViaUnreflectSpecial(lookup, method);
return mh;
}
}
private static Object mhViaFindSpecial(Object lookup, Method method) throws Exception {
return METHOD_HANDLES_LOOKUP_FIND_SPECIAL.invoke(
lookup,
method.getDeclaringClass(),
method.getName(),
METHOD_TYPE.invoke(null, method.getReturnType(), method.getParameterTypes()),
method.getDeclaringClass());
}
private static Object mhViaUnreflectSpecial(Object lookup, Method method) throws Exception {
Object l2 = METHOD_HANDLES_LOOKUP_IN.invoke(lookup, method.getDeclaringClass());
return METHOD_HANDLES_LOOKUP_UNREFLECT_SPECIAL.invoke(l2, method, method.getDeclaringClass());
}
private static Object createPrivateLookupIn(Class type, Object lookup) throws Exception {
return METHOD_HANDLES_PRIVATE_LOOKUP_IN.invoke(null, type, lookup);
}
private static Object createLookup() throws Exception {
return METHOD_HANDLES_LOOKUP.invoke(null);
}
/**
* Invokes a default method reflectively. The method must be called with
* the method handle for a default method on an interfaces.
*
* @param target object to invoke the supplied method handle on
* @param methodHandle retrieved via {@link #getMethodHandle(java.lang.reflect.Method)}
* @param args
* @return result of the invokation
* @throws Throwable
*/
public static Object invokeDefaultMethod(Object target, Object methodHandle, Object... args) throws Throwable {
Object boundMethodHandle = METHOD_HANDLES_BIND_TO.invoke(methodHandle, target);
return METHOD_HANDLES_INVOKE_WITH_ARGUMENTS.invoke(boundMethodHandle, new Object[]{args});
}
}