Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 100 additions & 2 deletions codegen/src/main/java/ObjectLayerGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ private record ClassSpec(String objectKey, String interfaceName, String superInt
/** The interface this run emits. */
private ClassSpec spec;

/** Base type name to its {@code {role -> collection type name}} map, from the catalog typeRelations. */
private final Map<String, Map<String, String>> typeRelByBase = new HashMap<>();

/** Temporal type name to its base type name, inverted from the typeRelations registry. */
private final Map<String, String> tempToBase = new HashMap<>();

/** The base type name of the class this run emits, or {@code null} for an abstract or time surface. */
private String valueBase;

/** The object-model roles this surface generates. */
private static final Set<String> ROLES =
Set.of("accessor", "conversion", "predicate", "restriction", "output", "constructor");
Expand Down Expand Up @@ -101,18 +110,31 @@ public static void main(String[] args) throws IOException {
}
}

/** Index the catalog once: enum names, functions, and the struct strides the folds read. */
/** Index the catalog once: enum names, functions, the struct strides and the type-relation registry. */
private void init(JsonNode root) {
collectEnumNames(root);
indexFunctions(root);
spanSize = structSize(root, "Span");
matchFields = structFields(root, "Match");
matchStride = structSize(root, "Match");
tboxStride = structSize(root, "TBox");
JsonNode byBase = root.path("typeRelations").path("byBase");
byBase.fields().forEachRemaining(e -> {
Map<String, String> roles = new HashMap<>();
e.getValue().fields().forEachRemaining(r -> roles.put(r.getKey(), r.getValue().asText()));
typeRelByBase.put(e.getKey(), roles);
String temporal = roles.get("temporal");
if (temporal != null) {
tempToBase.put(temporal, e.getKey());
}
});
}

private void run(ClassSpec spec, JsonNode root, Path out, String inputPath) throws IOException {
this.spec = spec;
// The base type this surface is over, when it is a concrete value type. A value-domain collection
// return then resolves to its concrete wrapper through the type-relation registry.
this.valueBase = tempToBase.get(spec.objectKey().toLowerCase());
deferred.clear();
System.out.println("--- " + spec.interfaceName() + " (objectModel.classes." + spec.objectKey() + ") ---");

Expand All @@ -138,6 +160,29 @@ private void run(ClassSpec spec, JsonNode root, Path out, String inputPath) thro
methods.add(method);
}
}
// A value-domain collection return (a value span or span set) is base-generic on the abstract
// superclass — its concrete wrapper is unknowable there — so it is emitted on this concrete
// surface, where the base resolves the wrapper. Pull those superclass methods down.
if (valueBase != null && spec.superInterface() != null) {
String superKey = spec.superInterface().replace("Generated", "");
for (JsonNode m : root.path("objectModel").path("classes").path(superKey).path("methods")) {
if (m.path("ooExclude").asBoolean(false) || !ROLES.contains(m.path("role").asText(""))) {
continue;
}
JsonNode fn = functions.get(m.path("function").asText());
if (fn == null) {
continue;
}
String retC = cleanType(fn.path("returnType").path("c").asText());
if (!retC.equals("Span *") && !retC.equals("SpanSet *") && !retC.equals("Set *")) {
continue; // only the base-generic value-collection returns are specialised here
}
Method method = classify(m);
if (method != null && seen.add(method.ooName)) {
methods.add(method);
}
}
}
methods.sort(Comparator.comparing(x -> x.ooName));

