Skip to content

Commit e597d2a

Browse files
authored
module implementation (#60)
1 parent 974c754 commit e597d2a

File tree

8 files changed

+125
-20
lines changed

8 files changed

+125
-20
lines changed

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,23 @@
33
import io.jbock.javapoet.ClassName;
44
import io.jbock.simple.Component;
55
import io.jbock.simple.Inject;
6+
import io.jbock.simple.processor.util.Visitors;
67

8+
import javax.lang.model.element.AnnotationMirror;
9+
import javax.lang.model.element.AnnotationValue;
10+
import javax.lang.model.element.ExecutableElement;
711
import javax.lang.model.element.TypeElement;
12+
import javax.lang.model.type.DeclaredType;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Objects;
816
import java.util.function.Supplier;
17+
import java.util.stream.Collectors;
918

1019
import static io.jbock.simple.processor.util.Suppliers.memoize;
20+
import static io.jbock.simple.processor.util.Visitors.ANNOTATION_VALUE_AS_TYPE;
21+
import static io.jbock.simple.processor.util.Visitors.DECLARED_TYPE_VISITOR;
22+
import static io.jbock.simple.processor.util.Visitors.TYPE_ELEMENT_VISITOR;
1123

1224
public final class ComponentElement {
1325

@@ -49,4 +61,38 @@ public boolean mockBuilder() {
4961
}
5062
return annotation.mockBuilder();
5163
}
64+
65+
public List<TypeElement> modules() {
66+
return modules.get();
67+
}
68+
69+
private final Supplier<List<TypeElement>> modules = memoize(() -> {
70+
List<? extends AnnotationMirror> annotationMirrors = element().getAnnotationMirrors();
71+
AnnotationMirror annotationMirror = annotationMirrors.stream()
72+
.filter(mirror -> {
73+
TypeElement tel = Visitors.TYPE_ELEMENT_VISITOR.visit(mirror.getAnnotationType().asElement());
74+
if (tel == null) {
75+
return false;
76+
}
77+
return tel.getQualifiedName().contentEquals(Component.class.getCanonicalName());
78+
})
79+
.findFirst()
80+
.orElseThrow(AssertionError::new);
81+
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
82+
annotationMirror.getElementValues();
83+
return elementValues.entrySet().stream()
84+
.filter(e -> "modules".contentEquals(e.getKey().getSimpleName()))
85+
.map(Map.Entry::getValue)
86+
.flatMap(m -> Visitors.ANNOTATION_VALUE_AS_ARRAY.visit(m, null).stream())
87+
.map(ANNOTATION_VALUE_AS_TYPE::visit)
88+
.filter(Objects::nonNull)
89+
.map(DECLARED_TYPE_VISITOR::visit)
90+
.filter(Objects::nonNull)
91+
.map(DeclaredType::asElement)
92+
.filter(Objects::nonNull)
93+
.map(TYPE_ELEMENT_VISITOR::visit)
94+
.collect(Collectors.toList());
95+
});
96+
97+
5298
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import javax.lang.model.element.TypeElement;
1515
import javax.lang.model.element.VariableElement;
1616
import javax.lang.model.type.TypeKind;
17+
import java.util.ArrayList;
1718
import java.util.Collection;
1819
import java.util.LinkedHashMap;
1920
import java.util.List;
@@ -119,10 +120,14 @@ public Collection<ParameterBinding> parameterBindings() {
119120
});
120121

