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
59 changes: 53 additions & 6 deletions codegen/src/main/java/FunctionsGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class FunctionsGenerator {
// Enum names extracted from the JSON "enums" section.
// These are mapped to int in Java (JNR-FFI represents C enums as int).
private final Set<String> enumNames = new HashSet<>();
/** The enums of the catalog, in its order, each written as a Java enum by {@link #writeEnumClasses}. */
private final List<JsonNode> enumDefs = new ArrayList<>();
/** Struct names the catalog defines, used to spot a by-value struct return. */
private final Set<String> structNames = new HashSet<>();
/** Fields of each struct the catalog defines, keyed by struct name. */
Expand Down Expand Up @@ -241,6 +243,7 @@ private void run(String inputPath, String outputPath) throws IOException {
Files.createDirectories(out.getParent());
Files.writeString(out, content);
System.out.println("Written: " + out.toAbsolutePath());
writeEnumClasses(out.getParent());
System.out.println("=== Done ===");
}

Expand Down Expand Up @@ -481,11 +484,61 @@ private void collectEnumNames(JsonNode root) {
JsonNode nameNode = e.get("name");
if (nameNode != null) {
enumNames.add(nameNode.asText());
enumDefs.add(e);
}
}
}
}

/**
* Writes one Java enum per enum of the catalog a binding reaches, under its C name and with
* the values MEOS gives its constants, beside {@code GeneratedFunctions}: the enums of the
* public headers of an enabled family, neither those of {@code meos_internal.h} nor those of
* a vendored library.
*/
private void writeEnumClasses(Path dir) throws IOException {
int written = 0;
for (JsonNode e : enumDefs) {
if (emitsEnum(e)) {
Files.writeString(dir.resolve(e.get("name").asText() + ".java"), enumSource(e));
written++;
}
}
System.out.println("Enums written: " + written + " of " + enumDefs.size());
}

/** Whether the enum belongs to the public surface this run generates. */
private boolean emitsEnum(JsonNode e) {
JsonNode file = e.get("file");
JsonNode vendored = e.get("vendored");
return (vendored == null || !vendored.asBoolean())
&& (file == null || !file.asText().startsWith("meos_internal"))
&& familyEnabled(familyOf(e));
}

/** The Java source of an enum of the catalog. */
static String enumSource(JsonNode e) {
String name = e.get("name").asText();
JsonNode file = e.get("file");
StringBuilder sb = new StringBuilder("package functions;\n\n");
sb.append("/** {@code ").append(name).append("} as ")
.append(file == null ? "MEOS" : "{@code " + file.asText() + "}")
.append(" declares it. */\n");
sb.append("public enum ").append(name).append(" {\n");
JsonNode values = e.get("values");
for (int i = 0; i < values.size(); i++) {
JsonNode v = values.get(i);
sb.append("\t").append(v.get("name").asText()).append("(").append(v.get("value").asLong())
.append(i + 1 < values.size() ? "),\n" : ");\n");
}
sb.append("\n\tprivate final int value;\n\n");
sb.append("\t").append(name).append("(int value) {\n\t\tthis.value = value;\n\t}\n\n");
sb.append("\t/** The value MEOS gives this constant. */\n");
sb.append("\tpublic int getValue() {\n\t\treturn value;\n\t}\n");
sb.append("}\n");
return sb.toString();
}