String content = generateInterface(methods);
Expand Down Expand Up @@ -403,6 +448,7 @@ && cleanType(fn.path("returnType").path("c").asText()).equals("Temporal *")) {
String arrayKind = arrayElement != null ? "objectArray"
: retC.equals("TimestampTz *") ? "scalarArray"
: retC.equals("Span *") && spec.spanReturnsAreTime() ? "spanArray"
: retC.equals("Span *") && valueWrapper("span") != null ? "valueSpanArray"
: retC.equals("TBox *") ? "tboxArray"
: retC.equals("Match *") ? "matchArray"
: null;
Expand All @@ -422,7 +468,9 @@ && cleanType(fn.path("returnType").path("c").asText()).equals("Temporal *")) {
foldArgs.add(a);
}
if (marshalled) {
String rt = switch (arrayKind) {
String rt = arrayKind.equals("valueSpanArray")
? "java.util.List<" + valueWrapper("span") + ">"
: switch (arrayKind) {
case "objectArray" -> "java.util.List<Temporal>";
case "scalarArray" -> "java.util.List<java.time.OffsetDateTime>";
case "spanArray" -> "java.util.List<types.collections.time.tstzspan>";
Expand Down Expand Up @@ -541,6 +589,18 @@ && cleanType(fn.path("returnType").path("c").asText()).equals("Temporal *")) {
// A Temporal's spanset is its time domain, a tstzspanset; a value spanset waits for the subclass.
returnType = "types.collections.time.tstzspanset";
returnKind = "tstzspanset";
} else if (retC.equals("Span *") && valueWrapper("span") != null) {
// A value temporal's span is its value extent, a base-typed span (an intspan, a floatspan).
returnType = valueWrapper("span");
returnKind = "valueCollection";
} else if (retC.equals("SpanSet *") && valueWrapper("spanset") != null) {
// getValues: a value temporal's range is its value span set (an intspanset, a floatspanset).
returnType = valueWrapper("spanset");
returnKind = "valueCollection";
} else if (retC.equals("Set *") && valueWrapper("set") != null) {
// A value temporal's distinct-value set (an intset, a floatset).
returnType = valueWrapper("set");
returnKind = "valueCollection";
} else if (retC.equals("TBox *")) {
// A number temporal's bounding box over its value and time extents.
returnType = "types.boxes.TBox";
Expand Down Expand Up @@ -702,6 +762,40 @@ private static String cleanType(String c) {
return c.replace("const ", "").trim();
}

/**
* The concrete value-collection wrapper class for a role ({@code span}, {@code spanset} or
* {@code set}) of the base type this surface is over, or {@code null} when the base has no such
* collection or the layer has no wrapper for it. The collection type name comes from the catalog
* type-relation registry; the wrapper is derived by the value-collection naming convention and used
* only when the hand layer actually provides it, so a base without a wrapper defers rather than
* dangling on a class that does not exist.
*/
private String valueWrapper(String role) {
if (valueBase == null) {
return null;
}
Map<String, String> roles = typeRelByBase.get(valueBase);
return roles == null ? null : wrapperClass(roles.get(role));
}

/** The Java wrapper for a value-collection type name (floatspanset -> FloatSpanSet), if it exists. */
private static String wrapperClass(String typeName) {
if (typeName == null) {
return null;
}
String suffix = typeName.endsWith("spanset") ? "SpanSet"
: typeName.endsWith("span") ? "Span"
: typeName.endsWith("set") ? "Set"
: null;
if (suffix == null) {
return null;
}
String cls = capitalize(typeName.substring(0, typeName.length() - suffix.length())) + suffix;
Path file = Paths.get(System.getProperty("user.dir"),
"jmeos-core/src/main/java/types/collections/number", cls + ".java");
return Files.exists(file) ? "types.collections.number." + cls : null;
}

/**
* The record component for one split bin-start out-parameter, or {@code null} if this slice does not
* model that dimension. A bin out-parameter is a pointer to an array of the bin-start values
Expand Down Expand Up @@ -822,6 +916,10 @@ private String generateMethod(Method m) {
case "tstzspan" -> "\t\treturn new types.collections.time.tstzspan(" + invocation + ");\n";
case "tstzspanset" -> "\t\treturn new types.collections.time.tstzspanset(" + invocation + ");\n";
case "tbox" -> "\t\treturn new types.boxes.TBox(" + invocation + ");\n";
case "valueCollection" -> "\t\treturn new " + m.returnType + "(" + invocation + ");\n";
case "valueSpanArray" -> arrayFoldBody(m, "new " + m.returnType.substring(
m.returnType.indexOf('<') + 1, m.returnType.lastIndexOf('>'))
+ "(GeneratedFunctions.span_copy(_array.slice((long) _i * " + spanSize + ")))");
case "objectArray" -> arrayFoldBody(m, "Factory.create_temporal(_array.getPointer("
+ "(long) _i * Long.BYTES), getCustomType(), " + m.returnSubtype + ")");
case "scalarArray" -> arrayFoldBody(m, "utils.TimestampTzConverter.toOffsetDateTime("
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.junit.jupiter.api.Test;
import types.basic.tfloat.TFloatSeq;
import types.basic.tint.TIntSeq;
import types.collections.number.FloatSpan;
import types.temporal.Temporal;
import types.temporal.TemporalType;
import utils.ConversionUtils;
Expand Down Expand Up @@ -111,6 +112,33 @@ void intValueAccessorsMatchTheLibrary() {
assertEquals(r2 == null ? null : Integer.valueOf(r2.getInt(0)), g.valueAtTimestamptz(t, true));
}

@Test
void floatValueCollectionsMatchTheLibrary() {
TFloatSeq a = new TFloatSeq("[1@2019-09-01, 3@2019-09-02, 2@2019-09-03]");
Pointer p = a.getInner();
GeneratedTFloat g = genFloat(a);
Runtime rt = Runtime.getSystemRuntime();
int spanBytes = 24; // sizeof(Span) on the 64-bit targets

// getValues: the value range as a concrete float span set, derived from the type-relation registry.
assertEquals(GeneratedFunctions.spanset_out(GeneratedFunctions.tnumber_valuespans(p), 15),
GeneratedFunctions.spanset_out(g.valuespans().get_inner(), 15));

// toSpan: the value extent as a concrete float span.
assertEquals(GeneratedFunctions.span_out(GeneratedFunctions.tnumber_to_span(p), 15),
GeneratedFunctions.span_out(g.toSpan().get_inner(), 15));

// valueBins: the value span array folded into a List<FloatSpan>.
Pointer c = Memory.allocate(rt, Integer.BYTES);
Pointer arr = GeneratedFunctions.tfloat_value_bins(p, 2.0, 0.0, c);
List<FloatSpan> bins = g.valueBins(2.0, 0.0);
assertEquals(c.getInt(0), bins.size());
for (int i = 0; i < bins.size(); i++) {
assertEquals(GeneratedFunctions.span_out(arr.slice((long) i * spanBytes), 15),
GeneratedFunctions.span_out(bins.get(i).get_inner(), 15));
}
}

@Test
void intValueSplitMatchesTheLibrary() {
TIntSeq a = new TIntSeq("[1@2019-09-01, 3@2019-09-02, 5@2019-09-03]");
Expand Down
Loading