Skip to content

Commit 0d44ad4

Browse files
committed
#214 - ENH: Add support for using @prototype on @factory @bean methods
1 parent ab899e4 commit 0d44ad4

File tree

8 files changed

+111
-23
lines changed

8 files changed

+111
-23
lines changed

blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package org.example.myapp.config;
22

33
import io.avaje.inject.Bean;
4+
import io.avaje.inject.Component;
45
import io.avaje.inject.Factory;
6+
import io.avaje.inject.Prototype;
7+
import jakarta.inject.Inject;
8+
import jakarta.inject.Provider;
59
import org.example.myapp.HelloData;
610

711
@Factory
@@ -20,8 +24,9 @@ public String helloData() {
2024
}
2125
}
2226

27+
@Prototype
2328
@Bean
24-
Builder newBuilder() {
29+
public Builder newBuilder() {
2530
return new Builder();
2631
}
2732

@@ -35,4 +40,19 @@ public static class Builder {
3540

3641
public static class Generated {
3742
}
43+
44+
@Component
45+
public static class BuilderUser {
46+
47+
final Provider<Builder> builderProvider;
48+
49+
@Inject
50+
public BuilderUser(Provider<Builder> builderProvider) {
51+
this.builderProvider = builderProvider;
52+
}
53+
54+
public Builder createBuilder() {
55+
return builderProvider.get();
56+
}
57+
}
3858
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.example.myapp;
2+
3+
import io.avaje.inject.BeanScope;
4+
import org.example.myapp.config.AppConfig;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
class FactoryPrototypeMethodTest {
10+
11+
@Test
12+
void test() {
13+
try (BeanScope beanScope = BeanScope.builder().build()) {
14+
15+
AppConfig.Builder one = beanScope.get(AppConfig.Builder.class);
16+
AppConfig.Builder two = beanScope.get(AppConfig.Builder.class);
17+
assertThat(one).isNotNull();
18+
assertThat(one).isNotSameAs(two);
19+
20+
AppConfig.BuilderUser builderUser = beanScope.get(AppConfig.BuilderUser.class);
21+
AppConfig.Builder b0 = builderUser.createBuilder();
22+
AppConfig.Builder b1 = builderUser.createBuilder();
23+
24+
assertThat(b0).isNotNull();
25+
assertThat(b0).isNotSameAs(b1);
26+
}
27+
}
28+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ boolean providedByDefaultModule(String dependency) {
4242
void readBeans(RoundEnvironment roundEnv) {
4343
for (Data data : scopeAnnotations.values()) {
4444
for (Element customBean : roundEnv.getElementsAnnotatedWith(data.type)) {
45-
// context.logWarn("read custom scope bean " + customBean + " for scope " + entry.getKey());
46-
data.scopeInfo.read((TypeElement) customBean, false);
45+
if (customBean instanceof TypeElement) {
46+
data.scopeInfo.read((TypeElement) customBean, false);
47+
}
4748
}
4849
}
4950
}

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

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class MethodReader {
1717
private final ExecutableElement element;
1818
private final String factoryType;
1919
private final String methodName;
20+
private final boolean prototype;
2021
private final String returnTypeRaw;
2122
private final GenericType genericType;
2223
private final String shortName;
@@ -31,11 +32,12 @@ class MethodReader {
3132
private final boolean optionalType;
3233

3334
MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType) {
34-
this(context, element, beanType, null, null);
35+
this(context, element, beanType, null, null, false);
3536
}
3637

37-
MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, Bean bean, Named named) {
38+
MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, Bean bean, Named named, boolean prototype) {
3839
this.isFactory = bean != null;
40+
this.prototype = prototype;
3941
this.element = element;
4042
this.methodName = element.getSimpleName().toString();
4143
TypeMirror returnMirror = element.getReturnType();
@@ -129,6 +131,31 @@ String builderBuildBean() {
129131
return sb.toString();
130132
}
131133

134+
public void builderAddProtoBean(Append writer) {
135+
if (isVoid) {
136+
writer.append("Error - void @Prototype method ?").eol();
137+
return;
138+
}
139+
if (optionalType) {
140+
writer.append("Error - Optional type with @Prototype method is not supported").eol();
141+
return;
142+
}
143+
String indent = " ";
144+
writer.append(indent).append(" // prototype scope bean method").eol();
145+
writer.append(indent).append(" builder.registerProvider(() -> {").eol();
146+
writer.append("%s return ", indent);
147+
writer.append(String.format("factory.%s(", methodName));
148+
for (int i = 0; i < params.size(); i++) {
149+
if (i > 0) {
150+
writer.append(", ");
151+
}
152+
writer.append(params.get(i).builderGetDependency("builder", true));
153+
}
154+
writer.append(");").eol();
155+
writer.append(indent).append(" });").eol();
156+
writer.append(indent).append("}").eol();
157+
}
158+
132159
void builderBuildAddBean(Append writer) {
133160
if (!isVoid) {
134161
String indent = optionalType ? " " : " ";
@@ -228,11 +255,15 @@ public void commentBuildMethod(Append writer) {
228255
writer.append(CODE_COMMENT_BUILD_FACTORYBEAN, shortName, factoryShortName, methodName).eol();
229256
}
230257

231-
public boolean isPublic() {
258+
boolean isProtoType() {
259+
return prototype;
260+
}
261+
262+
boolean isPublic() {
232263
return element.getModifiers().contains(Modifier.PUBLIC);
233264
}
234265

235-
public boolean isNotPrivate() {
266+
boolean isNotPrivate() {
236267
return !element.getModifiers().contains(Modifier.PRIVATE);
237268
}
238269

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@
1212
import javax.annotation.processing.ProcessingEnvironment;
1313
import javax.annotation.processing.RoundEnvironment;
1414
import javax.lang.model.SourceVersion;
15-
import javax.lang.model.element.*;
15+
import javax.lang.model.element.AnnotationMirror;
16+
import javax.lang.model.element.Element;
17+
import javax.lang.model.element.ElementKind;
18+
import javax.lang.model.element.TypeElement;
1619
import javax.lang.model.util.Elements;
17-
import java.util.*;
20+
import java.util.Iterator;
21+
import java.util.LinkedHashSet;
22+
import java.util.Set;
1823

1924
public class Processor extends AbstractProcessor {
2025

@@ -78,9 +83,10 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
7883
private void readScopes(Set<? extends Element> scopes) {
7984
for (Element element : scopes) {
8085
if (element.getKind() == ElementKind.ANNOTATION_TYPE) {
81-
// context.logDebug("detected scope annotation " + element);
82-
TypeElement type = (TypeElement) element;
83-
allScopes.addScopeAnnotation(type);
86+
if (element instanceof TypeElement) {
87+
TypeElement type = (TypeElement) element;
88+
allScopes.addScopeAnnotation(type);
89+
}
8490
}
8591
}
8692
addTestScope();
@@ -101,9 +107,8 @@ private void addTestScope() {
101107
*/
102108
private void readChangedBeans(Set<? extends Element> beans, boolean factory) {
103109
for (Element element : beans) {
104-
if (!(element instanceof TypeElement)) {
105-
context.logError("unexpected type [" + element + "]");
106-
} else {
110+
// ignore methods (e.g. factory methods with @Prototype on them)
111+
if (element instanceof TypeElement) {
107112
TypeElement typeElement = (TypeElement) element;
108113
final ScopeInfo scope = findScope(typeElement);
109114
if (!factory) {

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,13 @@ private void writeFactoryBeanMethod(MethodReader method) {
122122
writer.append(" public static void build_%s(%s builder) {", method.getName(), beanReader.builderType()).eol();
123123
method.buildAddFor(writer);
124124
writer.append(method.builderGetFactory()).eol();
125-
writer.append(method.builderBuildBean()).eol();
126-
method.builderBuildAddBean(writer);
127-
writer.append(" }").eol();
125+
if (method.isProtoType()) {
126+
method.builderAddProtoBean(writer);
127+
} else {
128+
writer.append(method.builderBuildBean()).eol();
129+
method.builderBuildAddBean(writer);
130+
writer.append(" }").eol();
131+
}
128132
writer.append(" }").eol().eol();
129133
}
130134

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.inject.generator;
22

33
import io.avaje.inject.Bean;
4+
import io.avaje.inject.Prototype;
45
import jakarta.inject.Inject;
56
import jakarta.inject.Named;
67

@@ -147,7 +148,8 @@ private void checkForAspect(ExecutableElement methodElement) {
147148
private void addFactoryMethod(ExecutableElement methodElement, Bean bean) {
148149
// Not yet reading Qualifier annotations, Named only at this stage
149150
Named named = methodElement.getAnnotation(Named.class);
150-
factoryMethods.add(new MethodReader(context, methodElement, baseType, bean, named).read());
151+
boolean prototype = methodElement.getAnnotation(Prototype.class) != null;
152+
factoryMethods.add(new MethodReader(context, methodElement, baseType, bean, named, prototype).read());
151153
}
152154

153155
BeanAspects hasAspects() {

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.avaje.inject;
22

3-
import jakarta.inject.Scope;
4-
53
import java.lang.annotation.ElementType;
64
import java.lang.annotation.Retention;
75
import java.lang.annotation.RetentionPolicy;
@@ -21,8 +19,7 @@
2119
* }
2220
* }</pre>
2321
*/
24-
@Target({ElementType.TYPE})
22+
@Target({ElementType.TYPE, ElementType.METHOD})
2523
@Retention(RetentionPolicy.RUNTIME)
26-
@Scope
2724
public @interface Prototype {
2825
}

0 commit comments

Comments
 (0)