Skip to content

Commit 7cd9740

Browse files
authored
ISSUES-17 allow sibling binding
1 parent 771230c commit 7cd9740

File tree

9 files changed

+112
-56
lines changed

9 files changed

+112
-56
lines changed

compiler/src/main/java/io/jbock/simple/processor/binding/InjectBindingFactory.java

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33
import io.jbock.simple.Inject;
44
import io.jbock.simple.processor.util.ClearableCache;
55
import io.jbock.simple.processor.util.TypeTool;
6+
import io.jbock.simple.processor.util.Util;
67

7-
import javax.lang.model.element.ExecutableElement;
88
import javax.lang.model.element.TypeElement;
99
import java.util.HashMap;
1010
import java.util.LinkedHashMap;
11-
import java.util.List;
1211
import java.util.Map;
1312
import java.util.Objects;
1413
import java.util.Optional;
15-
import java.util.stream.Collectors;
1614

1715
import static io.jbock.simple.processor.util.Visitors.EXECUTABLE_ELEMENT_VISITOR;
1816
import static io.jbock.simple.processor.util.Visitors.TYPE_ELEMENT_VISITOR;
@@ -22,50 +20,40 @@ public final class InjectBindingFactory implements ClearableCache {
2220
private final Map<TypeElement, Map<Key, InjectBinding>> injectBindingCache = new HashMap<>();
2321

2422
private final TypeTool tool;
25-
private final KeyFactory keyFactory;
2623
private final InjectBinding.Factory injectBindingFactory;
2724

2825
@Inject
2926
public InjectBindingFactory(
3027
TypeTool tool,
31-
KeyFactory keyFactory,
3228
InjectBinding.Factory injectBindingFactory) {
3329
this.tool = tool;
34-
this.keyFactory = keyFactory;
3530
this.injectBindingFactory = injectBindingFactory;
3631
}
3732

3833
public Map<Key, InjectBinding> injectBindings(TypeElement typeElement) {
39-
Map<Key, InjectBinding> result = injectBindingCache.get(typeElement);
40-
if (result != null) {
41-
return result;
42-
}
43-
List<? extends ExecutableElement> allMembers = tool.elements().getAllMembers(typeElement).stream()
44-
.filter(tool::hasInjectAnnotation)
45-
.map(EXECUTABLE_ELEMENT_VISITOR::visit)
46-
.filter(Objects::nonNull)
47-
.collect(Collectors.toList());
48-
if (allMembers.isEmpty()) {
49-
return Map.of();
50-
}
51-
result = new LinkedHashMap<>();
52-
for (ExecutableElement method : allMembers) {
53-
InjectBinding b = injectBindingFactory.create(method);
54-
result.put(b.key(), b); // duplicates handled elsewhere
34+
return injectBindingCache.computeIfAbsent(typeElement, this::injectBindingsMiss);
35+
}
36+
37+
private Map<Key, InjectBinding> injectBindingsMiss(TypeElement typeElement) {
38+
Map<Key, InjectBinding> result = new LinkedHashMap<>();
39+
for (TypeElement element : Util.getWithEnclosing(typeElement)) {
40+
tool.elements().getAllMembers(element).stream()
41+
.filter(tool::hasInjectAnnotation)
42+
.map(EXECUTABLE_ELEMENT_VISITOR::visit)
43+
.filter(Objects::nonNull)
44+
.map(injectBindingFactory::create)
45+
.forEach(b -> result.put(b.key(), b) /* duplicates handled elsewhere */);
5546
}
56-
injectBindingCache.put(typeElement, result);
5747
return result;
5848
}
5949

6050
public Optional<Binding> binding(Key key) {
61-
return tool.types().asElement(key.type()).flatMap(element -> {
62-
TypeElement typeElement = TYPE_ELEMENT_VISITOR.visit(element);
63-
if (typeElement == null) {
64-
return Optional.empty();
65-
}
66-
Map<Key, InjectBinding> m = injectBindings(typeElement);
67-
return Optional.ofNullable(m.get(key));
68-
});
51+
return tool.types().asElement(key.type())
52+
.map(TYPE_ELEMENT_VISITOR::visit)
53+
.flatMap(typeElement -> {
54+
Map<Key, InjectBinding> m = injectBindings(typeElement);
55+
return Optional.ofNullable(m.get(key));
56+
});
6957
}
7058

7159
@Override

compiler/src/main/java/io/jbock/simple/processor/graph/MissingBindingPrinter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ ValidationFailure fail(List<DependencyRequest> dependencyTrace) {
3131
private ValidationFailure failInternal(List<DependencyRequest> trace) {
3232
DependencyRequest request = trace.get(0);
3333
StringBuilder message = new StringBuilder();
34-
message.append(request.key().toString()).append(" cannot be provided.");
34+
message.append("No binding found for " + request.key().toString() + ".");
3535
for (int i = 0; i < trace.size(); i++) {
3636
DependencyRequest r = trace.get(i);
3737
String formatted = format(r, i == trace.size() - 1 ? "requested" : "injected");
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.jbock.simple.processor.util;
2+
3+
import javax.lang.model.element.TypeElement;
4+
import java.util.List;
5+
6+
public final class Util {
7+
8+
public static List<TypeElement> getWithEnclosing(TypeElement typeElement) {
9+
if (typeElement == null) {
10+
return List.of();
11+
}
12+
TypeElement el = Visitors.TYPE_ELEMENT_VISITOR.visit(typeElement.getEnclosingElement());
13+
return el == null ? List.of(typeElement) : List.of(typeElement, el);
14+
}
15+
16+
private Util() {
17+
}
18+
}

compiler/src/main/java/io/jbock/simple/processor/validation/InjectBindingValidator.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.jbock.simple.processor.binding.InjectBindingFactory;
66
import io.jbock.simple.processor.binding.Key;
77
import io.jbock.simple.processor.util.TypeTool;
8+
import io.jbock.simple.processor.util.Util;
89
import io.jbock.simple.processor.util.ValidationFailure;
910
import io.jbock.simple.processor.util.Visitors;
1011

@@ -13,6 +14,7 @@
1314
import javax.lang.model.element.TypeElement;
1415
import javax.lang.model.type.DeclaredType;
1516
import javax.lang.model.type.TypeKind;
17+
import java.util.List;
1618
import java.util.Map;
1719

1820
import static io.jbock.simple.processor.util.TypeNames.JAKARTA_INJECT;
@@ -38,37 +40,30 @@ public void validateConstructor(ExecutableElement element) {
3840

3941
public void validateStaticMethod(ExecutableElement method) {
4042
validate(method);
41-
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement());
42-
// List<TypeElement> hierarchyMethod = getEnclosingElements(typeElement);
43-
// List<TypeElement> hierarchyRt = tool.types().asElement(method.getReturnType())
44-
// .map(Visitors.TYPE_ELEMENT_VISITOR::visit)
45-
// .map(this::getEnclosingElements)
46-
// .orElse(List.of());
4743
if (!method.getModifiers().contains(Modifier.STATIC)) {
4844
throw new ValidationFailure("The factory method must be static", method);
4945
}
5046
if (method.getReturnType().getKind() == TypeKind.VOID) {
5147
throw new ValidationFailure("The factory method may not return void", method);
5248
}
53-
if (!tool.types().isSameType(method.getReturnType(), typeElement.asType())) {
49+
if (!isSibling(method)) {
5450
throw new ValidationFailure("The factory method must return the type of its enclosing class", method);
5551
}
5652
}
5753

58-
/*
59-
private List<TypeElement> getEnclosingElements(TypeElement typeElement) {
60-
if (typeElement == null) {
61-
return List.of();
62-
}
63-
List<TypeElement> acc = new ArrayList<>(2);
64-
acc.add(typeElement);
65-
TypeElement el = typeElement;
66-
if ((el = Visitors.TYPE_ELEMENT_VISITOR.visit(el.getEnclosingElement())) != null) {
67-
acc.add(el);
54+
private boolean isSibling(ExecutableElement method) {
55+
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement());
56+
List<TypeElement> hierarchyRt = tool.types().asElement(method.getReturnType())
57+
.map(Visitors.TYPE_ELEMENT_VISITOR::visit)
58+
.map(Util::getWithEnclosing)
59+
.orElse(List.of());
60+
for (TypeElement r : hierarchyRt) {
61+
if (r.equals(typeElement)) {
62+
return true;
63+
}
6864
}
69-
return acc;
65+
return false;
7066
}
71-
*/
7267

7368
private void validate(ExecutableElement element) {
7469
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(element.getEnclosingElement());

compiler/src/main/java/io/jbock/simple/processor/writing/ComponentImpl.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Objects;
2425
import java.util.function.Function;
2526

2627
import static javax.lang.model.element.Modifier.FINAL;
@@ -91,7 +92,8 @@ TypeSpec generate() {
9192
}
9293
spec.addAnnotation(AnnotationSpec.builder(Generated.class)
9394
.addMember("value", CodeBlock.of("$S", SimpleComponentProcessor.class.getCanonicalName()))
94-
.addMember("comments", CodeBlock.of("$S", "https://github.com/jbock-java/simple-component"))
95+
.addMember("comments", CodeBlock.of("$S", "https://github.com/jbock-java/simple-component " +
96+
Objects.toString(getClass().getPackage().getImplementationVersion(), "")))
9597
.build());
9698
spec.addMethod(generateAllParametersConstructor());
9799
spec.addOriginatingElement(component.element());

compiler/src/test/java/io/jbock/simple/processor/CycleTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,6 @@ void providerProvider() {
133133

134134
Compilation compilation = simpleCompiler().compile(component);
135135
assertThat(compilation).failed();
136-
assertThat(compilation).hadErrorContaining("io.jbock.simple.Provider<io.jbock.simple.Provider<test.TestClass.B>> cannot be provided.");
136+
assertThat(compilation).hadErrorContaining("No binding found for io.jbock.simple.Provider<io.jbock.simple.Provider<test.TestClass.B>>");
137137
}
138138
}

compiler/src/test/java/io/jbock/simple/processor/MissingBindingTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void missingBinding() {
3333

3434
Compilation compilation = simpleCompiler().compile(component);
3535
assertThat(compilation).failed();
36-
assertThat(compilation).hadErrorContainingMatch("java.lang.String cannot be provided.")
36+
assertThat(compilation).hadErrorContainingMatch("No binding found for java.lang.String.")
3737
.inFile(component)
3838
.onLineContaining("interface AComponent");
3939
}
@@ -77,7 +77,7 @@ void bindsMethodAppearsInTrace() {
7777
assertThat(compilation)
7878
.hadErrorContaining(
7979
TestUtils.message(
80-
"java.lang.String cannot be provided.",
80+
"No binding found for java.lang.String.",
8181
" java.lang.String is injected at",
8282
" TestImplementation(java.lang.String)",
8383
" p.TestImplementation is injected at",

compiler/src/test/java/io/jbock/simple/processor/ProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void qualifierFail() {
3838

3939
Compilation compilation = simpleCompiler().compile(component);
4040
assertThat(compilation).failed();
41-
assertThat(compilation).hadErrorContaining("io.jbock.simple.Provider<test.TestClass.B> with qualifier @Named(\"b\") cannot be provided.")
41+
assertThat(compilation).hadErrorContaining("No binding found for io.jbock.simple.Provider<test.TestClass.B> with qualifier @Named(\"b\").")
4242
.inFile(component)
4343
.onLineContaining("interface AComponent");
4444
}

compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,57 @@ void clashResolvedByQualifiers() {
6262
" }",
6363
"}");
6464
}
65+
66+
@Test
67+
void injectMethodIsSibling() {
68+
JavaFileObject component = forSourceLines("test.TestClass",
69+
"package test;",
70+
"",
71+
"import io.jbock.simple.Component;",
72+
"import io.jbock.simple.Inject;",
73+
"import io.jbock.simple.Named;",
74+
"",
75+
"final class TestClass {",
76+
"",
77+
" static class A {",
78+
" }",
79+
"",
80+
" static class B {",
81+
" }",
82+
"",
83+
" @Inject static A createA(@Named(\"1\") B b1, @Named(\"2\") B b2) { return null; }",
84+
" @Inject @Named(\"1\") static B createB1() { return null; }",
85+
" @Inject @Named(\"2\") static B createB2() { return null; }",
86+
"",
87+
" @Component",
88+
" interface AComponent {",
89+
" A getA();",
90+
" }",
91+
"}");
92+
Compilation compilation = simpleCompiler().compile(component);
93+
assertThat(compilation).succeeded();
94+
assertThat(compilation).generatedSourceFile("test.TestClass_AComponent_Impl")
95+
.containsLines(
96+
"package test;",
97+
"",
98+
"final class TestClass_AComponent_Impl implements TestClass.AComponent {",
99+
" private final TestClass.A a;",
100+
"",
101+
" private TestClass_AComponent_Impl(TestClass.A a) {",
102+
" this.a = a;",
103+
" }",
104+
"",
105+
" @Override",
106+
" public TestClass.A getA() {",
107+
" return a;",
108+
" }",
109+
"",
110+
" static TestClass.AComponent create() {",
111+
" TestClass.B b = TestClass.createB1();",
112+
" TestClass.B b2 = TestClass.createB2();",
113+
" TestClass.A a = TestClass.createA(b, b2);",
114+
" return new TestClass_AComponent_Impl(a);",
115+
" }",
116+
"}");
117+
}
65118
}

0 commit comments

Comments
 (0)