Skip to content

Commit 321eb21

Browse files
committed
#100 - Support Optional<T> as result type of @bean methods. Support Optional<T> and @nullable for injection
1 parent 23b919f commit 321eb21

File tree

24 files changed

+456
-21
lines changed

24 files changed

+456
-21
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
class Constants {
44

5+
static final String OPTIONAL = "java.util.Optional";
56
static final String KOTLIN_METADATA = "kotlin.Metadata";
67

78
static final String PROVIDER = "jakarta.inject.Provider";

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ class FieldReader {
77
private final Element element;
88
private final String name;
99
private final UtilType type;
10+
private final boolean nullable;
1011
private boolean requestParam;
1112
private String requestParamName;
1213

1314
FieldReader(Element element) {
1415
this.element = element;
1516
this.name = Util.getNamed(element);
17+
this.nullable = Util.isNullable(element);
1618
this.type = Util.determineType(element.asType());
1719
}
1820

@@ -22,7 +24,7 @@ String getFieldName() {
2224

2325
String builderGetDependency() {
2426
StringBuilder sb = new StringBuilder();
25-
sb.append("b.").append(type.getMethod());
27+
sb.append("b.").append(type.getMethod(nullable));
2628
sb.append(getFieldType()).append(".class");
2729
if (name != null) {
2830
sb.append(",\"").append(name).append("\"");

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class MethodReader {
2828
private final String name;
2929
private final TypeReader typeReader;
3030
private boolean beanLifeCycle;
31+
private boolean optionalType;
3132

3233
MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType) {
3334
this(context, element, beanType, null, null);
@@ -37,16 +38,23 @@ class MethodReader {
3738
this.isFactory = bean != null;
3839
this.element = element;
3940
this.methodName = element.getSimpleName().toString();
40-
TypeMirror returnType = element.getReturnType();
41-
this.returnTypeRaw = returnType.toString();
41+
TypeMirror returnMirror = element.getReturnType();
42+
String raw = returnMirror.toString();
43+
if (Util.isOptional(raw)) {
44+
optionalType = true;
45+
returnTypeRaw = Util.extractOptionalType(raw);
46+
} else {
47+
optionalType = false;
48+
returnTypeRaw = raw;
49+
}
4250
this.shortName = Util.shortName(returnTypeRaw);
4351
this.factoryType = beanType.getQualifiedName().toString();
4452
this.factoryShortName = Util.shortName(factoryType);
4553
this.isVoid = Util.isVoid(returnTypeRaw);
4654
this.initMethod = (bean == null) ? null : bean.initMethod();
4755
this.destroyMethod = (bean == null) ? null : bean.destroyMethod();
4856
this.name = (named == null) ? null : named.value();
49-
TypeElement returnElement = baseTypeElement(context.asElement(returnType));
57+
TypeElement returnElement = context.element(returnTypeRaw);
5058
if (returnElement == null) {
5159
this.typeReader = null;
5260
} else {
@@ -56,10 +64,6 @@ class MethodReader {
5664
}
5765
}
5866

59-
TypeElement baseTypeElement(Element element) {
60-
return element instanceof TypeElement ? (TypeElement) element : null;
61-
}
62-
6367
void read() {
6468
List<? extends VariableElement> ps = element.getParameters();
6569
for (VariableElement p : ps) {
@@ -99,14 +103,18 @@ String builderGetFactory() {
99103

100104
String builderBuildBean() {
101105
StringBuilder sb = new StringBuilder();
106+
sb.append(" ");
102107
if (isVoid) {
103-
sb.append(String.format(" factory.%s(", methodName));
108+
sb.append(String.format("factory.%s(", methodName));
104109
} else {
105-
sb.append(String.format(" %s bean = factory.%s(", Util.shortName(returnTypeRaw), methodName));
110+
String beanType = optionalType ? String.format("Optional<%s>", shortName) : shortName;
111+
String beanName = optionalType ? "optionalBean" : "bean";
112+
sb.append(beanType);
113+
sb.append(String.format(" %s = factory.%s(", beanName, methodName));
106114
}
107115
for (int i = 0; i < params.size(); i++) {
108116
if (i > 0) {
109-
sb.append(",");
117+
sb.append(", ");
110118
}
111119
sb.append(params.get(i).builderGetDependency("builder"));
112120
}
@@ -116,15 +124,23 @@ String builderBuildBean() {
116124

117125
void builderBuildAddBean(Append writer) {
118126
if (!isVoid) {
119-
writer.append(" ");
127+
String indent = optionalType ? " " : " ";
128+
if (optionalType) {
129+
writer.append(" if (optionalBean.isPresent()) {").eol();
130+
writer.append(" %s bean = optionalBean.get();", shortName).eol();
131+
}
132+
writer.append(indent);
120133
if (beanLifeCycle || hasLifecycleMethods()) {
121134
writer.append("%s $bean = ", shortName);
122135
}
123136
writer.append("builder.register(bean);").eol();
124137
if (beanLifeCycle) {
125-
writer.append(" builder.addLifecycle($bean);").eol();
138+
writer.append(indent).append("builder.addLifecycle($bean);").eol();
126139
} else if (hasLifecycleMethods()) {
127-
writer.append(" builder.addLifecycle(new %s$lifecycle($bean));", shortName).eol();
140+
writer.append(indent).append("builder.addLifecycle(new %s$lifecycle($bean));", shortName).eol();
141+
}
142+
if (optionalType) {
143+
writer.append(" }").eol();
128144
}
129145
}
130146
}
@@ -147,6 +163,9 @@ void addImports(Set<String> importTypes) {
147163
if (beanLifeCycle || hasLifecycleMethods()) {
148164
importTypes.add(Constants.BEAN_LIFECYCLE);
149165
}
166+
if (optionalType) {
167+
importTypes.add(Constants.OPTIONAL);
168+
}
150169
if (typeReader != null) {
151170
typeReader.addImports(importTypes);
152171
}
@@ -246,12 +265,14 @@ static class MethodParam {
246265
private final UtilType utilType;
247266
private final String paramType;
248267
private final GenericType genericType;
268+
private final boolean nullable;
249269
private int providerIndex;
250270
private boolean requestParam;
251271
private String requestParamName;
252272

253273
MethodParam(VariableElement param) {
254274
this.named = Util.getNamed(param);
275+
this.nullable = Util.isNullable(param);
255276
this.utilType = Util.determineType(param.asType());
256277
this.paramType = utilType.rawType();
257278
this.genericType = GenericType.maybe(paramType);
@@ -263,7 +284,7 @@ String builderGetDependency(String builderName) {
263284
// passed as provider to build method
264285
sb.append("prov").append(providerIndex).append(".get(");
265286
} else {
266-
sb.append(builderName).append(".").append(utilType.getMethod());
287+
sb.append(builderName).append(".").append(utilType.getMethod(nullable));
267288
}
268289
if (genericType == null) {
269290
sb.append(Util.shortName(paramType)).append(".class");

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import javax.annotation.processing.ProcessingEnvironment;
77
import javax.lang.model.element.Element;
88
import javax.lang.model.element.PackageElement;
9+
import javax.lang.model.element.TypeElement;
910
import javax.lang.model.type.TypeMirror;
1011
import javax.lang.model.util.Elements;
1112
import javax.lang.model.util.Types;
@@ -132,6 +133,10 @@ String getContextPackage() {
132133
return contextPackage;
133134
}
134135

136+
TypeElement element(String rawType) {
137+
return elementUtils.getTypeElement(rawType);
138+
}
139+
135140
Element asElement(TypeMirror returnType) {
136141
return typeUtils.asElement(returnType);
137142
}
@@ -182,5 +187,4 @@ private void buildStringArray(Append writer, String[] values, boolean asArray) {
182187
writer.append("}");
183188
}
184189
}
185-
186190
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private void writeStaticFactoryMethod() {
120120
List<MethodReader.MethodParam> params = constructor.getParams();
121121
for (int i = 0; i < params.size(); i++) {
122122
if (i > 0) {
123-
writer.append(",");
123+
writer.append(", ");
124124
}
125125
writer.append(params.get(i).builderGetDependency("builder"));
126126
}
@@ -143,7 +143,7 @@ private void writeStaticFactoryMethod() {
143143
List<MethodReader.MethodParam> methodParams = methodReader.getParams();
144144
for (int i = 0; i < methodParams.size(); i++) {
145145
if (i > 0) {
146-
writer.append(" ,");
146+
writer.append(", ");
147147
}
148148
writer.append(methodParams.get(i).builderGetDependency("b"));
149149
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
class Util {
1111

1212
private static final String PROVIDER_PREFIX = "jakarta.inject.Provider<";
13-
13+
private static final String OPTIONAL_PREFIX = "java.util.Optional<";
14+
private static final String NULLABLE = "Nullable";
1415
private static final int PROVIDER_LENGTH = PROVIDER_PREFIX.length();
1516

1617
static boolean isVoid(String type) {
@@ -58,6 +59,10 @@ static String shortName(String fullType) {
5859
}
5960
}
6061

62+
static boolean isOptional(String rawType) {
63+
return rawType.startsWith(OPTIONAL_PREFIX);
64+
}
65+
6166
static String extractOptionalType(String rawType) {
6267
return rawType.substring(19, rawType.length() - 1);
6368
}
@@ -132,6 +137,18 @@ public static String getNamed(Element p) {
132137
return null;
133138
}
134139

140+
/**
141+
* Return true if the element has a Nullable annotation.
142+
*/
143+
public static boolean isNullable(Element p) {
144+
for (AnnotationMirror mirror : p.getAnnotationMirrors()) {
145+
if (NULLABLE.equals(shortName(mirror.getAnnotationType().toString()))) {
146+
return true;
147+
}
148+
}
149+
return false;
150+
}
151+
135152
public static String addForInterface(String interfaceType) {
136153
if (interfaceType.contains("<")) {
137154
return null;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ String rawType() {
4545
}
4646
}
4747

48-
String getMethod() {
48+
String getMethod(boolean nullable) {
4949
switch (type) {
5050
case SET:
5151
return "getSet(";
@@ -56,7 +56,7 @@ String getMethod() {
5656
case PROVIDER:
5757
return "getProvider(";
5858
}
59-
return "get(";
59+
return nullable ? "getNullable(" : "get(";
6060
}
6161

6262
}

inject-test/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@
4545
<scope>test</scope>
4646
</dependency>
4747

48+
<dependency>
49+
<groupId>io.avaje</groupId>
50+
<artifactId>avaje-jsr305</artifactId>
51+
<version>1.0</version>
52+
<scope>test</scope>
53+
</dependency>
54+
4855
<dependency>
4956
<groupId>io.javalin</groupId>
5057
<artifactId>javalin</artifactId>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.example.optional;
2+
3+
import jakarta.inject.Named;
4+
import jakarta.inject.Singleton;
5+
6+
import java.util.Optional;
7+
8+
/**
9+
* Optional as constructor arguments.
10+
*/
11+
@Singleton
12+
public class AllQue {
13+
14+
private final Optional<Que> frodo;
15+
private final Optional<Que> sam;
16+
private final Optional<Que> bilbo;
17+
18+
AllQue(@Named("frodo") Optional<Que> frodo, @Named("sam") Optional<Que> sam, @Named("bilbo") Optional<Que> bilbo) {
19+
this.frodo = frodo;
20+
this.sam = sam;
21+
this.bilbo = bilbo;
22+
}
23+
24+
String whichSet() {
25+
return "f:"+frodo+"s:"+sam+"b:"+bilbo;
26+
}
27+
28+
public String frodoPush(String msg) {
29+
return frodo.get().push(msg);
30+
}
31+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example.optional;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Named;
5+
import jakarta.inject.Singleton;
6+
7+
import java.util.Optional;
8+
9+
/**
10+
* Optional as constructor arguments (orElse(null)).
11+
*/
12+
@Singleton
13+
public class AllQue2 {
14+
15+
@Inject @Named("frodo") Que frodo;
16+
@Inject @Named("sam") Optional<Que> sam;
17+
@Inject @Named("bilbo") Optional<Que> bilbo;
18+
19+
String whichSet() {
20+
return "f:"+frodo+"s:"+sam+"b:"+bilbo;
21+
}
22+
23+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.example.optional;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Named;
5+
import jakarta.inject.Singleton;
6+
7+
import java.util.Optional;
8+
9+
/**
10+
* Optional with method injection.
11+
*/
12+
@Singleton
13+
public class AllQue3 {
14+
15+
private final Que frodo;
16+
private Que sam;
17+
private Que bilbo;
18+
19+
AllQue3(@Named("frodo") Que frodo) {
20+
this.frodo = frodo;
21+
}
22+
23+
@Inject
24+
void with(@Named("sam") Optional<Que> sam, @Named("bilbo") Optional<Que> bilbo) {
25+
this.sam = sam.orElse(null);
26+
this.bilbo = bilbo.orElse(null);
27+
}
28+
29+
String whichSet() {
30+
return "f:"+frodo+"s:"+sam+"b:"+bilbo;
31+
}
32+
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.example.optional;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Named;
5+
import jakarta.inject.Singleton;
6+
7+
import javax.annotation.Nullable;
8+
9+
/**
10+
* Nullable with method injection.
11+
*/
12+
@Singleton
13+
public class AllQue4 {
14+
15+
private final Que frodo;
16+
private Que sam;
17+
private Que bilbo;
18+
19+
AllQue4(@Named("frodo") Que frodo) {
20+
this.frodo = frodo;
21+
}
22+
23+
@Inject
24+
void with(@Named("sam") Que sam, @Nullable @Named("bilbo") Que bilbo) {
25+
this.sam = sam;
26+
this.bilbo = bilbo;
27+
}
28+
29+
String whichSet() {
30+
return "f:"+frodo+"s:"+sam+"b:"+bilbo;
31+
}
32+
33+
}

0 commit comments

Comments
 (0)