private FunctionDef parseFunctionDef(JsonNode fn) {
String name = fn.get("name").asText();

Expand Down Expand Up @@ -609,10 +662,6 @@ private String mapCTypeToJava(String cType) {
// becomes Pointer, breaking every h3index/th3index binding.
case "H3Index" -> "long";

// Explicit enum names (in case not in JSON enums section)
case "interpType", "RTreeSearchOp",
"tempSubtype", "spatialRel",
"errorCode" -> "int";
default -> "Pointer";
};
}
Expand Down Expand Up @@ -781,8 +830,6 @@ private String generateFile(List<FunctionDef> functions) {
import jnr.ffi.byref.PointerByReference;
import jnr.ffi.Struct;
import utils.JarLibraryLoader;
import utils.meosCatalog.MeosEnums.meosType;
import utils.meosCatalog.MeosEnums.meosOper;
import functions.MeosErrorHandler;

import java.time.*;
Expand Down
84 changes: 77 additions & 7 deletions codegen/src/test/java/FunctionsGeneratorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ void unknownType() throws Exception {
}

@ParameterizedTest
@ValueSource(strings = {"interpType", "RTreeSearchOp", "tempSubtype", "spatialRel", "errorCode"})
@DisplayName("explicit enum names → int")
void explicitEnums(String cType) throws Exception {
assertEquals("int", mapJava(cType));
@ValueSource(strings = {"interpType", "IndexSearchOp", "tempSubtype", "spatialRel", "errorCode"})
@DisplayName("a name the catalog states no enum of is not an enum")
void enumOnlyFromCatalog(String cType) throws Exception {
assertEquals("Pointer", mapJava(cType));
}

@Test
Expand Down Expand Up @@ -376,7 +376,7 @@ void enumsPresent() throws Exception {
String json = """
{
"enums": [
{"name": "RTreeSearchOp"},
{"name": "IndexSearchOp"},
{"name": "interpType"}
],
"functions": []
Expand All @@ -396,7 +396,7 @@ void enumsPresent() throws Exception {
@SuppressWarnings("unchecked")
Set<String> names = (Set<String>) enumNamesField.get(generator);

assertTrue(names.contains("RTreeSearchOp"));
assertTrue(names.contains("IndexSearchOp"));
assertTrue(names.contains("interpType"));
assertEquals(2, names.size());
}
Expand All @@ -418,6 +418,76 @@ void noEnumsSection() throws Exception {
}
}

// =========================================================================
// The Java enums of the catalog
// =========================================================================

@Nested
@DisplayName("enum classes")
class EnumClassTests {

private JsonNode enumNode(String json) throws Exception {
return new ObjectMapper().readTree(json);
}

private boolean emits(JsonNode e) throws Exception {
Method m = FunctionsGenerator.class.getDeclaredMethod("emitsEnum", JsonNode.class);
m.setAccessible(true);
return (boolean) m.invoke(generator, e);
}

@Test
@DisplayName("an enum keeps its C name and the value MEOS gives each constant")
void enumSourceKeepsNamesAndValues() throws Exception {
String src = FunctionsGenerator.enumSource(enumNode("""
{"name": "MeosType", "file": "meos_catalog.h", "family": "CORE",
"values": [{"name": "T_UNKNOWN", "value": 0}, {"name": "T_DOUBLE2", "value": 8},
{"name": "T_FLOAT8", "value": 11}]}
"""));
assertTrue(src.startsWith("package functions;"));
assertTrue(src.contains("public enum MeosType {"));
assertTrue(src.contains("\tT_UNKNOWN(0),\n\tT_DOUBLE2(8),\n\tT_FLOAT8(11);\n"));
assertTrue(src.contains("public int getValue()"));
}

@Test
@DisplayName("the enums of a public header are written, internal and vendored ones are not")
void emitsPublicEnumsOnly() throws Exception {
assertTrue(emits(enumNode("""
{"name": "IndexSearchOp", "file": "meos.h", "family": "CORE", "vendored": false,
"values": [{"name": "INDEX_OVERLAPS", "value": 0}]}
""")));
assertFalse(emits(enumNode("""
{"name": "SkipListType", "file": "meos_internal.h", "family": "CORE", "vendored": false,
"values": [{"name": "TIMESTAMPTZ", "value": 0}]}
""")));
assertFalse(emits(enumNode("""
{"name": "ENDIANS", "file": "pc_api.h", "family": "POINTCLOUD", "vendored": true,
"values": [{"name": "PC_XDR", "value": 0}]}
""")));
}

@Test
@DisplayName("run writes each enum beside the generated functions")
void runWritesEnumFiles() throws Exception {
Path input = tempDir.resolve("idl.json");
Files.writeString(input, """
{"enums": [{"name": "IndexSearchOp", "file": "meos.h", "family": "CORE",
"values": [{"name": "INDEX_OVERLAPS", "value": 0},
{"name": "INDEX_ADJACENT", "value": 20}]},
{"name": "SkipListType", "file": "meos_internal.h", "family": "CORE",
"values": [{"name": "TIMESTAMPTZ", "value": 0}]}],
"functions": []}
""");
Path out = tempDir.resolve("functions/GeneratedFunctions.java");
run.invoke(generator, input.toString(), out.toString());
Path written = tempDir.resolve("functions/IndexSearchOp.java");
assertTrue(Files.exists(written));
assertTrue(Files.readString(written).contains("INDEX_ADJACENT(20);"));
assertFalse(Files.exists(tempDir.resolve("functions/SkipListType.java")));
}
}

// =========================================================================
// run(): end-to-end / integration
// =========================================================================
Expand Down Expand Up @@ -940,7 +1010,7 @@ void fileHeaderCorrect() throws Exception {
void enumFromJsonSection() throws Exception {
String json = """
{
"enums": [{"name": "MyOp"}],
"enums": [{"name": "MyOp", "file": "meos.h", "family": "CORE", "values": [{"name": "MY_OP", "value": 0}]}],
"functions": [{
"name": "do_op",
"returnType": {"c": "void"},
Expand Down
20 changes: 0 additions & 20 deletions jmeos-core/src/main/java/functions/RTreeSearchOp.java

This file was deleted.

130 changes: 0 additions & 130 deletions jmeos-core/src/main/java/utils/meosCatalog/MeosEnums.java

This file was deleted.

Loading
Loading