Skip to content

Commit

Permalink
fix: java type handling
Browse files Browse the repository at this point in the history
Improves the tests and fixes problems with the handling of some types,
there is still a significant amount of work to go.
  • Loading branch information
stuartwdouglas committed Aug 14, 2024
1 parent 2a3edbc commit b947adb
Show file tree
Hide file tree
Showing 15 changed files with 842 additions and 132 deletions.
11 changes: 11 additions & 0 deletions integration/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ func Chain(actions ...Action) Action {
}
}

// SubTests runs a list of individual actions as separate tests
func SubTests(tests ...SubTest) Action {
return func(t testing.TB, ic TestContext) {
for _, test := range tests {
ic.Run(test.Name, func(t *testing.T) {
ic.AssertWithRetry(t, test.Action)
})
}
}
}

// Repeat an action N times.
func Repeat(n int, action Action) Action {
return func(t testing.TB, ic TestContext) {
Expand Down
12 changes: 12 additions & 0 deletions integration/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ func run(t *testing.T, ftlConfigPath string, startController bool, requireJava b
workDir: tmpDir,
binDir: binDir,
Verbs: verbs,
realT: t,
}

if startController {
Expand Down Expand Up @@ -183,6 +184,12 @@ type TestContext struct {
Controller ftlv1connect.ControllerServiceClient
Console pbconsoleconnect.ConsoleServiceClient
Verbs ftlv1connect.VerbServiceClient

realT *testing.T
}

func (i TestContext) Run(name string, f func(t *testing.T)) bool {
return i.realT.Run(name, f)
}

// WorkingDir returns the temporary directory the test is executing in.
Expand Down Expand Up @@ -227,6 +234,11 @@ func (i TestContext) runAssertionOnce(t testing.TB, assertion Action) (err error

type Action func(t testing.TB, ic TestContext)

type SubTest struct {
Name string
Action Action
}

type logWriter struct {
mu sync.Mutex
logger interface{ Log(...any) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,20 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
typeBuilder.addSuperinterface(ClassName.get(VerbClientEmpty.class));
} else if (verb.getRequest().hasUnit()) {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSource.class),
toJavaTypeName(verb.getResponse(), typeAliasMap)));
toJavaTypeName(verb.getResponse(), typeAliasMap, true)));
typeBuilder.addMethod(MethodSpec.methodBuilder("call")
.returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap))
.addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build());
} else if (verb.getResponse().hasUnit()) {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSink.class),
toJavaTypeName(verb.getRequest(), typeAliasMap)));
toJavaTypeName(verb.getRequest(), typeAliasMap, true)));
typeBuilder.addMethod(MethodSpec.methodBuilder("call").returns(TypeName.VOID)
.addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap), "value")
.addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build());
} else {
typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClient.class),
toJavaTypeName(verb.getRequest(), typeAliasMap),
toJavaTypeName(verb.getResponse(), typeAliasMap)));
toJavaTypeName(verb.getRequest(), typeAliasMap, true),
toJavaTypeName(verb.getResponse(), typeAliasMap, true)));
typeBuilder.addMethod(MethodSpec.methodBuilder("call")
.returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap))
.addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap), "value")
Expand Down Expand Up @@ -274,52 +274,53 @@ private String toJavaName(String name) {
}

private TypeName toAnnotatedJavaTypeName(Type type, Map<Key, Type> typeAliasMap) {
var results = toJavaTypeName(type, typeAliasMap);
var results = toJavaTypeName(type, typeAliasMap, false);
if (type.hasRef() || type.hasArray() || type.hasBytes() || type.hasString() || type.hasMap() || type.hasTime()) {
return results.annotated(AnnotationSpec.builder(NotNull.class).build());
}
return results;
}

