diff --git a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/CommentKey.java b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/CommentKey.java deleted file mode 100644 index 0b1c64bca7..0000000000 --- a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/CommentKey.java +++ /dev/null @@ -1,23 +0,0 @@ -package xyz.block.ftl.deployment; - -public class CommentKey { - public static String ofVerb(String verb) { - return "verb." + verb; - } - - public static String ofData(String data) { - return "data." + data; - } - - public static String ofEnum(String enumName) { - return "enum." + enumName; - } - - public static String ofConfig(String config) { - return "config." + config; - } - - public static String ofSecret(String secret) { - return "secret." + secret; - } -} diff --git a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/CommentsBuildItem.java b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/CommentsBuildItem.java new file mode 100644 index 0000000000..0e70890de8 --- /dev/null +++ b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/CommentsBuildItem.java @@ -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> comments; + + public CommentsBuildItem(Map> comments) { + this.comments = comments; + } + + public Iterable getComments(String name) { + return comments.getOrDefault(name, List.of()); + } +} diff --git a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/EnumProcessor.java b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/EnumProcessor.java index 04a1db79a6..66c26ccfd2 100644 --- a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/EnumProcessor.java +++ b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/EnumProcessor.java @@ -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); } @@ -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 extractEnumDecls(CombinedIndexBuildItem index, Collection enumAnnotations, - FTLRecorder recorder, ModuleBuilder moduleBuilder) + FTLRecorder recorder, ModuleBuilder moduleBuilder, CommentsBuildItem commentsBuildItem) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { List decls = new ArrayList<>(); for (var enumAnnotation : enumAnnotations) { @@ -78,10 +81,10 @@ private List extractEnumDecls(CombinedIndexBuildItem index, Collection extractEnumDecls(CombinedIndexBuildItem index, Collection 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()); @@ -144,10 +150,13 @@ private record TypeEnum(Decl decl, List> variantClasses) { * - a class with arbitrary fields
*/ 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()); diff --git a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleBuilder.java b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleBuilder.java index 238a0bc409..63639ece62 100644 --- a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleBuilder.java +++ b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleBuilder.java @@ -95,13 +95,13 @@ public class ModuleBuilder { private final Map knownTopics; private final Map verbClients; private final FTLRecorder recorder; - private final Map> comments; + private final CommentsBuildItem comments; private final List validationFailures = new ArrayList<>(); private final boolean defaultToOptional; public ModuleBuilder(IndexView index, String moduleName, Map knownTopics, Map verbClients, FTLRecorder recorder, - Map> comments, boolean defaultToOptional) { + CommentsBuildItem comments, boolean defaultToOptional) { this.index = index; this.moduleName = moduleName; this.protoModuleBuilder = Module.newBuilder() @@ -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); } @@ -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); } @@ -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); } @@ -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; @@ -555,10 +548,11 @@ 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 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()); @@ -566,9 +560,7 @@ public void registerTypeAlias(String name, org.jboss.jandex.Type finalT, org.jbo 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()); } /** diff --git a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleProcessor.java b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleProcessor.java index 41f230d260..cb3ae949d7 100644 --- a/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleProcessor.java +++ b/jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleProcessor.java @@ -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; @@ -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> 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, @@ -110,9 +134,9 @@ public void generateSchema(CombinedIndexBuildItem index, TopicsBuildItem topicsBuildItem, VerbClientBuildItem verbClientBuildItem, DefaultOptionalBuildItem defaultOptionalBuildItem, - List schemaContributorBuildItems) throws Exception { + List schemaContributorBuildItems, + CommentsBuildItem comments) throws Exception { String moduleName = moduleNameBuildItem.getModuleName(); - Map> comments = readComments(); ModuleBuilder moduleBuilder = new ModuleBuilder(index.getComputingIndex(), moduleName, topicsBuildItem.getTopics(), verbClientBuildItem.getVerbClients(), recorder, comments, @@ -172,27 +196,4 @@ void openSocket(BuildProducer 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> readComments() throws IOException { - Map> 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; - } } diff --git a/jvm-runtime/ftl-runtime/common/runtime/src/main/java/xyz/block/ftl/runtime/processor/AnnotationProcessor.java b/jvm-runtime/ftl-runtime/common/runtime/src/main/java/xyz/block/ftl/runtime/processor/AnnotationProcessor.java index 326174237b..429c6be528 100644 --- a/jvm-runtime/ftl-runtime/common/runtime/src/main/java/xyz/block/ftl/runtime/processor/AnnotationProcessor.java +++ b/jvm-runtime/ftl-runtime/common/runtime/src/main/java/xyz/block/ftl/runtime/processor/AnnotationProcessor.java @@ -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; /** @@ -52,7 +54,7 @@ public Set getSupportedOptions() { @Override public Set 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 @@ -68,32 +70,26 @@ public void init(ProcessingEnvironment processingEnv) { @Override public boolean process(Set 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 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)); } }); } diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Animal.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Animal.java index 2e86fb1582..e6b14ba365 100644 --- a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Animal.java +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Animal.java @@ -4,6 +4,9 @@ import xyz.block.ftl.Enum; +/** + * Comment on TypeEnum + */ @Enum public interface Animal { @JsonIgnore diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Cat.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Cat.java index 3c7bce3e8c..0859614fb8 100644 --- a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Cat.java +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Cat.java @@ -2,6 +2,9 @@ import org.jetbrains.annotations.NotNull; +/** + * Comment on Type Enum variant + */ public class Cat implements Animal { private @NotNull String name; diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CommentedModule.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CommentedModule.java index a7fb5c4f03..4c4b6bc110 100644 --- a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CommentedModule.java +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CommentedModule.java @@ -29,5 +29,5 @@ public class CommentedModule { return EnumType.PORTENTOUS; } - //TODO TypeAlias, Database, Topic, Subscription, Lease, Cron + //TODO Database, Topic, Subscription, Lease, Cron } diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CustomSerializedType.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CustomSerializedType.java new file mode 100644 index 0000000000..f6e5d74abd --- /dev/null +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CustomSerializedType.java @@ -0,0 +1,14 @@ +package xyz.block.ftl.test; + +public class CustomSerializedType { + + private final String value; + + public CustomSerializedType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CustomSerializedTypeMapper.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CustomSerializedTypeMapper.java new file mode 100644 index 0000000000..8bde2582d6 --- /dev/null +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/CustomSerializedTypeMapper.java @@ -0,0 +1,20 @@ +package xyz.block.ftl.test; + +import xyz.block.ftl.TypeAlias; +import xyz.block.ftl.TypeAliasMapper; + +/** + * Comment on a TypeAlias + */ +@TypeAlias(name = "CustomSerializedType") +public class CustomSerializedTypeMapper implements TypeAliasMapper { + @Override + public String encode(xyz.block.ftl.test.CustomSerializedType object) { + return object.getValue(); + } + + @Override + public xyz.block.ftl.test.CustomSerializedType decode(String serialized) { + return new xyz.block.ftl.test.CustomSerializedType(serialized); + } +} diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/EnumType.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/EnumType.java index f9508dbe96..48720e60b8 100644 --- a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/EnumType.java +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/javacomments/EnumType.java @@ -4,7 +4,7 @@ import xyz.block.ftl.Export; /** - * Comment on an enum type + * Comment on a value enum */ @Enum @Export