Skip to content

Commit 49fe35e

Browse files
committed
fix field assisted inject
1 parent 832841a commit 49fe35e

File tree

10 files changed

+154
-51
lines changed

10 files changed

+154
-51
lines changed

inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package io.avaje.inject.generator;
22

3+
import static java.util.stream.Collectors.toList;
4+
35
import java.util.ArrayList;
46
import java.util.List;
7+
import java.util.Optional;
58
import java.util.Set;
69

710
import javax.lang.model.element.*;
11+
import javax.lang.model.util.ElementFilter;
812

913
import io.avaje.inject.generator.MethodReader.MethodParam;
1014

@@ -23,7 +27,7 @@ final class AssistBeanReader {
2327
private final TypeReader typeReader;
2428
private final TypeElement targetType;
2529
private final String qualifierName;
26-
private ExecutableElement factoryMethod;
30+
private final ExecutableElement factoryMethod;
2731

2832
AssistBeanReader(TypeElement beanType) {
2933
this.beanType = beanType;
@@ -38,35 +42,80 @@ final class AssistBeanReader {
3842
this.constructor = typeReader.constructor();
3943

4044
AssistFactoryPrism instanceOn = AssistFactoryPrism.getInstanceOn(beanType);
41-
targetType = APContext.asTypeElement(instanceOn.value());
42-
validateTarget(targetType);
43-
44-
for (Element enclosedElement : targetType.getEnclosedElements()) {
45-
if (enclosedElement.getKind() == ElementKind.METHOD) {
46-
factoryMethod = (ExecutableElement) enclosedElement;
47-
}
48-
}
45+
var factoryType = APContext.asTypeElement(instanceOn.value());
4946

5047
constructor.params().stream()
51-
.filter(MethodParam::assisted)
52-
.map(MethodParam::element)
53-
.forEach(assistedElements::add);
48+
.filter(MethodParam::assisted)
49+
.map(MethodParam::element)
50+
.forEach(assistedElements::add);
5451
injectFields.stream()
55-
.filter(FieldReader::assisted)
56-
.map(FieldReader::element)
57-
.forEach(assistedElements::add);
52+
.filter(FieldReader::assisted)
53+
.map(FieldReader::element)
54+
.forEach(assistedElements::add);
5855
injectMethods.stream()
59-
.map(MethodReader::params)
60-
.flatMap(List::stream)
61-
.filter(MethodParam::assisted)
62-
.map(MethodParam::element)
63-
.forEach(assistedElements::add);
56+
.map(MethodReader::params)
57+
.flatMap(List::stream)
58+
.filter(MethodParam::assisted)
59+
.map(MethodParam::element)
60+
.forEach(assistedElements::add);
61+
62+
factoryMethod =
63+
ElementFilter.methodsIn(factoryType.getEnclosedElements()).stream()
64+
.filter(e -> e.getModifiers().contains(Modifier.ABSTRACT))
65+
.findFirst()
66+
.orElse(null);
67+
68+
if (!factoryType.getQualifiedName().contentEquals("java.lang.Void")) {
69+
validateTarget(factoryType);
70+
this.targetType = factoryType;
71+
} else {
72+
targetType = null;
73+
}
6474
}
6575

6676
private void validateTarget(TypeElement t) {
67-
if (t.getKind() != ElementKind.INTERFACE || !t.getModifiers().contains(Modifier.ABSTRACT)) {
68-
APContext.logError(type, "@AssistFactory targets must be abstract");
77+
var methods = ElementFilter.methodsIn(t.getEnclosedElements());
78+
if (!APContext.elements().isFunctionalInterface(t)) {
79+
if (!t.getModifiers().contains(Modifier.ABSTRACT)) {
80+
APContext.logError(type, "@AssistFactory targets must be abstract");
81+
} else if (methods.stream()
82+
.filter(e -> e.getModifiers().contains(Modifier.ABSTRACT))
83+
.collect(toList())
84+
.size()
85+
!= 1) {
86+
APContext.logError(type, "@AssistFactory targets must have only one abstract method");
87+
}
88+
}
89+
var sb =
90+
new StringBuilder(
91+
String.format(
92+
"@AssistFactory targets for type %s must have an abstract method with form '%s <methodName>(",
93+
shortName(), shortName()));
94+
95+
for (var iterator = assistedElements.iterator(); iterator.hasNext(); ) {
96+
var element = iterator.next();
97+
98+
var typeName = UType.parse(element.asType());
99+
100+
sb.append(
101+
String.format("%s %s", typeName.shortWithoutAnnotations(), element.getSimpleName()));
102+
if (iterator.hasNext()) {
103+
sb.append(", ");
104+
}
69105
}
106+
var errorMsg = sb.append(")' method.").toString();
107+
108+
Optional.of(factoryMethod).stream()
109+
.map(ExecutableElement::getParameters)
110+
.findAny()
111+
.ifPresentOrElse(
112+
params -> {
113+
var mismatched = params.size() != assistedElements.size();
114+
if (mismatched) {
115+
APContext.logError(t, errorMsg);
116+
}
117+
},
118+
() -> APContext.logError(t, errorMsg));
70119
}
71120

72121
@Override

inject-generator/src/main/java/io/avaje/inject/generator/SimpleAssistWriter.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,14 @@ private void injectFields() {
229229
writer.indent(" ").append("bean.%s = %s;", fieldName, getDependency).eol();
230230
}
231231

232-
for (var field : assistedElements) {
233-
writer.indent(" ").append("bean.%s = %s;", field.getSimpleName(), field.getSimpleName()).eol();
234-
}
232+
assistedElements.stream()
233+
.filter(e -> e.getKind() == ElementKind.FIELD)
234+
.forEach(
235+
field ->
236+
writer
237+
.indent(" ")
238+
.append("bean.%s = %s;", field.getSimpleName(), field.getSimpleName())
239+
.eol());
235240
}
236241

237242
private void injectMethods() {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
import java.util.List;
4+
5+
import io.avaje.inject.AssistFactory;
6+
import io.avaje.inject.Assisted;
7+
import io.avaje.lang.Nullable;
8+
import jakarta.inject.Inject;
9+
import jakarta.inject.Named;
10+
11+
@Named("tomato")
12+
@AssistFactory(CarFactory.class)
13+
public class Car {
14+
15+
@Assisted List<String> type;
16+
@Inject Wheel wheel;
17+
18+
public Car(@Assisted Paint paint, @Nullable Engine engine) {}
19+
20+
@Inject
21+
void injectMethod(@Assisted int size, Model m) {}
22+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
import java.util.List;
4+
5+
public interface CarFactory {
6+
Car construct(Paint paint, int size, List<String> type);
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class Engine {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class Model {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
public interface Paint {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class Radio {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.avaje.inject.generator.models.valid.assist;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class Wheel {}

inject/src/main/java/io/avaje/inject/AssistFactory.java

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,37 @@
66
import java.lang.annotation.Target;
77

88
/**
9-
* Annotate a bean which we want avaje-inject to generate the factory for.
10-
* <p>
11-
* The bean will have some properties that are normal dependency injection
12-
* components that will be injected and others that will be parameters
13-
* to the method on the factory - these parameters are annotated with
9+
* Annotate a bean for which we want avaje-inject to generate a factory.
10+
*
11+
* <p>The bean will have some properties of normal dependency injection components and others that
12+
* will be parameters to the method on the factory - these parameters are annotated with
1413
* {@code @Assisted}.
1514
*
1615
* <h3>Example</h3>
17-
* <p>
18-
* Have an interface which we want avaje-inject to generate an implementation.
19-
* We desire this because the factory has some dependencies that are components
20-
* that avaje-inject should inject for us and some other dependencies that are
21-
* provided by the application (and annotated with {@code Assisted}).
22-
* <p>
23-
* The factory interface must only have 1 method defined.
2416
*
25-
* <pre>{@code
17+
* <p>Create an interface which we want avaje-inject to generate an implementation. We desire this
18+
* because the factory has some managed dependencies that should be injected for us and some
19+
* dependencies that are provided by the application (and annotated with {@code Assisted}).
2620
*
21+
* <p>The factory interface must be a functional interface, or an abstract class with only one
22+
* abstract method defined.
23+
*
24+
* <pre>{@code
2725
* public interface CssFactory {
2826
*
2927
* Scanner scanner(Path myPath);
3028
* }
3129
*
3230
* }</pre>
33-
* <p>
34-
* Have a bean annotated with {@code @AssistFactory} that specifies the factory.
35-
* Any dependencies that are annotated with {@code Assisted} will be parameters
36-
* of the factory method, the other dependencies are normal components that
37-
* will be injected.
38-
* <p>
39-
* The {@code Assisted} parameters must match the factory method parameters
40-
* by name and type. In this example {@code Path myPath} match in both CssScanner
41-
* and in CssFactory.
4231
*
43-
* <pre>{@code
32+
* <p>Create a bean annotated with {@code @AssistFactory} that specifies the factory. Any
33+
* dependencies that are annotated with {@code Assisted} will be parameters of the factory method,
34+
* the other dependencies will be managed and injected by avaje-inject.
35+
*
36+
* <p>The {@code Assisted} parameters must match the factory method parameters by name and type. In
37+
* this example, {@code Path myPath} match in both CssScanner and in CssFactory.
4438
*
39+
* <pre>{@code
4540
* @AssistFactory(CssFactory.class)
4641
* class CssScanner implements Scanner {
4742
*
@@ -62,8 +57,6 @@
6257
@Retention(RetentionPolicy.RUNTIME)
6358
public @interface AssistFactory {
6459

65-
/**
66-
* Specify the factory interface for which the implementation will be generated.
67-
*/
60+
/** Specify the factory interface for which the implementation will be generated. */
6861
Class<?> value();
6962
}

0 commit comments

Comments
 (0)