Skip to content

Commit 58ce5a0

Browse files
fix: use typemap in JVM (#2556)
fixes: #2513 fixes: #2510
1 parent 0db00b2 commit 58ce5a0

File tree

24 files changed

+609
-65
lines changed

24 files changed

+609
-65
lines changed

jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/FTLDotNames.java

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import xyz.block.ftl.LeaseClient;
99
import xyz.block.ftl.Secret;
1010
import xyz.block.ftl.Subscription;
11+
import xyz.block.ftl.TypeAlias;
12+
import xyz.block.ftl.TypeAliasMapper;
1113
import xyz.block.ftl.Verb;
1214

1315
public class FTLDotNames {
@@ -21,6 +23,8 @@ private FTLDotNames() {
2123
public static final DotName EXPORT = DotName.createSimple(Export.class);
2224
public static final DotName VERB = DotName.createSimple(Verb.class);
2325
public static final DotName CRON = DotName.createSimple(Cron.class);
26+
public static final DotName TYPE_ALIAS_MAPPER = DotName.createSimple(TypeAliasMapper.class);
27+
public static final DotName TYPE_ALIAS = DotName.createSimple(TypeAlias.class);
2428
public static final DotName SUBSCRIPTION = DotName.createSimple(Subscription.class);
2529
public static final DotName LEASE_CLIENT = DotName.createSimple(LeaseClient.class);
2630
}

jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/JVMCodeGenerator.java

+37-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.HashMap;
88
import java.util.List;
99
import java.util.Map;
10+
import java.util.Optional;
1011
import java.util.stream.Stream;
1112

1213
import org.eclipse.microprofile.config.Config;
@@ -24,6 +25,7 @@
2425
public abstract class JVMCodeGenerator implements CodeGenProvider {
2526

2627
public static final String PACKAGE_PREFIX = "ftl.";
28+
public static final String TYPE_MAPPER = "TypeAliasMapper";
2729

2830
@Override
2931
public String providerId() {
@@ -42,6 +44,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
4244
}
4345
List<Module> modules = new ArrayList<>();
4446
Map<DeclRef, Type> typeAliasMap = new HashMap<>();
47+
Map<DeclRef, String> nativeTypeAliasMap = new HashMap<>();
4548
try (Stream<Path> pathStream = Files.list(context.inputDir())) {
4649
for (var file : pathStream.toList()) {
4750
String fileName = file.getFileName().toString();
@@ -50,9 +53,30 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
5053
}
5154
var module = Module.parseFrom(Files.readAllBytes(file));
5255
for (var decl : module.getDeclsList()) {
56+
String packageName = PACKAGE_PREFIX + module.getName();
5357
if (decl.hasTypeAlias()) {
5458
var data = decl.getTypeAlias();
55-
typeAliasMap.put(new DeclRef(module.getName(), data.getName()), data.getType());
59+
boolean handled = false;
60+
for (var md : data.getMetadataList()) {
61+
if (md.hasTypeMap()) {
62+
String runtime = md.getTypeMap().getRuntime();
63+
if (runtime.equals("kotlin") || runtime.equals("java")) {
64+
nativeTypeAliasMap.put(new DeclRef(module.getName(), data.getName()),
65+
md.getTypeMap().getNativeName());
66+
generateTypeAliasMapper(module.getName(), data.getName(), packageName,
67+
Optional.of(md.getTypeMap().getNativeName()),
68+
context.outDir());
69+
handled = true;
70+
break;
71+
}
72+
}
73+
}
74+
if (!handled) {
75+
generateTypeAliasMapper(module.getName(), data.getName(), packageName, Optional.empty(),
76+
context.outDir());
77+
typeAliasMap.put(new DeclRef(module.getName(), data.getName()), data.getType());
78+
}
79+
5680
}
5781
}
5882
modules.add(module);
@@ -69,26 +93,27 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
6993
if (!verb.getExport()) {
7094
continue;
7195
}
72-
generateVerb(module, verb, packageName, typeAliasMap, context.outDir());
96+
generateVerb(module, verb, packageName, typeAliasMap, nativeTypeAliasMap, context.outDir());
7397
} else if (decl.hasData()) {
7498
var data = decl.getData();
7599
if (!data.getExport()) {
76100
continue;
77101
}
78-
generateDataObject(module, data, packageName, typeAliasMap, context.outDir());
102+
generateDataObject(module, data, packageName, typeAliasMap, nativeTypeAliasMap, context.outDir());
79103

80104
} else if (decl.hasEnum()) {
81105
var data = decl.getEnum();
82106
if (!data.getExport()) {
83107
continue;
84108
}
85-
generateEnum(module, data, packageName, typeAliasMap, context.outDir());
109+
generateEnum(module, data, packageName, typeAliasMap, nativeTypeAliasMap, context.outDir());
86110
} else if (decl.hasTopic()) {
87111
var data = decl.getTopic();
88112
if (!data.getExport()) {
89113
continue;
90114
}
91-
generateTopicSubscription(module, data, packageName, typeAliasMap, context.outDir());
115+
generateTopicSubscription(module, data, packageName, typeAliasMap, nativeTypeAliasMap,
116+
context.outDir());
92117
}
93118
}
94119
}
@@ -99,17 +124,20 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
99124
return true;
100125
}
101126

