Skip to content

Commit

Permalink
module implementation (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
h908714124 committed Dec 25, 2023
1 parent 974c754 commit e597d2a
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@
import io.jbock.javapoet.ClassName;
import io.jbock.simple.Component;
import io.jbock.simple.Inject;
import io.jbock.simple.processor.util.Visitors;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static io.jbock.simple.processor.util.Suppliers.memoize;
import static io.jbock.simple.processor.util.Visitors.ANNOTATION_VALUE_AS_TYPE;
import static io.jbock.simple.processor.util.Visitors.DECLARED_TYPE_VISITOR;
import static io.jbock.simple.processor.util.Visitors.TYPE_ELEMENT_VISITOR;

public final class ComponentElement {

Expand Down Expand Up @@ -49,4 +61,38 @@ public boolean mockBuilder() {
}
return annotation.mockBuilder();
}

public List<TypeElement> modules() {
return modules.get();
}

private final Supplier<List<TypeElement>> modules = memoize(() -> {
List<? extends AnnotationMirror> annotationMirrors = element().getAnnotationMirrors();
AnnotationMirror annotationMirror = annotationMirrors.stream()
.filter(mirror -> {
TypeElement tel = Visitors.TYPE_ELEMENT_VISITOR.visit(mirror.getAnnotationType().asElement());
if (tel == null) {
return false;
}
return tel.getQualifiedName().contentEquals(Component.class.getCanonicalName());
})
.findFirst()
.orElseThrow(AssertionError::new);
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
annotationMirror.getElementValues();
return elementValues.entrySet().stream()
.filter(e -> "modules".contentEquals(e.getKey().getSimpleName()))
.map(Map.Entry::getValue)
.flatMap(m -> Visitors.ANNOTATION_VALUE_AS_ARRAY.visit(m, null).stream())
.map(ANNOTATION_VALUE_AS_TYPE::visit)
.filter(Objects::nonNull)
.map(DECLARED_TYPE_VISITOR::visit)
.filter(Objects::nonNull)
.map(DeclaredType::asElement)
.filter(Objects::nonNull)
.map(TYPE_ELEMENT_VISITOR::visit)
.collect(Collectors.toList());
});


}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -119,10 +120,14 @@ public Collection<ParameterBinding> parameterBindings() {
});

