alef 0.25.49

Opinionated polyglot binding generator for Rust libraries
Documentation
// 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 Arena.ofAuto() for temporary allocation, then malloc and copy.
        try (Arena autoArena = Arena.ofAuto()) {
            MemorySegment responseSegment = autoArena.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);
            // Copy the response bytes to malloc'd memory.
            MemorySegment.copy(responseSegment, 0, mallocAddr, 0, responseLen);
            return mallocAddr;
        }
    }