127+
protected abstract void generateTypeAliasMapper(String module, String name, String packageName,
128+
Optional<String> nativeTypeAlias, Path outputDir) throws IOException;
129+
102130
protected abstract void generateTopicSubscription(Module module, Topic data, String packageName,
103-
Map<DeclRef, Type> typeAliasMap, Path outputDir) throws IOException;
131+
Map<DeclRef, Type> typeAliasMap, Map<DeclRef, String> nativeTypeAliasMap, Path outputDir) throws IOException;
104132

105133
protected abstract void generateEnum(Module module, Enum data, String packageName, Map<DeclRef, Type> typeAliasMap,
106-
Path outputDir) throws IOException;
134+
Map<DeclRef, String> nativeTypeAliasMap, Path outputDir) throws IOException;
107135

108136
protected abstract void generateDataObject(Module module, Data data, String packageName, Map<DeclRef, Type> typeAliasMap,
109-
Path outputDir) throws IOException;
137+
Map<DeclRef, String> nativeTypeAliasMap, Path outputDir) throws IOException;
110138

111139
protected abstract void generateVerb(Module module, Verb verb, String packageName, Map<DeclRef, Type> typeAliasMap,
112-
Path outputDir) throws IOException;
140+
Map<DeclRef, String> nativeTypeAliasMap, Path outputDir) throws IOException;
113141

