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

feat: Extract JVM comments for Enums and TypeAlias #3082

Merged
merged 1 commit into from
Oct 13, 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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package xyz.block.ftl.deployment;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import io.quarkus.builder.item.SimpleBuildItem;

public final class CommentsBuildItem extends SimpleBuildItem {

final Map<String, Collection<String>> comments;

public CommentsBuildItem(Map<String, Collection<String>> comments) {
this.comments = comments;
}

public Iterable<String> getComments(String name) {
return comments.getOrDefault(name, List.of());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ public class EnumProcessor {

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
SchemaContributorBuildItem handleEnums(CombinedIndexBuildItem index, FTLRecorder recorder) {
SchemaContributorBuildItem handleEnums(
CombinedIndexBuildItem index,
FTLRecorder recorder,
CommentsBuildItem commentsBuildItem) {
var enumAnnotations = index.getIndex().getAnnotations(FTLDotNames.ENUM);
log.infof("Processing %d enum annotations into decls", enumAnnotations.size());
return new SchemaContributorBuildItem(moduleBuilder -> {
try {
var decls = extractEnumDecls(index, enumAnnotations, recorder, moduleBuilder);
var decls = extractEnumDecls(index, enumAnnotations, recorder, moduleBuilder, commentsBuildItem);
for (var decl : decls) {
moduleBuilder.addDecls(decl);
}
Expand All @@ -64,7 +67,7 @@ SchemaContributorBuildItem handleEnums(CombinedIndexBuildItem index, FTLRecorder
* ModuleBuilder.buildType is used, and has the side effect of adding child Decls to the module.
*/
private List<Decl> extractEnumDecls(CombinedIndexBuildItem index, Collection<AnnotationInstance> enumAnnotations,
FTLRecorder recorder, ModuleBuilder moduleBuilder)
FTLRecorder recorder, ModuleBuilder moduleBuilder, CommentsBuildItem commentsBuildItem)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
List<Decl> decls = new ArrayList<>();
for (var enumAnnotation : enumAnnotations) {
Expand All @@ -78,10 +81,10 @@ private List<Decl> extractEnumDecls(CombinedIndexBuildItem index, Collection<Ann
// Value enum
recorder.registerEnum(clazz);
if (isLocalToModule) {
decls.add(extractValueEnum(classInfo, clazz, exported));
decls.add(extractValueEnum(classInfo, clazz, exported, commentsBuildItem));
}
} else {
var typeEnum = extractTypeEnum(index, moduleBuilder, classInfo, exported);
var typeEnum = extractTypeEnum(index, moduleBuilder, classInfo, exported, commentsBuildItem);
recorder.registerEnum(clazz, typeEnum.variantClasses);
if (isLocalToModule) {
decls.add(typeEnum.decl);
Expand All @@ -94,11 +97,14 @@ private List<Decl> extractEnumDecls(CombinedIndexBuildItem index, Collection<Ann
/**
* Value enums are Java language enums with a single field 'value'
*/
private Decl extractValueEnum(ClassInfo classInfo, Class<?> clazz, boolean exported)
private Decl extractValueEnum(ClassInfo classInfo, Class<?> clazz, boolean exported, CommentsBuildItem commentsBuildItem)
throws NoSuchFieldException, IllegalAccessException {
String name = classInfo.simpleName();
Enum.Builder enumBuilder = Enum.newBuilder()
.setName(classInfo.simpleName())
.setExport(exported);
.setName(name)
.setPos(PositionUtils.forClass(classInfo.name().toString()))
.setExport(exported)
.addAllComments(commentsBuildItem.getComments(name));
FieldInfo valueField = classInfo.field("value");
if (valueField == null) {
throw new RuntimeException("Enum must have a 'value' field: " + classInfo.name());
Expand Down Expand Up @@ -144,10 +150,13 @@ private record TypeEnum(Decl decl, List<Class<?>> variantClasses) {
* - a class with arbitrary fields </br>
*/
private TypeEnum extractTypeEnum(CombinedIndexBuildItem index, ModuleBuilder moduleBuilder,
ClassInfo classInfo, boolean exported) throws ClassNotFoundException {
ClassInfo classInfo, boolean exported, CommentsBuildItem commentsBuildItem) throws ClassNotFoundException {
String name = classInfo.simpleName();
Enum.Builder enumBuilder = Enum.newBuilder()
.setName(classInfo.simpleName())
.setExport(exported);
.setName(name)
.setPos(PositionUtils.forClass(classInfo.name().toString()))
.setExport(exported)
.addAllComments(commentsBuildItem.getComments(name));
var variants = index.getComputingIndex().getAllKnownImplementors(classInfo.name());
if (variants.isEmpty()) {
throw new RuntimeException("No variants found for enum: " + enumBuilder.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ public class ModuleBuilder {
private final Map<DotName, TopicsBuildItem.DiscoveredTopic> knownTopics;
private final Map<DotName, VerbClientBuildItem.DiscoveredClients> verbClients;
private final FTLRecorder recorder;
private final Map<String, Iterable<String>> comments;
private final CommentsBuildItem comments;
private final List<ValidationFailure> validationFailures = new ArrayList<>();
private final boolean defaultToOptional;

public ModuleBuilder(IndexView index, String moduleName, Map<DotName, TopicsBuildItem.DiscoveredTopic> knownTopics,
Map<DotName, VerbClientBuildItem.DiscoveredClients> verbClients, FTLRecorder recorder,
Map<String, Iterable<String>> comments, boolean defaultToOptional) {
CommentsBuildItem comments, boolean defaultToOptional) {
this.index = index;
this.moduleName = moduleName;
this.protoModuleBuilder = Module.newBuilder()
Expand Down Expand Up @@ -203,9 +203,8 @@ public void registerVerbMethod(MethodInfo method, String className,
if (!knownSecrets.contains(name)) {
xyz.block.ftl.v1.schema.Secret.Builder secretBuilder = xyz.block.ftl.v1.schema.Secret.newBuilder()
.setType(buildType(param.type(), false, param))
.setName(name);
Optional.ofNullable(comments.get(CommentKey.ofSecret(name)))
.ifPresent(secretBuilder::addAllComments);
.setName(name)
.addAllComments(comments.getComments(name));
addDecls(Decl.newBuilder().setSecret(secretBuilder).build());
knownSecrets.add(name);
}
Expand All @@ -218,9 +217,8 @@ public void registerVerbMethod(MethodInfo method, String className,
if (!knownConfig.contains(name)) {
xyz.block.ftl.v1.schema.Config.Builder configBuilder = xyz.block.ftl.v1.schema.Config.newBuilder()
.setType(buildType(param.type(), false, param))
.setName(name);
Optional.ofNullable(comments.get(CommentKey.ofConfig(name)))
.ifPresent(configBuilder::addAllComments);
.setName(name)
.addAllComments(comments.getComments(name));
addDecls(Decl.newBuilder().setConfig(configBuilder).build());
knownConfig.add(name);
}
Expand Down Expand Up @@ -271,15 +269,12 @@ public void registerVerbMethod(MethodInfo method, String className,
Class.forName(className, false, Thread.currentThread().getContextClassLoader()), paramMappers,
method.returnType() == VoidType.VOID);

verbBuilder
.setName(verbName)
verbBuilder.setName(verbName)
.setExport(exported)
.setPos(PositionUtils.forMethod(method))
.setRequest(buildType(bodyParamType, exported, bodyParamNullability))
.setResponse(buildType(method.returnType(), exported, method));
Optional.ofNullable(comments.get(CommentKey.ofVerb(verbName)))
.ifPresent(verbBuilder::addAllComments);

.setResponse(buildType(method.returnType(), exported, method))
.addAllComments(comments.getComments(verbName));
if (metadataCallback != null) {
metadataCallback.accept(verbBuilder);
}
Expand Down Expand Up @@ -402,23 +397,21 @@ public Type buildType(org.jboss.jandex.Type type, boolean export, Nullability nu

if (info.isEnum() || info.hasAnnotation(ENUM)) {
// Set only the name and export here. EnumProcessor will fill in the rest
xyz.block.ftl.v1.schema.Enum ennum = xyz.block.ftl.v1.schema.Enum.newBuilder()
xyz.block.ftl.v1.schema.Enum.Builder ennum = xyz.block.ftl.v1.schema.Enum.newBuilder()
.setName(name)
.setExport(type.hasAnnotation(EXPORT) || export)
.build();
addDecls(Decl.newBuilder().setEnum(ennum).build());
.setExport(type.hasAnnotation(EXPORT) || export);
addDecls(Decl.newBuilder().setEnum(ennum.build()).build());
return ref;
} else {
// If this data was processed already, skip early
if (setDeclExport(name, type.hasAnnotation(EXPORT) || export)) {
return ref;
}
Data.Builder data = Data.newBuilder();
data.setPos(PositionUtils.forClass(clazz.name().toString()));
data.setName(name);
data.setExport(type.hasAnnotation(EXPORT) || export);
Optional.ofNullable(comments.get(CommentKey.ofData(name)))
.ifPresent(data::addAllComments);
Data.Builder data = Data.newBuilder()
.setPos(PositionUtils.forClass(clazz.name().toString()))
.setName(name)
.setExport(type.hasAnnotation(EXPORT) || export)
.addAllComments(comments.getComments(name));
buildDataElement(data, clazz.name());
addDecls(Decl.newBuilder().setData(data).build());
return ref;
Expand Down Expand Up @@ -555,20 +548,19 @@ public void writeTo(OutputStream out) throws IOException {
public void registerTypeAlias(String name, org.jboss.jandex.Type finalT, org.jboss.jandex.Type finalS, boolean exported,
Map<String, String> languageMappings) {
validateName(finalT.name().toString(), name);
TypeAlias.Builder typeAlias = TypeAlias.newBuilder().setType(buildType(finalS, exported, Nullability.NOT_NULL))
TypeAlias.Builder typeAlias = TypeAlias.newBuilder()
.setType(buildType(finalS, exported, Nullability.NOT_NULL))
.setName(name)
.addMetadata(Metadata
.newBuilder()
.addAllComments(comments.getComments(name))
.addMetadata(Metadata.newBuilder()
.setTypeMap(MetadataTypeMap.newBuilder().setRuntime("java").setNativeName(finalT.toString())
.build())
.build());
for (var entry : languageMappings.entrySet()) {
typeAlias.addMetadata(Metadata.newBuilder().setTypeMap(MetadataTypeMap.newBuilder().setRuntime(entry.getKey())
.setNativeName(entry.getValue()).build()).build());
}
addDecls(Decl.newBuilder()
.setTypeAlias(typeAlias)
.build());
addDecls(Decl.newBuilder().setTypeAlias(typeAlias).build());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -101,6 +102,29 @@ ModuleNameBuildItem moduleName(ApplicationInfoBuildItem applicationInfoBuildItem
return new ModuleNameBuildItem(applicationInfoBuildItem.getName());
}

/**
* Bytecode doesn't retain comments, so they are stored in a separate file.
*/
@BuildStep
public CommentsBuildItem readComments() throws IOException {
Map<String, Collection<String>> comments = new HashMap<>();
try (var input = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/ftl-verbs.txt")) {
if (input != null) {
var contents = new String(input.readAllBytes(), StandardCharsets.UTF_8).split("\n");
for (var content : contents) {
var eq = content.indexOf('=');
if (eq == -1) {
continue;
}
String key = content.substring(0, eq);
String value = new String(Base64.getDecoder().decode(content.substring(eq + 1)), StandardCharsets.UTF_8);
comments.put(key, Arrays.asList(value.split("\n")));
}
}
}
return new CommentsBuildItem(comments);
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
public void generateSchema(CombinedIndexBuildItem index,
Expand All @@ -110,9 +134,9 @@ public void generateSchema(CombinedIndexBuildItem index,
TopicsBuildItem topicsBuildItem,
VerbClientBuildItem verbClientBuildItem,
DefaultOptionalBuildItem defaultOptionalBuildItem,
List<SchemaContributorBuildItem> schemaContributorBuildItems) throws Exception {
List<SchemaContributorBuildItem> schemaContributorBuildItems,
CommentsBuildItem comments) throws Exception {
String moduleName = moduleNameBuildItem.getModuleName();
Map<String, Iterable<String>> comments = readComments();

ModuleBuilder moduleBuilder = new ModuleBuilder(index.getComputingIndex(), moduleName, topicsBuildItem.getTopics(),
verbClientBuildItem.getVerbClients(), recorder, comments,
Expand Down Expand Up @@ -172,27 +196,4 @@ void openSocket(BuildProducer<RequireVirtualHttpBuildItem> virtual,
socket.produce(RequireSocketHttpBuildItem.MARKER);
virtual.produce(RequireVirtualHttpBuildItem.MARKER);
}

/**
* Bytecode doesn't retain comments, so they are stored in a separate file
* Each line is a key value pair separated by an '='. The key is the DeclRef and the value is the comment
*/
private Map<String, Iterable<String>> readComments() throws IOException {
Map<String, Iterable<String>> comments = new HashMap<>();
try (var input = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/ftl-verbs.txt")) {
if (input != null) {
var contents = new String(input.readAllBytes(), StandardCharsets.UTF_8).split("\n");
for (var content : contents) {
var eq = content.indexOf('=');
if (eq == -1) {
continue;
}
String key = content.substring(0, eq);
String value = new String(Base64.getDecoder().decode(content.substring(eq + 1)), StandardCharsets.UTF_8);
comments.put(key, Arrays.asList(value.split("\n")));
}
}
}
return comments;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
import javax.tools.StandardLocation;

import xyz.block.ftl.Config;
import xyz.block.ftl.Enum;
import xyz.block.ftl.Export;
import xyz.block.ftl.Secret;
import xyz.block.ftl.TypeAlias;
import xyz.block.ftl.Verb;

/**
Expand All @@ -52,7 +54,7 @@ public Set<String> getSupportedOptions() {

@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of(Verb.class.getName(), Export.class.getName());
return Set.of(Verb.class.getName(), Enum.class.getName(), Export.class.getName(), TypeAlias.class.getName());
}

@Override
Expand All @@ -68,32 +70,26 @@ public void init(ProcessingEnvironment processingEnv) {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//TODO: @VerbName, HTTP, CRON etc
roundEnv.getElementsAnnotatedWithAny(Set.of(Verb.class, Export.class))
roundEnv.getElementsAnnotatedWithAny(Set.of(Verb.class, Enum.class, Export.class, TypeAlias.class))
.forEach(element -> {
Optional<String> javadoc = getJavadoc(element);

javadoc.ifPresent(doc -> {
String strippedDownDoc = stripJavadocTags(doc);
String key = element.getSimpleName().toString();

if (element.getKind() == ElementKind.METHOD) {
saved.put("verb." + key, strippedDownDoc);
} else if (element.getKind() == ElementKind.CLASS) {
saved.put("data." + key, strippedDownDoc);
} else if (element.getKind() == ElementKind.ENUM) {
saved.put("enum." + key, strippedDownDoc);
if (element.getAnnotation(TypeAlias.class) != null) {
saved.put(element.getAnnotation(TypeAlias.class).name(), strippedDownDoc);
} else {
saved.put(element.getSimpleName().toString(), strippedDownDoc);
}

if (element.getKind() == ElementKind.METHOD) {
var executableElement = (ExecutableElement) element;
executableElement.getParameters().forEach(param -> {
Config config = param.getAnnotation(Config.class);
if (config != null) {
saved.put("config." + config.value(), extractCommentForParam(doc, param));
saved.put(config.value(), extractCommentForParam(doc, param));
}
Secret secret = param.getAnnotation(Secret.class);
if (secret != null) {
saved.put("secret." + secret.value(), extractCommentForParam(doc, param));
saved.put(secret.value(), extractCommentForParam(doc, param));
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import xyz.block.ftl.Enum;

/**
* Comment on TypeEnum
*/
@Enum
public interface Animal {
@JsonIgnore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import org.jetbrains.annotations.NotNull;

/**
* Comment on Type Enum variant
*/
public class Cat implements Animal {
private @NotNull String name;

Expand Down
Loading
Loading