Skip to content

#171 - Module (custom scope) dependencies aren't fully transitive #173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ ScopeInfo addScopeAnnotation(TypeElement type) {
}

boolean providedByDefaultModule(String dependency) {
return defaultScope.providesDependency(dependency);
return defaultScope.providesDependencyLocally(dependency);
}

void readBeans(RoundEnvironment roundEnv) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,11 +407,22 @@ boolean providedByOtherModule(String dependency) {
if (scopes.providedByDefaultModule(dependency)) {
return true;
}
return providesDependencyRecursive(dependency);
}

/**
* Recursively search including 'parent' scopes.
*/
private boolean providesDependencyRecursive(String dependency) {
if (providesDependencyLocally(dependency)) {
return true;
}
// look for required scopes ...
for (String require : requires) {
final ScopeInfo requiredScope = scopes.get(require);
if (requiredScope != null) {
if (requiredScope.providesDependency(dependency)) {
// recursively search parent scope
if (requiredScope.providesDependencyRecursive(dependency)) {
// context.logWarn("dependency " + dependency + " provided by other scope " + requiredScope.name);
return true;
}
Expand All @@ -421,9 +432,9 @@ boolean providedByOtherModule(String dependency) {
}

/**
* Return true if this module provides the dependency.
* Return true if this module provides the dependency (non-recursive, local only).
*/
boolean providesDependency(String dependency) {
boolean providesDependencyLocally(String dependency) {
if (requires.contains(dependency)) {
return true;
}
Expand Down
4 changes: 4 additions & 0 deletions inject-test/src/test/java/org/example/custom4/Build.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.example.custom4;

public class Build {
}
11 changes: 11 additions & 0 deletions inject-test/src/test/java/org/example/custom4/BuildOne.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example.custom4;

@BuildScope
public class BuildOne {

private final Build build;

BuildOne(Build build) {
this.build = build;
}
}
9 changes: 9 additions & 0 deletions inject-test/src/test/java/org/example/custom4/BuildScope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.custom4;

import io.avaje.inject.InjectModule;
import jakarta.inject.Scope;

@Scope
@InjectModule(requires = Build.class )
public @interface BuildScope {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.custom4;

import io.avaje.inject.InjectModule;
import jakarta.inject.Scope;

@Scope
@InjectModule(requires = BuildScope.class )
public @interface IntentionallyEmptyScope {
}
13 changes: 13 additions & 0 deletions inject-test/src/test/java/org/example/custom4/LinuxOne.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.example.custom4;

@LinuxScope
public class LinuxOne {

final Machine machine;
final Build build;

LinuxOne(Machine machine, Build build) {
this.machine = machine;
this.build = build;
}
}
9 changes: 9 additions & 0 deletions inject-test/src/test/java/org/example/custom4/LinuxScope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.custom4;

import io.avaje.inject.InjectModule;
import jakarta.inject.Scope;

@Scope
@InjectModule(requires = {MachineScope.class})
public @interface LinuxScope {
}
82 changes: 82 additions & 0 deletions inject-test/src/test/java/org/example/custom4/LinuxScopeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.example.custom4;

import io.avaje.inject.BeanScope;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class LinuxScopeTest {

@Test
void test_via_nested_scopes() {
Build buildExternal = new Build();
BuildModule buildModule = new BuildModule(buildExternal);

// our top scope
try (BeanScope buildScope = BeanScope.newBuilder()
.withModules(buildModule)
.build()) {

Build build = buildScope.get(Build.class);
assertThat(build).isSameAs(buildExternal);

Machine machineExternal = new Machine();
MachineModule machineModule = new MachineModule(machineExternal);

// our middle scope (depends on top scope)
try (BeanScope machineScope = BeanScope.newBuilder()
.withParent(buildScope)
.withModules(machineModule)
.build()) {

MachineOne machineOne = machineScope.get(MachineOne.class);
assertThat(machineOne.build).isSameAs(buildExternal);
assertThat(machineOne.machine).isSameAs(machineExternal);

// bottom scope depends on middle scope and transitively depends on top scope
// this is our case for Issue 171 where LinuxOne depends on Build
// which is transitively supplied via MachineScope
try (BeanScope linuxScope = BeanScope.newBuilder()
.withParent(machineScope)
.withModules(new LinuxModule())
.build()) {

MachineOne machineOne2 = linuxScope.get(MachineOne.class);
assertThat(machineOne2).isSameAs(machineOne);
assertThat(machineOne2.build).isSameAs(buildExternal);
assertThat(machineOne2.machine).isSameAs(machineExternal);

LinuxOne linuxOne = linuxScope.get(LinuxOne.class);
assertThat(linuxOne.build).isSameAs(buildExternal);
assertThat(linuxOne.machine).isSameAs(machineExternal);
}
}
}
}


@Test
void test_via_flattened_module_structure() {
// external dependencies
Build buildExternal = new Build();
Machine machineExternal = new Machine();

// our 'flattened' bean scope
try (BeanScope flatScope = BeanScope.newBuilder()
// all our scope modules
.withModules(new BuildModule(buildExternal), new MachineModule(machineExternal), new LinuxModule())
.build()) {

Build build = flatScope.get(Build.class);
assertThat(build).isSameAs(buildExternal);

MachineOne machineOne = flatScope.get(MachineOne.class);
assertThat(machineOne.build).isSameAs(buildExternal);
assertThat(machineOne.machine).isSameAs(machineExternal);

LinuxOne linuxOne = flatScope.get(LinuxOne.class);
assertThat(linuxOne.build).isSameAs(buildExternal);
assertThat(linuxOne.machine).isSameAs(machineExternal);
}
}
}
4 changes: 4 additions & 0 deletions inject-test/src/test/java/org/example/custom4/Machine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.example.custom4;

public class Machine {
}
13 changes: 13 additions & 0 deletions inject-test/src/test/java/org/example/custom4/MachineOne.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.example.custom4;

@MachineScope
public class MachineOne {

final Build build;
final Machine machine;

MachineOne(Build build, Machine machine) {
this.build = build;
this.machine = machine;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.custom4;

import io.avaje.inject.InjectModule;
import jakarta.inject.Scope;

@Scope
@InjectModule(requires = {Machine.class, IntentionallyEmptyScope.class})
public @interface MachineScope {
}