Skip to content

Commit 9c3d005

Browse files
committed
#243 - Fix for ClassCastException with generic types
The core fix is with DBeanMap. In detail, when wiring mocks/spy this part of DBeanMap additionally registers the mock with the generic type (which is in the generated code). Prior to this fix, this process was too loose and registered the mock with other types like Model2 in the test case.
1 parent 6b02e2c commit 9c3d005

File tree

17 files changed

+184
-12
lines changed

17 files changed

+184
-12
lines changed

blackbox-test-inject/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<artifactId>avaje-inject-parent</artifactId>
77
<groupId>io.avaje</groupId>
8-
<version>8.10</version>
8+
<version>8.11-SNAPSHOT</version>
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
1111

inject-generator/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>io.avaje</groupId>
66
<artifactId>avaje-inject-parent</artifactId>
7-
<version>8.10</version>
7+
<version>8.11-SNAPSHOT</version>
88
</parent>
99

1010
<artifactId>avaje-inject-generator</artifactId>
@@ -16,7 +16,7 @@
1616
<dependency>
1717
<groupId>io.avaje</groupId>
1818
<artifactId>avaje-inject</artifactId>
19-
<version>8.10</version>
19+
<version>8.11-SNAPSHOT</version>
2020
</dependency>
2121

2222
<!-- test dependencies -->

inject-test/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>io.avaje</groupId>
66
<artifactId>avaje-inject-parent</artifactId>
7-
<version>8.10</version>
7+
<version>8.11-SNAPSHOT</version>
88
</parent>
99

1010
<artifactId>avaje-inject-test</artifactId>
@@ -124,7 +124,7 @@
124124
<path>
125125
<groupId>io.avaje</groupId>
126126
<artifactId>avaje-inject-generator</artifactId>
127-
<version>8.10</version>
127+
<version>8.11-SNAPSHOT</version>
128128
</path>
129129
</annotationProcessorPaths>
130130
</configuration>

inject-test/src/test/java/org/example/circular/CupholderTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
package org.example.circular;
22

3+
import io.avaje.inject.BeanScope;
34
import io.avaje.inject.xtra.ApplicationScope;
45
import org.junit.jupiter.api.Test;
56

7+
import java.util.List;
8+
69
import static org.assertj.core.api.Assertions.assertThat;
710

