Skip to content

Commit d836a10

Browse files
committed
#262 - ENH: Simplify multi-module, add autoRequires()
autoRequires() are the dependencies that the compiling module depends on that are provided by other modules in the classpath.
1 parent f74aaec commit d836a10

File tree

11 files changed

+167
-60
lines changed

11 files changed

+167
-60
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ ScopeInfo addScopeAnnotation(TypeElement type) {
3535
return data.scopeInfo;
3636
}
3737

38-
boolean providedByDefaultModule(String dependency) {
38+
boolean providedByDefaultScope(String dependency) {
3939
return defaultScope.providesDependencyLocally(dependency);
4040
}
4141

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.avaje.inject.generator;
2+
3+
import io.avaje.inject.spi.Module;
4+
5+
import java.util.HashSet;
6+
import java.util.ServiceLoader;
7+
import java.util.Set;
8+
9+
/**
10+
* The types provided by other modules in the classpath at compile time.
11+
* <p>
12+
* When we depend on these types they add to the module autoRequires() classes.
13+
*/
14+
final class ExternalProvide {
15+
16+
private final Set<String> providedTypes = new HashSet<>();
17+
18+
void init() {
19+
for (Module module : ServiceLoader.load(Module.class)) {
20+
for (Class<?> provide : module.provides()) {
21+
providedTypes.add(provide.getCanonicalName());
22+
}
23+
for (Class<?> provide : module.autoProvides()) {
24+
providedTypes.add(provide.getCanonicalName());
25+
}
26+
}
27+
}
28+
29+
/**
30+
* Return true if this type is provided by another module in the classpath.
31+
* We will add it to autoRequires().
32+
*/
33+
boolean provides(String type) {
34+
return providedTypes.contains(type);
35+
}
36+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ final class MetaData {
3737
*/
3838
private String autoProvides;
3939
private boolean generateProxy;
40+
private boolean usesExternalDependency;
4041

4142
MetaData(DependencyMeta meta) {
4243
this.type = meta.type();
@@ -162,6 +163,9 @@ void buildMethod(Append append) {
162163
if (generateProxy) {
163164
return;
164165
}
166+
if (usesExternalDependency) {
167+
append.append(" // uses external dependency").append(NEWLINE);
168+
}
165169
append.append(" @DependencyMeta(type=\"").append(type).append("\"");
166170
if (name != null) {
167171
append.append(", name=\"").append(name).append("\"");
@@ -222,4 +226,11 @@ void setMethod(String method) {
222226
void setAutoProvides(String autoProvides) {
223227
this.autoProvides = autoProvides;
224228
}
229+
230+
/**
231+
* This depends on a dependency that comes from another module in the classpath.
232+
*/
233+
void markWithExternalDependency() {
234+
usesExternalDependency = true;
235+
}
225236
}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class MetaDataOrdering {
1717
private final Map<String, ProviderList> providers = new HashMap<>();
1818
private final List<DependencyLink> circularDependencies = new ArrayList<>();
1919
private final Set<String> missingDependencyTypes = new LinkedHashSet<>();
20+
private final Set<String> autoRequires = new TreeSet<>();
2021

2122
MetaDataOrdering(Collection<MetaData> values, ProcessingContext context, ScopeInfo scopeInfo) {
2223
this.context = context;
@@ -56,7 +57,12 @@ private ProviderList providerAdd(String requireType) {
5657
int processQueue() {
5758
int count;
5859
do {
59-
count = processQueueRound();
60+
// first run without external dependencies from other modules
61+
count = processQueueRound(false);
62+
} while (count > 0);
63+
do {
64+
// run again including externally provided dependencies from other modules
65+
count = processQueueRound(true);
6066
} while (count > 0);
6167

6268
int remaining = queue.size();
@@ -128,7 +134,7 @@ void missingDependencies() {
128134

129135
private void checkMissingDependencies(MetaData metaData) {
130136
for (Dependency dependency : metaData.getDependsOn()) {
131-
if (providers.get(dependency.getName()) == null && !scopeInfo.providedByOtherModule(dependency.getName())) {
137+
if (providers.get(dependency.getName()) == null && !scopeInfo.providedByOtherScope(dependency.getName())) {
132138
TypeElement element = context.elementMaybe(metaData.getType());
133139
context.logError(element, "No dependency provided for " + dependency + " on " + metaData.getType());
134140
missingDependencyTypes.add(dependency.getName());
@@ -160,13 +166,13 @@ void logWarnings() {
160166
}
161167
}
162168

163-
private int processQueueRound() {
169+
private int processQueueRound(boolean includeExternal) {
164170
// loop queue looking for entry that has all provides marked as included
165171
int count = 0;
166172
Iterator<MetaData> iterator = queue.iterator();
167173
while (iterator.hasNext()) {
168174
MetaData queuedMeta = iterator.next();
169-
if (allDependenciesWired(queuedMeta)) {
175+
if (allDependenciesWired(queuedMeta, includeExternal)) {
170176
orderedList.add(queuedMeta);
171177
queuedMeta.setWired();
172178
iterator.remove();
@@ -176,14 +182,19 @@ private int processQueueRound() {
176182
return count;
177183
}
178184

179-
private boolean allDependenciesWired(MetaData queuedMeta) {
185+
private boolean allDependenciesWired(MetaData queuedMeta, boolean includeExternal) {
180186
for (Dependency dependency : queuedMeta.getDependsOn()) {
181187
if (!Util.isProvider(dependency.getName())) {
182188
// check non-provider dependency is satisfied
183189
ProviderList providerList = providers.get(dependency.getName());
184190
if (providerList == null) {
185191
if (!scopeInfo.providedByOther(dependency)) {
186-
return false;
192+
if (includeExternal && context.externallyProvided(dependency.getName())) {
193+
autoRequires.add(dependency.getName());
194+
queuedMeta.markWithExternalDependency();
195+
} else {
196+
return false;
197+
}
187198
}
188199
} else {
189200
if (!providerList.isAllWired()) {
@@ -195,6 +206,10 @@ private boolean allDependenciesWired(MetaData queuedMeta) {
195206
return true;
196207
}
197208

209+
Set<String> autoRequires() {
210+
return autoRequires;
211+
}
212+
198213
List<MetaData> ordered() {
199214
return orderedList;
200215
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ final class ProcessingContext {
2828
private final Elements elementUtils;
2929
private final Types typeUtils;
3030
private final Set<String> uniqueModuleNames = new HashSet<>();
31+
private final ExternalProvide externalProvide = new ExternalProvide();
3132

3233
ProcessingContext(ProcessingEnvironment processingEnv) {
3334
this.processingEnv = processingEnv;
3435
this.messager = processingEnv.getMessager();
3536
this.filer = processingEnv.getFiler();
3637
this.elementUtils = processingEnv.getElementUtils();
3738
this.typeUtils = processingEnv.getTypeUtils();
39+
externalProvide.init();
3840
}
3941

4042
/**
@@ -141,4 +143,7 @@ boolean isDuplicateModule(String moduleFullName) {
141143
return uniqueModuleNames.contains(moduleFullName);
142144
}
143145

146+
boolean externallyProvided(String type) {
147+
return externalProvide.provides(type);
148+
}
144149
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -453,11 +453,11 @@ private void readFactoryMetaData(TypeElement moduleType) {
453453
* Return true if the scope is a custom scope and the dependency is provided
454454
* by the "default" module. We could/should move to be tighter here at some point.
455455
*/
456-
boolean providedByOtherModule(String dependency) {
456+
boolean providedByOtherScope(String dependency) {
457457
if (defaultScope) {
458458
return false;
459459
}
460-
if (scopes.providedByDefaultModule(dependency)) {
460+
if (scopes.providedByDefaultScope(dependency)) {
461461
return true;
462462
}
463463
return providesDependencyRecursive(dependency);
@@ -519,7 +519,7 @@ boolean providedByPackage(String dependency) {
519519
boolean providedByOther(Dependency dependency) {
520520
return dependency.isSoftDependency()
521521
|| providedByPackage(dependency.getName())
522-
|| providedByOtherModule(dependency.getName());
522+
|| providedByOtherScope(dependency.getName());
523523
}
524524

525525
Set<String> initModuleDependencies(Set<String> importTypes) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ private void writeAutoProvides() {
114114
writer.append(" public Class<?>[] autoProvidesAspects() {").eol();
115115
writeReturnClassArray(autoProvidesAspects);
116116
}
117+
Set<String> autoRequires = ordering.autoRequires();
118+
if (!autoRequires.isEmpty()) {
119+
writer.append(" @Override").eol();
120+
writer.append(" public Class<?>[] autoRequires() {").eol();
121+
writeReturnClassArray(autoRequires);
122+
}
117123
}
118124

119125
private void writeClassesMethod() {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
requires io.avaje.inject;
55

66
uses io.avaje.inject.spi.Plugin;
7+
uses io.avaje.inject.spi.Module;
78

89
provides javax.annotation.processing.Processor with io.avaje.inject.generator.Processor;
910
}

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

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -211,17 +211,16 @@ static class FactoryOrder {
211211
void add(Module module) {
212212
FactoryState factoryState = new FactoryState(module);
213213
providesMap.computeIfAbsent(module.getClass().getTypeName(), s -> new FactoryList()).add(factoryState);
214-
if (!factoryState.isProvidesEmpty()) {
215-
for (Class<?> feature : module.provides()) {
216-
providesMap.computeIfAbsent(feature.getTypeName(), s -> new FactoryList()).add(factoryState);
217-
}
218-
}
214+
addFactoryProvides(factoryState, module.provides());
215+
addFactoryProvides(factoryState, module.autoProvides());
216+
addFactoryProvides(factoryState, module.autoProvidesAspects());
217+
219218
if (factoryState.isRequiresEmpty()) {
220-
if (factoryState.isProvidesEmpty()) {
221-
// only has 'provides' so we can push this
219+
if (factoryState.explicitlyProvides()) {
220+
// push immediately when explicitly 'provides' with no 'requires'
222221
push(factoryState);
223222
} else {
224-
// hold until after all the 'provides only' modules are added
223+
// hold until after all the explicitly 'provides' modules are added
225224
queueNoDependencies.add(factoryState);
226225
}
227226
} else {
@@ -230,6 +229,12 @@ void add(Module module) {
230229
}
231230
}
232231

232+
private void addFactoryProvides(FactoryState factoryState, Class<?>[] provides) {
233+
for (Class<?> feature : provides) {
234+
providesMap.computeIfAbsent(feature.getTypeName(), s -> new FactoryList()).add(factoryState);
235+
}
236+
}
237+
233238
/**
234239
* Push the factory onto the build order (the wiring order for modules).
235240
*/
@@ -278,16 +283,9 @@ private void processQueue() {
278283
StringBuilder sb = new StringBuilder();
279284
for (FactoryState factory : queue) {
280285
sb.append("Module [").append(factory).append("] has unsatisfied");
281-
for (Class<?> depModuleName : factory.requires()) {
282-
if (notProvided(depModuleName.getTypeName())) {
283-
sb.append(String.format(" requires [%s]", depModuleName.getTypeName()));
284-
}
285-
}
286-
for (Class<?> depModuleName : factory.requiresPackages()) {
287-
if (notProvided(depModuleName.getTypeName())) {
288-
sb.append(String.format(" requiresPackages [%s]", depModuleName.getTypeName()));
289-
}
290-
}
286+
unsatisfiedRequires(sb, factory.requires(), "requires");
287+
unsatisfiedRequires(sb, factory.requiresPackages(), "requiresPackages");
288+
unsatisfiedRequires(sb, factory.autoRequires(), "autoRequires");
291289
}
292290
sb.append(" - none of the loaded modules ").append(moduleNames).append(" have this in their @InjectModule( provides = ... ). ");
293291
if (parent != null) {
@@ -298,6 +296,14 @@ private void processQueue() {
298296
}
299297
}
300298

299+
private void unsatisfiedRequires(StringBuilder sb, Class<?>[] requiredType, String requires) {
300+
for (Class<?> depModuleName : requiredType) {
301+
if (notProvided(depModuleName.getTypeName())) {
302+
sb.append(String.format(" %s [%s]", requires, depModuleName.getTypeName()));
303+
}
304+
}
305+
}
306+
301307
private boolean notProvided(String dependency) {
302308
if (parent != null && parent.contains(dependency)) {
303309
return false;
@@ -328,15 +334,16 @@ private int processQueuedFactories() {
328334
}
329335

330336
/**
331-
* Return true if the (module) dependencies are satisfied for this factory.
337+
* Return true if the (module) requires dependencies are satisfied for this factory.
332338
*/
333339
private boolean satisfiedDependencies(FactoryState factory) {
334-
for (Class<?> dependency : factory.requires()) {
335-
if (notProvided(dependency.getTypeName())) {
336-
return false;
337-
}
338-
}
339-
for (Class<?> dependency : factory.requiresPackages()) {
340+
return satisfiedDependencies(factory.requires())
341+
&& satisfiedDependencies(factory.requiresPackages())
342+
&& satisfiedDependencies(factory.autoRequires());
343+
}
344+
345+
private boolean satisfiedDependencies(Class<?>[] requires) {
346+
for (Class<?> dependency : requires) {
340347
if (notProvided(dependency.getTypeName())) {
341348
return false;
342349
}
@@ -384,17 +391,21 @@ Class<?>[] requiresPackages() {
384391
return factory.requiresPackages();
385392
}
386393

394+
Class<?>[] autoRequires() {
395+
return factory.autoRequires();
396+
}
397+
387398
@Override
388399
public String toString() {
389400
return factory.getClass().getTypeName();
390401
}
391402

392403
boolean isRequiresEmpty() {
393-
return isEmpty(factory.requires()) && isEmpty(factory.requiresPackages());
404+
return isEmpty(factory.requires()) && isEmpty(factory.requiresPackages()) && isEmpty(factory.autoRequires());
394405
}
395406

396-
boolean isProvidesEmpty() {
397-
return isEmpty(factory.provides());
407+
boolean explicitlyProvides() {
408+
return !isEmpty(factory.provides());
398409
}
399410

400411
private boolean isEmpty(@Nullable Class<?>[] values) {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ default Class<?>[] autoProvidesAspects() {
5353
return EMPTY_CLASSES;
5454
}
5555

56+
/**
57+
* These are the classes that this module requires for wiring that are provided by other
58+
* external modules (that are in the classpath at compile time).
59+
* <p>
60+
* This is a convenience when using multiple modules that is otherwise controlled manually by
61+
* explicitly using {@link InjectModule#requires()} or {@link InjectModule#requiresPackages()}.
62+
*/
63+
default Class<?>[] autoRequires() {
64+
return EMPTY_CLASSES;
65+
}
66+
5667
/**
5768
* Return public classes of the beans that would be registered by this module.
5869
* <p>

0 commit comments

Comments
 (0)