Skip to content

Commit 8ad545f

Browse files
committed
#157 - Support @factory with method return types including generic wildard <?> and generic types
1 parent 1a9d879 commit 8ad545f

File tree

8 files changed

+150
-20
lines changed

8 files changed

+150
-20
lines changed

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
*/
1010
class GenericType {
1111

12+
/**
13+
* Trim off generic wildcard from the raw type if present.
14+
*/
15+
static String trimWildcard(String rawType) {
16+
if (rawType.endsWith("<?>")) {
17+
return rawType.substring(0, rawType.length() - 3);
18+
} else {
19+
return rawType;
20+
}
21+
}
22+
1223
private final String raw;
1324
private String mainType;
1425

@@ -39,6 +50,10 @@ static boolean isGeneric(String raw) {
3950
* Parse and return as GenericType.
4051
*/
4152
static GenericType parse(String raw) {
53+
raw = trimWildcard(raw);
54+
if (raw.indexOf('<') == -1) {
55+
return new GenericType(raw);
56+
}
4257
return new GenericTypeParser(raw).parse();
4358
}
4459

@@ -87,8 +102,8 @@ void addImports(Set<String> importTypes) {
87102
}
88103
}
89104

90-
private boolean includeInImports(String type) {
91-
return !type.startsWith("java.lang.") && type.contains(".");
105+
private static boolean includeInImports(String type) {
106+
return type != null && !type.startsWith("java.lang.") && type.contains(".");
92107
}
93108

94109
/**
@@ -126,10 +141,15 @@ void shortName(StringBuilder sb) {
126141
}
127142

128143
private String trimExtends() {
129-
if (mainType.startsWith("? extends ")) {
130-
return mainType.substring(10);
144+
String type = topType();
145+
if (type != null && type.startsWith("? extends ")) {
146+
return type.substring(10);
131147
}
132-
return mainType;
148+
return type;
149+
}
150+
151+
String topType() {
152+
return (mainType != null) ? mainType : raw;
133153
}
134154

135155
/**

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class MethodReader {
1717
private final String factoryType;
1818
private final String methodName;
1919
private final String returnTypeRaw;
20+
private final GenericType genericType;
2021
private final String shortName;
2122
private final boolean isVoid;
2223
private final List<MethodParam> params = new ArrayList<>();
@@ -40,19 +41,21 @@ class MethodReader {
4041
String raw = returnMirror.toString();
4142
if (Util.isOptional(raw)) {
4243
optionalType = true;
43-
returnTypeRaw = Util.extractOptionalType(raw);
44+
returnTypeRaw = GenericType.trimWildcard(Util.extractOptionalType(raw));
4445
} else {
4546
optionalType = false;
46-
returnTypeRaw = raw;
47+
returnTypeRaw = GenericType.trimWildcard(raw);
4748
}
48-
this.shortName = Util.shortName(returnTypeRaw);
49+
this.genericType = GenericType.parse(returnTypeRaw);
50+
String topType = genericType.topType();
51+
this.shortName = Util.shortName(topType);
4952
this.factoryType = beanType.getQualifiedName().toString();
5053
this.factoryShortName = Util.shortName(factoryType);
51-
this.isVoid = Util.isVoid(returnTypeRaw);
54+
this.isVoid = Util.isVoid(topType);
5255
String initMethod = (bean == null) ? null : bean.initMethod();
5356
String destroyMethod = (bean == null) ? null : bean.destroyMethod();
5457
this.name = (named == null) ? null : named.value().toLowerCase();
55-
TypeElement returnElement = context.element(returnTypeRaw);
58+
TypeElement returnElement = context.element(topType);
5659
if (returnElement == null) {
5760
this.typeReader = null;
5861
this.initMethod = initMethod;
@@ -164,7 +167,7 @@ void addImports(Set<String> importTypes) {
164167
param.addImports(importTypes);
165168
}
166169
if (isFactory) {
167-
importTypes.add(returnTypeRaw);
170+
genericType.addImports(importTypes);
168171
}
169172
if (optionalType) {
170173
importTypes.add(Constants.OPTIONAL);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static String classOfMethod(String method) {
2727
}
2828

2929
static String shortMethod(String method) {
30+
method = trimGenerics(method);
3031
int p = method.lastIndexOf('.');
3132
if (p > -1) {
3233
p = method.lastIndexOf('.', p - 1);
@@ -37,6 +38,14 @@ static String shortMethod(String method) {
3738
return method;
3839
}
3940

41+
static String trimGenerics(String type) {
42+
int i = type.indexOf('<');
43+
if (i == -1) {
44+
return type;
45+
}
46+
return type.substring(0, i);
47+
}
48+
4049
static String packageOf(String cls) {
4150
int pos = cls.lastIndexOf('.');
4251
return (pos == -1) ? "" : cls.substring(0, pos);

inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,29 @@
33
import org.junit.jupiter.api.Test;
44

55
import java.io.StringWriter;
6+
import java.util.HashSet;
7+
import java.util.Set;
68

79
import static org.assertj.core.api.Assertions.assertThat;
8-
import static org.junit.jupiter.api.Assertions.assertFalse;
9-
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
import static org.junit.jupiter.api.Assertions.*;
1011

1112
class GenericTypeTest {
1213

1314
@Test
1415
void isGeneric() {
15-
1616
assertFalse(GenericType.isGeneric("java.lang.List"));
1717
assertTrue(GenericType.isGeneric("java.lang.List<Foo>"));
18+
assertTrue(GenericType.isGeneric("java.lang.List<?>"));
1819
}
1920

2021
@Test
2122
void maybe() {
22-
2323
assertThat(GenericType.maybe("java.lang.List<Foo>").getMainType()).isEqualTo("java.lang.List");
2424
assertThat(GenericType.maybe("java.lang.List")).isNull();
2525
}
2626

2727
@Test
2828
void parse() {
29-
3029
GenericType type = new GenericTypeParser("my.exa.Repo<T,K>").parse();
3130

3231
assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
@@ -36,8 +35,50 @@ void parse() {
3635
}
3736

3837
@Test
39-
void parse_withParams() {
38+
void parse_basic() {
39+
GenericType type = GenericType.parse("my.exa.Repo");
40+
assertThat(type.shortName()).isEqualTo("Repo");
41+
assertThat(type.topType()).isEqualTo("my.exa.Repo");
42+
assertThat(type.getMainType()).isNull();
43+
assertThat(type.getParams()).isEmpty();
44+
45+
Set<String> importSet = new HashSet<>();
46+
type.addImports(importSet);
47+
assertThat(importSet).hasSize(1);
48+
assertThat(importSet).containsOnly("my.exa.Repo");
49+
}
50+
51+
@Test
52+
void parse_genericWildcard() {
53+
GenericType type = GenericType.parse("my.exa.Repo<?>");
54+
assertThat(type.shortName()).isEqualTo("Repo");
55+
assertThat(type.topType()).isEqualTo("my.exa.Repo");
56+
assertThat(type.getMainType()).isNull();
57+
assertThat(type.getParams()).isEmpty();
58+
59+
Set<String> importSet = new HashSet<>();
60+
type.addImports(importSet);
61+
assertThat(importSet).hasSize(1);
62+
assertThat(importSet).containsOnly("my.exa.Repo");
63+
}
64+
65+
@Test
66+
void parse_withParam() {
67+
GenericType type = GenericType.parse("my.exa.Repo<my.Other>");
68+
assertThat(type.shortName()).isEqualTo("RepoOther");
69+
assertThat(type.topType()).isEqualTo("my.exa.Repo");
70+
assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
71+
assertThat(type.getParams()).hasSize(1);
72+
assertThat(type.getParams().get(0).getMainType()).isEqualTo("my.Other");
4073

74+
Set<String> importSet = new HashSet<>();
75+
type.addImports(importSet);
76+
assertThat(importSet).hasSize(2);
77+
assertThat(importSet).contains("my.exa.Repo", "my.Other");
78+
}
79+
80+
@Test
81+
void parse_withParams() {
4182
GenericType type = GenericType.parse("my.exa.Repo<my.d.Haz,java.lang.Long>");
4283

4384
assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
@@ -48,7 +89,6 @@ void parse_withParams() {
4889

4990
@Test
5091
void parse_withExtendsParams() {
51-
5292
GenericType type = new GenericTypeParser("my.exa.Repo<? extends my.d.Haz,java.lang.Long>").parse();
5393

5494
assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
@@ -59,7 +99,6 @@ void parse_withExtendsParams() {
5999

60100
@Test
61101
void parse_withNestedParams() {
62-
63102
GenericType type = new GenericTypeParser("my.exa.Repo<my.a.Prov<my.b.Haz>,my.a.Key<java.util.UUID>>").parse();
64103

65104
assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
@@ -96,8 +135,14 @@ void removeParameter() {
96135
}
97136

98137
@Test
99-
void write() {
138+
void trimWildcard() {
139+
assertEquals("my.exa.Repo", GenericType.trimWildcard("my.exa.Repo<?>"));
140+
assertEquals("my.exa.Repo", GenericType.trimWildcard("my.exa.Repo"));
141+
assertEquals("my.exa.Repo<my.Other>", GenericType.trimWildcard("my.exa.Repo<my.Other>"));
142+
}
100143

144+
@Test
145+
void write() {
101146
GenericType type = new GenericTypeParser("my.exa.Repo<my.a.Prov<my.b.Haz>,my.a.Key<java.util.UUID>>").parse();
102147

103148
StringWriter stringWriter = new StringWriter();

inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,15 @@ void initLower() {
6666
assertThat(Util.initLower("Time")).isEqualTo("time");
6767
assertThat(Util.initLower("TiMe")).isEqualTo("tiMe");
6868
}
69+
70+
@Test
71+
void trimGenerics() {
72+
assertThat(Util.trimGenerics("foo.bar.ProcessMe<java.lang.String>")).isEqualTo("foo.bar.ProcessMe");
73+
assertThat(Util.trimGenerics("foo.bar.ProcessMe")).isEqualTo("foo.bar.ProcessMe");
74+
}
75+
76+
@Test
77+
void trimMethod_when_genericType() {
78+
assertThat(Util.trimMethod("foo.bar.ProcessMe<java.lang.String>")).isEqualTo("bar_ProcessMe");
79+
}
6980
}

inject-test/src/test/java/org/example/autonamed/MyAutoNameFactory.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,29 @@
33
import io.avaje.inject.Bean;
44
import io.avaje.inject.Factory;
55
import jakarta.inject.Named;
6+
import org.example.iface.Some;
67

78
@Factory
89
public class MyAutoNameFactory {
910

11+
@Named("wild")
12+
@Bean
13+
MyGeneric<?> genericWildcard() {
14+
return new DMyGeneric<>();
15+
}
16+
17+
@Named("genericString")
18+
@Bean
19+
MyGeneric<String> genericString() {
20+
return new DMyGeneric<>();
21+
}
22+
23+
@Named("genericOther")
24+
@Bean
25+
MyGeneric<Some> genericWithSomeType() {
26+
return new DMyGeneric<>();
27+
}
28+
1029
@Bean
1130
AutoIface one() {
1231
return new DAutoIface("one");
@@ -47,4 +66,12 @@ public String who() {
4766
return name;
4867
}
4968
}
69+
70+
static class DMyGeneric<T> implements MyGeneric<T> {
71+
72+
@Override
73+
public T obtain() {
74+
return null;
75+
}
76+
}
5077
}

inject-test/src/test/java/org/example/autonamed/MyAutoNameTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ void test() {
3838

3939
final MyAutoName2 myAutoName2 = beanScope.get(MyAutoName2.class);
4040
assertThat(myAutoName2.who()).isEqualTo("one");
41+
42+
final MyGeneric<?> genericWild = beanScope.get(MyGeneric.class, "wild");
43+
final MyGeneric<?> generic2 = beanScope.get(MyGeneric.class, "genericString");
44+
final MyGeneric<?> generic3 = beanScope.get(MyGeneric.class, "genericOther");
45+
assertThat(genericWild).isNotNull();
46+
assertThat(generic2).isNotNull();
47+
assertThat(generic3).isNotNull();
48+
assertThat(genericWild).isNotSameAs(generic2);
49+
assertThat(generic2).isNotSameAs(generic3);
4150
}
4251
}
4352
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.example.autonamed;
2+
3+
public interface MyGeneric<T> {
4+
5+
T obtain();
6+
}

0 commit comments

Comments
 (0)