Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* Add @autoconstruct

* Bump version

* Fux AutoConstruct targets

* Fix targets again

* Fix @autoconstruct on methods/constructors parameters

* Improve javadocs of @autoconstruct

* Improve auto construct bind
  • Loading branch information
P3ridot authored Jul 15, 2023
1 parent d9e7961 commit 73ba49f
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public MethodInjector forGeneratedMethod(Method method) throws Exception {
}

@Override
public <T> @Nullable T invokeParameter(Parameter parameter, Object... injectorArgs) throws Exception {
public <T> @Nullable T invokeParameter(Parameter parameter, Object... injectorArgs) throws Throwable {
return ObjectUtils.cast(processor.tryFetchValue(processor, new PropertyParameter(parameter), injectorArgs));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public final class DependencyInjectionException extends RuntimeException {
super(message);
}

DependencyInjectionException(Throwable cause) {
super(cause);
}

DependencyInjectionException(String message, Throwable cause) {
super(message, cause);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.panda_lang.utilities.inject.annotations.AutoConstruct;
import org.panda_lang.utilities.inject.annotations.Inject;
import org.panda_lang.utilities.inject.annotations.PostConstruct;

public final class FieldsInjector<T> {

Expand All @@ -22,7 +22,7 @@ public T newInstance(Object... injectorArgs) throws Throwable {
T instance = constructorInjector.newInstance(injectorArgs);

for (Field field : getAllFields(instance.getClass())) {
if (!field.isAnnotationPresent(Inject.class)) {
if (!field.isAnnotationPresent(Inject.class) && !field.isAnnotationPresent(AutoConstruct.class)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.panda_lang.utilities.inject;

import org.jetbrains.annotations.Nullable;
import org.panda_lang.utilities.inject.annotations.AutoConstruct;
import org.panda_lang.utilities.inject.annotations.Injectable;
import panda.std.Option;
import panda.utilities.ObjectUtils;
Expand All @@ -37,8 +38,19 @@ final class InjectorProcessor {
private final Injector injector;
private final Map<Executable, Annotation[]> injectableCache = new HashMap<>();

private final Bind<Annotation> autoConstructBind;

InjectorProcessor(Injector injector) {
this.injector = injector;

this.autoConstructBind = new DefaultBind<>(AutoConstruct.class);
this.autoConstructBind.assignHandler((property, annotation, injectorArgs) -> {
try {
return injector.newInstanceWithFields(property.getType(), injectorArgs);
} catch (Throwable ex) {
throw new DependencyInjectionException(ex);
}
});
}

protected Object[] fetchValues(InjectorCache cache, Object... injectorArgs) throws Exception {
Expand Down Expand Up @@ -132,9 +144,12 @@ protected Bind<Annotation>[] fetchBinds(Annotation[] annotations, Executable exe
Parameter parameter = parameters[index];

Class<?> requiredType = annotation != null ? annotation.annotationType() : parameter.getType();
Option<Bind<Annotation>> bindValue = resources.getBind(requiredType);
Bind<Annotation> bind = resources.getBind(requiredType).orNull();
if (bind == null && parameter.getAnnotation(AutoConstruct.class) != null) {
bind = this.autoConstructBind;
}

binds[index] = bindValue.orThrow(() -> {
if (bind == null) {
String simplifiedParameters = Joiner.on(", ").join(Arrays.stream(executable.getParameters())
.map(p -> p.getType().getSimpleName() + " " + p.getName())
.collect(Collectors.toList()))
Expand All @@ -148,17 +163,24 @@ protected Bind<Annotation>[] fetchBinds(Annotation[] annotations, Executable exe
" in executable: " + executable.getDeclaringClass().getSimpleName() + "#" + executable.getName() + "(" + simplifiedParameters + ")" +
System.lineSeparator()
);
});
}
binds[index] = bind;
}

return binds;
}

protected Bind<Annotation> fetchBind(@Nullable Annotation annotation, Property property) {
Class<?> requiredType = annotation != null ? annotation.annotationType() : property.getType();
return injector.getResources().getBind(requiredType).orThrow(() -> {
Bind<Annotation> bind = this.injector.getResources().getBind(requiredType).orNull();
if (bind == null && property.getAnnotation(AutoConstruct.class) != null) {
bind = this.autoConstructBind;
}

if (bind == null) {
throw new DependencyInjectionException("Cannot find proper bind for " + property + " property (type " + property.getType() + ")");
});
}
return bind;
}

protected Collection<BindHandler<Annotation, Object, ?>>[] fetchHandlers(Executable executable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.panda_lang.utilities.inject.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks field/parameter that value instance should be constructed, if no other bind was found
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoConstruct {

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

import org.junit.jupiter.api.Test;
import org.panda_lang.utilities.inject.DependencyInjection;
import org.panda_lang.utilities.inject.Injector;
import org.panda_lang.utilities.inject.annotations.AutoConstruct;
import org.panda_lang.utilities.inject.annotations.Inject;
import org.panda_lang.utilities.inject.annotations.PostConstruct;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

final class AutoConstructTest {

static class Service {

@AutoConstruct
private Repository repository;

@PostConstruct
private void construct() {
assertNotNull(this.repository);
}

public void testMethod(@AutoConstruct DataProvider dataProvider) {
assertNotNull(dataProvider);
}

}

static class Repository {

@AutoConstruct
private DataProvider dataProvider;

@Inject
private String testValue;

@PostConstruct
private void construct() {
assertEquals("TestString", this.testValue);
assertNotNull(this.dataProvider);
}

}

static class DataProvider {

@Inject
private String testValue;

@PostConstruct
private void construct() {
assertEquals("TestString", this.testValue);
}

}

@Test
void shouldCreateInstances() throws Throwable {
Injector injector = DependencyInjection.createInjector(resources -> {
resources.on(String.class).assignInstance("TestString");
});

injector.newInstanceWithFields(DataProvider.class);
injector.newInstanceWithFields(Repository.class);
injector.newInstanceWithFields(Service.class);
}

@Test
void shouldRunMethod() throws Throwable {
Injector injector = DependencyInjection.createInjector(resources -> {
resources.on(String.class).assignInstance("TestString");
});

Service service = injector.newInstanceWithFields(Service.class);
injector.invokeMethod(Service.class.getMethod("testMethod", DataProvider.class), service);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.panda_lang.utilities.inject;
package org.panda_lang.utilities.inject.annotation;

import org.junit.jupiter.api.Test;
import org.panda_lang.utilities.inject.DependencyInjection;
import org.panda_lang.utilities.inject.Injector;
import org.panda_lang.utilities.inject.annotations.Inject;
import org.panda_lang.utilities.inject.annotations.PostConstruct;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down

0 comments on commit 73ba49f

Please sign in to comment.