From 8b1f40b3c12b2f5e1d94b421581029c17b9fb8b7 Mon Sep 17 00:00:00 2001 From: Tom Daffurn Date: Wed, 2 Oct 2024 12:39:31 +1000 Subject: [PATCH] Extract schema from type enums --- .../block/ftl/deployment/EnumProcessor.java | 136 +++++++++++------- .../block/ftl/deployment/ModuleBuilder.java | 22 ++- .../main/java/xyz/block/ftl/enums/Animal.java | 14 ++ .../main/java/xyz/block/ftl/enums/Cat.java | 22 +++ .../main/java/xyz/block/ftl/enums/Dog.java | 22 +++ .../main/java/xyz/block/ftl/enums/Verbs.java | 6 + 6 files changed, 155 insertions(+), 67 deletions(-) create mode 100644 jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Animal.java create mode 100644 jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Cat.java create mode 100644 jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Dog.java 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 e5d04dee14..1b06e67f3f 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 @@ -10,8 +10,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.Type; @@ -26,6 +28,7 @@ import xyz.block.ftl.v1.schema.Int; import xyz.block.ftl.v1.schema.IntValue; import xyz.block.ftl.v1.schema.StringValue; +import xyz.block.ftl.v1.schema.TypeValue; import xyz.block.ftl.v1.schema.Value; public class EnumProcessor { @@ -36,67 +39,94 @@ public class EnumProcessor { SchemaContributorBuildItem handleEnums(CombinedIndexBuildItem index) { var enumAnnotations = index.getIndex().getAnnotations(FTLDotNames.ENUM); log.info("Processing {} enum annotations into decls", enumAnnotations.size()); - List decls = new ArrayList<>(); - try { - // TODO how do we exclude @Enum annotations from generated verb clients? - for (var enumAnnotation : enumAnnotations) { - boolean exported = enumAnnotation.target().hasAnnotation(FTLDotNames.EXPORT); - ClassInfo enumClassInfo = enumAnnotation.target().asClass(); - if (enumClassInfo.hasDeclaredAnnotation(GENERATED_REF)) { - continue; - } - Enum.Builder enumBuilder = Enum.newBuilder() - .setName(enumClassInfo.simpleName()) - .setExport(exported); - if (enumClassInfo.isEnum()) { - // Value enums must have a type - FieldInfo valueField = enumClassInfo.field("value"); - if (valueField == null) { - throw new RuntimeException("Enum must have a 'value' field: " + enumClassInfo.name()); - } - Type type = valueField.type(); - xyz.block.ftl.v1.schema.Type.Builder typeBuilder = xyz.block.ftl.v1.schema.Type.newBuilder(); - if (isInt(type)) { - typeBuilder.setInt(Int.newBuilder().build()).build(); - } else if (type.name().equals(DotName.STRING_NAME)) { - typeBuilder.setString(xyz.block.ftl.v1.schema.String.newBuilder().build()); - } else { - throw new RuntimeException( - "Enum value type must be String, int, long, short, or byte: " + enumClassInfo.name()); - } - enumBuilder.setType(typeBuilder.build()); - Class enumClass = Class.forName(enumClassInfo.name().toString(), false, - Thread.currentThread().getContextClassLoader()); - for (var constant : enumClass.getEnumConstants()) { - Field value = constant.getClass().getDeclaredField("value"); - value.setAccessible(true); - Value.Builder valueBuilder = Value.newBuilder(); - if (isInt(type)) { - long aLong = value.getLong(constant); - valueBuilder.setIntValue(IntValue.newBuilder().setValue(aLong).build()); + return new SchemaContributorBuildItem(new Consumer() { + @Override + public void accept(ModuleBuilder moduleBuilder) { + List decls = new ArrayList<>(); + try { + for (var enumAnnotation : enumAnnotations) { + boolean exported = enumAnnotation.target().hasAnnotation(FTLDotNames.EXPORT); + ClassInfo enumClassInfo = enumAnnotation.target().asClass(); + if (enumClassInfo.hasDeclaredAnnotation(GENERATED_REF)) { + continue; + } + Enum.Builder enumBuilder = Enum.newBuilder() + .setName(enumClassInfo.simpleName()) + .setExport(exported); + if (enumClassInfo.isEnum()) { + decls.add(extractValueEnum(enumClassInfo, enumBuilder)); } else { - String aString = (String) value.get(constant); - valueBuilder.setStringValue(StringValue.newBuilder().setValue(aString).build()); + // Type enums + var variants = index.getComputingIndex().getAllKnownImplementors(enumClassInfo.name()); + if (variants.isEmpty()) { + throw new RuntimeException("No variants found for enum: " + enumBuilder.getName()); + } + for (var variant : variants) { + if (variant.hasDeclaredAnnotation(GENERATED_REF)) { + continue; + } + Type variantType = ClassType.builder(variant.name()).build(); + xyz.block.ftl.v1.schema.Type declType = moduleBuilder.buildType(variantType, exported); + TypeValue typeValue = TypeValue.newBuilder().setValue(declType).build(); + + EnumVariant.Builder variantBuilder = EnumVariant.newBuilder() + .setName(variant.simpleName()) + .setValue(Value.newBuilder().setTypeValue(typeValue).build()); + enumBuilder.addVariants(variantBuilder.build()); + } + decls.add(Decl.newBuilder().setEnum(enumBuilder).build()); } - EnumVariant variant = EnumVariant.newBuilder() - .setName(constant.toString()) - .setValue(valueBuilder) - .build(); - enumBuilder.addVariants(variant); } - // TODO move outside if - decls.add(Decl.newBuilder().setEnum(enumBuilder).build()); - } else { - // Type enums - // TODO + for (var decl : decls) { + moduleBuilder.addDecls(decl); + } + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); } + } + }); + } + + private Decl extractValueEnum(ClassInfo enumClassInfo, Enum.Builder enumBuilder) + throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + // Value enums must have a type + FieldInfo valueField = enumClassInfo.field("value"); + if (valueField == null) { + throw new RuntimeException("Enum must have a 'value' field: " + enumClassInfo.name()); + } + Type type = valueField.type(); + xyz.block.ftl.v1.schema.Type.Builder typeBuilder = xyz.block.ftl.v1.schema.Type.newBuilder(); + if (isInt(type)) { + typeBuilder.setInt(Int.newBuilder().build()).build(); + } else if (type.name().equals(DotName.STRING_NAME)) { + typeBuilder.setString(xyz.block.ftl.v1.schema.String.newBuilder().build()); + } else { + throw new RuntimeException( + "Enum value type must be String, int, long, short, or byte: " + enumClassInfo.name()); + } + enumBuilder.setType(typeBuilder.build()); + Class enumClass = Class.forName(enumClassInfo.name().toString(), false, + Thread.currentThread().getContextClassLoader()); + for (var constant : enumClass.getEnumConstants()) { + Field value = constant.getClass().getDeclaredField("value"); + value.setAccessible(true); + Value.Builder valueBuilder = Value.newBuilder(); + if (isInt(type)) { + long aLong = value.getLong(constant); + valueBuilder.setIntValue(IntValue.newBuilder().setValue(aLong).build()); + } else { + String aString = (String) value.get(constant); + valueBuilder.setStringValue(StringValue.newBuilder().setValue(aString).build()); } - return new SchemaContributorBuildItem(decls); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); + EnumVariant variant = EnumVariant.newBuilder() + .setName(constant.toString()) + .setValue(valueBuilder) + .build(); + enumBuilder.addVariants(variant); } + return Decl.newBuilder().setEnum(enumBuilder).build(); } private boolean isInt(Type type) { 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 78abc19857..c84a23da1b 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 @@ -345,14 +345,11 @@ public Type buildType(org.jboss.jandex.Type type, boolean export) { return Type.newBuilder().setTime(Time.newBuilder().build()).build(); } - var ref = Type.newBuilder().setRef(Ref.newBuilder() - .setName(clazz.name().local()) - .setModule(moduleName) - .build()) - .build(); + var ref = Type.newBuilder().setRef( + Ref.newBuilder().setName(clazz.name().local()).setModule(moduleName).build()).build(); if (info.isEnum() || info.hasAnnotation(ENUM)) { - // We set only the name and export here. EnumProcessor will fill in the rest + // 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() .setName(clazz.name().local()) .setExport(type.hasAnnotation(EXPORT) || export) @@ -360,11 +357,10 @@ public Type buildType(org.jboss.jandex.Type type, boolean export) { addDecls(Decl.newBuilder().setEnum(ennum).build()); return ref; } else { - // If we've processed this data already, skip early + // If this data was processed already, skip early if (updateData(clazz.name().local(), type.hasAnnotation(EXPORT) || export)) { return ref; } - Data.Builder data = Data.newBuilder(); data.setName(clazz.name().local()); data.setExport(type.hasAnnotation(EXPORT) || export); @@ -529,12 +525,10 @@ private boolean updateEnum(String name, Decl decl) { duplicateNameValidationError(name, decl.getEnum().getPos()); } var moreComplete = decl.getEnum().getVariantsCount() > 0 ? decl : existing; - var merged = existing.getEnum().toBuilder() - .setName(moreComplete.getEnum().getName()) - .setExport(decl.getEnum().getExport() || existing.getEnum().getExport()) - .addAllVariants(moreComplete.getEnum().getVariantsList()) - .addAllComments(moreComplete.getEnum().getCommentsList()) - .setType(moreComplete.getEnum().getType()).build(); + var lessComplete = decl.getEnum().getVariantsCount() > 0 ? existing : decl; + var merged = moreComplete.getEnum().toBuilder() + .setExport(lessComplete.getEnum().getExport() || existing.getEnum().getExport()) + .build(); decls.put(name, Decl.newBuilder().setEnum(merged).build()); return true; } 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 new file mode 100644 index 0000000000..bb11aa0584 --- /dev/null +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Animal.java @@ -0,0 +1,14 @@ +package xyz.block.ftl.enums; + +import xyz.block.ftl.Enum; + +@Enum +public interface Animal { + public boolean isCat(); + + public boolean isDog(); + + public Cat getCat(); + + public Dog getDog(); +} 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 new file mode 100644 index 0000000000..03960a24c6 --- /dev/null +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Cat.java @@ -0,0 +1,22 @@ +package xyz.block.ftl.enums; + +import xyz.block.ftl.EnumVariant; + +@EnumVariant +public class Cat implements Animal { + public boolean isCat() { + return true; + } + + public boolean isDog() { + return false; + } + + public Cat getCat() { + return this; + } + + public Dog getDog() { + return null; + } +} diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Dog.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Dog.java new file mode 100644 index 0000000000..d59ea4b8b5 --- /dev/null +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Dog.java @@ -0,0 +1,22 @@ +package xyz.block.ftl.enums; + +import xyz.block.ftl.EnumVariant; + +@EnumVariant +public class Dog implements Animal { + public boolean isCat() { + return false; + } + + public boolean isDog() { + return true; + } + + public Cat getCat() { + return null; + } + + public Dog getDog() { + return this; + } +} diff --git a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Verbs.java b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Verbs.java index 3873dbfa5f..6a2e528ddb 100644 --- a/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Verbs.java +++ b/jvm-runtime/testdata/java/javaserver/src/main/java/xyz/block/ftl/enums/Verbs.java @@ -32,4 +32,10 @@ public ColorInt valueEnumVerb(ColorInt color) { public Shape stringEnumVerb(Shape shape) { return shape; } + + @Export + @Verb + public Animal typeEnumVerb(Animal animal) { + return animal; + } }