diff --git a/compiler/src/main/java/io/jbock/simple/processor/ContextComponent.java b/compiler/src/main/java/io/jbock/simple/processor/ContextComponent.java index 67f0ea4..898768c 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/ContextComponent.java +++ b/compiler/src/main/java/io/jbock/simple/processor/ContextComponent.java @@ -2,15 +2,23 @@ import io.jbock.simple.Component; import io.jbock.simple.Inject; +import io.jbock.simple.Provides; +import io.jbock.simple.processor.binding.Binding; import io.jbock.simple.processor.binding.ComponentElement; +import io.jbock.simple.processor.binding.Key; import io.jbock.simple.processor.binding.KeyFactory; import io.jbock.simple.processor.graph.TopologicalSorter; import io.jbock.simple.processor.util.TypeTool; -import io.jbock.simple.processor.writing.Generator; +import io.jbock.simple.processor.writing.ComponentImpl; +import io.jbock.simple.processor.writing.Context; +import io.jbock.simple.processor.writing.ContextModule; +import io.jbock.simple.processor.writing.NamedBinding; import javax.lang.model.element.TypeElement; +import java.util.List; +import java.util.Map; -@Component +@Component(modules = ContextModule.class) public interface ContextComponent { @Component.Builder @@ -25,10 +33,17 @@ interface Builder { KeyFactory keyFactory(); ComponentElement componentElement(); - - Generator generator(); - TopologicalSorter topologicalSorter(); + ComponentImpl componentImpl(); + + @Provides + static Context createContext( + TopologicalSorter topologicalSorter, + KeyFactory keyFactory) { + List bindings = topologicalSorter.sortedBindings(); + Map sorted = ContextModule.addNames(keyFactory, bindings); + return new Context(sorted, ContextModule.createNames(sorted)); + } final class Factory { private final TypeTool tool; diff --git a/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBinding.java b/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBinding.java index a77002d..a378475 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBinding.java +++ b/compiler/src/main/java/io/jbock/simple/processor/binding/InjectBinding.java @@ -4,7 +4,6 @@ import io.jbock.javapoet.ParameterSpec; import io.jbock.javapoet.ParameterizedTypeName; import io.jbock.javapoet.TypeName; -import io.jbock.simple.Provides; import io.jbock.simple.processor.util.Visitors; import io.jbock.simple.processor.writing.NamedBinding; @@ -30,26 +29,17 @@ public final class InjectBinding extends Binding { private final KeyFactory keyFactory; - private static final List PROVIDES_METHOD_COMMON_PREFIXES = List.of( - "get", - "provides", - "provide", - "create"); - private final Supplier suggestedVariableName = memoize(() -> { - if (element().getAnnotation(Provides.class) != null) { - return lowerFirst(removeMethodNamePrefix(element().getSimpleName().toString())); - } Element enclosing = element().getEnclosingElement(); - if (enclosing != null) { - if (keyFactory().tool().isSameType(key().type(), enclosing.asType())) { - TypeElement enclosingOuter = Visitors.TYPE_ELEMENT_VISITOR.visit(enclosing.getEnclosingElement()); - if (enclosingOuter != null && keyFactory().tool().isSameType(key().type(), enclosing.asType())) { - return lowerFirst(enclosingOuter.getSimpleName().toString() + simpleName(key().typeName())); - } - } else { - return lowerFirst(enclosing.getSimpleName().toString() + simpleName(key().typeName())); - } + if (enclosing == null) { + return lowerFirst(simpleName(key().typeName())); + } + if (!keyFactory().tool().isSameType(key().type(), enclosing.asType())) { + return lowerFirst(enclosing.getSimpleName().toString() + simpleName(key().typeName())); + } + TypeElement enclosingOuter = Visitors.TYPE_ELEMENT_VISITOR.visit(enclosing.getEnclosingElement()); + if (enclosingOuter != null && keyFactory().tool().isSameType(key().type(), enclosing.asType())) { + return lowerFirst(enclosingOuter.getSimpleName().toString() + simpleName(key().typeName())); } return lowerFirst(simpleName(key().typeName())); }); @@ -61,15 +51,6 @@ private String simpleName(TypeName typeName) { return verySimpleTypeName(typeName.toString()); } - private static String removeMethodNamePrefix(String s) { - for (String p : PROVIDES_METHOD_COMMON_PREFIXES) { - if (s.startsWith(p) && s.length() > p.length()) { - return s.substring(p.length()); - } - } - return s; - } - static String simpleTypeName(ParameterizedTypeName type) { StringBuilder sb = new StringBuilder(); sb.append(type.rawType.simpleName()); @@ -92,6 +73,9 @@ static String verySimpleTypeName(String typeName) { if (i >= 0) { typeName = typeName.substring(i + 1); } + if (Character.isLowerCase(typeName.charAt(0))) { + typeName = Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); + } return typeName; } diff --git a/compiler/src/main/java/io/jbock/simple/processor/step/ComponentStep.java b/compiler/src/main/java/io/jbock/simple/processor/step/ComponentStep.java index 56427dc..6548b8b 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/step/ComponentStep.java +++ b/compiler/src/main/java/io/jbock/simple/processor/step/ComponentStep.java @@ -4,15 +4,14 @@ import io.jbock.javapoet.TypeSpec; import io.jbock.simple.Component; import io.jbock.simple.Inject; +import io.jbock.simple.Modulus; import io.jbock.simple.processor.ContextComponent; -import io.jbock.simple.processor.binding.Binding; import io.jbock.simple.processor.binding.KeyFactory; import io.jbock.simple.processor.util.SpecWriter; import io.jbock.simple.processor.util.TypeTool; import io.jbock.simple.processor.util.ValidationFailure; import io.jbock.simple.processor.validation.ExecutableElementValidator; import io.jbock.simple.processor.validation.TypeElementValidator; -import io.jbock.simple.processor.writing.Generator; import javax.annotation.processing.Messager; import javax.lang.model.element.Element; @@ -73,6 +72,11 @@ private void process(TypeElement typeElement) { typeElementValidator.validate(typeElement); ContextComponent context = contextComponentFactory.create(typeElement); KeyFactory keyFactory = context.keyFactory(); + for (TypeElement module : context.componentElement().modules()) { + if (module.getAnnotation(Modulus.class) == null) { + throw new ValidationFailure("The module must be annotated with @Modulus", typeElement); + } + } keyFactory.factoryElement().ifPresent(factory -> { ExecutableElement method = factory.singleAbstractMethod(); if (!tool.types().isSameType(method.getReturnType(), typeElement.asType())) { @@ -84,9 +88,7 @@ private void process(TypeElement typeElement) { executableElementValidator.validate(m); } } - Generator generator = context.generator(); - List bindings = context.topologicalSorter().sortedBindings(); - TypeSpec typeSpec = generator.generate(bindings); + TypeSpec typeSpec = context.componentImpl().generate(); specWriter.write(context.componentElement().generatedClass(), typeSpec); } } diff --git a/compiler/src/main/java/io/jbock/simple/processor/step/ProvidesStep.java b/compiler/src/main/java/io/jbock/simple/processor/step/ProvidesStep.java index 99ade03..52ee511 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/step/ProvidesStep.java +++ b/compiler/src/main/java/io/jbock/simple/processor/step/ProvidesStep.java @@ -3,6 +3,7 @@ import io.jbock.auto.common.BasicAnnotationProcessor.Step; import io.jbock.simple.Component; import io.jbock.simple.Inject; +import io.jbock.simple.Modulus; import io.jbock.simple.Provides; import io.jbock.simple.processor.util.ValidationFailure; @@ -50,8 +51,8 @@ public Set process(Map> elementsByAnnota throw new ValidationFailure("The @Provides method may not return void", m); } Element enclosing = m.getEnclosingElement(); - if (enclosing.getAnnotation(Component.class) == null) { - throw new ValidationFailure("The @Provides method must be nested inside a @Component", m); + if (enclosing.getAnnotation(Component.class) == null && enclosing.getAnnotation(Modulus.class) == null) { + throw new ValidationFailure("The @Provides method must be nested inside a @Component or @Modulus", m); } bindingRegistry.register(m); } diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/BuilderImpl.java b/compiler/src/main/java/io/jbock/simple/processor/writing/BuilderImpl.java index f62eb42..c671651 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/writing/BuilderImpl.java +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/BuilderImpl.java @@ -25,19 +25,19 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -public class BuilderImpl { +public final class BuilderImpl { private final ComponentElement component; private final Map sorted; private final Function names; - private BuilderImpl( + @Inject + public BuilderImpl( ComponentElement component, - Map sorted, - Function names) { + Context context) { this.component = component; - this.sorted = sorted; - this.names = names; + this.sorted = context.sorted(); + this.names = context.names(); } TypeSpec generate(BuilderElement builder, MockBuilder mockBuilder) { @@ -136,22 +136,4 @@ private List constructorParameters() { } return result; } - - public static final class Factory { - private final ComponentElement component; - - @Inject - public Factory(ComponentElement component) { - this.component = component; - } - - BuilderImpl create( - Map sorted, - Function names) { - return new BuilderImpl( - component, - sorted, - names); - } - } } 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 f19e9a5..b0885b0 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 @@ -47,18 +47,18 @@ public class ComponentImpl { private final FactoryImpl factoryImpl; private final Modifier[] modifiers; - private ComponentImpl( + @Inject + public ComponentImpl( KeyFactory keyFactory, ComponentElement component, - Map sorted, - Function names, + Context context, MockBuilder mockBuilder, BuilderImpl builderImpl, FactoryImpl factoryImpl) { this.keyFactory = keyFactory; this.component = component; - this.sorted = sorted; - this.names = names; + this.sorted = context.sorted(); + this.names = context.names(); this.modifiers = component.element().getModifiers().stream() .filter(m -> m == PUBLIC).toArray(Modifier[]::new); this.mockBuilder = mockBuilder; @@ -66,7 +66,7 @@ private ComponentImpl( this.factoryImpl = factoryImpl; } - TypeSpec generate() { + public TypeSpec generate() { TypeSpec.Builder spec = TypeSpec.classBuilder(component.generatedClass()) .addModifiers(modifiers) .addModifiers(FINAL) @@ -219,38 +219,4 @@ private MethodSpec generateAllParametersConstructor() { } return constructor.build(); } - - public static final class Factory { - private final KeyFactory keyFactory; - private final ComponentElement component; - private final MockBuilder.Factory mockBuilderFactory; - private final BuilderImpl.Factory builderImplFactory; - private final FactoryImpl.Factory factoryImplFactory; - - @Inject - public Factory( - KeyFactory keyFactory, - ComponentElement component, - MockBuilder.Factory mockBuilderFactory, - BuilderImpl.Factory builderImplFactory, - FactoryImpl.Factory factoryImplFactory) { - this.keyFactory = keyFactory; - this.component = component; - this.mockBuilderFactory = mockBuilderFactory; - this.builderImplFactory = builderImplFactory; - this.factoryImplFactory = factoryImplFactory; - } - - ComponentImpl create( - Map sorted, - Function names) { - return new ComponentImpl( - keyFactory, component, - sorted, - names, - mockBuilderFactory.create(sorted, names), - builderImplFactory.create(sorted, names), - factoryImplFactory.create(sorted, names)); - } - } } diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/Context.java b/compiler/src/main/java/io/jbock/simple/processor/writing/Context.java new file mode 100644 index 0000000..447e73b --- /dev/null +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/Context.java @@ -0,0 +1,26 @@ +package io.jbock.simple.processor.writing; + +import io.jbock.javapoet.ParameterSpec; +import io.jbock.simple.processor.binding.Key; + +import java.util.Map; +import java.util.function.Function; + +public final class Context { + + private final Map sorted; + private final Function names; + + public Context(Map sorted, Function names) { + this.sorted = sorted; + this.names = names; + } + + Map sorted() { + return sorted; + } + + Function names() { + return names; + } +} diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/Generator.java b/compiler/src/main/java/io/jbock/simple/processor/writing/ContextModule.java similarity index 79% rename from compiler/src/main/java/io/jbock/simple/processor/writing/Generator.java rename to compiler/src/main/java/io/jbock/simple/processor/writing/ContextModule.java index 6e708e8..e41004a 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/writing/Generator.java +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/ContextModule.java @@ -1,8 +1,7 @@ package io.jbock.simple.processor.writing; import io.jbock.javapoet.ParameterSpec; -import io.jbock.javapoet.TypeSpec; -import io.jbock.simple.Inject; +import io.jbock.simple.Modulus; import io.jbock.simple.processor.binding.Binding; import io.jbock.simple.processor.binding.Key; import io.jbock.simple.processor.binding.KeyFactory; @@ -15,25 +14,12 @@ import java.util.Map; import java.util.function.Function; -public class Generator { +@Modulus +public interface ContextModule { - private final ComponentImpl.Factory componentImpl; - private final KeyFactory keyFactory; - - @Inject - public Generator( - ComponentImpl.Factory componentImpl, - KeyFactory keyFactory) { - this.componentImpl = componentImpl; - this.keyFactory = keyFactory; - } - - public TypeSpec generate(List bindings) { - Map sorted = addNames(bindings); - return componentImpl.create(sorted, createNames(sorted)).generate(); - } - - private Map addNames(List bindings) { + static Map addNames( + KeyFactory keyFactory, + List bindings) { UniqueNameSet uniqueNameSet = new UniqueNameSet(); uniqueNameSet.claim("mockBuilder"); uniqueNameSet.claim("withMocks"); @@ -47,7 +33,7 @@ private Map addNames(List bindings) { return result; } - private static Function createNames( + static Function createNames( Map sorted) { Map cache = new HashMap<>(); return key -> { @@ -98,4 +84,5 @@ private static String protectAgainstKeywords(String candidateName) { return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName; } } + } diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/FactoryImpl.java b/compiler/src/main/java/io/jbock/simple/processor/writing/FactoryImpl.java index ecd313f..5efe2b6 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/writing/FactoryImpl.java +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/FactoryImpl.java @@ -25,19 +25,19 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -public class FactoryImpl { +public final class FactoryImpl { private final ComponentElement component; private final Map sorted; private final Function names; - private FactoryImpl( + @Inject + public FactoryImpl( ComponentElement component, - Map sorted, - Function names) { + Context context) { this.component = component; - this.sorted = sorted; - this.names = names; + this.sorted = context.sorted(); + this.names = context.names(); } TypeSpec generate(FactoryElement factory) { @@ -90,22 +90,4 @@ List parameters() { } return result; } - - public static final class Factory { - private final ComponentElement component; - - @Inject - public Factory(ComponentElement component) { - this.component = component; - } - - FactoryImpl create( - Map sorted, - Function names) { - return new FactoryImpl( - component, - sorted, - names); - } - } } diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/MockBuilder.java b/compiler/src/main/java/io/jbock/simple/processor/writing/MockBuilder.java index 7aa65b1..baf287e 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/writing/MockBuilder.java +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/MockBuilder.java @@ -24,20 +24,20 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -public class MockBuilder { +public final class MockBuilder { private final ComponentElement component; private final Map sorted; private final Function names; private final Modifier[] modifiers; - MockBuilder( + @Inject + public MockBuilder( ComponentElement component, - Map sorted, - Function names) { + Context context) { this.component = component; - this.sorted = sorted; - this.names = names; + this.sorted = context.sorted(); + this.names = context.names(); this.modifiers = component.element().getModifiers().stream() .filter(m -> m == PUBLIC).toArray(Modifier[]::new); } @@ -158,19 +158,4 @@ private List getMethods() { } return methods; } - - public static final class Factory { - private final ComponentElement component; - - @Inject - public Factory(ComponentElement component) { - this.component = component; - } - - MockBuilder create( - Map sorted, - Function names) { - return new MockBuilder(component, sorted, names); - } - } } diff --git a/compiler/src/main/java/io/jbock/simple/processor/writing/NamedBinding.java b/compiler/src/main/java/io/jbock/simple/processor/writing/NamedBinding.java index 179f27a..2b49ba4 100644 --- a/compiler/src/main/java/io/jbock/simple/processor/writing/NamedBinding.java +++ b/compiler/src/main/java/io/jbock/simple/processor/writing/NamedBinding.java @@ -9,7 +9,7 @@ public final class NamedBinding { private final String auxName; private final boolean componentRequest; - NamedBinding( + public NamedBinding( Binding binding, String name, String auxName, @@ -24,7 +24,7 @@ public Binding binding() { return binding; } - String name() { + public String name() { return name; } diff --git a/compiler/src/test/java/io/jbock/simple/processor/ModulusTest.java b/compiler/src/test/java/io/jbock/simple/processor/ModulusTest.java new file mode 100644 index 0000000..fa5ff82 --- /dev/null +++ b/compiler/src/test/java/io/jbock/simple/processor/ModulusTest.java @@ -0,0 +1,43 @@ +package io.jbock.simple.processor; + +import io.jbock.testing.compile.Compilation; +import org.junit.jupiter.api.Test; + +import javax.tools.JavaFileObject; + +import static io.jbock.simple.processor.Compilers.simpleCompiler; +import static io.jbock.testing.compile.CompilationSubject.assertThat; +import static io.jbock.testing.compile.JavaFileObjects.forSourceLines; + +class ModulusTest { + + @Test + void clashResolvedByQualifiers() { + JavaFileObject component = forSourceLines("test.TestClass", + "package test;", + "", + "import io.jbock.simple.Component;", + "import io.jbock.simple.Inject;", + "import io.jbock.simple.Provides;", + "import io.jbock.simple.Named;", + "import io.jbock.simple.Modulus;", + "", + "final class TestClass {", + "", + " static class A {", + " @Inject A() {}", + " }", + "", + " static class M {", + " }", + "", + " @Component(modules = M.class)", + " interface AComponent {", + " A getA();", + " }", + "}"); + Compilation compilation = simpleCompiler().compile(component); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorContaining("The module must be annotated with @Modulus"); + } +} diff --git a/compiler/src/test/java/io/jbock/simple/processor/PrimitiveTest.java b/compiler/src/test/java/io/jbock/simple/processor/PrimitiveTest.java index a4db0a7..f9ffb3f 100644 --- a/compiler/src/test/java/io/jbock/simple/processor/PrimitiveTest.java +++ b/compiler/src/test/java/io/jbock/simple/processor/PrimitiveTest.java @@ -93,27 +93,27 @@ void providesPrimitive() { " private static final class Factory_Impl implements TestClass.AComponent.Factory {", " @Override", " public TestClass.AComponent create(int i) {", - " int b = TestClass.AComponent.getB(i);", - " TestClass.A testClassA = new TestClass.A(b);", + " int aComponentInt = TestClass.AComponent.getB(i);", + " TestClass.A testClassA = new TestClass.A(aComponentInt);", " return new TestClass_AComponent_Impl(testClassA);", " }", " }", "", " public static final class MockBuilder {", " private final int i;", - " private int b;", - " private boolean b_isSet;", + " private int aComponentInt;", + " private boolean aComponentInt_isSet;", " private TestClass.A testClassA;", "", " public TestClass.AComponent build() {", - " int b = this.b_isSet ? this.b : TestClass.AComponent.getB(this.i);", - " TestClass.A testClassA = this.testClassA != null ? this.testClassA : new TestClass.A(b);", + " int aComponentInt = this.aComponentInt_isSet ? this.aComponentInt : TestClass.AComponent.getB(this.i);", + " TestClass.A testClassA = this.testClassA != null ? this.testClassA : new TestClass.A(aComponentInt);", " return new TestClass_AComponent_Impl(testClassA);", " }", "", - " public MockBuilder b(int b) {", - " this.b = b;", - " this.b_isSet = true;", + " public MockBuilder aComponentInt(int aComponentInt) {", + " this.aComponentInt = aComponentInt;", + " this.aComponentInt_isSet = true;", " return this;", " }", "", 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 dd7299c..f8ff217 100644 --- a/compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java +++ b/compiler/src/test/java/io/jbock/simple/processor/StaticInjectionTest.java @@ -18,6 +18,7 @@ void clashResolvedByQualifiers() { "", "import io.jbock.simple.Component;", "import io.jbock.simple.Inject;", + "import io.jbock.simple.Provides;", "import io.jbock.simple.Named;", "import io.jbock.simple.Modulus;", "",