Skip to content

Commit

Permalink
Create CodegenConstructorInjector
Browse files Browse the repository at this point in the history
  • Loading branch information
P3ridot committed Apr 1, 2024
1 parent e24cc75 commit 87270cd
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class InstanceConstructionBenchmark {

private static class Entity {
public static class Entity {

private final int id;
private final String name;
Expand Down Expand Up @@ -71,7 +71,7 @@ public void injected(DIState state) {

@Benchmark
public void injectedFast(DIState state) throws Exception {
state.entityInjector.forConstructor(state.entityConstructor).newInstance();
state.entityInjector.forConstructor(Entity.class).newInstance();
}

@Benchmark
Expand All @@ -84,6 +84,16 @@ public void injectedStaticFast(DIState state) throws Exception {
state.entityInjector.forConstructor(state.entityConstructor).newInstance();
}

@Benchmark
public void generatedInjected(DIState state) throws Exception {
state.entityInjector.forGeneratedConstructor(Entity.class).newInstance();
}

@Benchmark
public void generatedInjectedStatic(DIState state) throws Exception {
state.entityInjector.forGeneratedConstructor(state.entityConstructor).newInstance();
}

@State(Scope.Benchmark)
public static class DIState {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,119 @@
package org.panda_lang.utilities.inject;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatchers;
import org.jetbrains.annotations.ApiStatus;
import panda.utilities.ObjectUtils;

@ApiStatus.Internal
final class CodegenCache {

private CodegenCache() { }
private static final AtomicInteger CONSTRUCTORS_ID = new AtomicInteger();
private static final Map<Constructor<?>, Function<Object[], Object>> CONSTRUCTOR_INVOKERS = new ConcurrentHashMap<>();

private static final AtomicInteger METHODS_ID = new AtomicInteger();
private static final Map<Method, BiFunction<Object, Object[], Object>> METHOD_INVOKERS = new ConcurrentHashMap<>();

private CodegenCache() {
}

public static Function<Object[], Object> getConstructorInvoker(Constructor<?> constructor) {
return CONSTRUCTOR_INVOKERS.computeIfAbsent(constructor, key -> {
Class<?> declaringClass = constructor.getDeclaringClass();
if (!Modifier.isPublic(declaringClass.getModifiers())) {
throw new IllegalStateException(declaringClass + " has to be public");

Check warning on line 36 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L36

Added line #L36 was not covered by tests
}

if (!Modifier.isPublic(constructor.getModifiers())) {
throw new IllegalStateException(constructor + " has to be public");

Check warning on line 40 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L40

Added line #L40 was not covered by tests
}

ByteBuddy byteBuddy = new ByteBuddy();
DynamicType.Unloaded<?> classPackage = byteBuddy
.makePackage(declaringClass.getPackage().getName())
.make();

Class<?> loaded = byteBuddy.subclass(Object.class)
.implement(GeneratedFunction.class)
.name(declaringClass.getName() + "$" + constructor.getName() + "$" + CONSTRUCTORS_ID.incrementAndGet())
.method(ElementMatchers.named("apply"))
.intercept(MethodCall.construct(constructor)
.withArgumentArrayElements(0)
.withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
.make()
.include(classPackage)
.load(declaringClass.getClassLoader())
.getLoaded();

try {
return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |

Check warning on line 62 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L62

Added line #L62 was not covered by tests
NoSuchMethodException ex) {
throw new DependencyInjectionException("Failed to generate codegen constructor invoker", ex);

Check warning on line 64 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L64

Added line #L64 was not covered by tests
}
});
}

public static BiFunction<Object, Object[], Object> getMethodInvoker(Method method) {
return METHOD_INVOKERS.computeIfAbsent(method, key -> {
Class<?> declaringClass = method.getDeclaringClass();
if (!Modifier.isPublic(declaringClass.getModifiers())) {
throw new IllegalStateException(declaringClass + " has to be public");

Check warning on line 73 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L73

Added line #L73 was not covered by tests
}

if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalStateException(method + " has to be public");

Check warning on line 77 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L77

Added line #L77 was not covered by tests
}

ByteBuddy byteBuddy = new ByteBuddy();
DynamicType.Unloaded<?> classPackage = byteBuddy
.makePackage(declaringClass.getPackage().getName())
.make();

Class<?> loaded = byteBuddy.subclass(Object.class)
.implement(GeneratedBiFunction.class)
.name(declaringClass.getName() + "$" + method.getName() + "$" + METHODS_ID.incrementAndGet())
.method(ElementMatchers.named("apply"))
.intercept(MethodCall.invoke(method)
.onArgument(0)
.withArgumentArrayElements(1)
.withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
.make()
.include(classPackage)
.load(declaringClass.getClassLoader())
.getLoaded();

try {
return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |

Check warning on line 100 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L100

Added line #L100 was not covered by tests
NoSuchMethodException ex) {
throw new DependencyInjectionException("Failed to generate codegen method invoker", ex);

Check warning on line 102 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java#L102

Added line #L102 was not covered by tests
}
});
}

@ApiStatus.Internal
@FunctionalInterface
public interface GeneratedFunction extends Function<Object[], Object> {

}

@ApiStatus.Internal
@FunctionalInterface
public interface GeneratedBiFunction extends BiFunction<Object, Object[], Object> {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,18 @@ final class CodegenConstructorInjector<T> implements ConstructorInjector<T> {

private static final Object[] EMPTY = new Object[0];

private static final AtomicInteger ID = new AtomicInteger();

private final InjectorProcessor processor;
private final Constructor<T> constructor;
private final boolean empty;
private final InjectorCache cache;

private final Function<Object[], Object> generated;

CodegenConstructorInjector(InjectorProcessor processor, Constructor<T> constructor) throws Exception {
CodegenConstructorInjector(InjectorProcessor processor, Constructor<T> constructor) {
this.processor = processor;
this.constructor = constructor;
this.empty = constructor.getParameterCount() == 0;
this.cache = InjectorCache.of(processor, constructor);

this.generated = generate(constructor);
this.generated = CodegenCache.getConstructorInvoker(constructor);
}

@SuppressWarnings("unchecked")
Expand All @@ -61,45 +57,9 @@ public T newInstance(Object... injectorArgs) throws Exception {
);
}

private static Function<Object[], Object> generate(Constructor<?> constructor) throws Exception {
Class<?> declaringClass = constructor.getDeclaringClass();
if (!Modifier.isPublic(declaringClass.getModifiers())) {
throw new IllegalStateException(declaringClass + " has to be public");
}

if (!Modifier.isPublic(constructor.getModifiers())) {
throw new IllegalStateException(constructor + " has to be public");
}

ByteBuddy byteBuddy = new ByteBuddy();
DynamicType.Unloaded<?> classPackage = byteBuddy
.makePackage(declaringClass.getPackage().getName())
.make();

Class<?> loaded = byteBuddy.subclass(Object.class)
.implement(GeneratedFunction.class)
.name(declaringClass.getName() + "$" + constructor.getName() + "$" + ID.incrementAndGet())
.method(ElementMatchers.named("apply"))
.intercept(MethodCall.invoke(constructor)
.onArgument(0)
.withArgumentArrayElements(1)
.withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
.make()
.include(classPackage)
.load(declaringClass.getClassLoader())
.getLoaded();

return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance());
}

@Override
public Constructor<T> getConstructor() {
return this.constructor;

Check warning on line 62 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjector.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjector.java#L62

Added line #L62 was not covered by tests
}

@FunctionalInterface
public interface GeneratedFunction extends Function<Object[], Object> {

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.panda_lang.utilities.inject;

import java.lang.reflect.Constructor;

public class CodegenConstructorInjectorFactory<T> implements ConstructorInjectorFactory<T> {

@Override
public ConstructorInjector<T> createConstructorInjector(InjectorProcessor processor, Constructor<T> constructor) {
return new CodegenConstructorInjector<>(processor, constructor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,19 @@ final class CodegenMethodInjector implements MethodInjector {

private static final Object[] EMPTY = new Object[0];

private static final AtomicInteger ID = new AtomicInteger();

private final InjectorProcessor processor;
private final Method method;
private final InjectorCache cache;
private final boolean empty;

private final BiFunction<Object, Object[], Object> generated;

CodegenMethodInjector(InjectorProcessor processor, Method method) throws Exception {
CodegenMethodInjector(InjectorProcessor processor, Method method) {
this.processor = processor;
this.method = method;
this.cache = InjectorCache.of(processor, method);
this.empty = method.getParameterCount() == 0;

this.generated = generate(method);
this.generated = CodegenCache.getMethodInvoker(method);
}

@SuppressWarnings("unchecked")
Expand All @@ -59,43 +56,9 @@ public <T> T invoke(Object instance, Object... injectorArgs) throws Exception {
);
}

private static BiFunction<Object, Object[], Object> generate(Method method) throws Exception {
Class<?> declaringClass = method.getDeclaringClass();
if (!Modifier.isPublic(declaringClass.getModifiers())) {
throw new IllegalStateException(declaringClass + " has to be public");
}

if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalStateException(method + " has to be public");
}

ByteBuddy byteBuddy = new ByteBuddy();
DynamicType.Unloaded<?> classPackage = byteBuddy
.makePackage(declaringClass.getPackage().getName())
.make();

Class<?> loaded = byteBuddy.subclass(Object.class)
.implement(GeneratedFunction.class)
.name(declaringClass.getName() + "$" + method.getName() + "$" + ID.incrementAndGet())
.method(ElementMatchers.named("apply"))
.intercept(MethodCall.invoke(method)
.onArgument(0)
.withArgumentArrayElements(1)
.withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
.make()
.include(classPackage)
.load(declaringClass.getClassLoader())
.getLoaded();

return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance());
}

@Override
public Method getMethod() {
return this.method;

Check warning on line 61 in di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjector.java

View check run for this annotation

Codecov / codecov/patch

di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjector.java#L61

Added line #L61 was not covered by tests
}

@FunctionalInterface
public interface GeneratedFunction extends BiFunction<Object, Object[], Object> { }

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public final class CodegenMethodInjectorFactory implements MethodInjectorFactory {

@Override
public MethodInjector createMethodInjector(InjectorProcessor processor, Method method) throws Exception {
public MethodInjector createMethodInjector(InjectorProcessor processor, Method method) {
return new CodegenMethodInjector(processor, method);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.panda_lang.utilities.inject.CodegenConstructorInjectorFactory
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void testInjector() {
});

Assertions.assertDoesNotThrow(() -> {
TestClass instance = injector.newInstance(TestClass.class);
TestClass instance = injector.forGeneratedConstructor(TestClass.class).newInstance();

Method testTypeInvoke = ReflectionUtils.getMethod(TestClass.class, "testTypeInvoke", String.class).get();
assertEquals(HELLO, injector.invokeMethod(testTypeInvoke, instance));
Expand All @@ -87,7 +87,7 @@ void testInjector() {

public static final class TestClass {

TestClass(String value) {
public TestClass(String value) {
assertEquals(HELLO, value);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.panda_lang.utilities.inject;

import java.lang.reflect.Constructor;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
interface ConstructorInjectorFactory<T> {

ConstructorInjector<T> createConstructorInjector(InjectorProcessor processor, Constructor<T> constructor);

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ final class DefaultInjector implements Injector {
private final Resources resources;
private final InjectorProcessor processor;

private final Lazy<ConstructorInjectorFactory> constructorInjectorFactory = new Lazy<>(() ->
StreamSupport.stream(Spliterators.spliteratorUnknownSize(ServiceLoader.load(ConstructorInjectorFactory.class).iterator(), ORDERED), false)
.findAny()
.orElseGet(() -> ((processor, constructor) -> {
return this.forConstructor(constructor);

Check warning on line 43 in di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java

View check run for this annotation

Codecov / codecov/patch

di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java#L40-L43

Added lines #L40 - L43 were not covered by tests
}))
);
private final Lazy<MethodInjectorFactory> methodInjectorFactory = new Lazy<>(() ->
StreamSupport.stream(Spliterators.spliteratorUnknownSize(ServiceLoader.load(MethodInjectorFactory.class).iterator(), ORDERED), false)
.findAny()
Expand All @@ -58,13 +65,14 @@ public <T> ConstructorInjector<T> forConstructor(Constructor<T> constructor) {
}

@Override
public <T> ConstructorInjector<T> forGeneratedConstructor(Constructor<T> constructor) {
return this.forConstructor(constructor);
public <T> ConstructorInjector<T> forGeneratedConstructor(Class<T> type) {
return this.forGeneratedConstructor(ClassCache.getConstructor(type));

Check warning on line 69 in di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java

View check run for this annotation

Codecov / codecov/patch

di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java#L69

Added line #L69 was not covered by tests
}

@SuppressWarnings("unchecked")
@Override
public <T> ConstructorInjector<T> forGeneratedConstructor(Class<T> type) {
return this.forConstructor(type);
public <T> ConstructorInjector<T> forGeneratedConstructor(Constructor<T> constructor) {
return (ConstructorInjector<T>) this.constructorInjectorFactory.get().createConstructorInjector(this.processor, constructor);

Check warning on line 75 in di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java

View check run for this annotation

Codecov / codecov/patch

di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java#L75

Added line #L75 was not covered by tests
}

@Override
Expand Down Expand Up @@ -137,7 +145,7 @@ public MethodInjector forMethod(Method method) {
}

@Override
public MethodInjector forGeneratedMethod(Method method) throws Exception {
public MethodInjector forGeneratedMethod(Method method) {
return this.methodInjectorFactory.get().createMethodInjector(this.processor, method);

Check warning on line 149 in di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java

View check run for this annotation

Codecov / codecov/patch

di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java#L149

Added line #L149 was not covered by tests
}

Expand Down
Loading

0 comments on commit 87270cd

Please sign in to comment.