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 meta-annotation support #15

Merged
merged 5 commits into from Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/jilt/Builder.java
Expand Up @@ -225,7 +225,7 @@
* @see #buildMethod
* @see BuilderInterfaces
*/
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jilt/BuilderInterfaces.java
Expand Up @@ -25,7 +25,7 @@
* @see Builder#style
* @see BuilderStyle
*/
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface BuilderInterfaces {
/**
Expand Down
49 changes: 37 additions & 12 deletions src/main/java/org/jilt/JiltAnnotationProcessor.java
@@ -1,19 +1,22 @@
package org.jilt;

import org.jilt.internal.BuilderGeneratorFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.util.Collections;
import java.util.Set;
import org.jilt.internal.BuilderGeneratorFactory;
import org.jilt.utils.Annotations;

public class JiltAnnotationProcessor extends AbstractProcessor {
private Messager messager;
Expand All @@ -22,7 +25,7 @@ public class JiltAnnotationProcessor extends AbstractProcessor {
private BuilderGeneratorFactory builderGeneratorFactory;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
public synchronized void init(final ProcessingEnvironment processingEnv) {
super.init(processingEnv);

messager = processingEnv.getMessager();
Expand All @@ -32,20 +35,42 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Builder.class)) {
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
getAnnotatedElements(roundEnv).forEach((annotatedElement, builderAnnotations) -> {
try {
builderGeneratorFactory.forElement(annotatedElement).generateBuilderClass();
} catch (Exception e) {
builderGeneratorFactory.forElement(annotatedElement, builderAnnotations).generateBuilderClass();
} catch(final Exception e) {
error(annotatedElement, e.getMessage());
return true;
}
}
});

return true;
}

private void error(Element element, String msg, Object... args) {
private Map<Element, Annotations> getAnnotatedElements(final RoundEnvironment roundEnv) {
final Set<? extends Element> builderElements = roundEnv.getElementsAnnotatedWith(Builder.class);
final Map<Element, Annotations> annotatedElements = initMap(builderElements, null, null);
for(final Element builderElement : builderElements) {
if(builderElement.getKind() == ElementKind.ANNOTATION_TYPE) {
annotatedElements.remove(builderElement);
annotatedElements.putAll(initMap(roundEnv.getElementsAnnotatedWith((TypeElement) builderElement),
builderElement.getAnnotation(Builder.class), builderElement.getAnnotation(BuilderInterfaces.class)));
}
}

return annotatedElements;
}

private Map<Element, Annotations> initMap(final Set<? extends Element> builderElements, final Builder builderAnnotation,
final BuilderInterfaces builderInterfaces) {
final Map<Element, Annotations> map = new HashMap<>();
for(final Element element : builderElements) {
map.put(element, new Annotations(builderAnnotation, builderInterfaces));
}
return map;
}

private void error(final Element element, final String msg, final Object... args) {
messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), element);
}

Expand Down
24 changes: 12 additions & 12 deletions src/main/java/org/jilt/internal/BuilderGeneratorFactory.java
@@ -1,8 +1,9 @@
package org.jilt.internal;

import org.jilt.Builder;
import org.jilt.BuilderInterfaces;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
Expand All @@ -12,10 +13,9 @@
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.Elements;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jilt.Builder;
import org.jilt.BuilderInterfaces;
import org.jilt.utils.Annotations;

public final class BuilderGeneratorFactory {
private static final Set<String> ALLOWED_TYPE_KINDS;
Expand All @@ -34,9 +34,9 @@ public BuilderGeneratorFactory(Filer filer, Elements elements) {
this.elements = elements;
}

public BuilderGenerator forElement(Element annotatedElement) throws Exception {
TypeElement targetClass;
List<? extends VariableElement> attributes;
public BuilderGenerator forElement(final Element annotatedElement, final Annotations annotations) throws Exception {
final TypeElement targetClass;
final List<? extends VariableElement> attributes;
ExecutableElement targetFactoryMethod = null;

ElementKind kind = annotatedElement.getKind();
Expand Down Expand Up @@ -66,8 +66,8 @@ public BuilderGenerator forElement(Element annotatedElement) throws Exception {
"@Builder can only be placed on classes/records, constructors or static methods");
}

Builder builderAnnotation = annotatedElement.getAnnotation(Builder.class);
BuilderInterfaces builderInterfaces = annotatedElement.getAnnotation(BuilderInterfaces.class);
final Builder builderAnnotation = annotations.getBuilder() == null ? annotatedElement.getAnnotation(Builder.class) : annotations.getBuilder();
final BuilderInterfaces builderInterfaces = annotations.getBuilderInterface() == null ? annotatedElement.getAnnotation(BuilderInterfaces.class) : annotations.getBuilderInterface();
switch (builderAnnotation.style()) {
case STAGED:
case TYPE_SAFE:
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/jilt/utils/Annotations.java
@@ -0,0 +1,24 @@
package org.jilt.utils;

import org.jilt.Builder;
import org.jilt.BuilderInterfaces;

public final class Annotations {

private final Builder builder;
private final BuilderInterfaces builderInterfaces;

public Annotations(final Builder builder, final BuilderInterfaces builderInterfaces) {
this.builder = builder;
this.builderInterfaces = builderInterfaces;
}

public Builder getBuilder() {
return builder;
}

public BuilderInterfaces getBuilderInterface() {
return builderInterfaces;
}

}
25 changes: 25 additions & 0 deletions src/test/java/org/jilt/test/MetaAnnotationTest.java
@@ -0,0 +1,25 @@
package org.jilt.test;

import static org.assertj.core.api.Assertions.assertThat;

import org.jilt.test.data.constructor.MetaConstuctorValue;
import org.jilt.test.data.constructor.MetaConstuctorValueBuilder;
import org.junit.Test;

public class MetaAnnotationTest {

@Test
public void test__meta_builder_on_constructor() {
final MetaConstuctorValue value = MetaConstuctorValueBuilder.builder()
.withAttr2("attr2_value")
.withAttr4(4)
.withAttr3(true)
.build();

assertThat(value.attr1).isEqualTo(123);
assertThat(value.attr2).isEqualTo("attr2_value");
assertThat(value.attr3).isTrue();
assertThat(value.attr4).isEqualTo(4);
}

}
10 changes: 10 additions & 0 deletions src/test/java/org/jilt/test/data/annotations/MetaBuilder.java
@@ -0,0 +1,10 @@
package org.jilt.test.data.annotations;

import org.jilt.Builder;
import org.jilt.BuilderInterfaces;
import org.jilt.BuilderStyle;

@BuilderInterfaces(lastInnerName = "Meta")
@Builder(setterPrefix = "with", factoryMethod = "builder", style = BuilderStyle.STAGED)
public @interface MetaBuilder {
}
@@ -0,0 +1,20 @@
package org.jilt.test.data.constructor;

import org.jilt.test.data.annotations.MetaBuilder;

public class MetaConstuctorValue {

public final int attr1;
public final String attr2;
public final boolean attr3;
public final int attr4;

@MetaBuilder
public MetaConstuctorValue(final String attr2, final int attr4, final boolean attr3) {
attr1 = 123;
this.attr2 = attr2;
this.attr3 = attr3;
this.attr4 = attr4;
}

}