Skip to main content

alef_backend_java/
gen_bindings.rs

1use crate::type_map::{java_boxed_type, java_ffi_type, java_type};
2use alef_codegen::naming::{to_class_name, to_java_name};
3use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
4use alef_core::config::{AlefConfig, Language, resolve_output_dir};
5use alef_core::ir::{ApiSurface, EnumDef, FunctionDef, PrimitiveType, TypeDef, TypeRef};
6use heck::ToSnakeCase;
7use std::fmt::Write;
8use std::path::PathBuf;
9
10/// Names that conflict with methods on `java.lang.Object` and are therefore
11/// illegal as record component names or method names in generated Java code.
12const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
13    "wait",
14    "notify",
15    "notifyAll",
16    "getClass",
17    "hashCode",
18    "equals",
19    "toString",
20    "clone",
21    "finalize",
22];
23
24/// Sanitise a field/parameter name that would conflict with `java.lang.Object`
25/// methods.  Conflicting names get a `_` suffix (e.g. `wait` -> `wait_`), which
26/// is then converted to camelCase by `to_java_name`.
27fn safe_java_field_name(name: &str) -> String {
28    let java_name = to_java_name(name);
29    if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
30        format!("{}Value", java_name)
31    } else {
32        java_name
33    }
34}
35
36pub struct JavaBackend;
37
38impl JavaBackend {
39    /// Convert crate name to main class name (PascalCase).
40    fn resolve_main_class(api: &ApiSurface) -> String {
41        to_class_name(&api.crate_name.replace('-', "_"))
42    }
43}
44
45impl Backend for JavaBackend {
46    fn name(&self) -> &str {
47        "java"
48    }
49
50    fn language(&self) -> Language {
51        Language::Java
52    }
53
54    fn capabilities(&self) -> Capabilities {
55        Capabilities {
56            supports_async: true,
57            supports_classes: true,
58            supports_enums: true,
59            supports_option: true,
60            supports_result: true,
61            ..Capabilities::default()
62        }
63    }
64
65    fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
66        let package = config.java_package();
67        let prefix = config.ffi_prefix();
68        let main_class = Self::resolve_main_class(api);
69        let package_path = package.replace('.', "/");
70
71        let output_dir = resolve_output_dir(
72            config.output.java.as_ref(),
73            &config.crate_config.name,
74            "packages/java/src/main/java/",
75        );
76
77        let base_path = PathBuf::from(&output_dir).join(&package_path);
78
79        let mut files = Vec::new();
80
81        // 1. NativeLib.java - FFI method handles
82        files.push(GeneratedFile {
83            path: base_path.join("NativeLib.java"),
84            content: gen_native_lib(api, config, &package, &prefix),
85            generated_header: true,
86        });
87
88        // 2. Main wrapper class
89        files.push(GeneratedFile {
90            path: base_path.join(format!("{}.java", main_class)),
91            content: gen_main_class(api, config, &package, &main_class, &prefix),
92            generated_header: true,
93        });
94
95        // 3. Exception class
96        files.push(GeneratedFile {
97            path: base_path.join(format!("{}Exception.java", main_class)),
98            content: gen_exception_class(&package, &main_class),
99            generated_header: true,
100        });
101
102        // 4. Record types
103        for typ in &api.types {
104            if !typ.is_opaque && !typ.fields.is_empty() {
105                files.push(GeneratedFile {
106                    path: base_path.join(format!("{}.java", typ.name)),
107                    content: gen_record_type(&package, typ),
108                    generated_header: true,
109                });
110                // Generate builder class for types with defaults
111                if typ.has_default {
112                    files.push(GeneratedFile {
113                        path: base_path.join(format!("{}Builder.java", typ.name)),
114                        content: gen_builder_class(&package, typ),
115                        generated_header: true,
116                    });
117                }
118            }
119        }
120
121        // 4b. Opaque handle types
122        for typ in &api.types {
123            if typ.is_opaque {
124                files.push(GeneratedFile {
125                    path: base_path.join(format!("{}.java", typ.name)),
126                    content: gen_opaque_handle_class(&package, typ, &prefix),
127                    generated_header: true,
128                });
129            }
130        }
131
132        // 5. Enums
133        for enum_def in &api.enums {
134            files.push(GeneratedFile {
135                path: base_path.join(format!("{}.java", enum_def.name)),
136                content: gen_enum_class(&package, enum_def),
137                generated_header: true,
138            });
139        }
140
141        // 6. Error exception classes
142        for error in &api.errors {
143            for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
144                files.push(GeneratedFile {
145                    path: base_path.join(format!("{}.java", class_name)),
146                    content,
147                    generated_header: true,
148                });
149            }
150        }
151
152        // Build adapter body map (consumed by generators via body substitution)
153        let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
154
155        Ok(files)
156    }
157
158    fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
159        let package = config.java_package();
160        let prefix = config.ffi_prefix();
161        let main_class = Self::resolve_main_class(api);
162        let package_path = package.replace('.', "/");
163
164        let output_dir = resolve_output_dir(
165            config.output.java.as_ref(),
166            &config.crate_config.name,
167            "packages/java/src/main/java/",
168        );
169
170        let base_path = PathBuf::from(&output_dir).join(&package_path);
171
172        // Generate a high-level public API class that wraps the raw FFI class.
173        // Class name = main_class without "Rs" suffix (e.g., HtmlToMarkdownRs -> HtmlToMarkdown)
174        let public_class = main_class.trim_end_matches("Rs").to_string();
175        let facade_content = gen_facade_class(api, &package, &public_class, &main_class, &prefix);
176
177        Ok(vec![GeneratedFile {
178            path: base_path.join(format!("{}.java", public_class)),
179            content: facade_content,
180            generated_header: true,
181        }])
182    }
183
184    fn build_config(&self) -> Option<BuildConfig> {
185        Some(BuildConfig {
186            tool: "mvn",
187            crate_suffix: "",
188            depends_on_ffi: true,
189            post_build: vec![],
190        })
191    }
192}
193
194// ---------------------------------------------------------------------------
195// NativeLib.java - FFI method handles
196// ---------------------------------------------------------------------------
197
198fn gen_native_lib(api: &ApiSurface, config: &AlefConfig, package: &str, prefix: &str) -> String {
199    // Generate the class body first, then scan it to determine which imports are needed.
200    let mut body = String::with_capacity(2048);
201    // Derive the native library name from the FFI output path (directory name with hyphens replaced
202    // by underscores), falling back to `{ffi_prefix}_ffi`.
203    let lib_name = config.ffi_lib_name();
204
205    writeln!(body, "final class NativeLib {{").ok();
206    writeln!(body, "    private static final Linker LINKER = Linker.nativeLinker();").ok();
207    writeln!(body, "    private static final SymbolLookup LIB;").ok();
208    writeln!(body).ok();
209    writeln!(body, "    static {{").ok();
210    writeln!(body, "        System.loadLibrary(\"{}\");", lib_name).ok();
211    writeln!(body, "        LIB = SymbolLookup.loaderLookup();").ok();
212    writeln!(body, "    }}").ok();
213    writeln!(body).ok();
214
215    // Generate method handles for free functions
216    for func in &api.functions {
217        if !func.is_async {
218            let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
219            let return_layout = gen_ffi_layout(&func.return_type);
220            let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
221
222            let layout_str = gen_function_descriptor(&return_layout, &param_layouts);
223
224            let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
225
226            writeln!(
227                body,
228                "    static final MethodHandle {} = LINKER.downcallHandle(",
229                handle_name
230            )
231            .ok();
232            writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
233            writeln!(body, "        {}", layout_str).ok();
234            writeln!(body, "    );").ok();
235        }
236    }
237
238    // free_string handle for releasing FFI-allocated strings
239    {
240        let free_name = format!("{}_free_string", prefix);
241        let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
242        writeln!(body).ok();
243        writeln!(
244            body,
245            "    static final MethodHandle {} = LINKER.downcallHandle(",
246            handle_name
247        )
248        .ok();
249        writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", free_name).ok();
250        writeln!(body, "        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
251        writeln!(body, "    );").ok();
252    }
253
254    // Error handling — use the FFI's last_error_code and last_error_context symbols
255    {
256        writeln!(
257            body,
258            "    static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
259            prefix.to_uppercase()
260        )
261        .ok();
262        writeln!(body, "        LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
263        writeln!(body, "        FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
264        writeln!(body, "    );").ok();
265
266        writeln!(
267            body,
268            "    static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
269            prefix.to_uppercase()
270        )
271        .ok();
272        writeln!(
273            body,
274            "        LIB.find(\"{}_last_error_context\").orElseThrow(),",
275            prefix
276        )
277        .ok();
278        writeln!(body, "        FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
279        writeln!(body, "    );").ok();
280    }
281
282    // Accessor handles for Named return types (struct pointer → field accessor + free)
283    for func in &api.functions {
284        if let TypeRef::Named(name) = &func.return_type {
285            let type_snake = name.to_snake_case();
286            let type_upper = type_snake.to_uppercase();
287
288            // _content accessor: (struct_ptr) -> char*
289            let content_handle = format!("{}_{}_CONTENT", prefix.to_uppercase(), type_upper);
290            let content_ffi = format!("{}_{}_content", prefix, type_snake);
291            writeln!(body).ok();
292            writeln!(
293                body,
294                "    static final MethodHandle {} = LINKER.downcallHandle(",
295                content_handle
296            )
297            .ok();
298            writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", content_ffi).ok();
299            writeln!(
300                body,
301                "        FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
302            )
303            .ok();
304            writeln!(body, "    );").ok();
305
306            // _free: (struct_ptr) -> void
307            let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
308            let free_ffi = format!("{}_{}_free", prefix, type_snake);
309            writeln!(
310                body,
311                "    static final MethodHandle {} = LINKER.downcallHandle(",
312                free_handle
313            )
314            .ok();
315            writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
316            writeln!(body, "        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
317            writeln!(body, "    );").ok();
318        }
319    }
320
321    writeln!(body, "}}").ok();
322
323    // Now assemble the file with only the imports that are actually used in the body.
324    let mut out = String::with_capacity(body.len() + 512);
325
326    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
327    writeln!(out, "package {};", package).ok();
328    writeln!(out).ok();
329    if body.contains("Arena") {
330        writeln!(out, "import java.lang.foreign.Arena;").ok();
331    }
332    if body.contains("FunctionDescriptor") {
333        writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
334    }
335    if body.contains("Linker") {
336        writeln!(out, "import java.lang.foreign.Linker;").ok();
337    }
338    if body.contains("MemorySegment") {
339        writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
340    }
341    if body.contains("SymbolLookup") {
342        writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
343    }
344    if body.contains("ValueLayout") {
345        writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
346    }
347    if body.contains("MethodHandle") {
348        writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
349    }
350    writeln!(out).ok();
351
352    out.push_str(&body);
353
354    out
355}
356
357// ---------------------------------------------------------------------------
358// Main wrapper class
359// ---------------------------------------------------------------------------
360
361fn gen_main_class(api: &ApiSurface, _config: &AlefConfig, package: &str, class_name: &str, prefix: &str) -> String {
362    // Generate the class body first, then scan it to determine which imports are needed.
363    let mut body = String::with_capacity(4096);
364
365    writeln!(body, "public final class {} {{", class_name).ok();
366    writeln!(body, "    private {}() {{ }}", class_name).ok();
367    writeln!(body).ok();
368
369    // Generate static methods for free functions
370    for func in &api.functions {
371        // Always generate sync method
372        gen_sync_function_method(&mut body, func, prefix, class_name);
373        writeln!(body).ok();
374
375        // Also generate async wrapper if marked as async
376        if func.is_async {
377            gen_async_wrapper_method(&mut body, func);
378            writeln!(body).ok();
379        }
380    }
381
382    // Add helper methods only if they are referenced in the body
383    gen_helper_methods(&mut body);
384
385    writeln!(body, "}}").ok();
386
387    // Now assemble the file with only the imports that are actually used in the body.
388    let mut out = String::with_capacity(body.len() + 512);
389
390    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
391    writeln!(out, "package {};", package).ok();
392    writeln!(out).ok();
393    if body.contains("Arena") {
394        writeln!(out, "import java.lang.foreign.Arena;").ok();
395    }
396    if body.contains("FunctionDescriptor") {
397        writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
398    }
399    if body.contains("Linker") {
400        writeln!(out, "import java.lang.foreign.Linker;").ok();
401    }
402    if body.contains("MemorySegment") {
403        writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
404    }
405    if body.contains("SymbolLookup") {
406        writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
407    }
408    if body.contains("ValueLayout") {
409        writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
410    }
411    if body.contains("List<") {
412        writeln!(out, "import java.util.List;").ok();
413    }
414    if body.contains("Map<") {
415        writeln!(out, "import java.util.Map;").ok();
416    }
417    if body.contains("Optional<") {
418        writeln!(out, "import java.util.Optional;").ok();
419    }
420    if body.contains("HashMap<") || body.contains("new HashMap") {
421        writeln!(out, "import java.util.HashMap;").ok();
422    }
423    if body.contains("CompletableFuture") {
424        writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
425    }
426    if body.contains("CompletionException") {
427        writeln!(out, "import java.util.concurrent.CompletionException;").ok();
428    }
429    if body.contains("ObjectMapper") || body.contains("readValue") {
430        writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
431    }
432    writeln!(out).ok();
433
434    out.push_str(&body);
435
436    out
437}
438
439fn gen_sync_function_method(out: &mut String, func: &FunctionDef, prefix: &str, class_name: &str) {
440    let params: Vec<String> = func
441        .params
442        .iter()
443        .map(|p| {
444            let ptype = java_type(&p.ty);
445            format!("{} {}", ptype, to_java_name(&p.name))
446        })
447        .collect();
448
449    let return_type = java_type(&func.return_type);
450
451    writeln!(
452        out,
453        "    public static {} {}({}) throws {}Exception {{",
454        return_type,
455        to_java_name(&func.name),
456        params.join(", "),
457        class_name
458    )
459    .ok();
460
461    writeln!(out, "        try (var arena = Arena.ofConfined()) {{").ok();
462
463    // Marshal parameters (use camelCase Java names)
464    for param in &func.params {
465        marshal_param_to_ffi(out, &to_java_name(&param.name), &param.ty);
466    }
467
468    // Call FFI
469    let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
470
471    let call_args: Vec<String> = func
472        .params
473        .iter()
474        .map(|p| ffi_param_name(&to_java_name(&p.name), &p.ty))
475        .collect();
476
477    if matches!(func.return_type, TypeRef::Unit) {
478        writeln!(out, "            {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
479        writeln!(out, "        }} catch (Throwable e) {{").ok();
480        writeln!(
481            out,
482            "            throw new {}Exception(\"FFI call failed\", e);",
483            class_name
484        )
485        .ok();
486        writeln!(out, "        }}").ok();
487    } else if is_ffi_string_return(&func.return_type) {
488        let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
489        writeln!(
490            out,
491            "            var resultPtr = (MemorySegment) {}.invoke({});",
492            ffi_handle,
493            call_args.join(", ")
494        )
495        .ok();
496        writeln!(out, "            if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
497        writeln!(out, "                return null;").ok();
498        writeln!(out, "            }}").ok();
499        writeln!(
500            out,
501            "            String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
502        )
503        .ok();
504        writeln!(out, "            {}.invoke(resultPtr);", free_handle).ok();
505        writeln!(out, "            return result;").ok();
506        writeln!(out, "        }} catch (Throwable e) {{").ok();
507        writeln!(
508            out,
509            "            throw new {}Exception(\"FFI call failed\", e);",
510            class_name
511        )
512        .ok();
513        writeln!(out, "        }}").ok();
514    } else if matches!(func.return_type, TypeRef::Named(_)) {
515        // Named return types: FFI returns a struct pointer, use accessor functions.
516        let return_type_name = match &func.return_type {
517            TypeRef::Named(name) => name,
518            _ => unreachable!(),
519        };
520        let type_snake = return_type_name.to_snake_case();
521        let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
522        let content_handle = format!(
523            "NativeLib.{}_{}_CONTENT",
524            prefix.to_uppercase(),
525            type_snake.to_uppercase()
526        );
527        writeln!(
528            out,
529            "            var resultPtr = (MemorySegment) {}.invoke({});",
530            ffi_handle,
531            call_args.join(", ")
532        )
533        .ok();
534        writeln!(out, "            if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
535        writeln!(out, "                return null;").ok();
536        writeln!(out, "            }}").ok();
537        // Use accessor to get content, then free the struct
538        writeln!(
539            out,
540            "            var contentPtr = (MemorySegment) {}.invoke(resultPtr);",
541            content_handle
542        )
543        .ok();
544        writeln!(
545            out,
546            "            String content = contentPtr.equals(MemorySegment.NULL) ? null :"
547        )
548        .ok();
549        writeln!(
550            out,
551            "                contentPtr.reinterpret(Long.MAX_VALUE).getString(0);"
552        )
553        .ok();
554        writeln!(out, "            {}.invoke(resultPtr);", free_handle).ok();
555        // Construct result — content from accessor, other fields defaulted for now
556        writeln!(out, "            return new {}(", return_type_name).ok();
557        writeln!(out, "                java.util.Optional.ofNullable(content),").ok();
558        writeln!(out, "                java.util.Optional.empty(),").ok();
559        writeln!(out, "                java.util.List.of(),").ok();
560        writeln!(out, "                java.util.List.of()").ok();
561        writeln!(out, "            );").ok();
562        writeln!(out, "        }} catch (Throwable e) {{").ok();
563        writeln!(
564            out,
565            "            throw new {}Exception(\"FFI call failed\", e);",
566            class_name
567        )
568        .ok();
569        writeln!(out, "        }}").ok();
570    } else {
571        writeln!(
572            out,
573            "            return ({}) {}.invoke({});",
574            java_ffi_return_cast(&func.return_type),
575            ffi_handle,
576            call_args.join(", ")
577        )
578        .ok();
579        writeln!(out, "        }} catch (Throwable e) {{").ok();
580        writeln!(
581            out,
582            "            throw new {}Exception(\"FFI call failed\", e);",
583            class_name
584        )
585        .ok();
586        writeln!(out, "        }}").ok();
587    }
588
589    writeln!(out, "    }}").ok();
590}
591
592fn gen_async_wrapper_method(out: &mut String, func: &FunctionDef) {
593    let params: Vec<String> = func
594        .params
595        .iter()
596        .map(|p| {
597            let ptype = java_type(&p.ty);
598            format!("{} {}", ptype, to_java_name(&p.name))
599        })
600        .collect();
601
602    let return_type = match &func.return_type {
603        TypeRef::Unit => "Void".to_string(),
604        other => java_boxed_type(other).to_string(),
605    };
606
607    let sync_method_name = to_java_name(&func.name);
608    let async_method_name = format!("{}Async", sync_method_name);
609    let param_names: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
610
611    writeln!(
612        out,
613        "    public static CompletableFuture<{}> {}({}) {{",
614        return_type,
615        async_method_name,
616        params.join(", ")
617    )
618    .ok();
619    writeln!(out, "        return CompletableFuture.supplyAsync(() -> {{").ok();
620    writeln!(out, "            try {{").ok();
621    writeln!(
622        out,
623        "                return {}({});",
624        sync_method_name,
625        param_names.join(", ")
626    )
627    .ok();
628    writeln!(out, "            }} catch (Throwable e) {{").ok();
629    writeln!(out, "                throw new CompletionException(e);").ok();
630    writeln!(out, "            }}").ok();
631    writeln!(out, "        }});").ok();
632    writeln!(out, "    }}").ok();
633}
634
635// ---------------------------------------------------------------------------
636// Exception class
637// ---------------------------------------------------------------------------
638
639fn gen_exception_class(package: &str, class_name: &str) -> String {
640    let mut out = String::with_capacity(512);
641
642    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
643    writeln!(out, "package {};", package).ok();
644    writeln!(out).ok();
645
646    writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
647    writeln!(out, "    private final int code;").ok();
648    writeln!(out).ok();
649    writeln!(out, "    public {}Exception(int code, String message) {{", class_name).ok();
650    writeln!(out, "        super(message);").ok();
651    writeln!(out, "        this.code = code;").ok();
652    writeln!(out, "    }}").ok();
653    writeln!(out).ok();
654    writeln!(
655        out,
656        "    public {}Exception(String message, Throwable cause) {{",
657        class_name
658    )
659    .ok();
660    writeln!(out, "        super(message, cause);").ok();
661    writeln!(out, "        this.code = -1;").ok();
662    writeln!(out, "    }}").ok();
663    writeln!(out).ok();
664    writeln!(out, "    public int getCode() {{").ok();
665    writeln!(out, "        return code;").ok();
666    writeln!(out, "    }}").ok();
667    writeln!(out, "}}").ok();
668
669    out
670}
671
672// ---------------------------------------------------------------------------
673// High-level facade class (public API)
674// ---------------------------------------------------------------------------
675
676fn gen_facade_class(api: &ApiSurface, package: &str, public_class: &str, raw_class: &str, _prefix: &str) -> String {
677    let mut body = String::with_capacity(4096);
678
679    writeln!(body, "public final class {} {{", public_class).ok();
680    writeln!(body, "    private {}() {{ }}", public_class).ok();
681    writeln!(body).ok();
682
683    // Generate static methods for free functions
684    for func in &api.functions {
685        // Sync method
686        let params: Vec<String> = func
687            .params
688            .iter()
689            .map(|p| {
690                let ptype = java_type(&p.ty);
691                format!("{} {}", ptype, to_java_name(&p.name))
692            })
693            .collect();
694
695        let return_type = java_type(&func.return_type);
696
697        if !func.doc.is_empty() {
698            writeln!(body, "    /**").ok();
699            for line in func.doc.lines() {
700                writeln!(body, "     * {}", line).ok();
701            }
702            writeln!(body, "     */").ok();
703        }
704
705        writeln!(
706            body,
707            "    public static {} {}({}) throws {}Exception {{",
708            return_type,
709            to_java_name(&func.name),
710            params.join(", "),
711            raw_class
712        )
713        .ok();
714
715        // Null checks for required parameters
716        for param in &func.params {
717            if !param.optional {
718                let pname = to_java_name(&param.name);
719                writeln!(
720                    body,
721                    "        java.util.Objects.requireNonNull({}, \"{} must not be null\");",
722                    pname, pname
723                )
724                .ok();
725            }
726        }
727
728        // Delegate to the raw FFI class
729        let call_args: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
730
731        if matches!(func.return_type, TypeRef::Unit) {
732            writeln!(
733                body,
734                "        {}.{}({});",
735                raw_class,
736                to_java_name(&func.name),
737                call_args.join(", ")
738            )
739            .ok();
740        } else {
741            writeln!(
742                body,
743                "        return {}.{}({});",
744                raw_class,
745                to_java_name(&func.name),
746                call_args.join(", ")
747            )
748            .ok();
749        }
750
751        writeln!(body, "    }}").ok();
752        writeln!(body).ok();
753
754        // Generate overload without optional params (convenience method)
755        let has_optional = func.params.iter().any(|p| p.optional);
756        if has_optional {
757            let required_params: Vec<String> = func
758                .params
759                .iter()
760                .filter(|p| !p.optional)
761                .map(|p| {
762                    let ptype = java_type(&p.ty);
763                    format!("{} {}", ptype, to_java_name(&p.name))
764                })
765                .collect();
766
767            writeln!(
768                body,
769                "    public static {} {}({}) throws {}Exception {{",
770                return_type,
771                to_java_name(&func.name),
772                required_params.join(", "),
773                raw_class
774            )
775            .ok();
776
777            // Build call with null for optional params
778            let full_args: Vec<String> = func
779                .params
780                .iter()
781                .map(|p| {
782                    if p.optional {
783                        "null".to_string()
784                    } else {
785                        to_java_name(&p.name)
786                    }
787                })
788                .collect();
789
790            if matches!(func.return_type, TypeRef::Unit) {
791                writeln!(body, "        {}({});", to_java_name(&func.name), full_args.join(", ")).ok();
792            } else {
793                writeln!(
794                    body,
795                    "        return {}({});",
796                    to_java_name(&func.name),
797                    full_args.join(", ")
798                )
799                .ok();
800            }
801
802            writeln!(body, "    }}").ok();
803            writeln!(body).ok();
804        }
805    }
806
807    writeln!(body, "}}").ok();
808
809    // Now assemble the file with imports
810    let mut out = String::with_capacity(body.len() + 512);
811
812    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
813    writeln!(out, "package {};", package).ok();
814    writeln!(out).ok();
815
816    // Check what imports are needed based on content
817    if body.contains("List<") {
818        writeln!(out, "import java.util.List;").ok();
819    }
820    if body.contains("Map<") {
821        writeln!(out, "import java.util.Map;").ok();
822    }
823    if body.contains("Optional<") {
824        writeln!(out, "import java.util.Optional;").ok();
825    }
826
827    writeln!(out).ok();
828    out.push_str(&body);
829
830    out
831}
832
833// ---------------------------------------------------------------------------
834// Opaque handle classes
835// ---------------------------------------------------------------------------
836
837fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
838    let mut out = String::with_capacity(1024);
839    let class_name = &typ.name;
840    let type_snake = class_name.to_snake_case();
841
842    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
843    writeln!(out, "package {};", package).ok();
844    writeln!(out).ok();
845    writeln!(out, "import java.lang.foreign.Arena;").ok();
846    writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
847    writeln!(out).ok();
848
849    if !typ.doc.is_empty() {
850        writeln!(out, "/**").ok();
851        for line in typ.doc.lines() {
852            writeln!(out, " * {}", line).ok();
853        }
854        writeln!(out, " */").ok();
855    }
856
857    writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
858    writeln!(out, "    private final MemorySegment handle;").ok();
859    writeln!(out).ok();
860    writeln!(out, "    {}(MemorySegment handle) {{", class_name).ok();
861    writeln!(out, "        this.handle = handle;").ok();
862    writeln!(out, "    }}").ok();
863    writeln!(out).ok();
864    writeln!(out, "    MemorySegment handle() {{").ok();
865    writeln!(out, "        return this.handle;").ok();
866    writeln!(out, "    }}").ok();
867    writeln!(out).ok();
868    writeln!(out, "    @Override").ok();
869    writeln!(out, "    public void close() {{").ok();
870    writeln!(
871        out,
872        "        if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
873    )
874    .ok();
875    writeln!(out, "            try {{").ok();
876    writeln!(
877        out,
878        "                NativeLib.{}.invoke(handle);",
879        format!("{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase())
880    )
881    .ok();
882    writeln!(out, "            }} catch (Throwable e) {{").ok();
883    writeln!(
884        out,
885        "                throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
886        class_name
887    )
888    .ok();
889    writeln!(out, "            }}").ok();
890    writeln!(out, "        }}").ok();
891    writeln!(out, "    }}").ok();
892    writeln!(out, "}}").ok();
893
894    out
895}
896
897// ---------------------------------------------------------------------------
898// Record types (Java records)
899// ---------------------------------------------------------------------------
900
901/// Maximum line length before splitting record fields across multiple lines.
902/// Checkstyle enforces 120 chars; we split at 100 to leave headroom for indentation.
903const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
904
905fn gen_record_type(package: &str, typ: &TypeDef) -> String {
906    // Generate the record body first, then scan for needed imports
907    let field_list: Vec<String> = typ
908        .fields
909        .iter()
910        .map(|f| {
911            let ftype = if f.optional {
912                format!("Optional<{}>", java_boxed_type(&f.ty))
913            } else {
914                java_type(&f.ty).to_string()
915            };
916            format!("{} {}", ftype, safe_java_field_name(&f.name))
917        })
918        .collect();
919
920    // Build the single-line form to check length and scan for imports.
921    let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
922
923    // Build the actual record declaration, splitting across lines if too long.
924    let mut record_block = String::new();
925    if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
926        writeln!(record_block, "public record {}(", typ.name).ok();
927        for (i, field) in field_list.iter().enumerate() {
928            let comma = if i < field_list.len() - 1 { "," } else { "" };
929            writeln!(record_block, "    {}{}", field, comma).ok();
930        }
931        writeln!(record_block, ") {{").ok();
932    } else {
933        writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
934    }
935
936    // Add builder() factory method if type has defaults
937    if typ.has_default {
938        writeln!(record_block, "    public static {}Builder builder() {{", typ.name).ok();
939        writeln!(record_block, "        return new {}Builder();", typ.name).ok();
940        writeln!(record_block, "    }}").ok();
941    }
942
943    writeln!(record_block, "}}").ok();
944
945    // Scan the single-line form to determine which imports are needed
946    let mut out = String::with_capacity(record_block.len() + 512);
947    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
948    writeln!(out, "package {};", package).ok();
949    writeln!(out).ok();
950    if single_line.contains("List<") {
951        writeln!(out, "import java.util.List;").ok();
952    }
953    if single_line.contains("Map<") {
954        writeln!(out, "import java.util.Map;").ok();
955    }
956    if single_line.contains("Optional<") {
957        writeln!(out, "import java.util.Optional;").ok();
958    }
959    writeln!(out).ok();
960    write!(out, "{}", record_block).ok();
961
962    out
963}
964
965// ---------------------------------------------------------------------------
966// Enum classes
967// ---------------------------------------------------------------------------
968
969fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
970    let mut out = String::with_capacity(1024);
971
972    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
973    writeln!(out, "package {};", package).ok();
974    writeln!(out).ok();
975
976    writeln!(out, "public enum {} {{", enum_def.name).ok();
977
978    for (i, variant) in enum_def.variants.iter().enumerate() {
979        let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
980        writeln!(out, "    {}{}", variant.name, comma).ok();
981    }
982
983    writeln!(out, "}}").ok();
984
985    out
986}
987
988// ---------------------------------------------------------------------------
989// Helper functions for FFI marshalling
990// ---------------------------------------------------------------------------
991
992fn gen_ffi_layout(ty: &TypeRef) -> String {
993    match ty {
994        TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
995        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
996        TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
997        TypeRef::Optional(inner) => gen_ffi_layout(inner),
998        TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
999        TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
1000        TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
1001        TypeRef::Unit => "".to_string(),
1002        TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
1003    }
1004}
1005
1006fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef) {
1007    match ty {
1008        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1009            let cname = "c".to_string() + name;
1010            writeln!(out, "            var {} = arena.allocateFrom({});", cname, name).ok();
1011        }
1012        TypeRef::Named(_) => {
1013            // Named types are struct pointers in FFI.
1014            // For now, pass NULL to use defaults. Full support requires JSON serialization.
1015            let cname = "c".to_string() + name;
1016            writeln!(out, "            var {} = MemorySegment.NULL;", cname).ok();
1017        }
1018        TypeRef::Optional(inner) => {
1019            // For optional types, marshal the inner type if not null
1020            match inner.as_ref() {
1021                TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1022                    let cname = "c".to_string() + name;
1023                    writeln!(
1024                        out,
1025                        "            var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
1026                        cname, name, name
1027                    )
1028                    .ok();
1029                }
1030                TypeRef::Named(_) => {
1031                    // Optional named types also pass NULL for now
1032                    let cname = "c".to_string() + name;
1033                    writeln!(out, "            var {} = MemorySegment.NULL;", cname).ok();
1034                }
1035                _ => {
1036                    // Other optional types (primitives) pass through
1037                }
1038            }
1039        }
1040        _ => {
1041            // Primitives and others pass through directly
1042        }
1043    }
1044}
1045
1046fn ffi_param_name(name: &str, ty: &TypeRef) -> String {
1047    match ty {
1048        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
1049        TypeRef::Named(_) => "c".to_string() + name,
1050        TypeRef::Optional(inner) => match inner.as_ref() {
1051            TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
1052                "c".to_string() + name
1053            }
1054            _ => name.to_string(),
1055        },
1056        _ => name.to_string(),
1057    }
1058}
1059
1060/// Build a `FunctionDescriptor` string for a given return layout and parameter layouts.
1061/// Handles void returns (ofVoid) and non-void returns (of) correctly.
1062fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
1063    if return_layout.is_empty() {
1064        // Void return
1065        if param_layouts.is_empty() {
1066            "FunctionDescriptor.ofVoid()".to_string()
1067        } else {
1068            format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
1069        }
1070    } else {
1071        // Non-void return
1072        if param_layouts.is_empty() {
1073            format!("FunctionDescriptor.of({})", return_layout)
1074        } else {
1075            format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
1076        }
1077    }
1078}
1079
1080/// Returns true if the given return type maps to an FFI ADDRESS that represents a string
1081/// (i.e. the FFI returns `*mut c_char` which must be unmarshaled and freed).
1082fn is_ffi_string_return(ty: &TypeRef) -> bool {
1083    match ty {
1084        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
1085        TypeRef::Optional(inner) => is_ffi_string_return(inner),
1086        _ => false,
1087    }
1088}
1089
1090/// Returns the appropriate Java cast type for non-string FFI return values.
1091fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
1092    match ty {
1093        TypeRef::Primitive(prim) => match prim {
1094            PrimitiveType::Bool => "boolean",
1095            PrimitiveType::U8 | PrimitiveType::I8 => "byte",
1096            PrimitiveType::U16 | PrimitiveType::I16 => "short",
1097            PrimitiveType::U32 | PrimitiveType::I32 => "int",
1098            PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
1099            PrimitiveType::F32 => "float",
1100            PrimitiveType::F64 => "double",
1101        },
1102        TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
1103        _ => "MemorySegment",
1104    }
1105}
1106
1107fn gen_helper_methods(out: &mut String) {
1108    // Only emit helper methods that are actually called in the generated body.
1109    let needs_read_cstring = out.contains("readCString(");
1110    let needs_read_bytes = out.contains("readBytes(");
1111
1112    if !needs_read_cstring && !needs_read_bytes {
1113        return;
1114    }
1115
1116    writeln!(out, "    // Helper methods for FFI marshalling").ok();
1117    writeln!(out).ok();
1118
1119    if needs_read_cstring {
1120        writeln!(out, "    private static String readCString(MemorySegment ptr) {{").ok();
1121        writeln!(out, "        if (ptr == null || ptr.address() == 0) {{").ok();
1122        writeln!(out, "            return null;").ok();
1123        writeln!(out, "        }}").ok();
1124        writeln!(out, "        return ptr.getUtf8String(0);").ok();
1125        writeln!(out, "    }}").ok();
1126        writeln!(out).ok();
1127    }
1128
1129    if needs_read_bytes {
1130        writeln!(
1131            out,
1132            "    private static byte[] readBytes(MemorySegment ptr, long len) {{"
1133        )
1134        .ok();
1135        writeln!(out, "        if (ptr == null || ptr.address() == 0) {{").ok();
1136        writeln!(out, "            return new byte[0];").ok();
1137        writeln!(out, "        }}").ok();
1138        writeln!(out, "        byte[] bytes = new byte[(int) len];").ok();
1139        writeln!(
1140            out,
1141            "        MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
1142        )
1143        .ok();
1144        writeln!(out, "        return bytes;").ok();
1145        writeln!(out, "    }}").ok();
1146    }
1147}
1148
1149// ---------------------------------------------------------------------------
1150// Builder class for types with defaults
1151// ---------------------------------------------------------------------------
1152
1153/// Format a default value for an Optional field, wrapping it in Optional.of()
1154/// with proper Java literal syntax.
1155fn format_optional_value(ty: &TypeRef, default: &str) -> String {
1156    // Check if the default is already wrapped (e.g., "Optional.of(...)" or "Optional.empty()")
1157    if default.contains("Optional.") {
1158        return default.to_string();
1159    }
1160
1161    // Unwrap Optional types to get the inner type
1162    let inner_ty = match ty {
1163        TypeRef::Optional(inner) => inner.as_ref(),
1164        other => other,
1165    };
1166
1167    // Determine the proper literal suffix based on type
1168    let formatted_value = match inner_ty {
1169        TypeRef::Primitive(p) => match p {
1170            PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
1171                // Add 'L' suffix for long values if not already present
1172                if default.ends_with('L') || default.ends_with('l') {
1173                    default.to_string()
1174                } else if default.parse::<i64>().is_ok() {
1175                    format!("{}L", default)
1176                } else {
1177                    default.to_string()
1178                }
1179            }
1180            PrimitiveType::F32 => {
1181                // Add 'f' suffix for float values if not already present
1182                if default.ends_with('f') || default.ends_with('F') {
1183                    default.to_string()
1184                } else if default.parse::<f32>().is_ok() {
1185                    format!("{}f", default)
1186                } else {
1187                    default.to_string()
1188                }
1189            }
1190            PrimitiveType::F64 => {
1191                // Double defaults can have optional 'd' suffix, but 0.0 is fine
1192                default.to_string()
1193            }
1194            _ => default.to_string(),
1195        },
1196        _ => default.to_string(),
1197    };
1198
1199    format!("Optional.of({})", formatted_value)
1200}
1201
1202fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
1203    let mut body = String::with_capacity(2048);
1204
1205    writeln!(body, "public class {}Builder {{", typ.name).ok();
1206    writeln!(body).ok();
1207
1208    // Generate field declarations with defaults
1209    for field in &typ.fields {
1210        let field_name = safe_java_field_name(&field.name);
1211
1212        // Skip unnamed tuple fields (name is "_0", "_1", "0", "1", etc.) — Java requires named fields
1213        if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
1214            || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
1215        {
1216            continue;
1217        }
1218
1219        // Duration maps to primitive `long` in the public record, but in builder
1220        // classes we use boxed `Long` so that `null` can represent "not set".
1221        let field_type = if field.optional {
1222            format!("Optional<{}>", java_boxed_type(&field.ty))
1223        } else if matches!(field.ty, TypeRef::Duration) {
1224            java_boxed_type(&field.ty).to_string()
1225        } else {
1226            java_type(&field.ty).to_string()
1227        };
1228
1229        let default_value = if field.optional {
1230            // For Optional fields, always use Optional.empty() or Optional.of(value)
1231            if let Some(default) = &field.default {
1232                // If there's an explicit default, wrap it in Optional.of()
1233                format_optional_value(&field.ty, default)
1234            } else {
1235                // If no default, use Optional.empty()
1236                "Optional.empty()".to_string()
1237            }
1238        } else {
1239            // For non-Optional fields, use regular defaults
1240            if let Some(default) = &field.default {
1241                default.clone()
1242            } else {
1243                match &field.ty {
1244                    TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "\"\"".to_string(),
1245                    TypeRef::Bytes => "new byte[0]".to_string(),
1246                    TypeRef::Primitive(p) => match p {
1247                        PrimitiveType::Bool => "false".to_string(),
1248                        PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
1249                        _ => "0".to_string(),
1250                    },
1251                    TypeRef::Vec(_) => "List.of()".to_string(),
1252                    TypeRef::Map(_, _) => "Map.of()".to_string(),
1253                    TypeRef::Optional(_) => "Optional.empty()".to_string(),
1254                    TypeRef::Duration => "null".to_string(),
1255                    _ => "null".to_string(),
1256                }
1257            }
1258        };
1259
1260        writeln!(body, "    private {} {} = {};", field_type, field_name, default_value).ok();
1261    }
1262
1263    writeln!(body).ok();
1264
1265    // Generate withXxx() methods
1266    for field in &typ.fields {
1267        // Skip unnamed tuple fields (name is "_0", "_1", "0", "1", etc.) — Java requires named fields
1268        if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
1269            || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
1270        {
1271            continue;
1272        }
1273
1274        let field_name = safe_java_field_name(&field.name);
1275        let field_name_pascal = to_class_name(&field.name);
1276        let field_type = if field.optional {
1277            format!("Optional<{}>", java_boxed_type(&field.ty))
1278        } else if matches!(field.ty, TypeRef::Duration) {
1279            java_boxed_type(&field.ty).to_string()
1280        } else {
1281            java_type(&field.ty).to_string()
1282        };
1283
1284        writeln!(
1285            body,
1286            "    public {}Builder with{}({} value) {{",
1287            typ.name, field_name_pascal, field_type
1288        )
1289        .ok();
1290        writeln!(body, "        this.{} = value;", field_name).ok();
1291        writeln!(body, "        return this;").ok();
1292        writeln!(body, "    }}").ok();
1293        writeln!(body).ok();
1294    }
1295
1296    // Generate build() method
1297    writeln!(body, "    public {} build() {{", typ.name).ok();
1298    writeln!(body, "        return new {}(", typ.name).ok();
1299    let non_tuple_fields: Vec<_> = typ
1300        .fields
1301        .iter()
1302        .filter(|f| {
1303            // Include named fields (skip unnamed tuple fields)
1304            !(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
1305                || f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
1306        })
1307        .collect();
1308    for (i, field) in non_tuple_fields.iter().enumerate() {
1309        let field_name = safe_java_field_name(&field.name);
1310        let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
1311        writeln!(body, "            {}{}", field_name, comma).ok();
1312    }
1313    writeln!(body, "        );").ok();
1314    writeln!(body, "    }}").ok();
1315
1316    writeln!(body, "}}").ok();
1317
1318    // Now assemble with conditional imports based on what's actually used in the body
1319    let mut out = String::with_capacity(body.len() + 512);
1320
1321    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1322    writeln!(out, "package {};", package).ok();
1323    writeln!(out).ok();
1324
1325    if body.contains("List<") {
1326        writeln!(out, "import java.util.List;").ok();
1327    }
1328    if body.contains("Map<") {
1329        writeln!(out, "import java.util.Map;").ok();
1330    }
1331    if body.contains("Optional<") {
1332        writeln!(out, "import java.util.Optional;").ok();
1333    }
1334
1335    writeln!(out).ok();
1336    out.push_str(&body);
1337
1338    out
1339}