Skip to content

Commit 6c352bf

Browse files
committed
#11 - Add @primary and @secondary
1 parent f97995a commit 6c352bf

File tree

14 files changed

+362
-32
lines changed

14 files changed

+362
-32
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.dinject;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Identify a bean as being the preferred bean to inject when multiple beans implement
10+
* the intended interface.
11+
*
12+
* <pre>{@code
13+
*
14+
* @Primary
15+
* @Singleton
16+
* class PreferredEmailSender implements EmailSender {
17+
*
18+
* ...
19+
* }
20+
* }</pre>
21+
*/
22+
@Target({ElementType.TYPE, ElementType.METHOD})
23+
@Retention(RetentionPolicy.RUNTIME)
24+
public @interface Primary {
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.dinject;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Identify a bean as being the least preferred bean to inject when multiple beans implement
10+
* the intended interface.
11+
* <p>
12+
* This can use be used when we have a 'default' implementation that would only be used when
13+
* no other implementation is available in the context to inject.
14+
* </p>
15+
*
16+
* <pre>{@code
17+
*
18+
* @Secondary
19+
* @Singleton
20+
* class DefaultEmailSender implements EmailSender {
21+
*
22+
* ...
23+
* }
24+
* }</pre>
25+
*/
26+
@Target({ElementType.TYPE, ElementType.METHOD})
27+
@Retention(RetentionPolicy.RUNTIME)
28+
public @interface Secondary {
29+
}

src/main/java/io/dinject/core/Builder.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ public interface Builder {
6464
*/
6565
void register(Object bean, String name, Class<?>... types);
6666

67+
/**
68+
* Register the bean as a Primary bean.
69+
*/
70+
void registerPrimary(Object bean, String name, Class<?>... types);
71+
72+
/**
73+
* Register the bean as a secondary bean.
74+
*/
75+
void registerSecondary(Object bean, String name, Class<?>... types);
76+
6777
/**
6878
* Add a lifecycle bean.
6979
*/

src/main/java/io/dinject/core/DBeanMap.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import java.util.List;
66
import java.util.Map;
77

8+
import static io.dinject.core.Flag.NORMAL;
9+
import static io.dinject.core.Flag.PRIMARY;
10+
import static io.dinject.core.Flag.SECONDARY;
11+
812
/**
913
* Map of types (class types, interfaces and annotations) to a DContextEntry where the
1014
* entry holds a list of bean instances for that type.
@@ -45,7 +49,7 @@ private void addSuppliedBean(Object bean) {
4549
Named annotation = suppliedClass.getAnnotation(Named.class);
4650
String name = (annotation == null) ? null : annotation.value();
4751

48-
DContextEntryBean entryBean = DContextEntryBean.of(bean, name);
52+
DContextEntryBean entryBean = DContextEntryBean.of(bean, name, PRIMARY);
4953
beans.computeIfAbsent(suppliedType.getCanonicalName(), s -> new DContextEntry()).add(entryBean);
5054
for (Class<?> anInterface : suppliedClass.getInterfaces()) {
5155
beans.computeIfAbsent(anInterface.getCanonicalName(), s -> new DContextEntry()).add(entryBean);
@@ -65,9 +69,21 @@ private Class<?> suppliedType(Class<?> suppliedClass) {
6569
}
6670
}
6771

72+
void registerPrimary(Object bean, String name, Class<?>... types) {
73+
registerWith(PRIMARY, bean, name, types);
74+
}
75+
76+
void registerSecondary(Object bean, String name, Class<?>... types) {
77+
registerWith(SECONDARY, bean, name, types);
78+
}
79+
6880
void register(Object bean, String name, Class<?>... types) {
81+
registerWith(NORMAL, bean, name, types);
82+
}
83+
84+
void registerWith(int flag, Object bean, String name, Class<?>... types) {
6985

70-
DContextEntryBean entryBean = DContextEntryBean.of(bean, name);
86+
DContextEntryBean entryBean = DContextEntryBean.of(bean, name, flag);
7187
beans.computeIfAbsent(bean.getClass().getCanonicalName(), s -> new DContextEntry()).add(entryBean);
7288

7389
if (types != null) {

src/main/java/io/dinject/core/DBuilder.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ public void register(Object bean, String name, Class<?>... types) {
179179
beanMap.register(bean, name, types);
180180
}
181181

182+
@Override
183+
public void registerPrimary(Object bean, String name, Class<?>... types) {
184+
beanMap.registerPrimary(bean, name, types);
185+
}
186+
187+
@Override
188+
public void registerSecondary(Object bean, String name, Class<?>... types) {
189+
beanMap.registerSecondary(bean, name, types);
190+
}
191+
182192
@Override
183193
public void addLifecycle(BeanLifecycle wrapper) {
184194
lifecycleList.add(wrapper);
@@ -213,7 +223,11 @@ public <T> T get(Class<T> cls, String name) {
213223
if (name != null) {
214224
msg += " name:" + name;
215225
}
216-
msg += " when creating " + injectTarget;
226+
List<T> beanList = getList(cls);
227+
msg += " when creating " + injectTarget + " - potential beans to inject: " + beanList;
228+
if (!beanList.isEmpty()) {
229+
msg += ". Check @Named or Qualifier being used";
230+
}
217231
throw new IllegalStateException(msg);
218232
}
219233
return bean;

src/main/java/io/dinject/core/DContextEntry.java

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,10 @@ Object get(String name) {
2323
DContextEntryBean entry = entries.get(0);
2424
return entry.getIfMatchWithDefault(name);
2525
}
26-
List<Object> beans = allMatches(name);
27-
if (beans.isEmpty()) {
28-
return null;
29-
}
30-
if (beans.size() == 1) {
31-
return beans.get(0);
32-
} else {
33-
throw new IllegalStateException("Expecting only 1 bean match but found " + beans.size() + " beans: " + beans);
34-
}
35-
}
3626

37-
/**
38-
* Return all the beans that match on the given name.
39-
*/
40-
List<Object> allMatches(String name) {
41-
List<Object> beans = new ArrayList<>();
42-
for (DContextEntryBean entry : entries) {
43-
if (entry.isNameMatch(name)) {
44-
beans.add(entry.getBean());
45-
}
46-
}
47-
48-
return beans;
27+
EntryMatcher matcher = new EntryMatcher(name);
28+
matcher.match(entries);
29+
return matcher.getBean();
4930
}
5031

5132
/**
@@ -61,4 +42,64 @@ void add(DContextEntryBean entryBean) {
6142
entries.add(entryBean);
6243
}
6344

45+
static class EntryMatcher {
46+
47+
private final String name;
48+
49+
private DContextEntryBean match;
50+
private DContextEntryBean ignoredSecondaryMatch;
51+
52+
EntryMatcher(String name) {
53+
this.name = name;
54+
}
55+
56+
void match(List<DContextEntryBean> entries) {
57+
for (DContextEntryBean entry : entries) {
58+
if (entry.isNameMatch(name)) {
59+
checkMatch(entry);
60+
}
61+
}
62+
}
63+
64+
private void checkMatch(DContextEntryBean entry) {
65+
if (match == null) {
66+
match = entry;
67+
return;
68+
}
69+
if (match.isSecondary() && !entry.isSecondary()) {
70+
// secondary loses
71+
match = entry;
72+
return;
73+
}
74+
if (match.isPrimary()) {
75+
if (entry.isPrimary()) {
76+
throw new IllegalStateException("Expecting only 1 bean match but have multiple primary beans " + match.getBean() + " and " + entry.getBean());
77+
}
78+
// leave as is, current primary wins
79+
return;
80+
}
81+
if (entry.isSecondary()) {
82+
if (match.isSecondary()) {
83+
ignoredSecondaryMatch = entry;
84+
}
85+
return;
86+
}
87+
if (entry.isPrimary()) {
88+
// new primary wins
89+
match = entry;
90+
return;
91+
}
92+
throw new IllegalStateException("Expecting only 1 bean match but have multiple matching beans " + match.getBean() + " and " + entry.getBean());
93+
}
94+
95+
Object getBean() {
96+
if (match == null) {
97+
return null;
98+
}
99+
if (match.isSecondary() && ignoredSecondaryMatch != null) {
100+
throw new IllegalStateException("Expecting only 1 bean match but have multiple secondary beans " + match.getBean() + " and " + ignoredSecondaryMatch.getBean());
101+
}
102+
return match.getBean();
103+
}
104+
}
64105
}

src/main/java/io/dinject/core/DContextEntryBean.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,26 @@ class DContextEntryBean {
1313
*
1414
* @param bean The bean itself or provider of the bean
1515
* @param name The optional name for the bean
16+
* @param flag The flag for primary, secondary or normal
1617
*/
17-
public static DContextEntryBean of(Object bean, String name) {
18+
public static DContextEntryBean of(Object bean, String name, int flag) {
1819
if (bean instanceof Provider) {
19-
return new DContextEntryBean.Prov(bean, name);
20+
return new DContextEntryBean.Prov(bean, name, flag);
2021
} else {
21-
return new DContextEntryBean(bean, name);
22+
return new DContextEntryBean(bean, name, flag);
2223
}
2324
}
2425

25-
protected final Object source;
26+
final Object source;
2627

2728
private final String name;
2829

29-
private DContextEntryBean(Object source, String name) {
30+
private final int flag;
31+
32+
private DContextEntryBean(Object source, String name, int flag) {
3033
this.source = source;
3134
this.name = name;
35+
this.flag = flag;
3236
}
3337

3438
boolean isNameMatch(String name) {
@@ -52,15 +56,23 @@ Object getIfMatchWithDefault(String name) {
5256
}
5357
}
5458

59+
boolean isPrimary() {
60+
return flag == Flag.PRIMARY;
61+
}
62+
63+
boolean isSecondary() {
64+
return flag == Flag.SECONDARY;
65+
}
66+
5567
/**
5668
* Provider based entry - get it once.
5769
*/
5870
static class Prov extends DContextEntryBean {
5971

6072
private Object actualBean;
6173

62-
private Prov(Object provider, String name) {
63-
super(provider, name);
74+
private Prov(Object provider, String name, int flag) {
75+
super(provider, name, flag);
6476
}
6577

6678
@Override
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.dinject.core;
2+
3+
/**
4+
* Flags to map to <code>@Primary</code> and <code>@Secondary</code>
5+
*/
6+
class Flag {
7+
8+
static final int PRIMARY = 1;
9+
static final int NORMAL = 0;
10+
static final int SECONDARY = -1;
11+
}

0 commit comments

Comments
 (0)