Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an ability to introspect builders #135

Merged
merged 5 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package io.micronaut.sourcegen.annotations;

import io.micronaut.core.annotation.Introspected;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand All @@ -32,4 +35,14 @@
@Retention(RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
public @interface Builder {

/**
* Define what annotations should be added to the generated builder. By default,
* the builder will have {@link io.micronaut.core.annotation.Introspected} annotation
* so that introspection can be created for it.
*
* @return Array of annotations to apply on the builder
*/
Class<? extends Annotation>[] annotatedWith() default Introspected.class;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package io.micronaut.sourcegen.annotations;

import io.micronaut.core.annotation.Introspected;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand All @@ -33,4 +36,14 @@
@Retention(RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
public @interface SuperBuilder {

/**
* Define what annotations should be added to the generated builder. By default,
* the builder will have {@link io.micronaut.core.annotation.Introspected} annotation
* so that introspection can be created for it.
*
* @return Array of annotations to apply on the builder
*/
Class<? extends Annotation>[] annotatedWith() default Introspected.class;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
*/
package io.micronaut.sourcegen.generator.visitors;

import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.bind.annotation.Bindable;
Expand All @@ -32,6 +36,7 @@
import io.micronaut.sourcegen.generator.SourceGenerator;
import io.micronaut.sourcegen.generator.SourceGenerators;
import io.micronaut.sourcegen.model.ClassDef;
import io.micronaut.sourcegen.model.ClassDef.ClassDefBuilder;
import io.micronaut.sourcegen.model.ClassTypeDef;
import io.micronaut.sourcegen.model.ExpressionDef;
import io.micronaut.sourcegen.model.FieldDef;
Expand All @@ -41,7 +46,6 @@
import io.micronaut.sourcegen.model.TypeDef;
import io.micronaut.sourcegen.model.VariableDef;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import javax.lang.model.element.Modifier;
Expand Down Expand Up @@ -71,6 +75,8 @@
@Internal
public final class BuilderAnnotationVisitor implements TypeElementVisitor<Builder, Object> {

public static final String BUILDER_ANNOTATED_WITH_MEMBER = "annotatedWith";

private final Set<String> processed = new HashSet<>();

@Override
Expand Down Expand Up @@ -101,6 +107,7 @@ public void visitClass(ClassElement element, VisitorContext context) {

ClassDef.ClassDefBuilder builder = ClassDef.builder(builderClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
addAnnotations(builder, element.getAnnotation(Builder.class));

List<PropertyElement> properties = element.getBeanProperties();
for (PropertyElement beanProperty : properties) {
Expand Down Expand Up @@ -150,8 +157,22 @@ public void visitClass(ClassElement element, VisitorContext context) {
}
}

private MethodDef createAllPropertiesConstructor(ClassTypeDef builderType, List<PropertyElement> properties) {
MethodDef.MethodDefBuilder builder = MethodDef.constructor();
static void addAnnotations(ClassDefBuilder builder, AnnotationValue<?> annotation) {
Optional<AnnotationClassValue[]> annotatedWith = annotation.getConvertibleValues()
.get(BUILDER_ANNOTATED_WITH_MEMBER, AnnotationClassValue[].class);
if (annotatedWith.isEmpty()) {
// Apply the default annotation
builder.addAnnotation(Introspected.class);
} else {
for (AnnotationClassValue<?> value: annotatedWith.get()) {
builder.addAnnotation(value.getName());
}
}
}

static MethodDef createAllPropertiesConstructor(ClassTypeDef builderType, List<PropertyElement> properties) {
MethodDef.MethodDefBuilder builder = MethodDef.constructor()
.addAnnotation(Creator.class);
VariableDef.This self = new VariableDef.This(builderType);
for (PropertyElement parameter : properties) {
ParameterDef parameterDef = ParameterDef.of(parameter.getName(), TypeDef.of(parameter.getType()));
Expand Down Expand Up @@ -180,7 +201,7 @@ private MethodDef createAllPropertiesConstructor(ClassTypeDef builderType, List<
return builder.build();
}

private StatementDef iterableToArrayListStatement(VariableDef.This self, ParameterDef parameterDef) {
private static StatementDef iterableToArrayListStatement(VariableDef.This self, ParameterDef parameterDef) {
return ClassTypeDef.of(ArrayList.class)
.instantiate()
.newLocal(parameterDef.getName() + "ArrayList", arrayListVar ->
Expand All @@ -198,7 +219,7 @@ private StatementDef iterableToArrayListStatement(VariableDef.This self, Paramet
)));
}

private StatementDef mapToArrayListStatement(VariableDef.This self, ParameterDef parameterDef) {
private static StatementDef mapToArrayListStatement(VariableDef.This self, ParameterDef parameterDef) {
return self.field(parameterDef.getName(), parameterDef.getType())
.assign(
ClassTypeDef.of(ArrayList.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@
import io.micronaut.sourcegen.model.MethodDef;
import io.micronaut.sourcegen.model.TypeDef;

import java.util.HashSet;
import java.util.Set;
import javax.lang.model.element.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static io.micronaut.sourcegen.generator.visitors.BuilderAnnotationVisitor.addAnnotations;
import static io.micronaut.sourcegen.generator.visitors.BuilderAnnotationVisitor.createModifyPropertyMethod;

/**
Expand Down Expand Up @@ -148,6 +149,12 @@ public void visitClass(ClassElement element, VisitorContext context) {
)
)
);
addAnnotations(builder, element.getAnnotation(SuperBuilder.class));

builder.addMethod(MethodDef.constructor().build());
if (!properties.isEmpty()) {
builder.addMethod(BuilderAnnotationVisitor.createAllPropertiesConstructor(builderType, properties));
}

builder.addMethod(createSelfMethod());
builder.addMethod(BuilderAnnotationVisitor.createBuildMethod(element));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class BuilderAnnotationVisitorSpec extends AbstractTypeElementSpec {
package test;
import io.micronaut.sourcegen.annotations.Builder;

@Builder
@Builder(annotatedWith = {})
public record Walrus(
String name,
int age,
Expand All @@ -35,7 +35,7 @@ class BuilderAnnotationVisitorSpec extends AbstractTypeElementSpec {
package test;
import io.micronaut.sourcegen.annotations.Builder;

@Builder
@Builder(annotatedWith = {})
public record Walrus() {
}
""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
package io.micronaut.sourcegen.example;

import java.lang.reflect.Modifier;

import io.micronaut.core.beans.BeanIntrospection;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AnimalSuperBuilderTest {
Expand Down Expand Up @@ -61,6 +64,14 @@ public void testDog() {
}
//end::test[]

@Test
public void dogIntrospection() {
var introspection = BeanIntrospection.getIntrospection(DogSuperBuilder.class);
assertNotNull(introspection);
assertEquals(0, introspection.getBeanProperties().size());
assertEquals(6, introspection.getConstructorArguments().length);
}

@Test
public void internalTest() {
assertTrue(Modifier.isPublic(CatSuperBuilder.class.getModifiers()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
*/
package io.micronaut.sourcegen.example;

import io.micronaut.core.beans.BeanIntrospection;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class PersonBuilderTest {

Expand All @@ -36,6 +38,14 @@ public void buildsPerson() {
}
//end::test[]

@Test
public void personIntrospection() {
var introspection = BeanIntrospection.getIntrospection(PersonBuilder.class);
assertNotNull(introspection);
assertEquals(0, introspection.getBeanProperties().size());
assertEquals(3, introspection.getConstructorArguments().length);
}

@Test
public void buildsPersonWithPrimitiveDefaults() {
var person = Person2Builder.builder()
Expand Down
Loading