diff --git a/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBindingFactory.java b/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBindingFactory.java index 0bb1abc..e658f82 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBindingFactory.java +++ b/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBindingFactory.java @@ -3,16 +3,14 @@ import io.jbock.simple.Inject; import io.jbock.simple.processor.util.ClearableCache; import io.jbock.simple.processor.util.TypeTool; +import io.jbock.simple.processor.util.Util; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import static io.jbock.simple.processor.util.Visitors.EXECUTABLE_ELEMENT_VISITOR; import static io.jbock.simple.processor.util.Visitors.TYPE_ELEMENT_VISITOR; @@ -22,38 +20,30 @@ public final class InjectBindingFactory implements ClearableCache { private final Map> injectBindingCache = new HashMap<>(); private final TypeTool tool; - private final KeyFactory keyFactory; private final InjectBinding.Factory injectBindingFactory; @Inject public InjectBindingFactory( TypeTool tool, - KeyFactory keyFactory, InjectBinding.Factory injectBindingFactory) { this.tool = tool; - this.keyFactory = keyFactory; this.injectBindingFactory = injectBindingFactory; } public Map injectBindings(TypeElement typeElement) { - Map result = injectBindingCache.get(typeElement); - if (result != null) { - return result; - } - List allMembers = tool.elements().getAllMembers(typeElement).stream() - .filter(tool::hasInjectAnnotation) - .map(EXECUTABLE_ELEMENT_VISITOR::visit) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - if (allMembers.isEmpty()) { - return Map.of(); - } - result = new LinkedHashMap<>(); - for (ExecutableElement method : allMembers) { - InjectBinding b = injectBindingFactory.create(method); - result.put(b.key(), b); // duplicates handled elsewhere + return injectBindingCache.computeIfAbsent(typeElement, this::injectBindingsMiss); + } + + private Map injectBindingsMiss(TypeElement typeElement) { + Map result = new LinkedHashMap<>(); + for (TypeElement element : Util.getWithEnclosing(typeElement)) { + tool.elements().getAllMembers(element).stream() + .filter(tool::hasInjectAnnotation) + .map(EXECUTABLE_ELEMENT_VISITOR::visit) + .filter(Objects::nonNull) + .map(injectBindingFactory::create) + .forEach(b -> result.put(b.key(), b) /* duplicates handled elsewhere */); } - injectBindingCache.put(typeElement, result); return result; } diff --git a/compiler/src/main/java/io/jbock/simple/processor/graph/MissingBindingPrinter.java b/compiler/src/main/java/io/jbock/simple/processor/graph/MissingBindingPrinter.java index cc400ef..caca962 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/graph/MissingBindingPrinter.java +++ b/compiler/src/main/java/io/jbock/simple/processor/graph/MissingBindingPrinter.java @@ -31,7 +31,7 @@ ValidationFailure fail(List dependencyTrace) { private ValidationFailure failInternal(List trace) { DependencyRequest request = trace.get(0); StringBuilder message = new StringBuilder(); - message.append(request.key().toString()).append(" cannot be provided."); + message.append("No binding found for " + request.key().toString() + "."); for (int i = 0; i < trace.size(); i++) { DependencyRequest r = trace.get(i); String formatted = format(r, i == trace.size() - 1 ? "requested" : "injected"); diff --git a/compiler/src/main/java/io/jbock/simple/processor/util/Util.java b/compiler/src/main/java/io/jbock/simple/processor/util/Util.java new file mode 100644 index 0000000..2507a75 --- /dev/null +++ b/compiler/src/main/java/io/jbock/simple/processor/util/Util.java @@ -0,0 +1,18 @@ +package io.jbock.simple.processor.util; + +import javax.lang.model.element.TypeElement; +import java.util.List; + +public final class Util { + + public static List getWithEnclosing(TypeElement typeElement) { + if (typeElement == null) { + return List.of(); + } + TypeElement el = Visitors.TYPE_ELEMENT_VISITOR.visit(typeElement.getEnclosingElement()); + return el == null ? List.of(typeElement) : List.of(typeElement, el); + } + + private Util() { + } +} diff --git a/compiler/src/main/java/io/jbock/simple/processor/validation/InjectBindingValidator.java b/compiler/src/main/java/io/jbock/simple/processor/validation/InjectBindingValidator.java index b632edb..ac36afa 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/validation/InjectBindingValidator.java +++ b/compiler/src/main/java/io/jbock/simple/processor/validation/InjectBindingValidator.java @@ -5,6 +5,7 @@ import io.jbock.simple.processor.binding.InjectBindingFactory; import io.jbock.simple.processor.binding.Key; import io.jbock.simple.processor.util.TypeTool; +import io.jbock.simple.processor.util.Util; import io.jbock.simple.processor.util.ValidationFailure; import io.jbock.simple.processor.util.Visitors; @@ -13,6 +14,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; +import java.util.List; import java.util.Map; import static io.jbock.simple.processor.util.TypeNames.JAKARTA_INJECT; @@ -38,37 +40,30 @@ public void validateConstructor(ExecutableElement element) { public void validateStaticMethod(ExecutableElement method) { validate(method); - TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement()); -// List hierarchyMethod = getEnclosingElements(typeElement); -// List hierarchyRt = tool.types().asElement(method.getReturnType()) -// .map(Visitors.TYPE_ELEMENT_VISITOR::visit) -// .map(this::getEnclosingElements) -// .orElse(List.of()); if (!method.getModifiers().contains(Modifier.STATIC)) { throw new ValidationFailure("The factory method must be static", method); } if (method.getReturnType().getKind() == TypeKind.VOID) { throw new ValidationFailure("The factory method may not return void", method); } - if (!tool.types().isSameType(method.getReturnType(), typeElement.asType())) { + if (!isSibling(method)) { throw new ValidationFailure("The factory method must return the type of its enclosing class", method); } } -/* - private List getEnclosingElements(TypeElement typeElement) { - if (typeElement == null) { - return List.of(); - } - List acc = new ArrayList<>(2); - acc.add(typeElement); - TypeElement el = typeElement; - if ((el = Visitors.TYPE_ELEMENT_VISITOR.visit(el.getEnclosingElement())) != null) { - acc.add(el); + private boolean isSibling(ExecutableElement method) { + TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement()); + List hierarchyRt = tool.types().asElement(method.getReturnType()) + .map(Visitors.TYPE_ELEMENT_VISITOR::visit) + .map(Util::getWithEnclosing) + .orElse(List.of()); + for (TypeElement r : hierarchyRt) { + if (r.equals(typeElement)) { + return true; + } } - return acc; + return false; } -*/ private void validate(ExecutableElement element) { TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(element.getEnclosingElement()); diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/ComponentImpl.java b/compiler/src/main/java/io/jbock/simple/processor/writing/ComponentImpl.java index 7310625..3bcd6b6 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/writing/ComponentImpl.java +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/ComponentImpl.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import static javax.lang.model.element.Modifier.FINAL; @@ -91,7 +92,8 @@ TypeSpec generate() { } spec.addAnnotation(AnnotationSpec.builder(Generated.class) .addMember("value", CodeBlock.of("$S", SimpleComponentProcessor.class.getCanonicalName())) - .addMember("comments", CodeBlock.of("$S", "https://github.com/jbock-java/simple-component")) + .addMember("comments", CodeBlock.of("$S", "https://github.com/jbock-java/simple-component " + + Objects.toString(getClass().getPackage().getImplementationVersion(), ""))) .build()); spec.addMethod(generateAllParametersConstructor()); spec.addOriginatingElement(component.element()); diff --git a/compiler/src/test/java/io/jbock/simple/processor/CycleTest.java b/compiler/src/test/java/io/jbock/simple/processor/CycleTest.java index 72a88d5..de9102e 100644 --- a/compiler/src/test/java/io/jbock/simple/processor/CycleTest.java +++ b/compiler/src/test/java/io/jbock/simple/processor/CycleTest.java @@ -133,6 +133,6 @@ void providerProvider() { Compilation compilation = simpleCompiler().compile(component); assertThat(compilation).failed(); - assertThat(compilation).hadErrorContaining("io.jbock.simple.Provider> cannot be provided."); + assertThat(compilation).hadErrorContaining("No binding found for io.jbock.simple.Provider>"); } } \ No newline at end of file diff --git a/compiler/src/test/java/io/jbock/simple/processor/MissingBindingTest.java b/compiler/src/test/java/io/jbock/simple/processor/MissingBindingTest.java index 1231b2d..638dd1f 100644 --- a/compiler/src/test/java/io/jbock/simple/processor/MissingBindingTest.java +++ b/compiler/src/test/java/io/jbock/simple/processor/MissingBindingTest.java @@ -33,7 +33,7 @@ void missingBinding() { Compilation compilation = simpleCompiler().compile(component); assertThat(compilation).failed(); - assertThat(compilation).hadErrorContainingMatch("java.lang.String cannot be provided.") + assertThat(compilation).hadErrorContainingMatch("No binding found for java.lang.String.") .inFile(component) .onLineContaining("interface AComponent"); } @@ -77,7 +77,7 @@ void bindsMethodAppearsInTrace() { assertThat(compilation) .hadErrorContaining( TestUtils.message( - "java.lang.String cannot be provided.", + "No binding found for java.lang.String.", " java.lang.String is injected at", " TestImplementation(java.lang.String)", " p.TestImplementation is injected at", diff --git a/compiler/src/test/java/io/jbock/simple/processor/ProviderTest.java b/compiler/src/test/java/io/jbock/simple/processor/ProviderTest.java index af9201c..fc4868a 100644 --- a/compiler/src/test/java/io/jbock/simple/processor/ProviderTest.java +++ b/compiler/src/test/java/io/jbock/simple/processor/ProviderTest.java @@ -38,7 +38,7 @@ void qualifierFail() { Compilation compilation = simpleCompiler().compile(component); assertThat(compilation).failed(); - assertThat(compilation).hadErrorContaining("io.jbock.simple.Provider with qualifier @Named(\"b\") cannot be provided.") + assertThat(compilation).hadErrorContaining("No binding found for io.jbock.simple.Provider with qualifier @Named(\"b\").") .inFile(component) .onLineContaining("interface AComponent"); } diff --git a/compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java b/compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java index 2606933..b25f2ef 100644 --- a/compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java +++ b/compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java @@ -62,4 +62,57 @@ void clashResolvedByQualifiers() { " }", "}"); } + + @Test + void injectMethodIsSibling() { + JavaFileObject component = forSourceLines("test.TestClass", + "package test;", + "", + "import io.jbock.simple.Component;", + "import io.jbock.simple.Inject;", + "import io.jbock.simple.Named;", + "", + "final class TestClass {", + "", + " static class A {", + " }", + "", + " static class B {", + " }", + "", + " @Inject static A createA(@Named(\"1\") B b1, @Named(\"2\") B b2) { return null; }", + " @Inject @Named(\"1\") static B createB1() { return null; }", + " @Inject @Named(\"2\") static B createB2() { return null; }", + "", + " @Component", + " interface AComponent {", + " A getA();", + " }", + "}"); + Compilation compilation = simpleCompiler().compile(component); + assertThat(compilation).succeeded(); + assertThat(compilation).generatedSourceFile("test.TestClass_AComponent_Impl") + .containsLines( + "package test;", + "", + "final class TestClass_AComponent_Impl implements TestClass.AComponent {", + " private final TestClass.A a;", + "", + " private TestClass_AComponent_Impl(TestClass.A a) {", + " this.a = a;", + " }", + "", + " @Override", + " public TestClass.A getA() {", + " return a;", + " }", + "", + " static TestClass.AComponent create() {", + " TestClass.B b = TestClass.createB1();", + " TestClass.B b2 = TestClass.createB2();", + " TestClass.A a = TestClass.createA(b, b2);", + " return new TestClass_AComponent_Impl(a);", + " }", + "}"); + } }