Skip to content

Commit 99aff65

Browse files
committed
#193 - Add @InjectModule requirePackages ... for better multi-module requires/provides
This means that we don't need to reference each and every dependency in requires and instead can put one into requiresPackages such that any dependency in that package or a subpackage of that package is deemed to be supplied by another module.
1 parent 19485cb commit 99aff65

File tree

15 files changed

+196
-48
lines changed

15 files changed

+196
-48
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.0</version>
8+
<version>8.1.RC1</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.0</version>
7+
<version>8.1.RC1</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.0</version>
19+
<version>8.1.RC1</version>
2020
</dependency>
2121

2222
<!-- test dependencies -->

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ final class Dependency {
1414
this.softDependency = softDependency;
1515
}
1616

17+
@Override
18+
public String toString() {
19+
return name;
20+
}
21+
1722
String getName() {
1823
return name;
1924
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class MetaDataOrdering {
3838
}
3939

4040
/**
41-
* These if defined are expected to be required at wiring time probably via another module.
41+
* These, if defined are expected to be required at wiring time probably via another module.
4242
*/
4343
private void externallyRequiredDependencies() {
4444
for (String requireType : scopeInfo.requires()) {
@@ -121,8 +121,7 @@ void missingDependencies() {
121121

122122
private void checkMissingDependencies(MetaData metaData) {
123123
for (Dependency dependency : metaData.getDependsOn()) {
124-
if (providers.get(dependency.getName()) == null
125-
&& !scopeInfo.providedByOtherModule(dependency.getName())) {
124+
if (providers.get(dependency.getName()) == null && !scopeInfo.providedByOtherModule(dependency.getName())) {
126125
TypeElement element = context.elementMaybe(metaData.getType());
127126
context.logError(element, "No dependency provided for " + dependency + " on " + metaData.getType());
128127
missingDependencyTypes.add(dependency.getName());
@@ -176,7 +175,7 @@ private boolean allDependenciesWired(MetaData queuedMeta) {
176175
// check non-provider dependency is satisfied
177176
ProviderList providerList = providers.get(dependency.getName());
178177
if (providerList == null) {
179-
if (!scopeInfo.providedByOtherModule(dependency.getName()) && !dependency.isSoftDependency()) {
178+
if (!scopeInfo.providedByOther(dependency)) {
180179
return false;
181180
}
182181
} else {

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ String type() {
4747
private final ProcessingContext context;
4848
private final Set<String> requires = new LinkedHashSet<>();
4949
private final Set<String> provides = new LinkedHashSet<>();
50+
private final Set<String> requiresPackages = new LinkedHashSet<>();
51+
private final List<String> requirePkg = new ArrayList<>();
5052
private final boolean defaultScope;
5153
private final TypeElement annotationType;
5254
private final AllScopes scopes;
@@ -100,6 +102,10 @@ void details(String name, Element contextElement) {
100102
private void read(Element element) {
101103
requires(ScopeUtil.readRequires(element));
102104
provides(ScopeUtil.readProvides(element));
105+
for (String require : ScopeUtil.readRequiresPackages(element)) {
106+
requiresPackages.add(require);
107+
requirePkg.add(Util.packageOf(require) + ".");
108+
}
103109
}
104110

105111
private String initName(String topPackage) {
@@ -160,10 +166,6 @@ Set<String> requires() {
160166
return requires;
161167
}
162168

163-
Set<String> provides() {
164-
return provides;
165-
}
166-
167169
void writeBeanHelpers() {
168170
for (BeanReader beanReader : beanReaders) {
169171
try {
@@ -333,6 +335,10 @@ void buildAtInjectModule(Append writer) {
333335
attributeClasses(leadingComma, writer, "requires", requires);
334336
leadingComma = true;
335337
}
338+
if (!requiresPackages.isEmpty()) {
339+
attributeClasses(leadingComma, writer, "requiresPackages", requiresPackages);
340+
leadingComma = true;
341+
}
336342
if (annotationType != null) {
337343
if (leadingComma) {
338344
writer.append(", ");
@@ -379,6 +385,9 @@ void buildFields(Append writer) {
379385
writer.append(" private final Class<?>[] requires = ");
380386
buildClassArray(writer, requires);
381387
writer.append(";").eol();
388+
writer.append(" private final Class<?>[] requiresPackages = ");
389+
buildClassArray(writer, requiresPackages);
390+
writer.append(";").eol();
382391
writer.append(" private Builder builder;").eol().eol();
383392
}
384393

@@ -458,6 +467,21 @@ boolean providesDependencyLocally(String dependency) {
458467
return false;
459468
}
460469

470+
boolean providedByPackage(String dependency) {
471+
for (String pkg : requirePkg) {
472+
if (dependency.startsWith(pkg)) {
473+
return true;
474+
}
475+
}
476+
return false;
477+
}
478+
479+
boolean providedByOther(Dependency dependency) {
480+
return dependency.isSoftDependency()
481+
|| providedByPackage(dependency.getName())
482+
|| providedByOtherModule(dependency.getName());
483+
}
484+
461485
Set<String> initModuleDependencies(Set<String> importTypes) {
462486
if (defaultScope || requires.isEmpty()) {
463487
return importTypes;

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ class ScopeUtil {
1111
private static final String INJECT_MODULE = "io.avaje.inject.InjectModule";
1212

1313
static List<String> readProvides(Element element) {
14-
return readClasses(element, "provides");
14+
return readClasses(element, "provides(");
1515
}
1616

1717
static List<String> readRequires(Element element) {
18-
return readClasses(element, "requires");
18+
return readClasses(element, "requires(");
19+
}
20+
21+
static List<String> readRequiresPackages(Element element) {
22+
return readClasses(element, "requiresPackages(");
1923
}
2024

2125
static List<String> readClasses(Element element, String attributeName) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ private void writeStartClass() {
185185
writer.append(" public Class<?>[] requires() {").eol();
186186
writer.append(" return requires;").eol();
187187
writer.append(" }").eol().eol();
188+
189+
writer.append(" @Override").eol();
190+
writer.append(" public Class<?>[] requiresPackages() {").eol();
191+
writer.append(" return requiresPackages;").eol();
192+
writer.append(" }").eol().eol();
188193
}
189194

190195
private void writeWithBeans() {

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.0</version>
7+
<version>8.1.RC1</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.0</version>
127+
<version>8.1.RC1</version>
128128
</path>
129129
</annotationProcessorPaths>
130130
</configuration>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public Class<?>[] requires() {
3535
return new Class[0];
3636
}
3737

38+
@Override
39+
public Class<?>[] requiresPackages() {
40+
return new Class[0];
41+
}
42+
3843
@Override
3944
public Class<?>[] provides() {
4045
return new Class[0];

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.0</version>
7+
<version>8.1.RC1</version>
88
</parent>
99

1010
<artifactId>avaje-inject</artifactId>

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

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -191,32 +191,28 @@ static class FactoryOrder {
191191
}
192192
}
193193

194-
void add(Module factory) {
195-
FactoryState wrappedFactory = new FactoryState(factory);
196-
providesMap.computeIfAbsent(factory.getClass().getTypeName(), s -> new FactoryList()).add(wrappedFactory);
197-
if (!isEmpty(factory.provides())) {
198-
for (Class<?> feature : factory.provides()) {
199-
providesMap.computeIfAbsent(feature.getTypeName(), s -> new FactoryList()).add(wrappedFactory);
194+
void add(Module module) {
195+
FactoryState factoryState = new FactoryState(module);
196+
providesMap.computeIfAbsent(module.getClass().getTypeName(), s -> new FactoryList()).add(factoryState);
197+
if (!factoryState.isProvidesEmpty()) {
198+
for (Class<?> feature : module.provides()) {
199+
providesMap.computeIfAbsent(feature.getTypeName(), s -> new FactoryList()).add(factoryState);
200200
}
201201
}
202-
if (isEmpty(factory.requires())) {
203-
if (!isEmpty(factory.provides())) {
202+
if (factoryState.isRequiresEmpty()) {
203+
if (factoryState.isProvidesEmpty()) {
204204
// only has 'provides' so we can push this
205-
push(wrappedFactory);
205+
push(factoryState);
206206
} else {
207207
// hold until after all the 'provides only' modules are added
208-
queueNoDependencies.add(wrappedFactory);
208+
queueNoDependencies.add(factoryState);
209209
}
210210
} else {
211211
// queue it to process by dependency ordering
212-
queue.add(wrappedFactory);
212+
queue.add(factoryState);
213213
}
214214
}
215215

216-
private boolean isEmpty(@Nullable Class<?>[] values) {
217-
return values == null || values.length == 0;
218-
}
219-
220216
/**
221217
* Push the factory onto the build order (the wiring order for modules).
222218
*/
@@ -250,7 +246,6 @@ List<Module> factories() {
250246
* Process the queue pushing the factories in order to satisfy dependencies.
251247
*/
252248
private void processQueue() {
253-
254249
int count;
255250
do {
256251
count = processQueuedFactories();
@@ -262,24 +257,32 @@ private void processQueue() {
262257
for (FactoryState factoryState : queue) {
263258
push(factoryState);
264259
}
265-
266260
} else if (!queue.isEmpty()) {
267261
StringBuilder sb = new StringBuilder();
268262
for (FactoryState factory : queue) {
269-
sb.append("Module [").append(factory.getClass()).append("] has unsatisfied dependencies on modules:");
263+
sb.append("Module [").append(factory).append("] has unsatisfied");
270264
for (Class<?> depModuleName : factory.requires()) {
271-
if (!moduleNames.contains(depModuleName.getName())) {
272-
sb.append(String.format(" [%s]", depModuleName));
265+
if (notProvided(depModuleName.getTypeName())) {
266+
sb.append(String.format(" requires [%s]", depModuleName.getTypeName()));
267+
}
268+
}
269+
for (Class<?> depModuleName : factory.requiresPackages()) {
270+
if (notProvided(depModuleName.getTypeName())) {
271+
sb.append(String.format(" requiresPackages [%s]", depModuleName.getTypeName()));
273272
}
274273
}
275274
}
276-
277-
sb.append(". Modules that were loaded ok are:").append(moduleNames);
278-
sb.append(". Maybe need to add external dependencies via BeanScopeBuilder.withBean()?");
275+
sb.append(" - none of the loaded modules ").append(moduleNames).append(" have this in their @InjectModule( provides = ... ). ");
276+
sb.append("Either @InjectModule requires/provides are not aligned? or add external dependencies via BeanScopeBuilder.withBean()?");
279277
throw new IllegalStateException(sb.toString());
280278
}
281279
}
282280

281+
private boolean notProvided(String dependency) {
282+
FactoryList factories = providesMap.get(dependency);
283+
return (factories == null || !factories.allPushed());
284+
}
285+
283286
/**
284287
* Process the queued factories pushing them when all their (module) dependencies
285288
* are satisfied.
@@ -305,9 +308,13 @@ private int processQueuedFactories() {
305308
* Return true if the (module) dependencies are satisfied for this factory.
306309
*/
307310
private boolean satisfiedDependencies(FactoryState factory) {
308-
for (Class<?> moduleOrFeature : factory.requires()) {
309-
FactoryList factories = providesMap.get(moduleOrFeature.getTypeName());
310-
if (factories == null || !factories.allPushed()) {
311+
for (Class<?> dependency : factory.requires()) {
312+
if (notProvided(dependency.getTypeName())) {
313+
return false;
314+
}
315+
}
316+
for (Class<?> dependency : factory.requiresPackages()) {
317+
if (notProvided(dependency.getTypeName())) {
311318
return false;
312319
}
313320
}
@@ -349,6 +356,27 @@ Module factory() {
349356
Class<?>[] requires() {
350357
return factory.requires();
351358
}
359+
360+
Class<?>[] requiresPackages() {
361+
return factory.requiresPackages();
362+
}
363+
364+
@Override
365+
public String toString() {
366+
return factory.getClass().getTypeName();
367+
}
368+
369+
boolean isRequiresEmpty() {
370+
return isEmpty(factory.requires()) && isEmpty(factory.requiresPackages());
371+
}
372+
373+
boolean isProvidesEmpty() {
374+
return isEmpty(factory.provides());
375+
}
376+
377+
private boolean isEmpty(@Nullable Class<?>[] values) {
378+
return values == null || values.length == 0;
379+
}
352380
}
353381

354382
/**

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@
6060
*/
6161
Class<?>[] requires() default {};
6262

63+
/**
64+
* Dependencies in these packages are expected to be provided by other modules.
65+
* <p>
66+
* Instead of listing each and every dependency in {@code requires} we can use this to specify
67+
* that any required dependency that is in these packages is expected to be provided by another module.
68+
* <p>
69+
* Use this rather than {@code requires} when there are lots of required dependencies, and we don't
70+
* want to list each one in {@code requires} and {@code provides}.
71+
*/
72+
Class<?>[] requiresPackages() default {};
73+
6374
/**
6475
* Internal use only - identifies the custom scope annotation associated to this module.
6576
* <p>

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@
55
*/
66
public interface Module {
77

8+
/**
9+
* Return the set of types this module explicitly provides to other modules.
10+
*/
11+
Class<?>[] provides();
12+
813
/**
914
* Return the types this module needs to be provided externally or via other modules.
1015
*/
1116
Class<?>[] requires();
1217

1318
/**
14-
* Return the set of types this module explicitly provides to other modules.
19+
* Return the packages this module needs to be provided via other modules.
1520
*/
16-
Class<?>[] provides();
21+
Class<?>[] requiresPackages();
1722

1823
/**
1924
* Return public classes of the beans that would be registered by this module.

0 commit comments

Comments
 (0)