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