114142
@Override
115143
public boolean shouldRun(Path sourceDir, Config config) {

jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleBuilder.java

+13-5
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@
5353
import xyz.block.ftl.v1.schema.Metadata;
5454
import xyz.block.ftl.v1.schema.MetadataAlias;
5555
import xyz.block.ftl.v1.schema.MetadataCalls;
56+
import xyz.block.ftl.v1.schema.MetadataTypeMap;
5657
import xyz.block.ftl.v1.schema.Module;
5758
import xyz.block.ftl.v1.schema.Optional;
5859
import xyz.block.ftl.v1.schema.Ref;
5960
import xyz.block.ftl.v1.schema.Time;
6061
import xyz.block.ftl.v1.schema.Type;
62+
import xyz.block.ftl.v1.schema.TypeAlias;
6163
import xyz.block.ftl.v1.schema.Unit;
6264
import xyz.block.ftl.v1.schema.Verb;
6365

@@ -75,7 +77,7 @@ public class ModuleBuilder {
7577

7678
private final IndexView index;
7779
private final Module.Builder moduleBuilder;
78-
private final Map<TypeKey, ExistingRef> dataElements = new HashMap<>();
80+
private final Map<TypeKey, ExistingRef> dataElements;
7981
private final String moduleName;
8082
private final Set<String> knownSecrets = new HashSet<>();
8183
private final Set<String> knownConfig = new HashSet<>();
@@ -86,7 +88,7 @@ public class ModuleBuilder {
8688

8789
public ModuleBuilder(IndexView index, String moduleName, Map<DotName, TopicsBuildItem.DiscoveredTopic> knownTopics,
8890
Map<DotName, VerbClientBuildItem.DiscoveredClients> verbClients, FTLRecorder recorder,
89-
Map<String, String> verbDocs) {
91+
Map<String, String> verbDocs, Map<TypeKey, ExistingRef> typeAliases) {
9092
this.index = index;
9193
this.moduleName = moduleName;
9294
this.moduleBuilder = Module.newBuilder()
@@ -96,6 +98,7 @@ public ModuleBuilder(IndexView index, String moduleName, Map<DotName, TopicsBuil
9698
this.verbClients = verbClients;
9799
this.recorder = recorder;
98100
this.verbDocs = verbDocs;
101+
this.dataElements = new HashMap<>(typeAliases);
99102
}
100103

101104
public static @NotNull String methodToName(MethodInfo method) {
@@ -435,11 +438,16 @@ public void writeTo(OutputStream out) throws IOException {
435438
moduleBuilder.build().writeTo(out);
436439
}
437440

438-
record ExistingRef(Ref ref, boolean exported) {
439-
441+
public void registerTypeAlias(String name, org.jboss.jandex.Type finalT, org.jboss.jandex.Type finalS, boolean exported) {
442+
moduleBuilder.addDecls(Decl.newBuilder()
443+
.setTypeAlias(TypeAlias.newBuilder().setType(buildType(finalS, exported)).setName(name).addMetadata(Metadata
444+
.newBuilder()
445+
.setTypeMap(MetadataTypeMap.newBuilder().setRuntime("java").setNativeName(finalT.toString()).build())
446+
.build()))
447+
.build());
440448
}
441449

442-
private record TypeKey(String name, List<String> typeParams) {
450+
record ExistingRef(Ref ref, boolean exported) {
443451

444452
}
445453

jvm-runtime/ftl-runtime/common/deployment/src/main/java/xyz/block/ftl/deployment/ModuleProcessor.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.stream.Collectors;
1515

1616
import org.jboss.jandex.DotName;
17+
import org.jboss.jandex.ParameterizedType;
1718
import org.jboss.logging.Logger;
1819
import org.tomlj.Toml;
1920
import org.tomlj.TomlParseResult;
@@ -42,6 +43,7 @@
4243
import xyz.block.ftl.runtime.VerbRegistry;
4344
import xyz.block.ftl.runtime.config.FTLConfigSourceFactoryBuilder;
4445
import xyz.block.ftl.runtime.http.FTLHttpHandler;
46+
import xyz.block.ftl.v1.schema.Ref;
4547

4648
public class ModuleProcessor {
4749

@@ -108,6 +110,7 @@ public void generateSchema(CombinedIndexBuildItem index,
108110
ModuleNameBuildItem moduleNameBuildItem,
109111
TopicsBuildItem topicsBuildItem,
110112
VerbClientBuildItem verbClientBuildItem,
113+
List<TypeAliasBuildItem> typeAliasBuildItems,
111114
List<SchemaContributorBuildItem> schemaContributorBuildItems) throws Exception {
112115
String moduleName = moduleNameBuildItem.getModuleName();
113116
Map<String, String> verbDocs = new HashMap<>();
@@ -125,9 +128,25 @@ public void generateSchema(CombinedIndexBuildItem index,
125128
}
126129
}
127130
}
131+
Map<TypeKey, ModuleBuilder.ExistingRef> existingRefs = new HashMap<>();
132+
for (var i : typeAliasBuildItems) {
133+
String mn;
134+
if (i.getModule().isEmpty()) {
135+
mn = moduleNameBuildItem.getModuleName();
136+
} else {
137+
mn = i.getModule();
138+
}
139+
if (i.getLocalType() instanceof ParameterizedType) {
140+
//TODO: we can't handle this yet
141+
// existingRefs.put(new TypeKey(i.getLocalType().name().toString(), i.getLocalType().asParameterizedType().arguments().stream().map(i.)), new ModuleBuilder.ExistingRef(Ref.newBuilder().setModule(moduleName).setName(i.getName()).build(), i.isExported()));
142+
} else {
143+
existingRefs.put(new TypeKey(i.getLocalType().name().toString(), List.of()), new ModuleBuilder.ExistingRef(
144+
Ref.newBuilder().setModule(mn).setName(i.getName()).build(), i.isExported()));
145+
}
146+
}
128147

129148
ModuleBuilder moduleBuilder = new ModuleBuilder(index.getComputingIndex(), moduleName, topicsBuildItem.getTopics(),
130-
verbClientBuildItem.getVerbClients(), recorder, verbDocs);
149+
verbClientBuildItem.getVerbClients(), recorder, verbDocs, existingRefs);
131150

132151
for (var i : schemaContributorBuildItems) {
133152
i.getSchemaContributor().accept(moduleBuilder);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package xyz.block.ftl.deployment;
2+
3+
import org.jboss.jandex.Type;
4+
5+
import io.quarkus.builder.item.MultiBuildItem;
6+
7+
public final class TypeAliasBuildItem extends MultiBuildItem {
8+
9+
final String name;
10+
final String module;
11+
final Type localType;
12+
final Type serializedType;
13+
final boolean exported;
14+
15+
public TypeAliasBuildItem(String name, String module, Type localType, Type serializedType, boolean exported) {
16+
this.name = name;
17+
this.module = module;
18+
this.localType = localType;
19+
this.serializedType = serializedType;
20+
this.exported = exported;
21+
}
22+
23+
public String getName() {
24+
return name;
25+
}
26+
27+
public String getModule() {
28+
return module;
29+
}
30+
31+
public Type getLocalType() {
32+
return localType;
33+
}
34+
35+
public Type getSerializedType() {
36+
return serializedType;
37+
}
38+
39+
public boolean isExported() {
40+
return exported;
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package xyz.block.ftl.deployment;
2+
3+
import org.jboss.jandex.Type;
4+
import org.jboss.jandex.TypeVariable;
5+
6+
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
7+
import io.quarkus.deployment.annotations.BuildProducer;
8+
import io.quarkus.deployment.annotations.BuildStep;
9+
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
10+
11+
public class TypeAliasProcessor {
12+
13+
@BuildStep
14+
public void processTypeAlias(CombinedIndexBuildItem index,
15+
BuildProducer<SchemaContributorBuildItem> schemaContributorBuildItemBuildProducer,
16+
BuildProducer<AdditionalBeanBuildItem> additionalBeanBuildItem,
17+
BuildProducer<TypeAliasBuildItem> typeAliasBuildItemBuildProducer) {
18+
var beans = new AdditionalBeanBuildItem.Builder().setUnremovable();
19+
for (var mapper : index.getIndex().getAnnotations(FTLDotNames.TYPE_ALIAS)) {
20+
boolean exported = mapper.target().hasAnnotation(FTLDotNames.EXPORT);
21+
// This may or may not be the actual mapper, it may be a subclass
22+
23+
var mapperClass = mapper.target().asClass();
24+
var actualMapper = mapperClass;
25+
26+
Type t = null;
27+
Type s = null;
28+
if (mapperClass.isInterface()) {
29+
for (var i : mapperClass.interfaceTypes()) {
30+
if (i.name().equals(FTLDotNames.TYPE_ALIAS_MAPPER)) {
31+
t = i.asParameterizedType().arguments().get(0);
32+
s = i.asParameterizedType().arguments().get(1);
33+
break;
34+
}
35+
}
36+
var implementations = index.getComputingIndex().getAllKnownImplementors(mapperClass.name());
37+
if (implementations.isEmpty()) {
38+
continue;
39+
}
40+
if (implementations.size() > 1) {
41+
throw new RuntimeException(
42+
"Multiple implementations of " + mapperClass.name() + " found: " + implementations);
43+
}
44+
actualMapper = implementations.iterator().next();
45+
}
46+
47+
//TODO: this is a bit hacky and won't work for complex heirachies
48+
// it is enough to get us going through
49+
for (var i : actualMapper.interfaceTypes()) {
50+
if (i.name().equals(FTLDotNames.TYPE_ALIAS_MAPPER)) {
51+
t = i.asParameterizedType().arguments().get(0);
52+
s = i.asParameterizedType().arguments().get(1);
53+
break;
54+
} else if (i.name().equals(mapperClass.name())) {
55+
if (t instanceof TypeVariable) {
56+
t = i.asParameterizedType().arguments().get(0);
57+
}
58+
if (s instanceof TypeVariable) {
59+
s = i.asParameterizedType().arguments().get(1);
60+
}
61+
break;
62+
}
63+
}
64+
65+
beans.addBeanClass(actualMapper.name().toString());
66+
var finalT = t;
67+
var finalS = s;
68+
String module = mapper.value("module") == null ? "" : mapper.value("module").asString();
69+
String name = mapper.value("name").asString();
70+
typeAliasBuildItemBuildProducer.produce(new TypeAliasBuildItem(name, module, t, s, exported));
71+
if (module.isEmpty()) {
72+
schemaContributorBuildItemBuildProducer.produce(new SchemaContributorBuildItem(moduleBuilder -> moduleBuilder
73+
.registerTypeAlias(name, finalT, finalS, exported)));
74+
}
75+
76+
}
77+
additionalBeanBuildItem.produce(beans.build());
78+
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package xyz.block.ftl.deployment;
2+
3+
import java.util.List;
4+
5+
record TypeKey(String name, List<String> typeParams) {
6+
7+
}

jvm-runtime/ftl-runtime/common/runtime/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
<groupId>org.jetbrains</groupId>
5858
<artifactId>annotations</artifactId>
5959
</dependency>
60+
<dependency>
61+
<groupId>org.junit.jupiter</groupId>
62+
<artifactId>junit-jupiter</artifactId>
63+
<scope>test</scope>
64+
</dependency>
6065
</dependencies>
6166

6267
<build>

0 commit comments

Comments
 (0)