alef 0.25.53

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 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;
        }
    }