Use a MethodHandle to call event class constructors

This commit is contained in:
Luck 2020-03-30 19:46:30 +01:00
parent c3128dec0d
commit 6805574553
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B

View File

@ -44,7 +44,6 @@ import net.luckperms.api.event.util.Param;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
@ -60,23 +59,13 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
*/ */
public class GeneratedEventClass { public class GeneratedEventClass {
/** The MethodHandles.lookup() method */
private static final Method METHOD_HANDLES_LOOKUP;
static {
try {
METHOD_HANDLES_LOOKUP = MethodHandles.class.getMethod("lookup");
} catch (NoSuchMethodException e) {
throw new ExceptionInInitializerError(e);
}
}
/** /**
* A loading cache of event types to {@link GeneratedEventClass}es. * A loading cache of event types to {@link GeneratedEventClass}es.
*/ */
private static final Map<Class<? extends LuckPermsEvent>, GeneratedEventClass> CACHE = LoadingMap.of(clazz -> { private static final Map<Class<? extends LuckPermsEvent>, GeneratedEventClass> CACHE = LoadingMap.of(clazz -> {
try { try {
return new GeneratedEventClass(clazz); return new GeneratedEventClass(clazz);
} catch (ReflectiveOperationException e) { } catch (Throwable e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
@ -92,17 +81,16 @@ public class GeneratedEventClass {
} }
/** /**
* The constructor used to create instances of the generated class * A method handle for the constructor of the event class.
*/ */
private final Constructor<? extends AbstractEvent> constructor; private final MethodHandle constructor;
/** /**
* An array of {@link MethodHandle}s, which can set values for each of the properties in the event class. * An array of {@link MethodHandle}s, which can set values for each of the properties in the event class.
*/ */
private final MethodHandle[] setters; private final MethodHandle[] setters;
private GeneratedEventClass(Class<? extends LuckPermsEvent> eventClass) throws ReflectiveOperationException { private GeneratedEventClass(Class<? extends LuckPermsEvent> eventClass) throws Throwable {
// get a TypeDescription for the event class // get a TypeDescription for the event class
TypeDescription eventClassType = new TypeDescription.ForLoadedType(eventClass); TypeDescription eventClassType = new TypeDescription.ForLoadedType(eventClass);
@ -125,7 +113,7 @@ public class GeneratedEventClass {
.intercept(FixedValue.value(eventClassType)) .intercept(FixedValue.value(eventClassType))
// implement AbstractEvent#mh by calling & returning the value of MethodHandles.lookup() // implement AbstractEvent#mh by calling & returning the value of MethodHandles.lookup()
.method(named("mhl").and(returns(MethodHandles.Lookup.class)).and(takesArguments(0))) .method(named("mhl").and(returns(MethodHandles.Lookup.class)).and(takesArguments(0)))
.intercept(MethodCall.invoke(METHOD_HANDLES_LOOKUP)) .intercept(MethodCall.invoke(MethodHandles.class.getMethod("lookup")))
// implement a toString method // implement a toString method
.withToString(); .withToString();
@ -135,29 +123,24 @@ public class GeneratedEventClass {
.sorted(Comparator.comparingInt(o -> o.getAnnotation(Param.class).value())) .sorted(Comparator.comparingInt(o -> o.getAnnotation(Param.class).value()))
.toArray(Method[]::new); .toArray(Method[]::new);
// for each method on the event interface annotated with @Param // for each property, define a field on the generated class to hold the value
for (Method method : properties) { for (Method method : properties) {
if (!method.isAnnotationPresent(Param.class)) {
continue;
}
// define a field on the generated class to hold the value
builder = builder.defineField(method.getName(), method.getReturnType(), Visibility.PRIVATE); builder = builder.defineField(method.getName(), method.getReturnType(), Visibility.PRIVATE);
} }
// finish building, load the class, get a constructor // finish building, load the class, get a constructor
Class<? extends AbstractEvent> generatedClass = builder.make().load(GeneratedEventClass.class.getClassLoader()).getLoaded(); Class<? extends AbstractEvent> generatedClass = builder.make().load(GeneratedEventClass.class.getClassLoader()).getLoaded();
this.constructor = generatedClass.getDeclaredConstructor(LuckPerms.class); this.constructor = MethodHandles.publicLookup().in(generatedClass)
.findConstructor(generatedClass, MethodType.methodType(void.class, LuckPerms.class))
.asType(MethodType.methodType(AbstractEvent.class, LuckPerms.class));
// create a dummy instance of the generated class & get the method handle lookup instance // create a dummy instance of the generated class & get the method handle lookup instance
MethodHandles.Lookup lookup = this.constructor.newInstance(new Object[]{null}).mhl(); MethodHandles.Lookup lookup = ((AbstractEvent) this.constructor.invoke((Object) null)).mhl();
// get 'setter' MethodHandles // get 'setter' MethodHandles for each property
this.setters = new MethodHandle[properties.length]; this.setters = new MethodHandle[properties.length];
for (int i = 0; i < properties.length; i++) { for (int i = 0; i < properties.length; i++) {
Method method = properties[i]; Method method = properties[i];
// obtain a setter MethodHandle for the property
this.setters[i] = lookup.findSetter(generatedClass, method.getName(), method.getReturnType()) this.setters[i] = lookup.findSetter(generatedClass, method.getName(), method.getReturnType())
.asType(MethodType.methodType(void.class, new Class[]{AbstractEvent.class, Object.class})); .asType(MethodType.methodType(void.class, new Class[]{AbstractEvent.class, Object.class}));
} }
@ -177,7 +160,7 @@ public class GeneratedEventClass {
} }
// create a new instance of the event // create a new instance of the event
final AbstractEvent event = this.constructor.newInstance(api); final AbstractEvent event = (AbstractEvent) this.constructor.invokeExact(api);
// set the properties onto the event instance // set the properties onto the event instance
for (int i = 0; i < this.setters.length; i++) { for (int i = 0; i < this.setters.length; i++) {