Skip to content

Commit c5c90f7

Browse files
committed
#258 - ENH: Add Plugin API for 'default providers' and BeanScopeBuilder.provideDefault() methods
1 parent 0b298ba commit c5c90f7

File tree

11 files changed

+122
-16
lines changed

11 files changed

+122
-16
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ final class MetaDataOrdering {
2929
queue.add(metaData);
3030
}
3131
// register into map keyed by provider
32-
providers.computeIfAbsent(metaData.getType(), s -> new ProviderList()).add(metaData);
32+
providerAdd(metaData.getType()).add(metaData);
3333
for (String provide : metaData.getProvides()) {
34-
providers.computeIfAbsent(provide, s -> new ProviderList()).add(metaData);
34+
providerAdd(provide).add(metaData);
3535
}
3636
}
3737
externallyRequiredDependencies();
@@ -42,8 +42,15 @@ final class MetaDataOrdering {
4242
*/
4343
private void externallyRequiredDependencies() {
4444
for (String requireType : scopeInfo.requires()) {
45-
providers.computeIfAbsent(requireType, s -> new ProviderList());
45+
providerAdd(requireType);
4646
}
47+
for (String requireType : scopeInfo.pluginProvided()) {
48+
providerAdd(requireType);
49+
}
50+
}
51+
52+
private ProviderList providerAdd(String requireType) {
53+
return providers.computeIfAbsent(requireType, s -> new ProviderList());
4754
}
4855

4956
int processQueue() {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.avaje.inject.Factory;
55
import io.avaje.inject.InjectModule;
66
import io.avaje.inject.Prototype;
7+
import io.avaje.inject.spi.Plugin;
78
import io.avaje.inject.spi.Proxy;
89
import jakarta.inject.Scope;
910
import jakarta.inject.Singleton;
@@ -18,6 +19,7 @@
1819
import javax.lang.model.element.TypeElement;
1920
import javax.lang.model.util.Elements;
2021
import java.util.LinkedHashSet;
22+
import java.util.ServiceLoader;
2123
import java.util.Set;
2224

2325
public final class Processor extends AbstractProcessor {
@@ -43,6 +45,19 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
4345
this.elementUtils = processingEnv.getElementUtils();
4446
this.allScopes = new AllScopes(context);
4547
this.defaultScope = allScopes.defaultScope();
48+
registerPluginProvidedTypes();
49+
}
50+
51+
/**
52+
* Register types provided by the plugin so no compiler error when we have a dependency
53+
* on these types and the only thing providing them is the plugin.
54+
*/
55+
private void registerPluginProvidedTypes() {
56+
for (Plugin plugin : ServiceLoader.load(Plugin.class)) {
57+
for (Class<?> provide : plugin.provides()) {
58+
defaultScope.pluginProvided(provide.getCanonicalName());
59+
}
60+
}
4661
}
4762

4863
@Override

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ String type() {
4545
private final List<BeanReader> beanReaders = new ArrayList<>();
4646
private final Set<String> readBeans = new HashSet<>();
4747
private final ProcessingContext context;
48+
private final Set<String> pluginProvided = new HashSet<>();
4849
private final Set<String> requires = new LinkedHashSet<>();
4950
private final Set<String> provides = new LinkedHashSet<>();
5051
private final Set<String> requiresPackages = new LinkedHashSet<>();
@@ -90,6 +91,10 @@ public String toString() {
9091
'}';
9192
}
9293

94+
void pluginProvided(String pluginProvides) {
95+
pluginProvided.add(pluginProvides);
96+
}
97+
9398
boolean includeSingleton() {
9499
return !ignoreSingleton;
95100
}
@@ -172,6 +177,10 @@ Set<String> requires() {
172177
return requires;
173178
}
174179

180+
Set<String> pluginProvided() {
181+
return pluginProvided;
182+
}
183+
175184
void writeBeanHelpers() {
176185
for (BeanReader beanReader : beanReaders) {
177186
try {
@@ -453,7 +462,7 @@ private boolean providesDependencyRecursive(String dependency) {
453462
* Return true if this module provides the dependency (non-recursive, local only).
454463
*/
455464
boolean providesDependencyLocally(String dependency) {
456-
if (requires.contains(dependency)) {
465+
if (requires.contains(dependency) || pluginProvided.contains(dependency)) {
457466
return true;
458467
}
459468
for (MetaData meta : metaData.values()) {

inject-generator/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
requires java.compiler;
44
requires io.avaje.inject;
55

6+
uses io.avaje.inject.spi.Plugin;
7+
68
provides javax.annotation.processing.Processor with io.avaje.inject.generator.Processor;
79
}

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import io.avaje.inject.spi.Module;
44
import io.avaje.lang.NonNullApi;
5+
import io.avaje.lang.Nullable;
6+
import jakarta.inject.Provider;
57

68
import java.lang.reflect.Type;
79
import java.util.function.Consumer;
@@ -204,6 +206,31 @@ default <D> BeanScopeBuilder withBean(String name, Type type, D bean) {
204206
*/
205207
<D> BeanScopeBuilder bean(Type type, D bean);
206208

209+
/**
210+
* Add a supplied bean provider that acts as a default fallback for a dependency.
211+
* <p>
212+
* This provider is only called if nothing else provides the dependency. It effectively
213+
* uses `@Secondary` priority.
214+
*
215+
* @param type The type of the dependency
216+
* @param provider The provider of the dependency.
217+
*/
218+
default <D> BeanScopeBuilder provideDefault(Type type, Provider<D> provider) {
219+
return provideDefault(null, type, provider);
220+
}
221+
222+
/**
223+
* Add a supplied bean provider that acts as a default fallback for a dependency.
224+
* <p>
225+
* This provider is only called if nothing else provides the dependency. It effectively
226+
* uses `@Secondary` priority.
227+
*
228+
* @param name The name qualifier
229+
* @param type The type of the dependency
230+
* @param provider The provider of the dependency.
231+
*/
232+
<D> BeanScopeBuilder provideDefault(@Nullable String name, Type type, Provider<D> provider);
233+
207234
/**
208235
* Deprecated - migrate to bean().
209236
*/
@@ -314,7 +341,7 @@ interface ForTesting extends BeanScopeBuilder {
314341
*/
315342
@Deprecated
316343
default BeanScopeBuilder.ForTesting withMock(Class<?> type) {
317-
return mock(type);
344+
return mock(type);
318345
}
319346

320347
/**

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

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

33
import io.avaje.applog.AppLog;
4-
import io.avaje.inject.spi.Builder;
5-
import io.avaje.inject.spi.EnrichBean;
4+
import io.avaje.inject.spi.*;
65
import io.avaje.inject.spi.Module;
7-
import io.avaje.inject.spi.SuppliedBean;
86
import io.avaje.lang.NonNullApi;
97
import io.avaje.lang.Nullable;
8+
import jakarta.inject.Provider;
109

1110
import java.lang.System.Logger.Level;
1211
import java.lang.reflect.Type;
@@ -17,11 +16,10 @@
1716
* Build a bean scope with options for shutdown hook and supplying test doubles.
1817
*/
1918
@NonNullApi
20-
class DBeanScopeBuilder implements BeanScopeBuilder.ForTesting {
19+
final class DBeanScopeBuilder implements BeanScopeBuilder.ForTesting {
2120

2221
private static final System.Logger log = AppLog.getLogger("io.avaje.inject");
2322

24-
@SuppressWarnings("rawtypes")
2523
private final List<SuppliedBean> suppliedBeans = new ArrayList<>();
2624
@SuppressWarnings("rawtypes")
2725
private final List<EnrichBean> enrichBeans = new ArrayList<>();
@@ -84,6 +82,12 @@ public <D> BeanScopeBuilder bean(@Nullable String name, Type type, D bean) {
8482
return this;
8583
}
8684

85+
@Override
86+
public <D> BeanScopeBuilder provideDefault(String name, Type type, Provider<D> provider) {
87+
suppliedBeans.add(SuppliedBean.secondary(name, type, provider));
88+
return this;
89+
}
90+
8791
@Override
8892
public BeanScopeBuilder classLoader(ClassLoader classLoader) {
8993
this.classLoader = classLoader;
@@ -143,10 +147,12 @@ private <D> BeanScopeBuilder.ForTesting spy(Class<D> type, @Nullable String name
143147

144148
@Override
145149
public BeanScope build() {
150+
// load and apply plugins first
151+
var loader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
152+
ServiceLoader.load(Plugin.class, loader).forEach(plugin -> plugin.apply(this));
146153
// sort factories by dependsOn
147154
FactoryOrder factoryOrder = new FactoryOrder(parent, includeModules, !suppliedBeans.isEmpty());
148155
if (factoryOrder.isEmpty()) {
149-
var loader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
150156
ServiceLoader.load(Module.class, loader).forEach(factoryOrder::add);
151157
}
152158

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ void add(List<SuppliedBean> suppliedBeans) {
5151

5252
private void addSuppliedBean(SuppliedBean supplied) {
5353
Type suppliedType = supplied.type();
54-
DContextEntryBean entryBean = DContextEntryBean.of(supplied.source(), supplied.name(), supplied.priority());
54+
DContextEntryBean entryBean = DContextEntryBean.supplied(supplied.source(), supplied.name(), supplied.priority());
5555
beans.computeIfAbsent(suppliedType.getTypeName(), s -> new DContextEntry()).add(entryBean);
5656
for (Class<?> anInterface : supplied.interfaces()) {
5757
beans.computeIfAbsent(anInterface.getTypeName(), s -> new DContextEntry()).add(entryBean);

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ class DContextEntryBean {
1212

1313
/**
1414
* Create taking into account if it is a Provider or the bean itself.
15-
*
16-
* @param source The bean itself or provider of the bean
17-
* @param name The optional name for the bean
18-
* @param flag The flag for primary, secondary or normal
1915
*/
2016
static DContextEntryBean of(Object source, String name, int flag) {
2117
if (source instanceof Provider) {
@@ -25,6 +21,17 @@ static DContextEntryBean of(Object source, String name, int flag) {
2521
}
2622
}
2723

24+
/**
25+
* Create an entry with supplied Providers using a 'Once' / 'one instance' provider.
26+
*/
27+
static DContextEntryBean supplied(Object source, String name, int flag) {
28+
if (source instanceof Provider) {
29+
return new OnceProvider((Provider<?>)source, name, flag);
30+
} else {
31+
return new DContextEntryBean(source, name, flag);
32+
}
33+
}
34+
2835
static DContextEntryBean provider(boolean prototype, Provider<?> provider, String name, int flag) {
2936
return prototype ? new ProtoProvider(provider, name, flag) : new OnceProvider(provider, name, flag);
3037
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.avaje.inject.spi;
2+
3+
import io.avaje.inject.BeanScopeBuilder;
4+
import jakarta.inject.Provider;
5+
6+
import java.lang.reflect.Type;
7+
8+
/**
9+
* A Plugin that can be applied when creating a bean scope.
10+
* <p>
11+
* Typically, a plugin might provide a default dependency via {@link BeanScopeBuilder#provideDefault(Type, Provider)}.
12+
*/
13+
public interface Plugin {
14+
15+
/**
16+
* Return the classes that the plugin provides.
17+
*/
18+
Class<?>[] provides();
19+
20+
/**
21+
* Apply the plugin to the scope builder.
22+
*/
23+
void apply(BeanScopeBuilder builder);
24+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public static SuppliedBean ofType(String name, Type type, Object source) {
5050
return new SuppliedBean(BeanEntry.SUPPLIED, name, type, source);
5151
}
5252

53+
/**
54+
* Create a supplied bean with SECONDARY priority as a default fallback dependency that is
55+
* only used when no other matching one is provided.
56+
*/
57+
public static SuppliedBean secondary(String name, Type type, Object source) {
58+
return new SuppliedBean(BeanEntry.SECONDARY, name, type, source);
59+
}
60+
5361
private SuppliedBean(int priority, String name, Type type, Object source) {
5462
this.priority = priority;
5563
this.name = name;

inject/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
requires static org.mockito;
1111

1212
uses io.avaje.inject.spi.Module;
13+
uses io.avaje.inject.spi.Plugin;
1314

1415
}

0 commit comments

Comments
 (0)