ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/MethodHandleHelper.java

122 lines
4.9 KiB
Java

package com.comphenix.protocol.reflect.accessors;
import com.comphenix.protocol.ProtocolLogger;
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.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
final class MethodHandleHelper {
private static final Lookup LOOKUP;
// static fields, converted as "public Object get()" and "public void set(Object value)"
private static final MethodType STATIC_FIELD_GETTER = MethodType.methodType(Object.class);
private static final MethodType STATIC_FIELD_SETTER = MethodType.methodType(void.class, Object.class);
// instance fields, converted as "public Object get(Object instance)" and "public void set(Object instance, Object value)"
private static final MethodType VIRTUAL_FIELD_GETTER = MethodType.methodType(Object.class, Object.class);
private static final MethodType VIRTUAL_FIELD_SETTER = MethodType.methodType(void.class, Object.class, Object.class);
static {
Lookup lookup;
try {
// get the unsafe class
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
// get the unsafe instance
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null);
// get the trusted lookup field
Field trustedLookup = Lookup.class.getDeclaredField("IMPL_LOOKUP");
// get access to the base and offset value of it
long offset = unsafe.staticFieldOffset(trustedLookup);
Object baseValue = unsafe.staticFieldBase(trustedLookup);
// get the trusted lookup instance
lookup = (Lookup) unsafe.getObject(baseValue, offset);
} catch (Exception exception) {
ProtocolLogger.log(Level.SEVERE, "Unable to retrieve trusted lookup", exception);
lookup = MethodHandles.lookup();
}
LOOKUP = lookup;
}
// sealed class
private MethodHandleHelper() {
}
public static MethodAccessor getMethodAccessor(Method method) {
try {
MethodHandle unreflected = LOOKUP.unreflect(method);
boolean staticMethod = Modifier.isStatic(method.getModifiers());
MethodHandle generified = convertToGeneric(unreflected, staticMethod, false);
return new DefaultMethodAccessor(method, generified, staticMethod);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to access method " + method);
}
}
public static ConstructorAccessor getConstructorAccessor(Constructor<?> constructor) {
try {
MethodHandle unreflected = LOOKUP.unreflectConstructor(constructor);
MethodHandle generified = convertToGeneric(unreflected, false, true);
return new DefaultConstrutorAccessor(constructor, generified);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to access constructor " + constructor);
}
}
public static FieldAccessor getFieldAccessor(Field field) {
try {
boolean staticField = Modifier.isStatic(field.getModifiers());
// java hates us - unreflecting a trusted field always results in an exception, finding them doesn't...
MethodHandle getter;
MethodHandle setter;
if (staticField) {
getter = LOOKUP.findStaticGetter(field.getDeclaringClass(), field.getName(), field.getType());
setter = LOOKUP.findStaticSetter(field.getDeclaringClass(), field.getName(), field.getType());
} else {
getter = LOOKUP.findGetter(field.getDeclaringClass(), field.getName(), field.getType());
setter = LOOKUP.findSetter(field.getDeclaringClass(), field.getName(), field.getType());
}
// generify the method type so that we don't need to worry about it when using the handles
if (staticField) {
getter = getter.asType(STATIC_FIELD_GETTER);
setter = setter.asType(STATIC_FIELD_SETTER);
} else {
getter = getter.asType(VIRTUAL_FIELD_GETTER);
setter = setter.asType(VIRTUAL_FIELD_SETTER);
}
return new DefaultFieldAccessor(field, setter, getter, staticField);
} catch (IllegalAccessException | NoSuchFieldException exception) {
// NoSuchFieldException can never happen, the field always exists
throw new IllegalStateException("Unable to access field " + field);
}
}
private static MethodHandle convertToGeneric(MethodHandle handle, boolean staticMethod, boolean ctor) {
MethodHandle target = handle.asFixedArity();
// special thing - we do not need the trailing array if we have 0 arguments anyway
int paramCount = handle.type().parameterCount() - (ctor || staticMethod ? 0 : 1);
MethodType methodType = MethodType.genericMethodType(ctor ? 0 : 1, true);
// spread the arguments we give into the handle
target = target.asSpreader(Object[].class, paramCount);
// adds a leading 'this' argument which we can ignore
if (staticMethod) {
target = MethodHandles.dropArguments(target, 0, Object.class);
}
// convert the type to finish
return target.asType(methodType);
}
}