Skip to content

Commit

Permalink
Extract schema from type enums
Browse files Browse the repository at this point in the history
  • Loading branch information
tomdaffurn committed Oct 2, 2024
1 parent 15fc6b7 commit 8b1f40b
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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<Decl> 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<ModuleBuilder>() {
@Override
public void accept(ModuleBuilder moduleBuilder) {
List<Decl> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,26 +345,22 @@ 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)
.build();
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);
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

0 comments on commit 8b1f40b

Please sign in to comment.