811
public class CupholderTest {
912

13+
@Test
14+
void mockOne() {
15+
try (BeanScope beanScope = BeanScope.builder().forTesting()
16+
.mock(Cupholder.class)
17+
.build()) {
18+
19+
Cupholder cupholder = beanScope.get(Cupholder.class);
20+
assertThat(cupholder.hello()).isNull();
21+
22+
List<Cupholder> all = beanScope.list(Cupholder.class);
23+
assertThat(all).hasSize(1);
24+
}
25+
}
26+
1027
@Test
1128
void circularDependency_via_providerInterface() {
1229

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.example.generic.repo;
2+
3+
public abstract class AbstractRepo<T> {
4+
5+
public abstract T get();
6+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.example.generic.repo;
2+
3+
import io.avaje.inject.BeanScope;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class GenericWithMockTest {
9+
10+
@Test
11+
void withMock() {
12+
try (BeanScope beanScope = BeanScope.builder().forTesting()
13+
.mock(MapRepo1.class)
14+
.build()) {
15+
16+
ServiceClass serviceClass = beanScope.get(ServiceClass.class);
17+
MapRepo1 mapRepo1 = beanScope.get(MapRepo1.class);
18+
assertThat(mapRepo1.get()).isNull();
19+
20+
MapRepo2 mapRepo2 = beanScope.get(MapRepo2.class);
21+
assertThat(mapRepo2.get()).isNotNull();
22+
assertThat(serviceClass.map2.get()).isNotNull();
23+
}
24+
}
25+
26+
@Test
27+
void wireNoMocks() {
28+
try (BeanScope beanScope = BeanScope.builder().build()) {
29+
var serviceClass = beanScope.get(ServiceClass.class);
30+
String value = serviceClass.process();
31+
32+
assertThat(value).isEqualTo("{=Model1}|{=Model2}");
33+
}
34+
}
35+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.example.generic.repo;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class MapRepo1 extends AbstractRepo<Model1> {
7+
8+
@Override
9+
public Model1 get() {
10+
return new Model1();
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.example.generic.repo;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class MapRepo2 extends AbstractRepo<Model2> {
7+
8+
@Override
9+
public Model2 get() {
10+
return new Model2();
11+
}
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.example.generic.repo;
2+
3+
public interface MapService<T> {
4+
5+
T get();
6+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.example.generic.repo;
2+
3+
import jakarta.inject.Singleton;
4+
5+
import java.util.Map;
6+
7+
@Singleton
8+
public class MapService1 implements MapService<Map<String, Model1>> {
9+
10+
final MapRepo1 repo;
11+
12+
public MapService1(MapRepo1 repo) {
13+
this.repo = repo;
14+
}
15+
16+
@Override
17+
public Map<String, Model1> get() {
18+
return Map.of("", repo.get());
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.example.generic.repo;
2+
3+
import jakarta.inject.Singleton;
4+
5+
import java.util.Map;
6+
7+
@Singleton
8+
public class MapService2 implements MapService<Map<String, Model2>> {
9+
10+
final MapRepo2 repo;
11+
12+
public MapService2(MapRepo2 repo) {
13+
this.repo = repo;
14+
}
15+
16+
@Override
17+
public Map<String, Model2> get() {
18+
return Map.of("", repo.get());
19+
}
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.example.generic.repo;
2+
3+
public class Model1 {
4+
5+
@Override
6+
public String toString() {
7+
return "Model1";
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.example.generic.repo;
2+
3+
public class Model2 {
4+
5+
@Override
6+
public String toString() {
7+
return "Model2";
8+
}
9+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example.generic.repo;
2+
3+
import jakarta.inject.Singleton;
4+
5+
import java.util.Map;
6+
7+
@Singleton
8+
public class ServiceClass {
9+
10+
final MapService<Map<String, Model1>> map1;
11+
final MapService<Map<String, Model2>> map2;
12+
13+
public ServiceClass(MapService<Map<String, Model1>> map1, MapService<Map<String, Model2>> map2) {
14+
this.map1 = map1;
15+
this.map2 = map2;
16+
}
17+
18+
public String process() {
19+
Map<String, Model1> m1 = map1.get();
20+
Map<String, Model2> m2 = map2.get();
21+
return m1.toString() + "|" + m2.toString();
22+
}
23+
}

inject/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>io.avaje</groupId>
66
<artifactId>avaje-inject-parent</artifactId>
7-
<version>8.10</version>
7+
<version>8.11-SNAPSHOT</version>
88
</parent>
99

1010
<artifactId>avaje-inject</artifactId>

inject/src/main/java/io/avaje/inject/spi/DBeanMap.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.avaje.inject.BeanScope;
44
import jakarta.inject.Provider;
55

6+
import java.lang.reflect.ParameterizedType;
67
import java.lang.reflect.Type;
78
import java.util.Collections;
89
import java.util.LinkedHashMap;
@@ -145,8 +146,10 @@ boolean isSupplied(String qualifierName, Type... types) {
145146
DContextEntry entry = beans.get(type.getTypeName());
146147
if (entry != null) {
147148
DContextEntryBean suppliedBean = entry.supplied(qualifierName);
148-
if (suppliedBean != null && types.length > 1) {
149-
addSuppliedFor(type, types, suppliedBean);
149+
if (suppliedBean != null) {
150+
if (types.length > 1) {
151+
addSuppliedFor(type, types, suppliedBean);
152+
}
150153
return true;
151154
}
152155
}
@@ -156,12 +159,12 @@ boolean isSupplied(String qualifierName, Type... types) {
156159
}
157160

158161
/**
159-
* Register the suppliedBean entry with other types (like other interfaces)
160-
* IF those types don't already have a registered entry.
162+
* Register the suppliedBean entry with parameterized types IF those types
163+
* don't already have a registered entry.
161164
*/
162165
private void addSuppliedFor(Type matchType, Type[] types, DContextEntryBean suppliedBean) {
163166
for (Type type : types) {
164-
if (type != matchType) {
167+
if (type != matchType && type instanceof ParameterizedType) {
165168
beans.computeIfAbsent(type.getTypeName(), s -> new DContextEntry()).add(suppliedBean);
166169
}
167170
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<groupId>io.avaje</groupId>
1111
<artifactId>avaje-inject-parent</artifactId>
12-
<version>8.10</version>
12+
<version>8.11-SNAPSHOT</version>
1313
<packaging>pom</packaging>
1414
<name>avaje inject parent</name>
1515
<description>parent pom for avaje inject library</description>

0 commit comments

Comments
 (0)