Skip to content

Commit 824efcb

Browse files
committed
#230 - ENH: Add support for injecting into Map<String,T> (by qualifier)
1 parent 5839695 commit 824efcb

File tree

14 files changed

+194
-38
lines changed

14 files changed

+194
-38
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ static String extractSet(String rawType) {
129129
return setType;
130130
}
131131

132+
static String extractMap(String rawType) {
133+
String valType = rawType.substring(31, rawType.length() - 1);
134+
if (valType.startsWith("? extends")) {
135+
return valType.substring(10);
136+
}
137+
return valType;
138+
}
139+
132140
static UtilType determineType(TypeMirror rawType) {
133141
return UtilType.of(rawType.toString());
134142
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class UtilType {
55
private enum Type {
66
LIST,
77
SET,
8+
MAP,
89
OPTIONAL,
910
PROVIDER,
1011
OTHER
@@ -23,6 +24,8 @@ static UtilType of(String rawType) {
2324
return new UtilType(Type.LIST, rawType);
2425
} else if (rawType.startsWith("java.util.Set<")) {
2526
return new UtilType(Type.SET, rawType);
27+
} else if (rawType.startsWith("java.util.Map<java.lang.String,")) {
28+
return new UtilType(Type.MAP, rawType);
2629
} else if (rawType.startsWith("java.util.Optional<")) {
2730
return new UtilType(Type.OPTIONAL, rawType);
2831
} else if (Util.isProvider(rawType)) {
@@ -49,6 +52,8 @@ String rawType() {
4952
return Util.extractSet(rawType);
5053
case LIST:
5154
return Util.extractList(rawType);
55+
case MAP:
56+
return Util.extractMap(rawType);
5257
case OPTIONAL:
5358
return Util.extractOptionalType(rawType);
5459
default:
@@ -62,6 +67,8 @@ String getMethod(boolean nullable) {
6267
return "set(";
6368
case LIST:
6469
return "list(";
70+
case MAP:
71+
return "map(";
6572
case OPTIONAL:
6673
return "getOptional(";
6774
case PROVIDER:

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,26 @@ void unwrapProvider() {
3939
assertEquals(Util.unwrapProvider("jakarta.inject.Provider<org.Foo<com.Bazz>>"), "org.Foo<com.Bazz>");
4040
}
4141

42+
@Test
43+
void extractMap() {
44+
assertEquals("Foo", Util.extractMap("java.util.Map<java.lang.String,? extends Foo>"));
45+
assertEquals("org.foo.Bar", Util.extractMap("java.util.Map<java.lang.String,? extends org.foo.Bar>"));
46+
assertEquals("org.foo.Bar", Util.extractMap("java.util.Map<java.lang.String,org.foo.Bar>"));
47+
}
48+
4249
@Test
4350
void extractList() {
44-
assertEquals("Foo", Util.extractList("List<? extends Foo>"));
45-
assertEquals("org.foo.Bar", Util.extractList("List<? extends org.foo.Bar>"));
51+
assertEquals("Foo", Util.extractList("java.util.List<? extends Foo>"));
52+
assertEquals("org.foo.Bar", Util.extractList("java.util.List<? extends org.foo.Bar>"));
53+
assertEquals("org.foo.Bar", Util.extractList("java.util.List<org.foo.Bar>"));
54+
4655
}
4756

4857
@Test
4958
void extractSet() {
50-
assertEquals("Foo", Util.extractSet("Set<? extends Foo>"));
51-
assertEquals("org.foo.Bar", Util.extractSet("Set<? extends org.foo.Bar>"));
59+
assertEquals("Foo", Util.extractSet("java.util.Set<? extends Foo>"));
60+
assertEquals("org.foo.Bar", Util.extractSet("java.util.Set<? extends org.foo.Bar>"));
61+
assertEquals("org.foo.Bar", Util.extractSet("java.util.Set<org.foo.Bar>"));
5262
}
5363

5464
@Test

inject-test/src/test/java/org/example/coffee/InjectListTest.java

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

33
import io.avaje.inject.BeanScope;
4+
import org.example.coffee.list.CombinedMapSomei;
45
import org.example.coffee.list.CombinedSetSomei;
56
import org.example.coffee.list.CombinedSomei;
67
import org.example.coffee.list.SomeInterfaceConsumer;
@@ -30,6 +31,17 @@ void test_set() {
3031
}
3132
}
3233

34+
@Test
35+
void test_map() {
36+
try (BeanScope context = BeanScope.builder().build()) {
37+
CombinedMapSomei bean = context.get(CombinedMapSomei.class);
38+
List<String> keys = bean.someKeys();
39+
assertThat(keys).containsOnly("a", "b", "a2");
40+
List<String> vals = bean.someVals();
41+
assertThat(vals).containsOnly("a", "b", "a2");
42+
}
43+
}
44+
3345
@Test
3446
void testEmptyList() {
3547
try (BeanScope context = BeanScope.builder().build()) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.example.coffee.list;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Singleton;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
10+
11+
@Singleton
12+
public class CombinedMapSomei {
13+
14+
private final Map<String, Somei> somes;
15+
16+
/**
17+
* Inject map of beans keyed by qualifier name.
18+
*/
19+
@Inject
20+
public CombinedMapSomei(Map<String, Somei> somes) {
21+
this.somes = somes;
22+
}
23+
24+
public List<String> someKeys() {
25+
return new ArrayList<>(somes.keySet());
26+
}
27+
28+
public List<String> someVals() {
29+
return somes.values().stream()
30+
.map(Somei::some)
31+
.collect(Collectors.toList());
32+
}
33+
}
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
11
package org.example.coffee.qualifier;
22

33
import io.avaje.inject.BeanScope;
4+
import org.example.autonamed.MyAutoB2;
45
import org.junit.jupiter.api.Test;
56

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

8-
public class StoreManagerWithNamedTest {
11+
class StoreManagerWithNamedTest {
912

1013
@Test
11-
public void test() {
12-
13-
try (BeanScope context = BeanScope.builder().build()) {
14-
StoreManagerWithNamed manager = context.get(StoreManagerWithNamed.class);
14+
void test() {
15+
try (BeanScope beanScope = BeanScope.builder().build()) {
16+
StoreManagerWithNamed manager = beanScope.get(StoreManagerWithNamed.class);
1517
String store = manager.store();
1618
assertThat(store).isEqualTo("blue");
19+
20+
SomeStore greenStore = beanScope.get(SomeStore.class, "green");
21+
SomeStore blueStore = beanScope.get(SomeStore.class, "blue");
22+
Map<String, SomeStore> stores = beanScope.map(SomeStore.class);
23+
24+
SomeStore green = stores.get("green");
25+
assertThat(green).isSameAs(greenStore);
26+
SomeStore blue = stores.get("blue");
27+
assertThat(blue).isSameAs(blueStore);
28+
29+
// a map with unnamed component
30+
Map<String, MyAutoB2> mapWithUnnamed = beanScope.map(MyAutoB2.class);
31+
assertThat(mapWithUnnamed).hasSize(1);
1732
}
1833
}
1934
}

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.lang.annotation.Annotation;
77
import java.lang.reflect.Type;
88
import java.util.List;
9+
import java.util.Map;
910
import java.util.Optional;
1011

1112
/**
@@ -187,7 +188,7 @@ static BeanScopeBuilder newBuilder() {
187188
List<Object> listByAnnotation(Class<?> annotation);
188189

189190
/**
190-
* Return the list of beans that implement the interface.
191+
* Return the list of beans for a given type.
191192
*
192193
* <pre>{@code
193194
*
@@ -197,30 +198,37 @@ static BeanScopeBuilder newBuilder() {
197198
*
198199
* }</pre>
199200
*
200-
* @param interfaceType An interface class.
201+
* @param type The type of beans to return.
201202
*/
202-
<T> List<T> list(Class<T> interfaceType);
203+
<T> List<T> list(Class<T> type);
203204

204205
/**
205206
* Return the list of beans that implement the given type.
206207
*/
207-
<T> List<T> list(Type interfaceType);
208+
<T> List<T> list(Type type);
208209

209210
/**
210211
* Return the list of beans that implement the interface sorting by priority.
211212
*/
212-
<T> List<T> listByPriority(Class<T> interfaceType);
213+
<T> List<T> listByPriority(Class<T> type);
213214

214215
/**
215216
* Return the beans that implement the interface sorting by the priority annotation used.
216217
* <p>
217218
* The priority annotation will typically be either <code>javax.annotation.Priority</code>
218219
* or <code>jakarta.annotation.Priority</code>.
219220
*
220-
* @param interfaceType The interface type of the beans to return
221-
* @param priority The priority annotation used to sort the beans
221+
* @param type The interface type of the beans to return
222+
* @param priority The priority annotation used to sort the beans
222223
*/
223-
<T> List<T> listByPriority(Class<T> interfaceType, Class<? extends Annotation> priority);
224+
<T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priority);
225+
226+
/**
227+
* Return the beans for this type mapped by their qualifier name.
228+
* <p>
229+
* Beans with no qualifier name get a generated unique key to use instead.
230+
*/
231+
<T> Map<String, T> map(Type type);
224232

225233
/**
226234
* Return all the bean entries from the scope.

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java.lang.reflect.Type;
77
import java.util.List;
8+
import java.util.Map;
89
import java.util.Optional;
910
import java.util.Set;
1011
import java.util.function.Consumer;
@@ -146,15 +147,20 @@ static Builder newBuilder(List<SuppliedBean> suppliedBeans, List<EnrichBean> enr
146147
<T> T get(Type cls, String name);
147148

148149
/**
149-
* Get a list of dependencies for the interface type .
150+
* Get a list of dependencies for the type.
150151
*/
151152
<T> List<T> list(Type interfaceType);
152153

153154
/**
154-
* Get a set of dependencies for the interface type .
155+
* Get a set of dependencies for the type.
155156
*/
156157
<T> Set<T> set(Type interfaceType);
157158

159+
/**
160+
* Return a map of dependencies keyed by qualifier name.
161+
*/
162+
<T> Map<String, T> map(Type interfaceType);
163+
158164
/**
159165
* Build and return the bean scope.
160166
*/

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.avaje.inject.spi;
22

3+
import io.avaje.inject.BeanScope;
34
import jakarta.inject.Provider;
45

56
import java.lang.reflect.Type;
@@ -118,6 +119,23 @@ List<Object> all(Type type) {
118119
return entry != null ? entry.all() : Collections.emptyList();
119120
}
120121

122+
/**
123+
* Return a map of bean instances keyed by qualifier name.
124+
*/
125+
Map<String, Object> map(Type type, BeanScope parent) {
126+
if (parent == null) {
127+
return map(type);
128+
}
129+
Map<String, Object> result = parent.map(type);
130+
result.putAll(map(type));
131+
return result;
132+
}
133+
134+
private Map<String, Object> map(Type type) {
135+
DContextEntry entry = beans.get(type.getTypeName());
136+
return entry != null ? entry.map() : Collections.emptyMap();
137+
}
138+
121139
/**
122140
* Return true if there is a supplied bean for the name and types.
123141
*/

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

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import io.avaje.lang.NonNullApi;
77
import io.avaje.lang.Nullable;
88

9-
109
import java.lang.System.Logger.Level;
1110
import java.lang.annotation.Annotation;
1211
import java.lang.reflect.Type;
@@ -84,7 +83,7 @@ public <T> T get(Type type, @Nullable String name) {
8483
return getByType(type, name);
8584
}
8685

87-
private <T> T getByType(Type type, @Nullable String name) {
86+
private <T> T getByType(Type type, @Nullable String name) {
8887
final T bean = beans.get(type, name);
8988
if (bean != null) {
9089
return bean;
@@ -107,7 +106,7 @@ Object getStrict(String name, Type[] types) {
107106
}
108107
}
109108
if (parent instanceof DBeanScope) {
110-
DBeanScope dParent = (DBeanScope)parent;
109+
DBeanScope dParent = (DBeanScope) parent;
111110
return dParent.getStrict(name, types);
112111
}
113112
return null;
@@ -134,23 +133,29 @@ private <T> Optional<T> getMaybe(Type type, @Nullable String name) {
134133
return parent.getOptional(type, name);
135134
}
136135

136+
@SuppressWarnings("unchecked")
137+
@Override
138+
public <T> Map<String, T> map(Type type) {
139+
return (Map<String, T>) beans.map(type, parent);
140+
}
141+
137142
@Override
138-
public <T> List<T> list(Class<T> interfaceType) {
139-
return listOf(interfaceType);
143+
public <T> List<T> list(Class<T> type) {
144+
return listOf(type);
140145
}
141146

142147
@Override
143-
public <T> List<T> list(Type interfaceType) {
144-
return listOf(interfaceType);
148+
public <T> List<T> list(Type type) {
149+
return listOf(type);
145150
}
146151

147152
@SuppressWarnings("unchecked")
148-
private <T> List<T> listOf(Type interfaceType) {
149-
List<T> values = (List<T>) beans.all(interfaceType);
153+
private <T> List<T> listOf(Type type) {
154+
List<T> values = (List<T>) beans.all(type);
150155
if (parent == null) {
151156
return values;
152157
}
153-
return combine(values, parent.list(interfaceType));
158+
return combine(values, parent.list(type));
154159
}
155160

156161
static <T> List<T> combine(List<T> values, List<T> parentValues) {
@@ -165,13 +170,13 @@ static <T> List<T> combine(List<T> values, List<T> parentValues) {
165170
}
166171

167172
@Override
168-
public <T> List<T> listByPriority(Class<T> interfaceType) {
169-
return listByPriority(interfaceType, Priority.class);
173+
public <T> List<T> listByPriority(Class<T> type) {
174+
return listByPriority(type, Priority.class);
170175
}
171176

172177
@Override
173-
public <T> List<T> listByPriority(Class<T> interfaceType, Class<? extends Annotation> priorityAnnotation) {
174-
List<T> list = list(interfaceType);
178+
public <T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priorityAnnotation) {
179+
List<T> list = list(type);
175180
return list.size() > 1 ? sortByPriority(list, priorityAnnotation) : list;
176181
}
177182

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ public <T> List<T> list(Type interfaceType) {
116116
return combine(values, parent.list(interfaceType));
117117
}
118118

119+
@Override
120+
@SuppressWarnings("unchecked")
121+
public <T> Map<String, T> map(Type type) {
122+
return (Map<String, T>) beanMap.map(type, parent);
123+
}
124+
119125
private <T> T getMaybe(Type type, String name) {
120126
T bean = beanMap.get(type, name);
121127
if (bean != null) {

0 commit comments

Comments
 (0)