Skip to content

Commit

Permalink
Extend the annotations api
Browse files Browse the repository at this point in the history
Extend the annotations api to include all possible annotation definitions
  • Loading branch information
graemerocher authored Jan 16, 2024
2 parents 07ae78a + a9fd116 commit b819aff
Show file tree
Hide file tree
Showing 13 changed files with 536 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.micronaut.sourcegen.generator.SourceGenerator;
import io.micronaut.sourcegen.javapoet.AnnotationSpec;
import io.micronaut.sourcegen.javapoet.ClassName;
import io.micronaut.sourcegen.javapoet.CodeBlock;
import io.micronaut.sourcegen.javapoet.FieldSpec;
import io.micronaut.sourcegen.javapoet.JavaFile;
import io.micronaut.sourcegen.javapoet.MethodSpec;
Expand Down Expand Up @@ -49,8 +50,8 @@
import javax.lang.model.element.Modifier;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;

/**
* The Java source generator.
Expand Down Expand Up @@ -276,25 +277,33 @@ private TypeVariableName asTypeVariable(TypeDef.TypeVariable tv) {
private AnnotationSpec asAnnotationSpec(AnnotationDef annotationDef) {
AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassName.bestGuess(annotationDef.getType().getName()));
for (Map.Entry<String, Object> e : annotationDef.getValues().entrySet()) {
String memberName = e.getKey();
Object value = e.getValue();
if (value instanceof Class<?>) {
builder = builder.addMember(memberName, "$T.class", value);
} else if (value instanceof Enum) {
builder = builder.addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
} else if (value instanceof String) {
builder = builder.addMember(memberName, "$S", value);
} else if (value instanceof Float) {
builder = builder.addMember(memberName, "$Lf", value);
} else if (value instanceof Character) {
builder = builder.addMember(memberName, "'$L'", io.micronaut.sourcegen.javapoet.Util.characterLiteralWithoutSingleQuotes((char) value));
} else {
builder = builder.addMember(memberName, "$L", value);
}
addAnnotationValue(builder, e.getKey(), e.getValue());
}
return builder.build();
}

private void addAnnotationValue(AnnotationSpec.Builder builder, String memberName, Object value) {
if (value instanceof Collection<?> collection) {
collection.forEach(v -> addAnnotationValue(builder, memberName, v));
} else if (value instanceof AnnotationDef annotationValue) {
builder.addMember(memberName, asAnnotationSpec(annotationValue));
} else if (value instanceof VariableDef variableDef) {
builder.addMember(memberName, renderVariable(null, null, variableDef));
} else if (value instanceof Class<?>) {
builder.addMember(memberName, "$T.class", value);
} else if (value instanceof Enum) {
builder.addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
} else if (value instanceof String) {
builder.addMember(memberName, "$S", value);
} else if (value instanceof Float) {
builder.addMember(memberName, "$Lf", value);
} else if (value instanceof Character) {
builder.addMember(memberName, "'$L'", io.micronaut.sourcegen.javapoet.Util.characterLiteralWithoutSingleQuotes((char) value));
} else {
builder.addMember(memberName, "$L", value);
}
}

private TypeName asType(TypeDef typeDef) {
if (typeDef instanceof ClassTypeDef.Parameterized parameterized) {
return ParameterizedTypeName.get(
Expand Down Expand Up @@ -344,48 +353,65 @@ private static ClassName asClassType(ClassTypeDef classTypeDef) {
return ClassName.bestGuess(classTypeDef.getName());
}

private static String renderStatement(@Nullable ObjectDef objectDef, MethodDef methodDef, StatementDef statementDef) {
private CodeBlock renderStatement(@Nullable ObjectDef objectDef, MethodDef methodDef, StatementDef statementDef) {
if (statementDef instanceof StatementDef.Return aReturn) {
return "return " + renderExpression(objectDef, methodDef, aReturn.expression());
return CodeBlock.concat(
CodeBlock.of("return "),
renderExpression(objectDef, methodDef, aReturn.expression())
);
}
if (statementDef instanceof StatementDef.Assign assign) {
return renderExpression(objectDef, methodDef, assign.variable())
+ " = " +
renderExpression(objectDef, methodDef, assign.expression());
return CodeBlock.concat(
renderExpression(objectDef, methodDef, assign.variable()),
CodeBlock.of(" = "),
renderExpression(objectDef, methodDef, assign.expression())
);
}
throw new IllegalStateException("Unrecognized statement: " + statementDef);
}

private static String renderExpression(@Nullable ObjectDef objectDef, MethodDef methodDef, ExpressionDef expressionDef) {
private CodeBlock renderExpression(@Nullable ObjectDef objectDef, MethodDef methodDef, ExpressionDef expressionDef) {
if (expressionDef instanceof ExpressionDef.NewInstance newInstance) {
return "new " + newInstance.type().getName()
+ "(" + newInstance.values()
.stream()
.map(exp -> renderExpression(objectDef, methodDef, exp)).collect(Collectors.joining(", "))
+ ")";
return CodeBlock.concat(
CodeBlock.of("new " + newInstance.type().getName() + "("),
newInstance.values()
.stream()
.map(exp -> renderExpression(objectDef, methodDef, exp))
.collect(CodeBlock.joining(", ")),
CodeBlock.of(")")
);
}
if (expressionDef instanceof ExpressionDef.Convert convertExpressionDef) {
return renderVariable(objectDef, methodDef, convertExpressionDef.variable());
}
if (expressionDef instanceof ExpressionDef.CallInstanceMethod callInstanceMethod) {
return renderVariable(objectDef, methodDef, callInstanceMethod.instance())
+ "." + callInstanceMethod.name()
+ "(" + callInstanceMethod.parameters()
.stream()
.map(exp -> renderExpression(objectDef, methodDef, expressionDef))
.collect(Collectors.joining(", "))
+ ")";
return CodeBlock.concat(
CodeBlock.of(renderVariable(objectDef, methodDef, callInstanceMethod.instance())
+ "." + callInstanceMethod.name()
+ "("),
callInstanceMethod.parameters()
.stream()
.map(exp -> renderExpression(objectDef, methodDef, expressionDef))
.collect(CodeBlock.joining(", ")),
CodeBlock.of(")")
);
}
if (expressionDef instanceof VariableDef variableDef) {
return renderVariable(objectDef, methodDef, variableDef);
}
throw new IllegalStateException("Unrecognized expression: " + expressionDef);
}

private static String renderVariable(@Nullable ObjectDef objectDef, MethodDef methodDef, VariableDef variableDef) {
private CodeBlock renderVariable(@Nullable ObjectDef objectDef, @Nullable MethodDef methodDef, VariableDef variableDef) {
if (variableDef instanceof VariableDef.MethodParameter parameterVariableDef) {
if (methodDef == null) {
throw new IllegalStateException("Accessing method parameters is not available");
}
methodDef.getParameter(parameterVariableDef.name()); // Check if exists
return parameterVariableDef.name();
return CodeBlock.of(parameterVariableDef.name());
}
if (variableDef instanceof VariableDef.StaticField staticField) {
return CodeBlock.of("$T.$L", asType(staticField.ownerType()), staticField.name());
}
if (variableDef instanceof VariableDef.Field field) {
if (objectDef == null) {
Expand All @@ -396,13 +422,13 @@ private static String renderVariable(@Nullable ObjectDef objectDef, MethodDef me
} else {
throw new IllegalStateException("Field access no supported on the object definition: " + objectDef);
}
return renderExpression(objectDef, methodDef, field.instanceVariable()) + "." + field.name();
return CodeBlock.of(renderExpression(objectDef, methodDef, field.instanceVariable()) + "." + field.name());
}
if (variableDef instanceof VariableDef.This) {
if (objectDef == null) {
throw new IllegalStateException("Accessing 'this' is not available");
}
return "this";
return CodeBlock.of("this");
}
throw new IllegalStateException("Unrecognized variable: " + variableDef);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public final class AnnotationSpec {
public static final String VALUE = "value";

public final TypeName type;
public final Map<String, List<CodeBlock>> members;
public final Map<String, List<AnnotationValueSpec>> members;

private AnnotationSpec(Builder builder) {
this.type = builder.type;
Expand All @@ -57,7 +57,7 @@ void emit(CodeWriter codeWriter, boolean inline) throws IOException {
} else if (members.size() == 1 && members.containsKey("value")) {
// @Named("foo")
codeWriter.emit("@$T(", type);
emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"));
emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"), inline);
codeWriter.emit(")");
} else {
// Inline:
Expand All @@ -70,11 +70,11 @@ void emit(CodeWriter codeWriter, boolean inline) throws IOException {
// )
codeWriter.emit("@$T(" + whitespace, type);
codeWriter.indent(2);
for (Iterator<Map.Entry<String, List<CodeBlock>>> i
for (Iterator<Map.Entry<String, List<AnnotationValueSpec>>> i
= members.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<String, List<CodeBlock>> entry = i.next();
Map.Entry<String, List<AnnotationValueSpec>> entry = i.next();
codeWriter.emit("$L = ", entry.getKey());
emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue());
emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue(), inline);
if (i.hasNext()) codeWriter.emit(memberSeparator);
}
codeWriter.unindent(2);
Expand All @@ -83,24 +83,25 @@ void emit(CodeWriter codeWriter, boolean inline) throws IOException {
}

private void emitAnnotationValues(CodeWriter codeWriter, String whitespace,
String memberSeparator, List<CodeBlock> values) throws IOException {
if (values.size() == 1) {
String memberSeparator, List<AnnotationValueSpec> values, boolean inline) throws IOException {
if (values.size() != 1) {
codeWriter.emit("{" + whitespace);
codeWriter.indent(2);
codeWriter.emit(values.get(0));
codeWriter.unindent(2);
return;
}

codeWriter.emit("{" + whitespace);
codeWriter.indent(2);
boolean first = true;
for (CodeBlock codeBlock : values) {
for (AnnotationValueSpec value : values) {
if (!first) codeWriter.emit(memberSeparator);
codeWriter.emit(codeBlock);
if (value instanceof AnnotationSpecValue annotationValue) {
annotationValue.annotation.emit(codeWriter, inline);
} else if (value instanceof CodeAnnotationValue codeValue) {
codeWriter.emit(codeValue.codeBlock);
}
first = false;
}
codeWriter.unindent(2);
codeWriter.emit(whitespace + "}");
if (values.size() != 1) {
codeWriter.unindent(2);
codeWriter.emit(whitespace + "}");
}
}

public static AnnotationSpec get(Annotation annotation) {
Expand Down Expand Up @@ -160,7 +161,7 @@ public static Builder builder(Class<?> type) {

public Builder toBuilder() {
Builder builder = new Builder(type);
for (Map.Entry<String, List<CodeBlock>> entry : members.entrySet()) {
for (Map.Entry<String, List<AnnotationValueSpec>> entry : members.entrySet()) {
builder.members.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return builder;
Expand Down Expand Up @@ -191,7 +192,7 @@ public Builder toBuilder() {
public static final class Builder {
private final TypeName type;

public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
public final Map<String, List<AnnotationValueSpec>> members = new LinkedHashMap<>();

private Builder(TypeName type) {
this.type = type;
Expand All @@ -202,11 +203,17 @@ public Builder addMember(String name, String format, Object... args) {
}

public Builder addMember(String name, CodeBlock codeBlock) {
List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
values.add(codeBlock);
List<AnnotationValueSpec> values = members.computeIfAbsent(name, k -> new ArrayList<>());
values.add(new CodeAnnotationValue(codeBlock));
return this;
}

public Builder addMember(String name, AnnotationSpec annotation) {
List<AnnotationValueSpec> values = members.computeIfAbsent(name, k -> new ArrayList<>());
values.add(new AnnotationSpecValue(annotation));
return this;
}

/**
* Delegates to {@link #addMember(String, String, Object...)}, with parameter {@code format}
* depending on the given {@code value} object. Falls back to {@code "$L"} literal format if
Expand Down Expand Up @@ -277,4 +284,19 @@ private static class Visitor extends SimpleAnnotationValueVisitor8<Builder, Stri
return builder;
}
}

public sealed interface AnnotationValueSpec permits AnnotationSpecValue, CodeAnnotationValue {
}

public record AnnotationSpecValue(
AnnotationSpec annotation
) implements AnnotationValueSpec {

}

public record CodeAnnotationValue(
CodeBlock codeBlock
) implements AnnotationValueSpec {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -103,6 +104,13 @@ public static CodeBlock of(String format, Object... args) {
return new Builder().add(format, args).build();
}

/**
* Concatenates {@code codeBlocks} into a single {@link CodeBlock}.
*/
public static CodeBlock concat(CodeBlock... codeBlocks) {
return Arrays.stream(codeBlocks).collect(joining(""));
}

/**
* Joins {@code codeBlocks} into a single {@link CodeBlock}, each separated by {@code separator}.
* For example, joining {@code String s}, {@code Object o} and {@code int i} using {@code ", "}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.micronaut.sourcegen.javapoet;

import com.google.testing.compile.CompilationRule;
import io.micronaut.sourcegen.javapoet.AnnotationSpec.CodeAnnotationValue;
import org.junit.Rule;
import org.junit.Test;

Expand Down Expand Up @@ -375,7 +376,7 @@ public class IsAnnotated {
.addMember("value", "$S", "Foo");

builder.members.clear();
builder.members.put("value", List.of(CodeBlock.of("$S", "Bar")));
builder.members.put("value", List.of(new CodeAnnotationValue(CodeBlock.of("$S", "Bar"))));

assertThat(builder.build().toString()).isEqualTo("@java.lang.SuppressWarnings(\"Bar\")");
}
Expand Down
Loading

0 comments on commit b819aff

Please sign in to comment.