private TypeName toJavaTypeName(Type type, Map<Key, Type> typeAliasMap) {
private TypeName toJavaTypeName(Type type, Map<Key, Type> typeAliasMap, boolean boxPrimitives) {
if (type.hasArray()) {
return ParameterizedTypeName.get(ClassName.get(List.class),
toJavaTypeName(type.getArray().getElement(), typeAliasMap));
toJavaTypeName(type.getArray().getElement(), typeAliasMap, false));
} else if (type.hasString()) {
return ClassName.get(String.class);
} else if (type.hasOptional()) {
return toJavaTypeName(type.getOptional().getType(), typeAliasMap);
// Always box for optional, as normal primities can't be null
return toJavaTypeName(type.getOptional().getType(), typeAliasMap, true);
} else if (type.hasRef()) {
if (type.getRef().getModule().isEmpty()) {
return TypeVariableName.get(type.getRef().getName());
}

Key key = new Key(type.getRef().getModule(), type.getRef().getName());
if (typeAliasMap.containsKey(key)) {
return toJavaTypeName(typeAliasMap.get(key), typeAliasMap);
return toJavaTypeName(typeAliasMap.get(key), typeAliasMap, boxPrimitives);
}
var params = type.getRef().getTypeParametersList();
ClassName className = ClassName.get(PACKAGE_PREFIX + type.getRef().getModule(), type.getRef().getName());
if (params.isEmpty()) {
return className;
}
List<TypeName> javaTypes = params.stream()
.map(s -> s.hasUnit() ? WildcardTypeName.subtypeOf(Object.class) : toJavaTypeName(s, typeAliasMap))
.map(s -> s.hasUnit() ? WildcardTypeName.subtypeOf(Object.class) : toJavaTypeName(s, typeAliasMap, true))
.toList();
return ParameterizedTypeName.get(className, javaTypes.toArray(new TypeName[javaTypes.size()]));
} else if (type.hasMap()) {
return ParameterizedTypeName.get(ClassName.get(Map.class), toJavaTypeName(type.getMap().getKey(), typeAliasMap),
toJavaTypeName(type.getMap().getValue(), typeAliasMap));
return ParameterizedTypeName.get(ClassName.get(Map.class),
toJavaTypeName(type.getMap().getKey(), typeAliasMap, true),
toJavaTypeName(type.getMap().getValue(), typeAliasMap, true));
} else if (type.hasTime()) {
return ClassName.get(Instant.class);
} else if (type.hasInt()) {
return TypeName.LONG;
return boxPrimitives ? ClassName.get(Long.class) : TypeName.LONG;
} else if (type.hasUnit()) {
return TypeName.VOID;
} else if (type.hasBool()) {
return TypeName.BOOLEAN;
return boxPrimitives ? ClassName.get(Boolean.class) : TypeName.BOOLEAN;
} else if (type.hasFloat()) {
return TypeName.DOUBLE;
return boxPrimitives ? ClassName.get(Double.class) : TypeName.DOUBLE;
} else if (type.hasBytes()) {
return ArrayTypeName.of(TypeName.BYTE);
} else if (type.hasAny()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.EnumSet;
Expand All @@ -20,10 +21,12 @@

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ArrayType;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.VoidType;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.common.model.MethodParameter;
Expand Down Expand Up @@ -85,6 +88,7 @@
import xyz.block.ftl.v1.CallRequest;
import xyz.block.ftl.v1.schema.Array;
import xyz.block.ftl.v1.schema.Bool;
import xyz.block.ftl.v1.schema.Bytes;
import xyz.block.ftl.v1.schema.Data;
import xyz.block.ftl.v1.schema.Decl;
import xyz.block.ftl.v1.schema.Field;
Expand Down Expand Up @@ -123,6 +127,7 @@ class FtlProcessor {
public static final DotName OFFSET_DATE_TIME = DotName.createSimple(OffsetDateTime.class.getName());
public static final DotName GENERATED_REF = DotName.createSimple(GeneratedRef.class);
public static final DotName LEASE_CLIENT = DotName.createSimple(LeaseClient.class);
public static final DotName INSTANT = DotName.createSimple(Instant.class);

@BuildStep
ModuleNameBuildItem moduleName(ApplicationInfoBuildItem applicationInfoBuildItem) {
Expand Down Expand Up @@ -326,9 +331,11 @@ public void registerVerbs(CombinedIndexBuildItem index,

output = outputTargetBuildItem.getOutputDirectory().resolve("main");
try (var out = Files.newOutputStream(output)) {
out.write("""
#!/bin/bash
exec java -jar quarkus-app/quarkus-run.jar""".getBytes(StandardCharsets.UTF_8));
out.write(
"""
#!/bin/bash
exec java -jar quarkus-app/quarkus-run.jar"""
.getBytes(StandardCharsets.UTF_8));
}
var perms = Files.getPosixFilePermissions(output);
EnumSet<PosixFilePermission> newPerms = EnumSet.copyOf(perms);
Expand Down Expand Up @@ -536,9 +543,33 @@ private static Class<?> loadClass(org.jboss.jandex.Type param) throws ClassNotFo
default:
throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive());
}
} else {
throw new RuntimeException("Unknown type " + param.kind());
} else if (param.kind() == org.jboss.jandex.Type.Kind.ARRAY) {
ArrayType array = param.asArrayType();
if (array.componentType().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE) {
switch (array.componentType().asPrimitiveType().primitive()) {
case BOOLEAN:
return boolean[].class;
case BYTE:
return byte[].class;
case SHORT:
return short[].class;
case INT:
return int[].class;
case LONG:
return long[].class;
case FLOAT:
return float[].class;
case DOUBLE:
return double[].class;
case CHAR:
return char[].class;
default:
throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive());
}
}
}
throw new RuntimeException("Unknown type " + param.kind());

}

/**
Expand Down Expand Up @@ -592,8 +623,13 @@ private Type buildType(ExtractionContext context, org.jboss.jandex.Type type) {
return Type.newBuilder().setUnit(Unit.newBuilder().build()).build();
}
case ARRAY -> {
ArrayType arrayType = type.asArrayType();
if (arrayType.componentType().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE && arrayType
.componentType().asPrimitiveType().primitive() == PrimitiveType.Primitive.BYTE) {
return Type.newBuilder().setBytes(Bytes.newBuilder().build()).build();
}
return Type.newBuilder()
.setArray(Array.newBuilder().setElement(buildType(context, type.asArrayType().componentType())).build())
.setArray(Array.newBuilder().setElement(buildType(context, arrayType.componentType())).build())
.build();
}
case CLASS -> {
Expand All @@ -612,6 +648,9 @@ private Type buildType(ExtractionContext context, org.jboss.jandex.Type type) {
if (clazz.name().equals(OFFSET_DATE_TIME)) {
return Type.newBuilder().setTime(Time.newBuilder().build()).build();
}
if (clazz.name().equals(INSTANT)) {
return Type.newBuilder().setTime(Time.newBuilder().build()).build();
}
var existing = context.dataElements.get(new TypeKey(clazz.name().toString(), List.of()));
if (existing != null) {
return Type.newBuilder().setRef(existing).build();
Expand Down
Loading

0 comments on commit b947adb

Please sign in to comment.