From ea06c7009d434def2c38a9ebf2a03f7f06612168 Mon Sep 17 00:00:00 2001 From: JFronny Date: Mon, 26 Sep 2022 15:09:20 +0200 Subject: [PATCH] [reflect] Use LambdaMetafactory + MethodHandles to (hopefully) speed up generated lambdas --- .../jfronny/commons/reflect/Reflect.java | 263 ++++++++++++++++-- .../jfronny/commons/test/ReflectTest.java | 97 +++++++ .../other/location/InstanceConstruct.java | 29 ++ .../java/some/other/location/ToConstruct.java | 96 +++++++ 4 files changed, 466 insertions(+), 19 deletions(-) create mode 100644 src/test/java/io/gitlab/jfronny/commons/test/ReflectTest.java create mode 100644 src/test/java/some/other/location/InstanceConstruct.java create mode 100644 src/test/java/some/other/location/ToConstruct.java diff --git a/src/main/java/io/gitlab/jfronny/commons/reflect/Reflect.java b/src/main/java/io/gitlab/jfronny/commons/reflect/Reflect.java index 026a8a2..c0db2b3 100644 --- a/src/main/java/io/gitlab/jfronny/commons/reflect/Reflect.java +++ b/src/main/java/io/gitlab/jfronny/commons/reflect/Reflect.java @@ -4,39 +4,264 @@ import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction; import io.gitlab.jfronny.commons.throwable.ThrowingFunction; import io.gitlab.jfronny.commons.throwable.ThrowingSupplier; -import java.lang.reflect.Constructor; +import java.lang.invoke.*; +import java.util.function.*; +@SuppressWarnings("unchecked") public class Reflect { - // Constructor without parameters - public static ThrowingSupplier getConstructor(Class toConstruct) throws NoSuchMethodException { - Constructor constructor = (Constructor) toConstruct.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor::newInstance; + private static final MethodHandles.Lookup LOOKUP_ROOT = MethodHandles.lookup(); + + @Deprecated + public static ThrowingSupplier getConstructor(Class toConstruct) throws Throwable { + Supplier constructor = constructor(toConstruct); + return constructor::get; } - public static ThrowingSupplier getConstructor(String targetClassName) throws NoSuchMethodException, ClassNotFoundException { + @Deprecated + public static ThrowingSupplier getConstructor(String targetClassName) throws Throwable { return getConstructor(Class.forName(targetClassName)); } - // Constructor with one parameter - public static ThrowingFunction getConstructor(Class toConstruct, Class parameterType) throws NoSuchMethodException { - Constructor constructor = (Constructor) toConstruct.getDeclaredConstructor(parameterType); - constructor.setAccessible(true); - return constructor::newInstance; + @Deprecated + public static ThrowingFunction getConstructor(Class toConstruct, Class parameterType) throws Throwable { + Function constructor = constructor(toConstruct, parameterType); + return constructor::apply; } - public static ThrowingFunction getConstructor(String targetClassName, Class parameterType) throws NoSuchMethodException, ClassNotFoundException { + @Deprecated + public static ThrowingFunction getConstructor(String targetClassName, Class parameterType) throws Throwable { return getConstructor(Class.forName(targetClassName), parameterType); } - // Constructor with two parameters - public static ThrowingBiFunction getConstructor(Class toConstruct, Class parameterType1, Class parameterType2) throws NoSuchMethodException { - Constructor constructor = (Constructor) toConstruct.getDeclaredConstructor(parameterType1, parameterType2); - constructor.setAccessible(true); - return constructor::newInstance; + @Deprecated + public static ThrowingBiFunction getConstructor(Class toConstruct, Class parameterType1, Class parameterType2) throws Throwable { + BiFunction constructor = constructor(toConstruct, parameterType1, parameterType2); + return constructor::apply; } - public static ThrowingBiFunction getConstructor(String targetClassName, Class parameterType1, Class parameterType2) throws NoSuchMethodException, ClassNotFoundException { + @Deprecated + public static ThrowingBiFunction getConstructor(String targetClassName, Class parameterType1, Class parameterType2) throws Throwable { return getConstructor(Class.forName(targetClassName), parameterType1, parameterType2); } + + // Constructor without parameters + public static Supplier constructor(Class toConstruct) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(toConstruct, LOOKUP_ROOT); + return (Supplier) LambdaMetafactory.metafactory( + lookup, + "get", + MethodType.methodType(Supplier.class), + MethodType.methodType(Object.class), + lookup.unreflectConstructor(toConstruct.getDeclaredConstructor()), + MethodType.methodType(toConstruct) + ).getTarget().invoke(); + } + + public static Supplier constructor(String targetClassName) throws Throwable { + return constructor(Class.forName(targetClassName)); + } + + // Constructor with one parameter + public static Function constructor(Class toConstruct, Class parameterType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(toConstruct, LOOKUP_ROOT); + return (Function) LambdaMetafactory.metafactory( + lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), + lookup.unreflectConstructor(toConstruct.getDeclaredConstructor(parameterType)), + MethodType.methodType(toConstruct, parameterType) + ).getTarget().invoke(); + } + + public static Function constructor(String targetClassName, Class parameterType) throws Throwable { + return constructor(Class.forName(targetClassName), parameterType); + } + + // Constructor with two parameters + public static BiFunction constructor(Class toConstruct, Class parameterType1, Class parameterType2) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(toConstruct, LOOKUP_ROOT); + return (BiFunction) LambdaMetafactory.metafactory( + lookup, + "apply", + MethodType.methodType(BiFunction.class), + MethodType.methodType(Object.class, Object.class, Object.class), + lookup.unreflectConstructor(toConstruct.getDeclaredConstructor(parameterType1, parameterType2)), + MethodType.methodType(toConstruct, parameterType1, parameterType2) + ).getTarget().invoke(); + } + + public static BiFunction constructor(String targetClassName, Class parameterType1, Class parameterType2) throws Throwable { + return constructor(Class.forName(targetClassName), parameterType1, parameterType2); + } + + // Procedure without parameters + public static Runnable staticProcedure(Class targetClass, String name) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (Runnable) LambdaMetafactory.metafactory( + lookup, + "run", + MethodType.methodType(Runnable.class), + MethodType.methodType(Void.TYPE), + lookup.unreflect(targetClass.getDeclaredMethod(name)), + MethodType.methodType(Void.TYPE) + ).getTarget().invoke(); + } + + public static Runnable staticProcedure(String targetClassName, String name) throws Throwable { + return staticProcedure(Class.forName(targetClassName), name); + } + + // Procedure with one parameter + public static Consumer staticProcedure(Class targetClass, String name, Class parameterType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (Consumer) LambdaMetafactory.metafactory( + lookup, + "accept", + MethodType.methodType(Consumer.class), + MethodType.methodType(Void.TYPE, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)), + MethodType.methodType(Void.TYPE, parameterType) + ).getTarget().invoke(); + } + + public static Consumer staticProcedure(String targetClassName, String name, Class parameterType) throws Throwable { + return staticProcedure(Class.forName(targetClassName), name, parameterType); + } + + // Procedure with two parameters + public static BiConsumer staticProcedure(Class targetClass, String name, Class parameterType1, Class parameterType2) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (BiConsumer) LambdaMetafactory.metafactory( + lookup, + "accept", + MethodType.methodType(BiConsumer.class), + MethodType.methodType(Void.TYPE, Object.class, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType1, parameterType2)), + MethodType.methodType(Void.TYPE, parameterType1, parameterType2) + ).getTarget().invoke(); + } + + public static BiConsumer staticProcedure(String targetClassName, String name, Class parameterType1, Class parameterType2) throws Throwable { + return staticProcedure(Class.forName(targetClassName), name, parameterType1, parameterType2); + } + + // Function without parameters + public static Supplier staticFunction(Class targetClass, String name, Class returnType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (Supplier) LambdaMetafactory.metafactory( + lookup, + "get", + MethodType.methodType(Supplier.class), + MethodType.methodType(Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name)), + MethodType.methodType(returnType) + ).getTarget().invoke(); + } + + public static Supplier staticFunction(String targetClassName, String name, Class returnType) throws Throwable { + return staticFunction(Class.forName(targetClassName), name, returnType); + } + + // Function with one parameter + public static Function staticFunction(Class targetClass, String name, Class returnType, Class parameterType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (Function) LambdaMetafactory.metafactory( + lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)), + MethodType.methodType(returnType, parameterType) + ).getTarget().invoke(); + } + + public static Function staticFunction(String targetClassName, String name, Class returnType, Class parameterType) throws Throwable { + return staticFunction(Class.forName(targetClassName), name, returnType, parameterType); + } + + // Function with two parameters + public static BiFunction staticFunction(Class targetClass, String name, Class returnType, Class parameterType1, Class parameterType2) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (BiFunction) LambdaMetafactory.metafactory( + lookup, + "apply", + MethodType.methodType(BiFunction.class), + MethodType.methodType(Object.class, Object.class, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType1, parameterType2)), + MethodType.methodType(returnType, parameterType1, parameterType2) + ).getTarget().invoke(); + } + + public static BiFunction staticFunction(String targetClassName, String name, Class returnType, Class parameterType1, Class parameterType2) throws Throwable { + return staticFunction(Class.forName(targetClassName), name, returnType, parameterType1, parameterType2); + } + + // Procedure without parameters + public static Consumer instanceProcedure(Class targetClass, String name) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (Consumer) LambdaMetafactory.metafactory( + lookup, + "accept", + MethodType.methodType(Consumer.class), + MethodType.methodType(Void.TYPE, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name)), + MethodType.methodType(Void.TYPE, targetClass) + ).getTarget().invoke(); + } + + public static Consumer instanceProcedure(String targetClassName, String name) throws Throwable { + return (Consumer) instanceProcedure(Class.forName(targetClassName), name); + } + + // Procedure with one parameter + public static BiConsumer instanceProcedure(Class targetClass, String name, Class parameterType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (BiConsumer) LambdaMetafactory.metafactory( + lookup, + "accept", + MethodType.methodType(BiConsumer.class), + MethodType.methodType(Void.TYPE, Object.class, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)), + MethodType.methodType(Void.TYPE, targetClass, parameterType) + ).getTarget().invoke(); + } + + public static BiConsumer instanceProcedure(String targetClassName, String name, Class parameterType) throws Throwable { + return (BiConsumer) instanceProcedure(Class.forName(targetClassName), name, parameterType); + } + + // Function without parameters + public static Function instanceFunction(Class targetClass, String name, Class returnType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (Function) LambdaMetafactory.metafactory( + lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name)), + MethodType.methodType(returnType, targetClass) + ).getTarget().invoke(); + } + + public static Function instanceFunction(String targetClassName, String name, Class returnType) throws Throwable { + return (Function) instanceFunction(Class.forName(targetClassName), name, returnType); + } + + // Function with one parameter + public static BiFunction instanceFunction(Class targetClass, String name, Class returnType, Class parameterType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(targetClass, LOOKUP_ROOT); + return (BiFunction) LambdaMetafactory.metafactory( + lookup, + "apply", + MethodType.methodType(BiFunction.class), + MethodType.methodType(Object.class, Object.class, Object.class), + lookup.unreflect(targetClass.getDeclaredMethod(name, parameterType)), + MethodType.methodType(returnType, targetClass, parameterType) + ).getTarget().invoke(); + } + + public static BiFunction instanceFunction(String targetClassName, String name, Class returnType, Class parameterType) throws Throwable { + return (BiFunction) instanceFunction(Class.forName(targetClassName), name, returnType, parameterType); + } } diff --git a/src/test/java/io/gitlab/jfronny/commons/test/ReflectTest.java b/src/test/java/io/gitlab/jfronny/commons/test/ReflectTest.java new file mode 100644 index 0000000..3533dcf --- /dev/null +++ b/src/test/java/io/gitlab/jfronny/commons/test/ReflectTest.java @@ -0,0 +1,97 @@ +package io.gitlab.jfronny.commons.test; + +import io.gitlab.jfronny.commons.reflect.Reflect; +import org.junit.jupiter.api.Test; +import some.other.location.InstanceConstruct; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReflectTest { + private static final String CLASS_NAME = "some.other.location.ToConstruct"; + + @Test + void noArgConstructor() { + assertEquals(InstanceConstruct.construct(), assertDoesNotThrow(() -> Reflect.constructor(CLASS_NAME).get())); + assertEquals(InstanceConstruct.LastProc.C0, InstanceConstruct.getLastProc()); + } + + @Test + void oneArgConstructor() { + String arg1 = "Some arg"; + assertEquals(InstanceConstruct.construct(arg1), assertDoesNotThrow(() -> Reflect.constructor(CLASS_NAME, String.class).apply(arg1))); + assertEquals(InstanceConstruct.LastProc.C1, InstanceConstruct.getLastProc()); + } + + @Test + void twoArgConstructor() { + String arg1 = "Some other arg"; + List arg2 = List.of("Hello", "World"); + assertEquals(InstanceConstruct.construct(arg1, arg2), assertDoesNotThrow(() -> Reflect.constructor(CLASS_NAME, String.class, List.class).apply(arg1, arg2))); + assertEquals(InstanceConstruct.LastProc.C2, InstanceConstruct.getLastProc()); + } + + @Test + void noArgProcedure() { + assertDoesNotThrow(() -> Reflect.staticProcedure(CLASS_NAME, "procedure").run()); + assertEquals(InstanceConstruct.LastProc.P0, InstanceConstruct.getLastProc()); + } + + @Test + void oneArgProcedure() { + String arg1 = "Some arg"; + assertDoesNotThrow(() -> Reflect.staticProcedure(CLASS_NAME, "procedure", String.class).accept(arg1)); + assertEquals(InstanceConstruct.LastProc.P1, InstanceConstruct.getLastProc()); + } + + @Test + void noArgInstanceProcedure() { + assertDoesNotThrow(() -> Reflect.instanceProcedure(CLASS_NAME, "instanceProcedure").accept(InstanceConstruct.construct())); + assertEquals(InstanceConstruct.LastProc.P1, InstanceConstruct.getLastProc()); + } + + @Test + void twoArgProcedure() { + String arg1 = "Some other arg"; + List arg2 = List.of("Hello", "World"); + assertDoesNotThrow(() -> Reflect.staticProcedure(CLASS_NAME, "procedure", String.class, List.class).accept(arg1, arg2)); + assertEquals(InstanceConstruct.LastProc.P2, InstanceConstruct.getLastProc()); + } + + @Test + void oneArgInstanceProcedure() { + String arg1 = "Some arg"; + assertDoesNotThrow(() -> Reflect.instanceProcedure(CLASS_NAME, "instanceProcedure", String.class).accept(InstanceConstruct.construct(), arg1)); + assertEquals(InstanceConstruct.LastProc.P2, InstanceConstruct.getLastProc()); + } + + @Test + void noArgFunction() { //TODO fix + assertEquals(assertDoesNotThrow(() -> Reflect.staticFunction(CLASS_NAME, "function", String.class).get()), InstanceConstruct.getLastProc().toString()); + } + + @Test + void oneArgFunction() { + String arg1 = "Some arg"; + assertEquals(assertDoesNotThrow(() -> Reflect.staticFunction(CLASS_NAME, "function", String.class, String.class).apply(arg1)), InstanceConstruct.getLastProc().toString()); + } + + @Test + void noArgInstanceFunction() { + assertEquals(assertDoesNotThrow(() -> Reflect.instanceFunction(CLASS_NAME, "instanceFunction", String.class).apply(InstanceConstruct.construct())), InstanceConstruct.getLastProc().toString()); + } + + @Test + void twoArgFunction() { + String arg1 = "Some other arg"; + List arg2 = List.of("Hello", "World"); + assertEquals(assertDoesNotThrow(() -> Reflect.staticFunction(CLASS_NAME, "function", String.class, String.class, List.class).apply(arg1, arg2)), InstanceConstruct.getLastProc().toString()); + } + + @Test + void oneArgInstanceFunction() { + String arg1 = "Some arg"; + assertEquals(assertDoesNotThrow(() -> Reflect.instanceFunction(CLASS_NAME, "instanceFunction", String.class, String.class).apply(InstanceConstruct.construct(), arg1)), InstanceConstruct.getLastProc().toString()); + } +} diff --git a/src/test/java/some/other/location/InstanceConstruct.java b/src/test/java/some/other/location/InstanceConstruct.java new file mode 100644 index 0000000..dec043e --- /dev/null +++ b/src/test/java/some/other/location/InstanceConstruct.java @@ -0,0 +1,29 @@ +package some.other.location; + +import java.util.List; + +public class InstanceConstruct { + static LastProc lastProc; + + public static LastProc getLastProc() { + LastProc tmp = lastProc; + lastProc = null; + return tmp; + } + + public static Object construct() { + return ToConstruct.create(); + } + + public static Object construct(String arg1) { + return ToConstruct.create(arg1); + } + + public static Object construct(String arg1, List arg2) { + return ToConstruct.create(arg1, arg2); + } + + public enum LastProc { + C0, C1, C2, P0, P1, P2, F0, F1, F2 + } +} diff --git a/src/test/java/some/other/location/ToConstruct.java b/src/test/java/some/other/location/ToConstruct.java new file mode 100644 index 0000000..5f7d218 --- /dev/null +++ b/src/test/java/some/other/location/ToConstruct.java @@ -0,0 +1,96 @@ +package some.other.location; + +import java.util.List; +import java.util.Objects; + +class ToConstruct { + private final int argCount; + private final String arg1; + private final List arg2; + + private ToConstruct() { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.C0; + this.argCount = 1; + this.arg1 = null; + this.arg2 = null; + } + + private ToConstruct(String arg1) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.C1; + this.argCount = 2; + this.arg1 = arg1; + this.arg2 = null; + } + + private ToConstruct(String arg1, List arg2) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.C2; + this.argCount = 3; + this.arg1 = arg1; + this.arg2 = arg2; + } + + static Object create() { + return new ToConstruct(); + } + + static Object create(String arg1) { + return new ToConstruct(arg1); + } + + static Object create(String arg1, List arg2) { + return new ToConstruct(arg1, arg2); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ToConstruct other + && argCount == other.argCount + && Objects.equals(arg1, other.arg1) + && Objects.equals(arg2, other.arg2); + } + + private static void procedure() { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.P0; + } + + private void instanceProcedure() { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.P1; + } + + private static void procedure(String arg1) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.P1; + } + + private void instanceProcedure(String arg1) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.P2; + } + + private static void procedure(String arg1, List arg2) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.P2; + } + + private static String function() { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.F0; + return InstanceConstruct.lastProc.toString(); + } + + private String instanceFunction() { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.F1; + return InstanceConstruct.lastProc.toString(); + } + + private static String function(String arg1) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.F1; + return InstanceConstruct.lastProc.toString(); + } + + private String instanceFunction(String arg1) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.F2; + return InstanceConstruct.lastProc.toString(); + } + + private static String function(String arg1, List arg2) { + InstanceConstruct.lastProc = InstanceConstruct.LastProc.F2; + return InstanceConstruct.lastProc.toString(); + } +}