private final Supplier<Map<Key, InjectBinding>> providesBindings = memoize(() -> {
List<ExecutableElement> methods = methodsIn(componentElement().element().getEnclosedElements());
List<ExecutableElement> methods = new ArrayList<>();
methods.addAll(methodsIn(componentElement().element().getEnclosedElements()));
for (TypeElement module : componentElement().modules()) {
methods.addAll(methodsIn(module.getEnclosedElements()));
}
Map<Key, InjectBinding> result = new LinkedHashMap<>();
for (ExecutableElement method : methods) {
if (method.getAnnotation(Provides.class) == null && !tool().hasInjectAnnotation(method)) {
if (!hasProvidesOrInjectAnnotation(method)) {
continue; // ignore
}
Key key = keyCache().getKey(method);
Expand All @@ -139,7 +144,7 @@ public Collection<ParameterBinding> parameterBindings() {
if (method.getModifiers().contains(Modifier.STATIC)) {
continue; // ignore
}
if (method.getAnnotation(Provides.class) != null || tool().hasInjectAnnotation(method)) {
if (hasProvidesOrInjectAnnotation(method)) {
continue; // ignore
}
if (!method.getParameters().isEmpty()) {
Expand All @@ -157,6 +162,10 @@ public Collection<ParameterBinding> parameterBindings() {
return result;
});

private boolean hasProvidesOrInjectAnnotation(ExecutableElement method) {
return method.getAnnotation(Provides.class) != null || tool().hasInjectAnnotation(method);
}

public Optional<BuilderElement> builderElement() {
return builderElement.get();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package io.jbock.simple.processor.util;

import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.util.SimpleAnnotationValueVisitor9;
import javax.lang.model.util.SimpleElementVisitor9;
import javax.lang.model.util.SimpleTypeVisitor9;
import java.util.List;

public final class Visitors {

Expand Down Expand Up @@ -49,6 +54,27 @@ public DeclaredType visitDeclared(DeclaredType declaredType, Void unused) {
}
};

public static final AnnotationValueVisitor<TypeMirror, Void> ANNOTATION_VALUE_AS_TYPE = new SimpleAnnotationValueVisitor9<>() {

@Override
public TypeMirror visitType(TypeMirror mirror, Void unused) {
return mirror;
}
};

public static final AnnotationValueVisitor<List<? extends AnnotationValue>, Void> ANNOTATION_VALUE_AS_ARRAY = new SimpleAnnotationValueVisitor9<>() {

@Override
public List<? extends AnnotationValue> visitArray(List<? extends AnnotationValue> array, Void unused) {
return array;
}

@Override
protected List<? extends AnnotationValue> defaultAction(Object o, Void unused) {
return List.of();
}
};

private Visitors() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.jbock.simple.Component;
import io.jbock.simple.Inject;
import io.jbock.simple.Modulus;
import io.jbock.simple.processor.binding.InjectBindingScanner;
import io.jbock.simple.processor.util.TypeTool;
import io.jbock.simple.processor.util.Util;
Expand Down Expand Up @@ -44,7 +45,7 @@ public void validateStaticMethod(ExecutableElement method) {
if (method.getReturnType().getKind() == TypeKind.VOID) {
throw new ValidationFailure("The factory method may not return void", method);
}
if (!isSibling(method) && !isInComponent(method)) {
if (!isSibling(method) && !isInComponent(method) && !isInModule(method)) {
throw new ValidationFailure("The factory method must return the type of its enclosing class", method);
}
}
Expand All @@ -54,6 +55,11 @@ private boolean isInComponent(ExecutableElement method) {
return typeElement.getAnnotation(Component.class) != null;
}

private boolean isInModule(ExecutableElement method) {
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement());
return typeElement.getAnnotation(Modulus.class) != null;
}

private boolean isSibling(ExecutableElement method) {
TypeElement typeElement = Visitors.TYPE_ELEMENT_VISITOR.visit(method.getEnclosingElement());
List<TypeElement> hierarchyRt = tool.types().asElement(method.getReturnType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ void clashResolvedByQualifiers() {
"import io.jbock.simple.Component;",
"import io.jbock.simple.Inject;",
"import io.jbock.simple.Named;",
"import io.jbock.simple.Modulus;",
"",
"final class TestClass {",
"",
Expand All @@ -27,11 +28,16 @@ void clashResolvedByQualifiers() {
" }",
"",
" static class B {",
" @Inject @Named(\"1\") static B create1() { return null; }",
" @Inject @Named(\"1\") static B create1(String s) { return null; }",
" @Inject @Named(\"2\") static B create2() { return null; }",
" }",
"",
" @Component",
" @Modulus",
" static class M {",
" @Inject static String createString() { return \"\"; }",
" }",
"",
" @Component(modules = M.class)",
" interface AComponent {",
" A getA();",
" }",
Expand All @@ -55,7 +61,8 @@ void clashResolvedByQualifiers() {
" }",
"",
" static TestClass.AComponent create() {",
" TestClass.B testClassB = TestClass.B.create1();",
" String mString = TestClass.M.createString();",
" TestClass.B testClassB = TestClass.B.create1(mString);",
" TestClass.B testClassB2 = TestClass.B.create2();",
" TestClass.A testClassA = new TestClass.A(testClassB, testClassB2);",
" return new TestClass_AComponent_Impl(testClassA);",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
public @interface Component {

/**
* A list of classes annotated with {@link Module} whose bindings are used to generate the
* A list of classes annotated with {@link Modulus} whose bindings are used to generate the
* component implementation.
*/
Class<?>[] modules() default {};
Expand Down
12 changes: 0 additions & 12 deletions simple-component/src/main/java/io/jbock/simple/Module.java

This file was deleted.

23 changes: 23 additions & 0 deletions simple-component/src/main/java/io/jbock/simple/Modulus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.jbock.simple;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotates a class that contributes to the object graph.
* A binding is a {@code static} methods that is annotated with either
* {@code @Provides} or {@code @Inject}.
*
* <p>The annotated class must also be referenced in the
* {@code modules} of a {@link Component}.
*
* <p>This class is called {@code Modulus}, not {@code Module}
* because there is a class {@code Module} in {@code java.lang}
* since Java 9.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Modulus {
}

0 comments on commit e597d2a

Please sign in to comment.