121122
private final Supplier<Map<Key, InjectBinding>> providesBindings = memoize(() -> {
122-
List<ExecutableElement> methods = methodsIn(componentElement().element().getEnclosedElements());
123+
List<ExecutableElement> methods = new ArrayList<>();
124+
methods.addAll(methodsIn(componentElement().element().getEnclosedElements()));
125+
for (TypeElement module : componentElement().modules()) {
126+
methods.addAll(methodsIn(module.getEnclosedElements()));
127+
}
123128
Map<Key, InjectBinding> result = new LinkedHashMap<>();
124129
for (ExecutableElement method : methods) {
125-
if (method.getAnnotation(Provides.class) == null && !tool().hasInjectAnnotation(method)) {
130+
if (!hasProvidesOrInjectAnnotation(method)) {
126131
continue; // ignore
127132
}
128133
Key key = keyCache().getKey(method);
@@ -139,7 +144,7 @@ public Collection<ParameterBinding> parameterBindings() {
139144
if (method.getModifiers().contains(Modifier.STATIC)) {
140145
continue; // ignore
141146
}
142-
if (method.getAnnotation(Provides.class) != null || tool().hasInjectAnnotation(method)) {
147+
if (hasProvidesOrInjectAnnotation(method)) {
143148
continue; // ignore
144149
}
145150
if (!method.getParameters().isEmpty()) {
@@ -157,6 +162,10 @@ public Collection<ParameterBinding> parameterBindings() {
157162
return result;
158163
});
159164

165+
private boolean hasProvidesOrInjectAnnotation(ExecutableElement method) {
166+
return method.getAnnotation(Provides.class) != null || tool().hasInjectAnnotation(method);
167+
}
168+
160169
public Optional<BuilderElement> builderElement() {
161170
return builderElement.get();
162171
}

compiler/src/main/java/io/jbock/simple/processor/util/Visitors.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package io.jbock.simple.processor.util;
22

3+
import javax.lang.model.element.AnnotationValue;
4+
import javax.lang.model.element.AnnotationValueVisitor;
35
import javax.lang.model.element.ElementVisitor;
46
import javax.lang.model.element.ExecutableElement;
57
import javax.lang.model.element.PackageElement;
68
import javax.lang.model.element.TypeElement;
79
import javax.lang.model.element.VariableElement;
810
import javax.lang.model.type.DeclaredType;
11+
import javax.lang.model.type.TypeMirror;
912
import javax.lang.model.type.TypeVisitor;
13+
import javax.lang.model.util.SimpleAnnotationValueVisitor9;
1014
import javax.lang.model.util.SimpleElementVisitor9;
1115
import javax.lang.model.util.SimpleTypeVisitor9;
16+
import java.util.List;
1217

1318
public final class Visitors {
1419

@@ -49,6 +54,27 @@ public DeclaredType visitDeclared(DeclaredType declaredType, Void unused) {
4954
}
5055
};
5156

57+
public static final AnnotationValueVisitor<TypeMirror, Void> ANNOTATION_VALUE_AS_TYPE = new SimpleAnnotationValueVisitor9<>() {
58+
59+
@Override
60+
public TypeMirror visitType(TypeMirror mirror, Void unused) {
61+
return mirror;
62+
}
63+
};
64+
65+
public static final AnnotationValueVisitor<List<? extends AnnotationValue>, Void> ANNOTATION_VALUE_AS_ARRAY = new SimpleAnnotationValueVisitor9<>() {
66+
67+
@Override
68+
public List<? extends AnnotationValue> visitArray(List<? extends AnnotationValue> array, Void unused) {
69+
return array;
70+
}
71+
72+
@Override
73+
protected List<? extends AnnotationValue> defaultAction(Object o, Void unused) {
74+
return List.of();
75+
}
76+
};
77+
5278
private Visitors() {
5379
}
5480
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.jbock.simple.Component;
44
import io.jbock.simple.Inject;
5+
import io.jbock.simple.Modulus;
56
import io.jbock.simple.processor.binding.InjectBindingScanner;
67
import io.jbock.simple.processor.util.TypeTool;
78
import io.jbock.simple.processor.util.Util;
@@ -44,7 +45,7 @@ public void validateStaticMethod(ExecutableElement method) {
4445
if (method.getReturnType().getKind() == TypeKind.VOID) {
4546
throw new ValidationFailure("The factory method may not return void", method);
4647
}
47-
if (!isSibling(method) && !isInComponent(method)) {
48+
if (!isSibling(method) && !isInComponent(method) && !isInModule(method)) {
4849
throw new ValidationFailure("The factory method must return the type of its enclosing class", method);
4950
}
5051
}
@@ -54,6 +55,11 @@ private boolean isInComponent(ExecutableElement method) {
5455
return typeElement.getAnnotation(Component.class) != null;
5556
}
5657

58+
private boolean isInModule(ExecutableElement method) {
59+
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement());
60+
return typeElement.getAnnotation(Modulus.class) != null;
61+
}
62+
5763
private boolean isSibling(ExecutableElement method) {
5864
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement());
5965
List<TypeElement> hierarchyRt = tool.types().asElement(method.getReturnType())

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ void clashResolvedByQualifiers() {
1919
"import io.jbock.simple.Component;",
2020
"import io.jbock.simple.Inject;",
2121
"import io.jbock.simple.Named;",
22+
"import io.jbock.simple.Modulus;",
2223
"",
2324
"final class TestClass {",
2425
"",
@@ -27,11 +28,16 @@ void clashResolvedByQualifiers() {
2728
" }",
2829
"",
2930
" static class B {",
30-
" @Inject @Named(\"1\") static B create1() { return null; }",
31+
" @Inject @Named(\"1\") static B create1(String s) { return null; }",
3132
" @Inject @Named(\"2\") static B create2() { return null; }",
3233
" }",
3334
"",
34-
" @Component",
35+
" @Modulus",
36+
" static class M {",
37+
" @Inject static String createString() { return \"\"; }",
38+
" }",
39+
"",
40+
" @Component(modules = M.class)",
3541
" interface AComponent {",
3642
" A getA();",
3743
" }",
@@ -55,7 +61,8 @@ void clashResolvedByQualifiers() {
5561
" }",
5662
"",
5763
" static TestClass.AComponent create() {",
58-
" TestClass.B testClassB = TestClass.B.create1();",
64+
" String mString = TestClass.M.createString();",
65+
" TestClass.B testClassB = TestClass.B.create1(mString);",
5966
" TestClass.B testClassB2 = TestClass.B.create2();",
6067
" TestClass.A testClassA = new TestClass.A(testClassB, testClassB2);",
6168
" return new TestClass_AComponent_Impl(testClassA);",

simple-component/src/main/java/io/jbock/simple/Component.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
public @interface Component {
3232

3333
/**
34-
* A list of classes annotated with {@link Module} whose bindings are used to generate the
34+
* A list of classes annotated with {@link Modulus} whose bindings are used to generate the
3535
* component implementation.
3636
*/
3737
Class<?>[] modules() default {};

simple-component/src/main/java/io/jbock/simple/Module.java

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.jbock.simple;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Annotates a class that contributes to the object graph.
10+
* A binding is a {@code static} methods that is annotated with either
11+
* {@code @Provides} or {@code @Inject}.
12+
*
13+
* <p>The annotated class must also be referenced in the
14+
* {@code modules} of a {@link Component}.
15+
*
16+
* <p>This class is called {@code Modulus}, not {@code Module}
17+
* because there is a class {@code Module} in {@code java.lang}
18+
* since Java 9.
19+
*/
20+
@Retention(RetentionPolicy.RUNTIME)
21+
@Target(ElementType.TYPE)
22+
public @interface Modulus {
23+
}

0 commit comments

Comments
 (0)