1use crate::type_map::{java_boxed_type, java_ffi_type, java_type};
2use ahash::AHashSet;
3use alef_codegen::naming::{to_class_name, to_java_name};
4use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
5use alef_core::config::{AlefConfig, Language, resolve_output_dir};
6use alef_core::ir::{ApiSurface, EnumDef, FunctionDef, PrimitiveType, TypeDef, TypeRef};
7use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
8use std::fmt::Write;
9use std::path::PathBuf;
10
11const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
14 "wait",
15 "notify",
16 "notifyAll",
17 "getClass",
18 "hashCode",
19 "equals",
20 "toString",
21 "clone",
22 "finalize",
23];
24
25fn safe_java_field_name(name: &str) -> String {
29 let java_name = to_java_name(name);
30 if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
31 format!("{}Value", java_name)
32 } else {
33 java_name
34 }
35}
36
37pub struct JavaBackend;
38
39impl JavaBackend {
40 fn resolve_main_class(api: &ApiSurface) -> String {
46 let base = to_class_name(&api.crate_name.replace('-', "_"));
47 if base.ends_with("Rs") {
48 base
49 } else {
50 format!("{}Rs", base)
51 }
52 }
53}
54
55impl Backend for JavaBackend {
56 fn name(&self) -> &str {
57 "java"
58 }
59
60 fn language(&self) -> Language {
61 Language::Java
62 }
63
64 fn capabilities(&self) -> Capabilities {
65 Capabilities {
66 supports_async: true,
67 supports_classes: true,
68 supports_enums: true,
69 supports_option: true,
70 supports_result: true,
71 ..Capabilities::default()
72 }
73 }
74
75 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
76 let package = config.java_package();
77 let prefix = config.ffi_prefix();
78 let main_class = Self::resolve_main_class(api);
79 let package_path = package.replace('.', "/");
80
81 let output_dir = resolve_output_dir(
82 config.output.java.as_ref(),
83 &config.crate_config.name,
84 "packages/java/src/main/java/",
85 );
86
87 let base_path = PathBuf::from(&output_dir).join(&package_path);
88
89 let mut files = Vec::new();
90
91 let description = config
93 .scaffold
94 .as_ref()
95 .and_then(|s| s.description.as_deref())
96 .unwrap_or("High-performance HTML to Markdown converter.");
97 files.push(GeneratedFile {
98 path: base_path.join("package-info.java"),
99 content: format!(
100 "/**\n * {description}\n */\npackage {package};\n",
101 description = description,
102 package = package,
103 ),
104 generated_header: true,
105 });
106
107 files.push(GeneratedFile {
109 path: base_path.join("NativeLib.java"),
110 content: gen_native_lib(api, config, &package, &prefix),
111 generated_header: true,
112 });
113
114 files.push(GeneratedFile {
116 path: base_path.join(format!("{}.java", main_class)),
117 content: gen_main_class(api, config, &package, &main_class, &prefix),
118 generated_header: true,
119 });
120
121 files.push(GeneratedFile {
123 path: base_path.join(format!("{}Exception.java", main_class)),
124 content: gen_exception_class(&package, &main_class),
125 generated_header: true,
126 });
127
128 let complex_enums: AHashSet<String> = api
132 .enums
133 .iter()
134 .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
135 .map(|e| e.name.clone())
136 .collect();
137
138 let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
140
141 for typ in &api.types {
143 if !typ.is_opaque && !typ.fields.is_empty() {
144 files.push(GeneratedFile {
145 path: base_path.join(format!("{}.java", typ.name)),
146 content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
147 generated_header: true,
148 });
149 if typ.has_default {
151 files.push(GeneratedFile {
152 path: base_path.join(format!("{}Builder.java", typ.name)),
153 content: gen_builder_class(&package, typ),
154 generated_header: true,
155 });
156 }
157 }
158 }
159
160 let builder_class_names: AHashSet<String> = api
163 .types
164 .iter()
165 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
166 .map(|t| format!("{}Builder", t.name))
167 .collect();
168
169 for typ in &api.types {
171 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
172 files.push(GeneratedFile {
173 path: base_path.join(format!("{}.java", typ.name)),
174 content: gen_opaque_handle_class(&package, typ, &prefix),
175 generated_header: true,
176 });
177 }
178 }
179
180 for enum_def in &api.enums {
182 files.push(GeneratedFile {
183 path: base_path.join(format!("{}.java", enum_def.name)),
184 content: gen_enum_class(&package, enum_def),
185 generated_header: true,
186 });
187 }
188
189 for error in &api.errors {
191 for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
192 files.push(GeneratedFile {
193 path: base_path.join(format!("{}.java", class_name)),
194 content,
195 generated_header: true,
196 });
197 }
198 }
199
200 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
202
203 Ok(files)
204 }
205
206 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
207 let package = config.java_package();
208 let prefix = config.ffi_prefix();
209 let main_class = Self::resolve_main_class(api);
210 let package_path = package.replace('.', "/");
211
212 let output_dir = resolve_output_dir(
213 config.output.java.as_ref(),
214 &config.crate_config.name,
215 "packages/java/src/main/java/",
216 );
217
218 let base_path = PathBuf::from(&output_dir).join(&package_path);
219
220 let public_class = main_class.trim_end_matches("Rs").to_string();
223 let facade_content = gen_facade_class(api, &package, &public_class, &main_class, &prefix);
224
225 Ok(vec![GeneratedFile {
226 path: base_path.join(format!("{}.java", public_class)),
227 content: facade_content,
228 generated_header: true,
229 }])
230 }
231
232 fn build_config(&self) -> Option<BuildConfig> {
233 Some(BuildConfig {
234 tool: "mvn",
235 crate_suffix: "",
236 depends_on_ffi: true,
237 post_build: vec![],
238 })
239 }
240}
241
242fn gen_native_lib(api: &ApiSurface, config: &AlefConfig, package: &str, prefix: &str) -> String {
247 let mut body = String::with_capacity(2048);
249 let lib_name = config.ffi_lib_name();
252
253 writeln!(body, "final class NativeLib {{").ok();
254 writeln!(body, " private static final Linker LINKER = Linker.nativeLinker();").ok();
255 writeln!(body, " private static final SymbolLookup LIB;").ok();
256 writeln!(body).ok();
257 writeln!(body, " static {{").ok();
258 writeln!(body, " System.loadLibrary(\"{}\");", lib_name).ok();
259 writeln!(body, " LIB = SymbolLookup.loaderLookup();").ok();
260 writeln!(body, " }}").ok();
261 writeln!(body).ok();
262
263 for func in &api.functions {
267 let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
268 let return_layout = gen_ffi_layout(&func.return_type);
269 let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
270
271 let layout_str = gen_function_descriptor(&return_layout, ¶m_layouts);
272
273 let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
274
275 writeln!(
276 body,
277 " static final MethodHandle {} = LINKER.downcallHandle(",
278 handle_name
279 )
280 .ok();
281 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
282 writeln!(body, " {}", layout_str).ok();
283 writeln!(body, " );").ok();
284 }
285
286 {
288 let free_name = format!("{}_free_string", prefix);
289 let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
290 writeln!(body).ok();
291 writeln!(
292 body,
293 " static final MethodHandle {} = LINKER.downcallHandle(",
294 handle_name
295 )
296 .ok();
297 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_name).ok();
298 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
299 writeln!(body, " );").ok();
300 }
301
302 {
304 writeln!(
305 body,
306 " static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
307 prefix.to_uppercase()
308 )
309 .ok();
310 writeln!(body, " LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
311 writeln!(body, " FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
312 writeln!(body, " );").ok();
313
314 writeln!(
315 body,
316 " static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
317 prefix.to_uppercase()
318 )
319 .ok();
320 writeln!(
321 body,
322 " LIB.find(\"{}_last_error_context\").orElseThrow(),",
323 prefix
324 )
325 .ok();
326 writeln!(body, " FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
327 writeln!(body, " );").ok();
328 }
329
330 let mut emitted_free_handles: AHashSet<String> = AHashSet::new();
333
334 let opaque_type_names: AHashSet<String> = api
336 .types
337 .iter()
338 .filter(|t| t.is_opaque)
339 .map(|t| t.name.clone())
340 .collect();
341
342 for func in &api.functions {
344 if let TypeRef::Named(name) = &func.return_type {
345 let type_snake = name.to_snake_case();
346 let type_upper = type_snake.to_uppercase();
347 let is_opaque = opaque_type_names.contains(name.as_str());
348
349 if is_opaque {
350 } else {
353 let to_json_handle = format!("{}_{}_TO_JSON", prefix.to_uppercase(), type_upper);
357 let to_json_ffi = format!("{}_{}_to_json", prefix, type_snake);
358 writeln!(body).ok();
359 writeln!(
360 body,
361 " static final MethodHandle {} = LINKER.downcallHandle(",
362 to_json_handle
363 )
364 .ok();
365 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", to_json_ffi).ok();
366 writeln!(
367 body,
368 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
369 )
370 .ok();
371 writeln!(body, " );").ok();
372 }
373
374 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
376 let free_ffi = format!("{}_{}_free", prefix, type_snake);
377 if emitted_free_handles.insert(free_handle.clone()) {
378 writeln!(body).ok();
379 writeln!(
380 body,
381 " static final MethodHandle {} = LINKER.downcallHandle(",
382 free_handle
383 )
384 .ok();
385 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
386 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
387 writeln!(body, " );").ok();
388 }
389 }
390 }
391
392 let mut emitted_from_json_handles: AHashSet<String> = AHashSet::new();
395 for func in &api.functions {
396 for param in &func.params {
397 let inner_name = match ¶m.ty {
399 TypeRef::Named(n) => Some(n.clone()),
400 TypeRef::Optional(inner) => {
401 if let TypeRef::Named(n) = inner.as_ref() {
402 Some(n.clone())
403 } else {
404 None
405 }
406 }
407 _ => None,
408 };
409 if let Some(name) = inner_name {
410 if !opaque_type_names.contains(name.as_str()) {
411 let type_snake = name.to_snake_case();
412 let type_upper = type_snake.to_uppercase();
413
414 let from_json_handle = format!("{}_{}_FROM_JSON", prefix.to_uppercase(), type_upper);
416 let from_json_ffi = format!("{}_{}_from_json", prefix, type_snake);
417 if emitted_from_json_handles.insert(from_json_handle.clone()) {
418 writeln!(body).ok();
419 writeln!(
420 body,
421 " static final MethodHandle {} = LINKER.downcallHandle(",
422 from_json_handle
423 )
424 .ok();
425 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", from_json_ffi).ok();
426 writeln!(
427 body,
428 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
429 )
430 .ok();
431 writeln!(body, " );").ok();
432 }
433
434 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
436 let free_ffi = format!("{}_{}_free", prefix, type_snake);
437 if emitted_free_handles.insert(free_handle.clone()) {
438 writeln!(body).ok();
439 writeln!(
440 body,
441 " static final MethodHandle {} = LINKER.downcallHandle(",
442 free_handle
443 )
444 .ok();
445 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
446 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
447 writeln!(body, " );").ok();
448 }
449 }
450 }
451 }
452 }
453
454 let builder_class_names: AHashSet<String> = api
457 .types
458 .iter()
459 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
460 .map(|t| format!("{}Builder", t.name))
461 .collect();
462
463 for typ in &api.types {
465 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
466 let type_snake = typ.name.to_snake_case();
467 let type_upper = type_snake.to_uppercase();
468 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
469 let free_ffi = format!("{}_{}_free", prefix, type_snake);
470 if emitted_free_handles.insert(free_handle.clone()) {
471 writeln!(body).ok();
472 writeln!(
473 body,
474 " static final MethodHandle {} = LINKER.downcallHandle(",
475 free_handle
476 )
477 .ok();
478 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
479 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
480 writeln!(body, " );").ok();
481 }
482 }
483 }
484
485 writeln!(body, "}}").ok();
486
487 let mut out = String::with_capacity(body.len() + 512);
489
490 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
491 writeln!(out, "package {};", package).ok();
492 writeln!(out).ok();
493 if body.contains("Arena") {
494 writeln!(out, "import java.lang.foreign.Arena;").ok();
495 }
496 if body.contains("FunctionDescriptor") {
497 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
498 }
499 if body.contains("Linker") {
500 writeln!(out, "import java.lang.foreign.Linker;").ok();
501 }
502 if body.contains("MemorySegment") {
503 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
504 }
505 if body.contains("SymbolLookup") {
506 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
507 }
508 if body.contains("ValueLayout") {
509 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
510 }
511 if body.contains("MethodHandle") {
512 writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
513 }
514 writeln!(out).ok();
515
516 out.push_str(&body);
517
518 out
519}
520
521fn gen_main_class(api: &ApiSurface, _config: &AlefConfig, package: &str, class_name: &str, prefix: &str) -> String {
526 let opaque_types: AHashSet<String> = api
528 .types
529 .iter()
530 .filter(|t| t.is_opaque)
531 .map(|t| t.name.clone())
532 .collect();
533
534 let mut body = String::with_capacity(4096);
536
537 writeln!(body, "public final class {} {{", class_name).ok();
538 writeln!(body, " private {}() {{ }}", class_name).ok();
539 writeln!(body).ok();
540
541 for func in &api.functions {
543 gen_sync_function_method(&mut body, func, prefix, class_name, &opaque_types);
545 writeln!(body).ok();
546
547 if func.is_async {
549 gen_async_wrapper_method(&mut body, func);
550 writeln!(body).ok();
551 }
552 }
553
554 gen_helper_methods(&mut body);
556
557 writeln!(body, "}}").ok();
558
559 let mut out = String::with_capacity(body.len() + 512);
561
562 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
563 writeln!(out, "package {};", package).ok();
564 writeln!(out).ok();
565 if body.contains("Arena") {
566 writeln!(out, "import java.lang.foreign.Arena;").ok();
567 }
568 if body.contains("FunctionDescriptor") {
569 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
570 }
571 if body.contains("Linker") {
572 writeln!(out, "import java.lang.foreign.Linker;").ok();
573 }
574 if body.contains("MemorySegment") {
575 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
576 }
577 if body.contains("SymbolLookup") {
578 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
579 }
580 if body.contains("ValueLayout") {
581 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
582 }
583 if body.contains("List<") {
584 writeln!(out, "import java.util.List;").ok();
585 }
586 if body.contains("Map<") {
587 writeln!(out, "import java.util.Map;").ok();
588 }
589 if body.contains("Optional<") {
590 writeln!(out, "import java.util.Optional;").ok();
591 }
592 if body.contains("HashMap<") || body.contains("new HashMap") {
593 writeln!(out, "import java.util.HashMap;").ok();
594 }
595 if body.contains("CompletableFuture") {
596 writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
597 }
598 if body.contains("CompletionException") {
599 writeln!(out, "import java.util.concurrent.CompletionException;").ok();
600 }
601 if body.contains(" ObjectMapper") {
605 writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
606 }
607 writeln!(out).ok();
608
609 out.push_str(&body);
610
611 out
612}
613
614fn gen_sync_function_method(
615 out: &mut String,
616 func: &FunctionDef,
617 prefix: &str,
618 class_name: &str,
619 opaque_types: &AHashSet<String>,
620) {
621 let params: Vec<String> = func
622 .params
623 .iter()
624 .map(|p| {
625 let ptype = java_type(&p.ty);
626 format!("{} {}", ptype, to_java_name(&p.name))
627 })
628 .collect();
629
630 let return_type = java_type(&func.return_type);
631
632 writeln!(
633 out,
634 " public static {} {}({}) throws {}Exception {{",
635 return_type,
636 to_java_name(&func.name),
637 params.join(", "),
638 class_name
639 )
640 .ok();
641
642 writeln!(out, " try (var arena = Arena.ofConfined()) {{").ok();
643
644 let ffi_ptr_params: Vec<(String, String)> = func
647 .params
648 .iter()
649 .filter_map(|p| {
650 let inner_name = match &p.ty {
651 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => Some(n.clone()),
652 TypeRef::Optional(inner) => {
653 if let TypeRef::Named(n) = inner.as_ref() {
654 if !opaque_types.contains(n.as_str()) {
655 Some(n.clone())
656 } else {
657 None
658 }
659 } else {
660 None
661 }
662 }
663 _ => None,
664 };
665 inner_name.map(|type_name| {
666 let cname = "c".to_string() + &to_java_name(&p.name);
667 let type_snake = type_name.to_snake_case();
668 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
669 (cname, free_handle)
670 })
671 })
672 .collect();
673
674 for param in &func.params {
676 marshal_param_to_ffi(out, &to_java_name(¶m.name), ¶m.ty, opaque_types, prefix);
677 }
678
679 let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
681
682 let call_args: Vec<String> = func
683 .params
684 .iter()
685 .map(|p| ffi_param_name(&to_java_name(&p.name), &p.ty, opaque_types))
686 .collect();
687
688 let emit_ffi_ptr_cleanup = |out: &mut String| {
690 for (cname, free_handle) in &ffi_ptr_params {
691 writeln!(out, " if (!{}.equals(MemorySegment.NULL)) {{", cname).ok();
692 writeln!(out, " {}.invoke({});", free_handle, cname).ok();
693 writeln!(out, " }}").ok();
694 }
695 };
696
697 if matches!(func.return_type, TypeRef::Unit) {
698 writeln!(out, " {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
699 emit_ffi_ptr_cleanup(out);
700 writeln!(out, " }} catch (Throwable e) {{").ok();
701 writeln!(
702 out,
703 " throw new {}Exception(\"FFI call failed\", e);",
704 class_name
705 )
706 .ok();
707 writeln!(out, " }}").ok();
708 } else if is_ffi_string_return(&func.return_type) {
709 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
710 writeln!(
711 out,
712 " var resultPtr = (MemorySegment) {}.invoke({});",
713 ffi_handle,
714 call_args.join(", ")
715 )
716 .ok();
717 emit_ffi_ptr_cleanup(out);
718 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
719 writeln!(
720 out,
721 " int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
722 prefix.to_uppercase()
723 )
724 .ok();
725 writeln!(out, " if (errCode != 0) {{").ok();
726 writeln!(
727 out,
728 " var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
729 prefix.to_uppercase()
730 )
731 .ok();
732 writeln!(
733 out,
734 " String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
735 )
736 .ok();
737 writeln!(
738 out,
739 " throw new {}Exception(errCode, msg);",
740 class_name
741 )
742 .ok();
743 writeln!(out, " }}").ok();
744 writeln!(out, " return null;").ok();
745 writeln!(out, " }}").ok();
746 writeln!(
747 out,
748 " String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
749 )
750 .ok();
751 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
752 writeln!(out, " return result;").ok();
753 writeln!(out, " }} catch (Throwable e) {{").ok();
754 writeln!(
755 out,
756 " throw new {}Exception(\"FFI call failed\", e);",
757 class_name
758 )
759 .ok();
760 writeln!(out, " }}").ok();
761 } else if matches!(func.return_type, TypeRef::Named(_)) {
762 let return_type_name = match &func.return_type {
764 TypeRef::Named(name) => name,
765 _ => unreachable!(),
766 };
767 let is_opaque = opaque_types.contains(return_type_name.as_str());
768
769 writeln!(
770 out,
771 " var resultPtr = (MemorySegment) {}.invoke({});",
772 ffi_handle,
773 call_args.join(", ")
774 )
775 .ok();
776 emit_ffi_ptr_cleanup(out);
777 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
778 writeln!(
779 out,
780 " int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
781 prefix.to_uppercase()
782 )
783 .ok();
784 writeln!(out, " if (errCode != 0) {{").ok();
785 writeln!(
786 out,
787 " var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
788 prefix.to_uppercase()
789 )
790 .ok();
791 writeln!(
792 out,
793 " String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
794 )
795 .ok();
796 writeln!(
797 out,
798 " throw new {}Exception(errCode, msg);",
799 class_name
800 )
801 .ok();
802 writeln!(out, " }}").ok();
803 writeln!(out, " return null;").ok();
804 writeln!(out, " }}").ok();
805
806 if is_opaque {
807 writeln!(out, " return new {}(resultPtr);", return_type_name).ok();
809 } else {
810 let type_snake = return_type_name.to_snake_case();
813 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
814 let to_json_handle = format!(
815 "NativeLib.{}_{}_TO_JSON",
816 prefix.to_uppercase(),
817 type_snake.to_uppercase()
818 );
819 writeln!(
820 out,
821 " var jsonPtr = (MemorySegment) {}.invoke(resultPtr);",
822 to_json_handle
823 )
824 .ok();
825 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
826 writeln!(out, " if (jsonPtr.equals(MemorySegment.NULL)) {{").ok();
827 writeln!(
828 out,
829 " int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
830 prefix.to_uppercase()
831 )
832 .ok();
833 writeln!(out, " if (errCode != 0) {{").ok();
834 writeln!(
835 out,
836 " var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
837 prefix.to_uppercase()
838 )
839 .ok();
840 writeln!(
841 out,
842 " String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
843 )
844 .ok();
845 writeln!(
846 out,
847 " throw new {}Exception(errCode, msg);",
848 class_name
849 )
850 .ok();
851 writeln!(out, " }}").ok();
852 writeln!(out, " return null;").ok();
853 writeln!(out, " }}").ok();
854 writeln!(
855 out,
856 " String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);"
857 )
858 .ok();
859 writeln!(
860 out,
861 " NativeLib.{}_FREE_STRING.invoke(jsonPtr);",
862 prefix.to_uppercase()
863 )
864 .ok();
865 writeln!(
866 out,
867 " return createObjectMapper().readValue(json, {}.class);",
868 return_type_name
869 )
870 .ok();
871 }
872
873 writeln!(out, " }} catch (Throwable e) {{").ok();
874 writeln!(
875 out,
876 " throw new {}Exception(\"FFI call failed\", e);",
877 class_name
878 )
879 .ok();
880 writeln!(out, " }}").ok();
881 } else if matches!(func.return_type, TypeRef::Vec(_)) {
882 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
884 writeln!(
885 out,
886 " var resultPtr = (MemorySegment) {}.invoke({});",
887 ffi_handle,
888 call_args.join(", ")
889 )
890 .ok();
891 emit_ffi_ptr_cleanup(out);
892 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
893 writeln!(out, " return java.util.List.of();").ok();
894 writeln!(out, " }}").ok();
895 writeln!(
896 out,
897 " String json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
898 )
899 .ok();
900 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
901 let element_type = match &func.return_type {
903 TypeRef::Vec(inner) => java_type(inner),
904 _ => unreachable!(),
905 };
906 writeln!(
907 out,
908 " return createObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<java.util.List<{}>>() {{ }});",
909 element_type
910 )
911 .ok();
912 writeln!(out, " }} catch (Throwable e) {{").ok();
913 writeln!(
914 out,
915 " throw new {}Exception(\"FFI call failed\", e);",
916 class_name
917 )
918 .ok();
919 writeln!(out, " }}").ok();
920 } else {
921 writeln!(
922 out,
923 " var primitiveResult = ({}) {}.invoke({});",
924 java_ffi_return_cast(&func.return_type),
925 ffi_handle,
926 call_args.join(", ")
927 )
928 .ok();
929 emit_ffi_ptr_cleanup(out);
930 writeln!(out, " return primitiveResult;").ok();
931 writeln!(out, " }} catch (Throwable e) {{").ok();
932 writeln!(
933 out,
934 " throw new {}Exception(\"FFI call failed\", e);",
935 class_name
936 )
937 .ok();
938 writeln!(out, " }}").ok();
939 }
940
941 writeln!(out, " }}").ok();
942}
943
944fn gen_async_wrapper_method(out: &mut String, func: &FunctionDef) {
945 let params: Vec<String> = func
946 .params
947 .iter()
948 .map(|p| {
949 let ptype = java_type(&p.ty);
950 format!("{} {}", ptype, to_java_name(&p.name))
951 })
952 .collect();
953
954 let return_type = match &func.return_type {
955 TypeRef::Unit => "Void".to_string(),
956 other => java_boxed_type(other).to_string(),
957 };
958
959 let sync_method_name = to_java_name(&func.name);
960 let async_method_name = format!("{}Async", sync_method_name);
961 let param_names: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
962
963 writeln!(
964 out,
965 " public static CompletableFuture<{}> {}({}) {{",
966 return_type,
967 async_method_name,
968 params.join(", ")
969 )
970 .ok();
971 writeln!(out, " return CompletableFuture.supplyAsync(() -> {{").ok();
972 writeln!(out, " try {{").ok();
973 writeln!(
974 out,
975 " return {}({});",
976 sync_method_name,
977 param_names.join(", ")
978 )
979 .ok();
980 writeln!(out, " }} catch (Throwable e) {{").ok();
981 writeln!(out, " throw new CompletionException(e);").ok();
982 writeln!(out, " }}").ok();
983 writeln!(out, " }});").ok();
984 writeln!(out, " }}").ok();
985}
986
987fn gen_exception_class(package: &str, class_name: &str) -> String {
992 let mut out = String::with_capacity(512);
993
994 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
995 writeln!(out, "package {};", package).ok();
996 writeln!(out).ok();
997
998 writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
999 writeln!(out, " private final int code;").ok();
1000 writeln!(out).ok();
1001 writeln!(out, " public {}Exception(int code, String message) {{", class_name).ok();
1002 writeln!(out, " super(message);").ok();
1003 writeln!(out, " this.code = code;").ok();
1004 writeln!(out, " }}").ok();
1005 writeln!(out).ok();
1006 writeln!(
1007 out,
1008 " public {}Exception(String message, Throwable cause) {{",
1009 class_name
1010 )
1011 .ok();
1012 writeln!(out, " super(message, cause);").ok();
1013 writeln!(out, " this.code = -1;").ok();
1014 writeln!(out, " }}").ok();
1015 writeln!(out).ok();
1016 writeln!(out, " public int getCode() {{").ok();
1017 writeln!(out, " return code;").ok();
1018 writeln!(out, " }}").ok();
1019 writeln!(out, "}}").ok();
1020
1021 out
1022}
1023
1024fn gen_facade_class(api: &ApiSurface, package: &str, public_class: &str, raw_class: &str, _prefix: &str) -> String {
1029 let mut body = String::with_capacity(4096);
1030
1031 writeln!(body, "public final class {} {{", public_class).ok();
1032 writeln!(body, " private {}() {{ }}", public_class).ok();
1033 writeln!(body).ok();
1034
1035 for func in &api.functions {
1037 let params: Vec<String> = func
1039 .params
1040 .iter()
1041 .map(|p| {
1042 let ptype = java_type(&p.ty);
1043 format!("{} {}", ptype, to_java_name(&p.name))
1044 })
1045 .collect();
1046
1047 let return_type = java_type(&func.return_type);
1048
1049 if !func.doc.is_empty() {
1050 writeln!(body, " /**").ok();
1051 for line in func.doc.lines() {
1052 writeln!(body, " * {}", line).ok();
1053 }
1054 writeln!(body, " */").ok();
1055 }
1056
1057 writeln!(
1058 body,
1059 " public static {} {}({}) throws {}Exception {{",
1060 return_type,
1061 to_java_name(&func.name),
1062 params.join(", "),
1063 raw_class
1064 )
1065 .ok();
1066
1067 for param in &func.params {
1069 if !param.optional {
1070 let pname = to_java_name(¶m.name);
1071 writeln!(
1072 body,
1073 " java.util.Objects.requireNonNull({}, \"{} must not be null\");",
1074 pname, pname
1075 )
1076 .ok();
1077 }
1078 }
1079
1080 let call_args: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
1082
1083 if matches!(func.return_type, TypeRef::Unit) {
1084 writeln!(
1085 body,
1086 " {}.{}({});",
1087 raw_class,
1088 to_java_name(&func.name),
1089 call_args.join(", ")
1090 )
1091 .ok();
1092 } else {
1093 writeln!(
1094 body,
1095 " return {}.{}({});",
1096 raw_class,
1097 to_java_name(&func.name),
1098 call_args.join(", ")
1099 )
1100 .ok();
1101 }
1102
1103 writeln!(body, " }}").ok();
1104 writeln!(body).ok();
1105
1106 let has_optional = func.params.iter().any(|p| p.optional);
1108 if has_optional {
1109 let required_params: Vec<String> = func
1110 .params
1111 .iter()
1112 .filter(|p| !p.optional)
1113 .map(|p| {
1114 let ptype = java_type(&p.ty);
1115 format!("{} {}", ptype, to_java_name(&p.name))
1116 })
1117 .collect();
1118
1119 writeln!(
1120 body,
1121 " public static {} {}({}) throws {}Exception {{",
1122 return_type,
1123 to_java_name(&func.name),
1124 required_params.join(", "),
1125 raw_class
1126 )
1127 .ok();
1128
1129 let full_args: Vec<String> = func
1131 .params
1132 .iter()
1133 .map(|p| {
1134 if p.optional {
1135 "null".to_string()
1136 } else {
1137 to_java_name(&p.name)
1138 }
1139 })
1140 .collect();
1141
1142 if matches!(func.return_type, TypeRef::Unit) {
1143 writeln!(body, " {}({});", to_java_name(&func.name), full_args.join(", ")).ok();
1144 } else {
1145 writeln!(
1146 body,
1147 " return {}({});",
1148 to_java_name(&func.name),
1149 full_args.join(", ")
1150 )
1151 .ok();
1152 }
1153
1154 writeln!(body, " }}").ok();
1155 writeln!(body).ok();
1156 }
1157 }
1158
1159 writeln!(body, "}}").ok();
1160
1161 let mut out = String::with_capacity(body.len() + 512);
1163
1164 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1165 writeln!(out, "package {};", package).ok();
1166
1167 let has_list = body.contains("List<");
1169 let has_map = body.contains("Map<");
1170 let has_optional = body.contains("Optional<");
1171 let has_imports = has_list || has_map || has_optional;
1172
1173 if has_imports {
1174 writeln!(out).ok();
1175 if has_list {
1176 writeln!(out, "import java.util.List;").ok();
1177 }
1178 if has_map {
1179 writeln!(out, "import java.util.Map;").ok();
1180 }
1181 if has_optional {
1182 writeln!(out, "import java.util.Optional;").ok();
1183 }
1184 }
1185
1186 writeln!(out).ok();
1187 out.push_str(&body);
1188
1189 out
1190}
1191
1192fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
1197 let mut out = String::with_capacity(1024);
1198 let class_name = &typ.name;
1199 let type_snake = class_name.to_snake_case();
1200
1201 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1202 writeln!(out, "package {};", package).ok();
1203 writeln!(out).ok();
1204 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1205 writeln!(out).ok();
1206
1207 if !typ.doc.is_empty() {
1208 writeln!(out, "/**").ok();
1209 for line in typ.doc.lines() {
1210 writeln!(out, " * {}", line).ok();
1211 }
1212 writeln!(out, " */").ok();
1213 }
1214
1215 writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
1216 writeln!(out, " private final MemorySegment handle;").ok();
1217 writeln!(out).ok();
1218 writeln!(out, " {}(MemorySegment handle) {{", class_name).ok();
1219 writeln!(out, " this.handle = handle;").ok();
1220 writeln!(out, " }}").ok();
1221 writeln!(out).ok();
1222 writeln!(out, " MemorySegment handle() {{").ok();
1223 writeln!(out, " return this.handle;").ok();
1224 writeln!(out, " }}").ok();
1225 writeln!(out).ok();
1226 writeln!(out, " @Override").ok();
1227 writeln!(out, " public void close() {{").ok();
1228 writeln!(
1229 out,
1230 " if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
1231 )
1232 .ok();
1233 writeln!(out, " try {{").ok();
1234 writeln!(
1235 out,
1236 " NativeLib.{}_{}_FREE.invoke(handle);",
1237 prefix.to_uppercase(),
1238 type_snake.to_uppercase()
1239 )
1240 .ok();
1241 writeln!(out, " }} catch (Throwable e) {{").ok();
1242 writeln!(
1243 out,
1244 " throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
1245 class_name
1246 )
1247 .ok();
1248 writeln!(out, " }}").ok();
1249 writeln!(out, " }}").ok();
1250 writeln!(out, " }}").ok();
1251 writeln!(out, "}}").ok();
1252
1253 out
1254}
1255
1256const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
1263
1264fn gen_record_type(package: &str, typ: &TypeDef, complex_enums: &AHashSet<String>, lang_rename_all: &str) -> String {
1265 let field_list: Vec<String> = typ
1269 .fields
1270 .iter()
1271 .map(|f| {
1272 let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
1275 let ftype = if is_complex {
1276 "Object".to_string()
1277 } else if f.optional {
1278 format!("Optional<{}>", java_boxed_type(&f.ty))
1279 } else {
1280 java_type(&f.ty).to_string()
1281 };
1282 let jname = safe_java_field_name(&f.name);
1283 if lang_rename_all == "camelCase" && f.name.contains('_') {
1287 format!("@JsonProperty(\"{}\") {} {}", f.name, ftype, jname)
1288 } else {
1289 format!("{} {}", ftype, jname)
1290 }
1291 })
1292 .collect();
1293
1294 let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
1296
1297 let mut record_block = String::new();
1299 if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
1300 writeln!(record_block, "public record {}(", typ.name).ok();
1301 for (i, field) in field_list.iter().enumerate() {
1302 let comma = if i < field_list.len() - 1 { "," } else { "" };
1303 writeln!(record_block, " {}{}", field, comma).ok();
1304 }
1305 writeln!(record_block, ") {{").ok();
1306 } else {
1307 writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
1308 }
1309
1310 if typ.has_default {
1312 writeln!(record_block, " public static {}Builder builder() {{", typ.name).ok();
1313 writeln!(record_block, " return new {}Builder();", typ.name).ok();
1314 writeln!(record_block, " }}").ok();
1315 }
1316
1317 writeln!(record_block, "}}").ok();
1318
1319 let needs_json_property = field_list.iter().any(|f| f.contains("@JsonProperty("));
1321 let mut out = String::with_capacity(record_block.len() + 512);
1322 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1323 writeln!(out, "package {};", package).ok();
1324 writeln!(out).ok();
1325 if single_line.contains("List<") {
1326 writeln!(out, "import java.util.List;").ok();
1327 }
1328 if single_line.contains("Map<") {
1329 writeln!(out, "import java.util.Map;").ok();
1330 }
1331 if single_line.contains("Optional<") {
1332 writeln!(out, "import java.util.Optional;").ok();
1333 }
1334 if needs_json_property {
1335 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1336 }
1337 writeln!(out).ok();
1338 write!(out, "{}", record_block).ok();
1339
1340 out
1341}
1342
1343fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
1349 match rename_all {
1350 Some("snake_case") => name.to_snake_case(),
1351 Some("camelCase") => name.to_lower_camel_case(),
1352 Some("PascalCase") => name.to_pascal_case(),
1353 Some("SCREAMING_SNAKE_CASE") => name.to_snake_case().to_uppercase(),
1354 Some("lowercase") => name.to_lowercase(),
1355 Some("UPPERCASE") => name.to_uppercase(),
1356 _ => name.to_lowercase(),
1357 }
1358}
1359
1360fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
1361 let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
1362
1363 if enum_def.serde_tag.is_some() && has_data_variants {
1365 return gen_java_tagged_union(package, enum_def);
1366 }
1367
1368 let mut out = String::with_capacity(1024);
1369
1370 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1371 writeln!(out, "package {};", package).ok();
1372 writeln!(out).ok();
1373 writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
1374 writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
1375 writeln!(out).ok();
1376
1377 writeln!(out, "public enum {} {{", enum_def.name).ok();
1378
1379 for (i, variant) in enum_def.variants.iter().enumerate() {
1380 let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
1381 let json_name = variant
1383 .serde_rename
1384 .clone()
1385 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1386 writeln!(out, " {}(\"{}\"){}", variant.name, json_name, comma).ok();
1387 }
1388
1389 writeln!(out).ok();
1390 writeln!(out, " private final String value;").ok();
1391 writeln!(out).ok();
1392 writeln!(out, " {}(String value) {{", enum_def.name).ok();
1393 writeln!(out, " this.value = value;").ok();
1394 writeln!(out, " }}").ok();
1395 writeln!(out).ok();
1396 writeln!(out, " @JsonValue").ok();
1397 writeln!(out, " public String getValue() {{").ok();
1398 writeln!(out, " return value;").ok();
1399 writeln!(out, " }}").ok();
1400 writeln!(out).ok();
1401 writeln!(out, " @JsonCreator").ok();
1402 writeln!(out, " public static {} fromValue(String value) {{", enum_def.name).ok();
1403 writeln!(out, " for ({} e : values()) {{", enum_def.name).ok();
1404 writeln!(out, " if (e.value.equalsIgnoreCase(value)) {{").ok();
1405 writeln!(out, " return e;").ok();
1406 writeln!(out, " }}").ok();
1407 writeln!(out, " }}").ok();
1408 writeln!(
1409 out,
1410 " throw new IllegalArgumentException(\"Unknown value: \" + value);"
1411 )
1412 .ok();
1413 writeln!(out, " }}").ok();
1414
1415 writeln!(out, "}}").ok();
1416
1417 out
1418}
1419
1420fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
1425 let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
1426
1427 let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
1432 let optional_type = if variant_names.contains("Optional") {
1433 "java.util.Optional"
1434 } else {
1435 "Optional"
1436 };
1437
1438 let mut out = String::with_capacity(2048);
1439 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1440 writeln!(out, "package {};", package).ok();
1441 writeln!(out).ok();
1442 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1443 writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
1444 writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
1445
1446 let needs_list = !variant_names.contains("List")
1448 && enum_def
1449 .variants
1450 .iter()
1451 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
1452 let needs_map = !variant_names.contains("Map")
1453 && enum_def
1454 .variants
1455 .iter()
1456 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
1457 let needs_optional =
1458 !variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
1459 if needs_list {
1460 writeln!(out, "import java.util.List;").ok();
1461 }
1462 if needs_map {
1463 writeln!(out, "import java.util.Map;").ok();
1464 }
1465 if needs_optional {
1466 writeln!(out, "import java.util.Optional;").ok();
1467 }
1468 writeln!(out).ok();
1469
1470 writeln!(
1472 out,
1473 "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
1474 )
1475 .ok();
1476 writeln!(out, "@JsonSubTypes({{").ok();
1477 for (i, variant) in enum_def.variants.iter().enumerate() {
1478 let discriminator = variant
1479 .serde_rename
1480 .clone()
1481 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1482 let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
1483 writeln!(
1484 out,
1485 " @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
1486 enum_def.name, variant.name, discriminator, comma
1487 )
1488 .ok();
1489 }
1490 writeln!(out, "}})").ok();
1491 writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
1492
1493 for variant in &enum_def.variants {
1495 writeln!(out).ok();
1496 if variant.fields.is_empty() {
1497 writeln!(out, " record {}() implements {} {{", variant.name, enum_def.name).ok();
1499 writeln!(out, " }}").ok();
1500 } else {
1501 let field_parts: Vec<String> = variant
1503 .fields
1504 .iter()
1505 .map(|f| {
1506 let json_name = f.name.trim_start_matches('_');
1507 let ftype = if f.optional {
1508 let inner = java_boxed_type(&f.ty);
1509 let inner_str = inner.as_ref();
1510 let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
1512 inner_str.replacen("List<", "java.util.List<", 1)
1513 } else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
1514 inner_str.replacen("Map<", "java.util.Map<", 1)
1515 } else {
1516 inner_str.to_string()
1517 };
1518 format!("{optional_type}<{inner_qualified}>")
1519 } else {
1520 let t = java_type(&f.ty);
1521 let t_str = t.as_ref();
1522 if t_str.starts_with("List<") && variant_names.contains("List") {
1523 t_str.replacen("List<", "java.util.List<", 1)
1524 } else if t_str.starts_with("Map<") && variant_names.contains("Map") {
1525 t_str.replacen("Map<", "java.util.Map<", 1)
1526 } else {
1527 t_str.to_string()
1528 }
1529 };
1530 let jname = safe_java_field_name(json_name);
1531 format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
1532 })
1533 .collect();
1534
1535 let single = format!(
1536 " record {}({}) implements {} {{ }}",
1537 variant.name,
1538 field_parts.join(", "),
1539 enum_def.name
1540 );
1541
1542 if single.len() > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
1543 writeln!(out, " record {}(", variant.name).ok();
1544 for (i, fp) in field_parts.iter().enumerate() {
1545 let comma = if i < field_parts.len() - 1 { "," } else { "" };
1546 writeln!(out, " {}{}", fp, comma).ok();
1547 }
1548 writeln!(out, " ) implements {} {{", enum_def.name).ok();
1549 writeln!(out, " }}").ok();
1550 } else {
1551 writeln!(
1552 out,
1553 " record {}({}) implements {} {{ }}",
1554 variant.name,
1555 field_parts.join(", "),
1556 enum_def.name
1557 )
1558 .ok();
1559 }
1560 }
1561 }
1562
1563 writeln!(out).ok();
1564 writeln!(out, "}}").ok();
1565 out
1566}
1567
1568fn gen_ffi_layout(ty: &TypeRef) -> String {
1573 match ty {
1574 TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
1575 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
1576 TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
1577 TypeRef::Optional(inner) => gen_ffi_layout(inner),
1578 TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
1579 TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
1580 TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
1581 TypeRef::Unit => "".to_string(),
1582 TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
1583 }
1584}
1585
1586fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef, opaque_types: &AHashSet<String>, prefix: &str) {
1587 match ty {
1588 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1589 let cname = "c".to_string() + name;
1590 writeln!(out, " var {} = arena.allocateFrom({});", cname, name).ok();
1591 }
1592 TypeRef::Named(type_name) => {
1593 let cname = "c".to_string() + name;
1594 if opaque_types.contains(type_name.as_str()) {
1595 writeln!(out, " var {} = {}.handle();", cname, name).ok();
1597 } else {
1598 let type_snake = type_name.to_snake_case();
1601 let from_json_handle = format!(
1602 "NativeLib.{}_{}_FROM_JSON",
1603 prefix.to_uppercase(),
1604 type_snake.to_uppercase()
1605 );
1606 let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1607 writeln!(
1608 out,
1609 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
1610 cname, name, name
1611 )
1612 .ok();
1613 writeln!(
1614 out,
1615 " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
1616 cname, cname, cname
1617 )
1618 .ok();
1619 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
1620 writeln!(
1621 out,
1622 " ? (MemorySegment) {}.invoke({}JsonSeg)",
1623 from_json_handle, cname
1624 )
1625 .ok();
1626 writeln!(out, " : MemorySegment.NULL;").ok();
1627 }
1628 }
1629 TypeRef::Optional(inner) => {
1630 match inner.as_ref() {
1632 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1633 let cname = "c".to_string() + name;
1634 writeln!(
1635 out,
1636 " var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
1637 cname, name, name
1638 )
1639 .ok();
1640 }
1641 TypeRef::Named(type_name) => {
1642 let cname = "c".to_string() + name;
1643 if opaque_types.contains(type_name.as_str()) {
1644 writeln!(
1645 out,
1646 " var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
1647 cname, name, name
1648 )
1649 .ok();
1650 } else {
1651 let type_snake = type_name.to_snake_case();
1653 let from_json_handle = format!(
1654 "NativeLib.{}_{}_FROM_JSON",
1655 prefix.to_uppercase(),
1656 type_snake.to_uppercase()
1657 );
1658 writeln!(
1659 out,
1660 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
1661 cname, name, name
1662 )
1663 .ok();
1664 writeln!(out, " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
1665 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
1666 writeln!(
1667 out,
1668 " ? (MemorySegment) {}.invoke({}JsonSeg)",
1669 from_json_handle, cname
1670 )
1671 .ok();
1672 writeln!(out, " : MemorySegment.NULL;").ok();
1673 }
1674 }
1675 _ => {
1676 }
1678 }
1679 }
1680 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
1681 let cname = "c".to_string() + name;
1683 writeln!(
1684 out,
1685 " var {}Json = createObjectMapper().writeValueAsString({});",
1686 cname, name
1687 )
1688 .ok();
1689 writeln!(out, " var {} = arena.allocateFrom({}Json);", cname, cname).ok();
1690 }
1691 _ => {
1692 }
1694 }
1695}
1696
1697fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
1698 match ty {
1699 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
1700 TypeRef::Named(_) => "c".to_string() + name,
1701 TypeRef::Vec(_) | TypeRef::Map(_, _) => "c".to_string() + name,
1702 TypeRef::Optional(inner) => match inner.as_ref() {
1703 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
1704 "c".to_string() + name
1705 }
1706 _ => name.to_string(),
1707 },
1708 _ => name.to_string(),
1709 }
1710}
1711
1712fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
1715 if return_layout.is_empty() {
1716 if param_layouts.is_empty() {
1718 "FunctionDescriptor.ofVoid()".to_string()
1719 } else {
1720 format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
1721 }
1722 } else {
1723 if param_layouts.is_empty() {
1725 format!("FunctionDescriptor.of({})", return_layout)
1726 } else {
1727 format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
1728 }
1729 }
1730}
1731
1732fn is_ffi_string_return(ty: &TypeRef) -> bool {
1735 match ty {
1736 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
1737 TypeRef::Optional(inner) => is_ffi_string_return(inner),
1738 _ => false,
1739 }
1740}
1741
1742fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
1744 match ty {
1745 TypeRef::Primitive(prim) => match prim {
1746 PrimitiveType::Bool => "boolean",
1747 PrimitiveType::U8 | PrimitiveType::I8 => "byte",
1748 PrimitiveType::U16 | PrimitiveType::I16 => "short",
1749 PrimitiveType::U32 | PrimitiveType::I32 => "int",
1750 PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
1751 PrimitiveType::F32 => "float",
1752 PrimitiveType::F64 => "double",
1753 },
1754 TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
1755 _ => "MemorySegment",
1756 }
1757}
1758
1759fn gen_helper_methods(out: &mut String) {
1760 let needs_read_cstring = out.contains("readCString(");
1762 let needs_read_bytes = out.contains("readBytes(");
1763 let needs_create_object_mapper = out.contains("createObjectMapper()");
1764
1765 if !needs_read_cstring && !needs_read_bytes && !needs_create_object_mapper {
1766 return;
1767 }
1768
1769 writeln!(out, " // Helper methods for FFI marshalling").ok();
1770 writeln!(out).ok();
1771
1772 if needs_create_object_mapper {
1773 writeln!(
1779 out,
1780 " private static com.fasterxml.jackson.databind.ObjectMapper createObjectMapper() {{"
1781 )
1782 .ok();
1783 writeln!(out, " return new com.fasterxml.jackson.databind.ObjectMapper()").ok();
1784 writeln!(
1785 out,
1786 " .registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module())"
1787 )
1788 .ok();
1789 writeln!(out, " .findAndRegisterModules()").ok();
1790 writeln!(
1791 out,
1792 " .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
1793 )
1794 .ok();
1795 writeln!(
1796 out,
1797 " .configure(com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);"
1798 )
1799 .ok();
1800 writeln!(out, " }}").ok();
1801 writeln!(out).ok();
1802 }
1803
1804 if needs_read_cstring {
1805 writeln!(out, " private static String readCString(MemorySegment ptr) {{").ok();
1806 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
1807 writeln!(out, " return null;").ok();
1808 writeln!(out, " }}").ok();
1809 writeln!(out, " return ptr.getUtf8String(0);").ok();
1810 writeln!(out, " }}").ok();
1811 writeln!(out).ok();
1812 }
1813
1814 if needs_read_bytes {
1815 writeln!(
1816 out,
1817 " private static byte[] readBytes(MemorySegment ptr, long len) {{"
1818 )
1819 .ok();
1820 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
1821 writeln!(out, " return new byte[0];").ok();
1822 writeln!(out, " }}").ok();
1823 writeln!(out, " byte[] bytes = new byte[(int) len];").ok();
1824 writeln!(
1825 out,
1826 " MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
1827 )
1828 .ok();
1829 writeln!(out, " return bytes;").ok();
1830 writeln!(out, " }}").ok();
1831 }
1832}
1833
1834fn format_optional_value(ty: &TypeRef, default: &str) -> String {
1841 if default.contains("Optional.") {
1843 return default.to_string();
1844 }
1845
1846 let inner_ty = match ty {
1848 TypeRef::Optional(inner) => inner.as_ref(),
1849 other => other,
1850 };
1851
1852 let formatted_value = match inner_ty {
1854 TypeRef::Primitive(p) => match p {
1855 PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
1856 if default.ends_with('L') || default.ends_with('l') {
1858 default.to_string()
1859 } else if default.parse::<i64>().is_ok() {
1860 format!("{}L", default)
1861 } else {
1862 default.to_string()
1863 }
1864 }
1865 PrimitiveType::F32 => {
1866 if default.ends_with('f') || default.ends_with('F') {
1868 default.to_string()
1869 } else if default.parse::<f32>().is_ok() {
1870 format!("{}f", default)
1871 } else {
1872 default.to_string()
1873 }
1874 }
1875 PrimitiveType::F64 => {
1876 default.to_string()
1878 }
1879 _ => default.to_string(),
1880 },
1881 _ => default.to_string(),
1882 };
1883
1884 format!("Optional.of({})", formatted_value)
1885}
1886
1887fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
1888 let mut body = String::with_capacity(2048);
1889
1890 writeln!(body, "public class {}Builder {{", typ.name).ok();
1891 writeln!(body).ok();
1892
1893 for field in &typ.fields {
1895 let field_name = safe_java_field_name(&field.name);
1896
1897 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
1899 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
1900 {
1901 continue;
1902 }
1903
1904 let field_type = if field.optional {
1907 format!("Optional<{}>", java_boxed_type(&field.ty))
1908 } else if matches!(field.ty, TypeRef::Duration) {
1909 java_boxed_type(&field.ty).to_string()
1910 } else {
1911 java_type(&field.ty).to_string()
1912 };
1913
1914 let default_value = if field.optional {
1915 if let Some(default) = &field.default {
1917 format_optional_value(&field.ty, default)
1919 } else {
1920 "Optional.empty()".to_string()
1922 }
1923 } else {
1924 if let Some(default) = &field.default {
1926 default.clone()
1927 } else {
1928 match &field.ty {
1929 TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
1930 TypeRef::Json => "null".to_string(),
1931 TypeRef::Bytes => "new byte[0]".to_string(),
1932 TypeRef::Primitive(p) => match p {
1933 PrimitiveType::Bool => "false".to_string(),
1934 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
1935 _ => "0".to_string(),
1936 },
1937 TypeRef::Vec(_) => "List.of()".to_string(),
1938 TypeRef::Map(_, _) => "Map.of()".to_string(),
1939 TypeRef::Optional(_) => "Optional.empty()".to_string(),
1940 TypeRef::Duration => "null".to_string(),
1941 _ => "null".to_string(),
1942 }
1943 }
1944 };
1945
1946 writeln!(body, " private {} {} = {};", field_type, field_name, default_value).ok();
1947 }
1948
1949 writeln!(body).ok();
1950
1951 for field in &typ.fields {
1953 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
1955 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
1956 {
1957 continue;
1958 }
1959
1960 let field_name = safe_java_field_name(&field.name);
1961 let field_name_pascal = to_class_name(&field.name);
1962 let field_type = if field.optional {
1963 format!("Optional<{}>", java_boxed_type(&field.ty))
1964 } else if matches!(field.ty, TypeRef::Duration) {
1965 java_boxed_type(&field.ty).to_string()
1966 } else {
1967 java_type(&field.ty).to_string()
1968 };
1969
1970 writeln!(
1971 body,
1972 " public {}Builder with{}({} value) {{",
1973 typ.name, field_name_pascal, field_type
1974 )
1975 .ok();
1976 writeln!(body, " this.{} = value;", field_name).ok();
1977 writeln!(body, " return this;").ok();
1978 writeln!(body, " }}").ok();
1979 writeln!(body).ok();
1980 }
1981
1982 writeln!(body, " public {} build() {{", typ.name).ok();
1984 writeln!(body, " return new {}(", typ.name).ok();
1985 let non_tuple_fields: Vec<_> = typ
1986 .fields
1987 .iter()
1988 .filter(|f| {
1989 !(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
1991 || f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
1992 })
1993 .collect();
1994 for (i, field) in non_tuple_fields.iter().enumerate() {
1995 let field_name = safe_java_field_name(&field.name);
1996 let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
1997 writeln!(body, " {}{}", field_name, comma).ok();
1998 }
1999 writeln!(body, " );").ok();
2000 writeln!(body, " }}").ok();
2001
2002 writeln!(body, "}}").ok();
2003
2004 let mut out = String::with_capacity(body.len() + 512);
2006
2007 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2008 writeln!(out, "package {};", package).ok();
2009 writeln!(out).ok();
2010
2011 if body.contains("List<") {
2012 writeln!(out, "import java.util.List;").ok();
2013 }
2014 if body.contains("Map<") {
2015 writeln!(out, "import java.util.Map;").ok();
2016 }
2017 if body.contains("Optional<") {
2018 writeln!(out, "import java.util.Optional;").ok();
2019 }
2020
2021 writeln!(out).ok();
2022 out.push_str(&body);
2023
2024 out
2025}