// Auto-generated by alef — DO NOT EDIT
package {{ package }};
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* Service wrapper for {{ service_name }} using Panama FFM.
*
* Binds to C FFI symbols:
* - {{ ffi_prefix }}_{{ service_snake }}_new() -> opaque handle
* - {{ ffi_prefix }}_{{ service_snake }}_free(opaque)
{{ bindings_doc }} */
@SuppressWarnings("PMD")
public class {{ class_name }} implements AutoCloseable {
private MemorySegment ownerHandle;
private final Arena arena = Arena.ofShared();
private static final Linker LINKER = Linker.nativeLinker();
private static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();
static {
// Force NativeLib static initialization to load the native library
// This ensures all FFI symbols are available before we try to look them up.
// NativeLib is a package-private class, but accessing its class forces
// its static initializer to run and load the native library.
try {
Class.forName("{{ package }}.NativeLib");
} catch (ClassNotFoundException ignored) {
// NativeLib not available; native library may be pre-loaded
}
}
// Adapter for handler upcalls: marshals C pointers <-> Java strings
private static MemorySegment invokeHandlerWithMarshal(
MemorySegment contextPtr,
MemorySegment requestPtr,
Callable handler,
Arena arena) throws Throwable {
// Upcall pointer args arrive as zero-length native segments (the linker has
// no length for a raw `*const c_char`). Reinterpret to an unbounded segment
// before reading, otherwise getString() throws and the uncaught exception
// aborts the VM.
String requestStr = requestPtr.reinterpret(Long.MAX_VALUE).getString(0);
String responseStr = handler.handle(requestStr);
// Allocate response string using malloc (not Arena) so the pointer remains
// valid after this upcall returns. Rust will free() the returned pointer.
// Use a confined arena for the temporary scratch allocation: it is
// closeable (unlike Arena.ofAuto(), whose close() throws
// "non-closeable session"), and its memory is freed deterministically at
// block exit — safe because the bytes are copied into malloc'd memory first.
try (Arena scratchArena = Arena.ofConfined()) {
MemorySegment responseSegment = scratchArena.allocateFrom(responseStr);
long responseLen = responseSegment.byteSize();
// Call malloc to allocate memory that persists beyond this upcall.
MethodHandle mallocHandle = LINKER.downcallHandle(
LOOKUP.find("malloc").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
);
MemorySegment mallocAddr = (MemorySegment) mallocHandle.invoke(responseLen);
// Reinterpret the malloc'd segment to its actual size before copying.
// The FFI linker returns a zero-sized segment for bare ADDRESS returns;
// reinterpret(size) gives it the proper bounds for MemorySegment.copy().
MemorySegment mallocSegment = mallocAddr.reinterpret(responseLen);
// Copy the response bytes to malloc'd memory.
MemorySegment.copy(responseSegment, 0, mallocSegment, 0, responseLen);
return mallocAddr;
}
}