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::collections::HashSet;
9use std::fmt::Write;
10use std::path::PathBuf;
11
12const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
15 "wait",
16 "notify",
17 "notifyAll",
18 "getClass",
19 "hashCode",
20 "equals",
21 "toString",
22 "clone",
23 "finalize",
24];
25
26fn escape_javadoc_line(s: &str) -> String {
32 let mut result = String::with_capacity(s.len());
33 let mut chars = s.chars().peekable();
34 while let Some(ch) = chars.next() {
35 if ch == '`' {
36 let mut code = String::new();
37 for c in chars.by_ref() {
38 if c == '`' {
39 break;
40 }
41 code.push(c);
42 }
43 result.push_str("{@code ");
44 result.push_str(&code);
45 result.push('}');
46 } else if ch == '<' {
47 result.push_str("<");
48 } else if ch == '>' {
49 result.push_str(">");
50 } else if ch == '&' {
51 result.push_str("&");
52 } else if ch == '*' && chars.peek() == Some(&'/') {
53 chars.next();
54 result.push_str("* /");
55 } else if ch == '@' {
56 result.push_str("{@literal @}");
57 } else {
58 result.push(ch);
59 }
60 }
61 result
62}
63
64fn is_tuple_field_name(name: &str) -> bool {
65 let stripped = name.trim_start_matches('_');
66 !stripped.is_empty() && stripped.chars().all(|c| c.is_ascii_digit())
67}
68
69fn safe_java_field_name(name: &str) -> String {
73 let java_name = to_java_name(name);
74 if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
75 format!("{}Value", java_name)
76 } else {
77 java_name
78 }
79}
80
81pub struct JavaBackend;
82
83impl JavaBackend {
84 fn resolve_main_class(api: &ApiSurface) -> String {
90 let base = to_class_name(&api.crate_name.replace('-', "_"));
91 if base.ends_with("Rs") {
92 base
93 } else {
94 format!("{}Rs", base)
95 }
96 }
97}
98
99impl Backend for JavaBackend {
100 fn name(&self) -> &str {
101 "java"
102 }
103
104 fn language(&self) -> Language {
105 Language::Java
106 }
107
108 fn capabilities(&self) -> Capabilities {
109 Capabilities {
110 supports_async: true,
111 supports_classes: true,
112 supports_enums: true,
113 supports_option: true,
114 supports_result: true,
115 ..Capabilities::default()
116 }
117 }
118
119 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
120 let package = config.java_package();
121 let prefix = config.ffi_prefix();
122 let main_class = Self::resolve_main_class(api);
123 let package_path = package.replace('.', "/");
124
125 let output_dir = resolve_output_dir(
126 config.output.java.as_ref(),
127 &config.crate_config.name,
128 "packages/java/src/main/java/",
129 );
130
131 let base_path = PathBuf::from(&output_dir).join(&package_path);
132
133 let bridge_param_names: HashSet<String> = config
136 .trait_bridges
137 .iter()
138 .filter_map(|b| b.param_name.clone())
139 .collect();
140 let bridge_type_aliases: HashSet<String> = config
141 .trait_bridges
142 .iter()
143 .filter_map(|b| b.type_alias.clone())
144 .collect();
145 let has_visitor_bridge = !config.trait_bridges.is_empty();
146
147 let mut files = Vec::new();
148
149 let description = config
151 .scaffold
152 .as_ref()
153 .and_then(|s| s.description.as_deref())
154 .unwrap_or("High-performance HTML to Markdown converter.");
155 files.push(GeneratedFile {
156 path: base_path.join("package-info.java"),
157 content: format!(
158 "/**\n * {description}\n */\npackage {package};\n",
159 description = description,
160 package = package,
161 ),
162 generated_header: true,
163 });
164
165 files.push(GeneratedFile {
167 path: base_path.join("NativeLib.java"),
168 content: gen_native_lib(api, config, &package, &prefix, has_visitor_bridge),
169 generated_header: true,
170 });
171
172 files.push(GeneratedFile {
174 path: base_path.join(format!("{}.java", main_class)),
175 content: gen_main_class(
176 api,
177 config,
178 &package,
179 &main_class,
180 &prefix,
181 &bridge_param_names,
182 &bridge_type_aliases,
183 has_visitor_bridge,
184 ),
185 generated_header: true,
186 });
187
188 files.push(GeneratedFile {
190 path: base_path.join(format!("{}Exception.java", main_class)),
191 content: gen_exception_class(&package, &main_class),
192 generated_header: true,
193 });
194
195 let complex_enums: AHashSet<String> = api
199 .enums
200 .iter()
201 .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
202 .map(|e| e.name.clone())
203 .collect();
204
205 let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
207
208 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
210 if !typ.is_opaque && !typ.fields.is_empty() {
211 if has_visitor_bridge && (typ.name == "NodeContext" || typ.name == "VisitResult") {
213 continue;
214 }
215 files.push(GeneratedFile {
216 path: base_path.join(format!("{}.java", typ.name)),
217 content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
218 generated_header: true,
219 });
220 if typ.has_default {
222 files.push(GeneratedFile {
223 path: base_path.join(format!("{}Builder.java", typ.name)),
224 content: gen_builder_class(&package, typ),
225 generated_header: true,
226 });
227 }
228 }
229 }
230
231 let builder_class_names: AHashSet<String> = api
234 .types
235 .iter()
236 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
237 .map(|t| format!("{}Builder", t.name))
238 .collect();
239
240 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
242 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
243 files.push(GeneratedFile {
244 path: base_path.join(format!("{}.java", typ.name)),
245 content: gen_opaque_handle_class(&package, typ, &prefix),
246 generated_header: true,
247 });
248 }
249 }
250
251 for enum_def in &api.enums {
253 if has_visitor_bridge && enum_def.name == "VisitResult" {
255 continue;
256 }
257 files.push(GeneratedFile {
258 path: base_path.join(format!("{}.java", enum_def.name)),
259 content: gen_enum_class(&package, enum_def),
260 generated_header: true,
261 });
262 }
263
264 for error in &api.errors {
266 for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
267 files.push(GeneratedFile {
268 path: base_path.join(format!("{}.java", class_name)),
269 content,
270 generated_header: true,
271 });
272 }
273 }
274
275 if has_visitor_bridge {
277 for (filename, content) in crate::gen_visitor::gen_visitor_files(&package, &main_class) {
278 files.push(GeneratedFile {
279 path: base_path.join(filename),
280 content,
281 generated_header: false, });
283 }
284 }
285
286 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
288
289 Ok(files)
290 }
291
292 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
293 let package = config.java_package();
294 let prefix = config.ffi_prefix();
295 let main_class = Self::resolve_main_class(api);
296 let package_path = package.replace('.', "/");
297
298 let output_dir = resolve_output_dir(
299 config.output.java.as_ref(),
300 &config.crate_config.name,
301 "packages/java/src/main/java/",
302 );
303
304 let base_path = PathBuf::from(&output_dir).join(&package_path);
305
306 let bridge_param_names: HashSet<String> = config
308 .trait_bridges
309 .iter()
310 .filter_map(|b| b.param_name.clone())
311 .collect();
312 let bridge_type_aliases: HashSet<String> = config
313 .trait_bridges
314 .iter()
315 .filter_map(|b| b.type_alias.clone())
316 .collect();
317 let has_visitor_bridge = !config.trait_bridges.is_empty();
318
319 let public_class = main_class.trim_end_matches("Rs").to_string();
322 let facade_content = gen_facade_class(
323 api,
324 &package,
325 &public_class,
326 &main_class,
327 &prefix,
328 &bridge_param_names,
329 &bridge_type_aliases,
330 has_visitor_bridge,
331 );
332
333 Ok(vec![GeneratedFile {
334 path: base_path.join(format!("{}.java", public_class)),
335 content: facade_content,
336 generated_header: true,
337 }])
338 }
339
340 fn build_config(&self) -> Option<BuildConfig> {
341 Some(BuildConfig {
342 tool: "mvn",
343 crate_suffix: "",
344 depends_on_ffi: true,
345 post_build: vec![],
346 })
347 }
348}
349
350fn gen_native_lib(
355 api: &ApiSurface,
356 config: &AlefConfig,
357 package: &str,
358 prefix: &str,
359 has_visitor_bridge: bool,
360) -> String {
361 let mut body = String::with_capacity(2048);
363 let lib_name = config.ffi_lib_name();
366
367 writeln!(body, "final class NativeLib {{").ok();
368 writeln!(body, " private static final Linker LINKER = Linker.nativeLinker();").ok();
369 writeln!(body, " private static final SymbolLookup LIB;").ok();
370 writeln!(
371 body,
372 " private static final String NATIVES_RESOURCE_ROOT = \"/natives\";"
373 )
374 .ok();
375 writeln!(
376 body,
377 " private static final Object NATIVE_EXTRACT_LOCK = new Object();"
378 )
379 .ok();
380 writeln!(body, " private static String cachedExtractKey;").ok();
381 writeln!(body, " private static Path cachedExtractDir;").ok();
382 writeln!(body).ok();
383 writeln!(body, " static {{").ok();
384 writeln!(body, " loadNativeLibrary();").ok();
385 writeln!(body, " LIB = SymbolLookup.loaderLookup();").ok();
386 writeln!(body, " }}").ok();
387 writeln!(body).ok();
388 writeln!(body, " private static void loadNativeLibrary() {{").ok();
389 writeln!(
390 body,
391 " String osName = System.getProperty(\"os.name\", \"\").toLowerCase(java.util.Locale.ROOT);"
392 )
393 .ok();
394 writeln!(
395 body,
396 " String osArch = System.getProperty(\"os.arch\", \"\").toLowerCase(java.util.Locale.ROOT);"
397 )
398 .ok();
399 writeln!(body).ok();
400 writeln!(body, " String libName;").ok();
401 writeln!(body, " String libExt;").ok();
402 writeln!(
403 body,
404 " if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {{"
405 )
406 .ok();
407 writeln!(body, " libName = \"lib{}\";", lib_name).ok();
408 writeln!(body, " libExt = \".dylib\";").ok();
409 writeln!(body, " }} else if (osName.contains(\"win\")) {{").ok();
410 writeln!(body, " libName = \"{}\";", lib_name).ok();
411 writeln!(body, " libExt = \".dll\";").ok();
412 writeln!(body, " }} else {{").ok();
413 writeln!(body, " libName = \"lib{}\";", lib_name).ok();
414 writeln!(body, " libExt = \".so\";").ok();
415 writeln!(body, " }}").ok();
416 writeln!(body).ok();
417 writeln!(body, " String nativesRid = resolveNativesRid(osName, osArch);").ok();
418 writeln!(
419 body,
420 " String nativesDir = NATIVES_RESOURCE_ROOT + \"/\" + nativesRid;"
421 )
422 .ok();
423 writeln!(body).ok();
424 writeln!(
425 body,
426 " Path extracted = tryExtractAndLoadFromResources(nativesDir, libName, libExt);"
427 )
428 .ok();
429 writeln!(body, " if (extracted != null) {{").ok();
430 writeln!(body, " return;").ok();
431 writeln!(body, " }}").ok();
432 writeln!(body).ok();
433 writeln!(body, " try {{").ok();
434 writeln!(body, " System.loadLibrary(\"{}\");", lib_name).ok();
435 writeln!(body, " }} catch (UnsatisfiedLinkError e) {{").ok();
436 writeln!(
437 body,
438 " String msg = \"Failed to load {} native library. Expected resource: \" + nativesDir + \"/\" + libName",
439 lib_name
440 ).ok();
441 writeln!(
442 body,
443 " + libExt + \" (RID: \" + nativesRid + \"). \""
444 )
445 .ok();
446 writeln!(
447 body,
448 " + \"Ensure the library is bundled in the JAR under natives/{{os-arch}}/, \""
449 )
450 .ok();
451 writeln!(
452 body,
453 " + \"or place it on the system library path (java.library.path).\";",
454 )
455 .ok();
456 writeln!(
457 body,
458 " UnsatisfiedLinkError out = new UnsatisfiedLinkError(msg + \" Original error: \" + e.getMessage());"
459 )
460 .ok();
461 writeln!(body, " out.initCause(e);").ok();
462 writeln!(body, " throw out;").ok();
463 writeln!(body, " }}").ok();
464 writeln!(body, " }}").ok();
465 writeln!(body).ok();
466 writeln!(
467 body,
468 " private static Path tryExtractAndLoadFromResources(String nativesDir, String libName, String libExt) {{"
469 )
470 .ok();
471 writeln!(
472 body,
473 " String resourcePath = nativesDir + \"/\" + libName + libExt;"
474 )
475 .ok();
476 writeln!(
477 body,
478 " URL resource = NativeLib.class.getResource(resourcePath);"
479 )
480 .ok();
481 writeln!(body, " if (resource == null) {{").ok();
482 writeln!(body, " return null;").ok();
483 writeln!(body, " }}").ok();
484 writeln!(body).ok();
485 writeln!(body, " try {{").ok();
486 writeln!(
487 body,
488 " Path tempDir = extractOrReuseNativeDirectory(nativesDir);"
489 )
490 .ok();
491 writeln!(body, " Path libPath = tempDir.resolve(libName + libExt);").ok();
492 writeln!(body, " if (!Files.exists(libPath)) {{").ok();
493 writeln!(
494 body,
495 " throw new UnsatisfiedLinkError(\"Missing extracted native library: \" + libPath);"
496 )
497 .ok();
498 writeln!(body, " }}").ok();
499 writeln!(body, " System.load(libPath.toAbsolutePath().toString());").ok();
500 writeln!(body, " return libPath;").ok();
501 writeln!(body, " }} catch (Exception e) {{").ok();
502 writeln!(body, " System.err.println(\"[NativeLib] Failed to extract and load native library from resources: \" + e.getMessage());").ok();
503 writeln!(body, " return null;").ok();
504 writeln!(body, " }}").ok();
505 writeln!(body, " }}").ok();
506 writeln!(body).ok();
507 writeln!(
508 body,
509 " private static Path extractOrReuseNativeDirectory(String nativesDir) throws Exception {{"
510 )
511 .ok();
512 writeln!(
513 body,
514 " URL location = NativeLib.class.getProtectionDomain().getCodeSource().getLocation();"
515 )
516 .ok();
517 writeln!(body, " if (location == null) {{").ok();
518 writeln!(
519 body,
520 " throw new IllegalStateException(\"Missing code source location for {} JAR\");",
521 lib_name
522 )
523 .ok();
524 writeln!(body, " }}").ok();
525 writeln!(body).ok();
526 writeln!(body, " Path codePath = Path.of(location.toURI());").ok();
527 writeln!(
528 body,
529 " String key = codePath.toAbsolutePath() + \"::\" + nativesDir;"
530 )
531 .ok();
532 writeln!(body).ok();
533 writeln!(body, " synchronized (NATIVE_EXTRACT_LOCK) {{").ok();
534 writeln!(
535 body,
536 " if (cachedExtractDir != null && key.equals(cachedExtractKey)) {{"
537 )
538 .ok();
539 writeln!(body, " return cachedExtractDir;").ok();
540 writeln!(body, " }}").ok();
541 writeln!(
542 body,
543 " Path tempDir = Files.createTempDirectory(\"{}_native\");",
544 lib_name
545 )
546 .ok();
547 writeln!(body, " tempDir.toFile().deleteOnExit();").ok();
548 writeln!(
549 body,
550 " List<Path> extracted = extractNativeDirectory(codePath, nativesDir, tempDir);"
551 )
552 .ok();
553 writeln!(body, " if (extracted.isEmpty()) {{").ok();
554 writeln!(body, " throw new IllegalStateException(\"No native files extracted from resources dir: \" + nativesDir);").ok();
555 writeln!(body, " }}").ok();
556 writeln!(body, " cachedExtractKey = key;").ok();
557 writeln!(body, " cachedExtractDir = tempDir;").ok();
558 writeln!(body, " return tempDir;").ok();
559 writeln!(body, " }}").ok();
560 writeln!(body, " }}").ok();
561 writeln!(body).ok();
562 writeln!(body, " private static List<Path> extractNativeDirectory(Path codePath, String nativesDir, Path destDir) throws Exception {{").ok();
563 writeln!(
564 body,
565 " if (!Files.exists(destDir) || !Files.isDirectory(destDir)) {{"
566 )
567 .ok();
568 writeln!(
569 body,
570 " throw new IllegalArgumentException(\"Destination directory does not exist: \" + destDir);"
571 )
572 .ok();
573 writeln!(body, " }}").ok();
574 writeln!(body).ok();
575 writeln!(
576 body,
577 " String prefix = nativesDir.startsWith(\"/\") ? nativesDir.substring(1) : nativesDir;"
578 )
579 .ok();
580 writeln!(body, " if (!prefix.endsWith(\"/\")) {{").ok();
581 writeln!(body, " prefix = prefix + \"/\";").ok();
582 writeln!(body, " }}").ok();
583 writeln!(body).ok();
584 writeln!(body, " if (Files.isDirectory(codePath)) {{").ok();
585 writeln!(body, " Path nativesPath = codePath.resolve(prefix);").ok();
586 writeln!(
587 body,
588 " if (!Files.exists(nativesPath) || !Files.isDirectory(nativesPath)) {{"
589 )
590 .ok();
591 writeln!(body, " return List.of();").ok();
592 writeln!(body, " }}").ok();
593 writeln!(body, " return copyDirectory(nativesPath, destDir);").ok();
594 writeln!(body, " }}").ok();
595 writeln!(body).ok();
596 writeln!(body, " List<Path> extracted = new ArrayList<>();").ok();
597 writeln!(body, " try (JarFile jar = new JarFile(codePath.toFile())) {{").ok();
598 writeln!(body, " Enumeration<JarEntry> entries = jar.entries();").ok();
599 writeln!(body, " while (entries.hasMoreElements()) {{").ok();
600 writeln!(body, " JarEntry entry = entries.nextElement();").ok();
601 writeln!(body, " String name = entry.getName();").ok();
602 writeln!(
603 body,
604 " if (!name.startsWith(prefix) || entry.isDirectory()) {{"
605 )
606 .ok();
607 writeln!(body, " continue;").ok();
608 writeln!(body, " }}").ok();
609 writeln!(
610 body,
611 " String relative = name.substring(prefix.length());"
612 )
613 .ok();
614 writeln!(body, " Path out = safeResolve(destDir, relative);").ok();
615 writeln!(body, " Files.createDirectories(out.getParent());").ok();
616 writeln!(body, " try (var in = jar.getInputStream(entry)) {{").ok();
617 writeln!(
618 body,
619 " Files.copy(in, out, StandardCopyOption.REPLACE_EXISTING);"
620 )
621 .ok();
622 writeln!(body, " }}").ok();
623 writeln!(body, " out.toFile().deleteOnExit();").ok();
624 writeln!(body, " extracted.add(out);").ok();
625 writeln!(body, " }}").ok();
626 writeln!(body, " }}").ok();
627 writeln!(body, " return extracted;").ok();
628 writeln!(body, " }}").ok();
629 writeln!(body).ok();
630 writeln!(
631 body,
632 " private static List<Path> copyDirectory(Path srcDir, Path destDir) throws Exception {{"
633 )
634 .ok();
635 writeln!(body, " List<Path> copied = new ArrayList<>();").ok();
636 writeln!(body, " try (var paths = Files.walk(srcDir)) {{").ok();
637 writeln!(body, " for (Path src : (Iterable<Path>) paths::iterator) {{").ok();
638 writeln!(body, " if (Files.isDirectory(src)) {{").ok();
639 writeln!(body, " continue;").ok();
640 writeln!(body, " }}").ok();
641 writeln!(body, " Path relative = srcDir.relativize(src);").ok();
642 writeln!(
643 body,
644 " Path out = safeResolve(destDir, relative.toString());"
645 )
646 .ok();
647 writeln!(body, " Files.createDirectories(out.getParent());").ok();
648 writeln!(
649 body,
650 " Files.copy(src, out, StandardCopyOption.REPLACE_EXISTING);"
651 )
652 .ok();
653 writeln!(body, " out.toFile().deleteOnExit();").ok();
654 writeln!(body, " copied.add(out);").ok();
655 writeln!(body, " }}").ok();
656 writeln!(body, " }}").ok();
657 writeln!(body, " return copied;").ok();
658 writeln!(body, " }}").ok();
659 writeln!(body).ok();
660 writeln!(
661 body,
662 " private static Path safeResolve(Path destDir, String relative) throws Exception {{"
663 )
664 .ok();
665 writeln!(
666 body,
667 " Path normalizedDest = destDir.toAbsolutePath().normalize();"
668 )
669 .ok();
670 writeln!(body, " Path out = normalizedDest.resolve(relative).normalize();").ok();
671 writeln!(body, " if (!out.startsWith(normalizedDest)) {{").ok();
672 writeln!(body, " throw new SecurityException(\"Blocked extracting native file outside destination directory: \" + relative);").ok();
673 writeln!(body, " }}").ok();
674 writeln!(body, " return out;").ok();
675 writeln!(body, " }}").ok();
676 writeln!(body).ok();
677 writeln!(
678 body,
679 " private static String resolveNativesRid(String osName, String osArch) {{"
680 )
681 .ok();
682 writeln!(body, " String arch;").ok();
683 writeln!(
684 body,
685 " if (osArch.contains(\"aarch64\") || osArch.contains(\"arm64\")) {{"
686 )
687 .ok();
688 writeln!(body, " arch = \"arm64\";").ok();
689 writeln!(
690 body,
691 " }} else if (osArch.contains(\"x86_64\") || osArch.contains(\"amd64\")) {{"
692 )
693 .ok();
694 writeln!(body, " arch = \"x86_64\";").ok();
695 writeln!(body, " }} else {{").ok();
696 writeln!(body, " arch = osArch.replaceAll(\"[^a-z0-9_]+\", \"\");").ok();
697 writeln!(body, " }}").ok();
698 writeln!(body).ok();
699 writeln!(body, " String os;").ok();
700 writeln!(
701 body,
702 " if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {{"
703 )
704 .ok();
705 writeln!(body, " os = \"macos\";").ok();
706 writeln!(body, " }} else if (osName.contains(\"win\")) {{").ok();
707 writeln!(body, " os = \"windows\";").ok();
708 writeln!(body, " }} else {{").ok();
709 writeln!(body, " os = \"linux\";").ok();
710 writeln!(body, " }}").ok();
711 writeln!(body).ok();
712 writeln!(body, " return os + \"-\" + arch;").ok();
713 writeln!(body, " }}").ok();
714 writeln!(body).ok();
715
716 for func in &api.functions {
720 let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
721 let return_layout = gen_ffi_layout(&func.return_type);
722 let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
723
724 let layout_str = gen_function_descriptor(&return_layout, ¶m_layouts);
725
726 let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
727
728 writeln!(
729 body,
730 " static final MethodHandle {} = LINKER.downcallHandle(",
731 handle_name
732 )
733 .ok();
734 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
735 writeln!(body, " {}", layout_str).ok();
736 writeln!(body, " );").ok();
737 }
738
739 {
741 let free_name = format!("{}_free_string", prefix);
742 let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
743 writeln!(body).ok();
744 writeln!(
745 body,
746 " static final MethodHandle {} = LINKER.downcallHandle(",
747 handle_name
748 )
749 .ok();
750 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_name).ok();
751 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
752 writeln!(body, " );").ok();
753 }
754
755 {
757 writeln!(
758 body,
759 " static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
760 prefix.to_uppercase()
761 )
762 .ok();
763 writeln!(body, " LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
764 writeln!(body, " FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
765 writeln!(body, " );").ok();
766
767 writeln!(
768 body,
769 " static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
770 prefix.to_uppercase()
771 )
772 .ok();
773 writeln!(
774 body,
775 " LIB.find(\"{}_last_error_context\").orElseThrow(),",
776 prefix
777 )
778 .ok();
779 writeln!(body, " FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
780 writeln!(body, " );").ok();
781 }
782
783 let mut emitted_free_handles: AHashSet<String> = AHashSet::new();
786
787 let opaque_type_names: AHashSet<String> = api
789 .types
790 .iter()
791 .filter(|t| t.is_opaque)
792 .map(|t| t.name.clone())
793 .collect();
794
795 for func in &api.functions {
797 if let TypeRef::Named(name) = &func.return_type {
798 let type_snake = name.to_snake_case();
799 let type_upper = type_snake.to_uppercase();
800 let is_opaque = opaque_type_names.contains(name.as_str());
801
802 if is_opaque {
803 } else {
806 let to_json_handle = format!("{}_{}_TO_JSON", prefix.to_uppercase(), type_upper);
810 let to_json_ffi = format!("{}_{}_to_json", prefix, type_snake);
811 writeln!(body).ok();
812 writeln!(
813 body,
814 " static final MethodHandle {} = LINKER.downcallHandle(",
815 to_json_handle
816 )
817 .ok();
818 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", to_json_ffi).ok();
819 writeln!(
820 body,
821 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
822 )
823 .ok();
824 writeln!(body, " );").ok();
825 }
826
827 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
829 let free_ffi = format!("{}_{}_free", prefix, type_snake);
830 if emitted_free_handles.insert(free_handle.clone()) {
831 writeln!(body).ok();
832 writeln!(
833 body,
834 " static final MethodHandle {} = LINKER.downcallHandle(",
835 free_handle
836 )
837 .ok();
838 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
839 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
840 writeln!(body, " );").ok();
841 }
842 }
843 }
844
845 let mut emitted_from_json_handles: AHashSet<String> = AHashSet::new();
848 for func in &api.functions {
849 for param in &func.params {
850 let inner_name = match ¶m.ty {
852 TypeRef::Named(n) => Some(n.clone()),
853 TypeRef::Optional(inner) => {
854 if let TypeRef::Named(n) = inner.as_ref() {
855 Some(n.clone())
856 } else {
857 None
858 }
859 }
860 _ => None,
861 };
862 if let Some(name) = inner_name {
863 if !opaque_type_names.contains(name.as_str()) {
864 let type_snake = name.to_snake_case();
865 let type_upper = type_snake.to_uppercase();
866
867 let from_json_handle = format!("{}_{}_FROM_JSON", prefix.to_uppercase(), type_upper);
869 let from_json_ffi = format!("{}_{}_from_json", prefix, type_snake);
870 if emitted_from_json_handles.insert(from_json_handle.clone()) {
871 writeln!(body).ok();
872 writeln!(
873 body,
874 " static final MethodHandle {} = LINKER.downcallHandle(",
875 from_json_handle
876 )
877 .ok();
878 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", from_json_ffi).ok();
879 writeln!(
880 body,
881 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
882 )
883 .ok();
884 writeln!(body, " );").ok();
885 }
886
887 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
889 let free_ffi = format!("{}_{}_free", prefix, type_snake);
890 if emitted_free_handles.insert(free_handle.clone()) {
891 writeln!(body).ok();
892 writeln!(
893 body,
894 " static final MethodHandle {} = LINKER.downcallHandle(",
895 free_handle
896 )
897 .ok();
898 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
899 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
900 writeln!(body, " );").ok();
901 }
902 }
903 }
904 }
905 }
906
907 let builder_class_names: AHashSet<String> = api
910 .types
911 .iter()
912 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
913 .map(|t| format!("{}Builder", t.name))
914 .collect();
915
916 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
918 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
919 let type_snake = typ.name.to_snake_case();
920 let type_upper = type_snake.to_uppercase();
921 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
922 let free_ffi = format!("{}_{}_free", prefix, type_snake);
923 if emitted_free_handles.insert(free_handle.clone()) {
924 writeln!(body).ok();
925 writeln!(
926 body,
927 " static final MethodHandle {} = LINKER.downcallHandle(",
928 free_handle
929 )
930 .ok();
931 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
932 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
933 writeln!(body, " );").ok();
934 }
935 }
936 }
937
938 if has_visitor_bridge {
940 body.push_str(&crate::gen_visitor::gen_native_lib_visitor_handles(prefix));
941 }
942
943 writeln!(body, "}}").ok();
944
945 let mut out = String::with_capacity(body.len() + 512);
947
948 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
949 writeln!(out, "package {};", package).ok();
950 writeln!(out).ok();
951 if body.contains("Arena") {
952 writeln!(out, "import java.lang.foreign.Arena;").ok();
953 }
954 if body.contains("FunctionDescriptor") {
955 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
956 }
957 if body.contains("Linker") {
958 writeln!(out, "import java.lang.foreign.Linker;").ok();
959 }
960 if body.contains("MemorySegment") {
961 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
962 }
963 if body.contains("SymbolLookup") {
964 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
965 }
966 if body.contains("ValueLayout") {
967 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
968 }
969 if body.contains("MethodHandle") {
970 writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
971 }
972 writeln!(out, "import java.net.URL;").ok();
974 writeln!(out, "import java.nio.file.Files;").ok();
975 writeln!(out, "import java.nio.file.Path;").ok();
976 writeln!(out, "import java.nio.file.StandardCopyOption;").ok();
977 writeln!(out, "import java.util.ArrayList;").ok();
978 writeln!(out, "import java.util.Enumeration;").ok();
979 writeln!(out, "import java.util.List;").ok();
980 writeln!(out, "import java.util.jar.JarEntry;").ok();
981 writeln!(out, "import java.util.jar.JarFile;").ok();
982 writeln!(out).ok();
983
984 out.push_str(&body);
985
986 out
987}
988
989#[allow(clippy::too_many_arguments)]
994fn gen_main_class(
995 api: &ApiSurface,
996 _config: &AlefConfig,
997 package: &str,
998 class_name: &str,
999 prefix: &str,
1000 bridge_param_names: &HashSet<String>,
1001 bridge_type_aliases: &HashSet<String>,
1002 has_visitor_bridge: bool,
1003) -> String {
1004 let opaque_types: AHashSet<String> = api
1006 .types
1007 .iter()
1008 .filter(|t| t.is_opaque)
1009 .map(|t| t.name.clone())
1010 .collect();
1011
1012 let mut body = String::with_capacity(4096);
1014
1015 writeln!(body, "public final class {} {{", class_name).ok();
1016 writeln!(body, " private {}() {{ }}", class_name).ok();
1017 writeln!(body).ok();
1018
1019 for func in &api.functions {
1021 gen_sync_function_method(
1023 &mut body,
1024 func,
1025 prefix,
1026 class_name,
1027 &opaque_types,
1028 bridge_param_names,
1029 bridge_type_aliases,
1030 );
1031 writeln!(body).ok();
1032
1033 if func.is_async {
1035 gen_async_wrapper_method(&mut body, func, bridge_param_names, bridge_type_aliases);
1036 writeln!(body).ok();
1037 }
1038 }
1039
1040 if has_visitor_bridge {
1042 body.push_str(&crate::gen_visitor::gen_convert_with_visitor_method(class_name, prefix));
1043 writeln!(body).ok();
1044 }
1045
1046 gen_helper_methods(&mut body, prefix, class_name);
1048
1049 writeln!(body, "}}").ok();
1050
1051 let mut out = String::with_capacity(body.len() + 512);
1053
1054 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1055 writeln!(out, "package {};", package).ok();
1056 writeln!(out).ok();
1057 if body.contains("Arena") {
1058 writeln!(out, "import java.lang.foreign.Arena;").ok();
1059 }
1060 if body.contains("FunctionDescriptor") {
1061 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
1062 }
1063 if body.contains("Linker") {
1064 writeln!(out, "import java.lang.foreign.Linker;").ok();
1065 }
1066 if body.contains("MemorySegment") {
1067 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1068 }
1069 if body.contains("SymbolLookup") {
1070 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
1071 }
1072 if body.contains("ValueLayout") {
1073 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
1074 }
1075 if body.contains("List<") {
1076 writeln!(out, "import java.util.List;").ok();
1077 }
1078 if body.contains("Map<") {
1079 writeln!(out, "import java.util.Map;").ok();
1080 }
1081 if body.contains("Optional<") {
1082 writeln!(out, "import java.util.Optional;").ok();
1083 }
1084 if body.contains("HashMap<") || body.contains("new HashMap") {
1085 writeln!(out, "import java.util.HashMap;").ok();
1086 }
1087 if body.contains("CompletableFuture") {
1088 writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
1089 }
1090 if body.contains("CompletionException") {
1091 writeln!(out, "import java.util.concurrent.CompletionException;").ok();
1092 }
1093 if body.contains(" ObjectMapper") {
1097 writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
1098 }
1099 writeln!(out).ok();
1100
1101 out.push_str(&body);
1102
1103 out
1104}
1105
1106fn is_bridge_param_java(
1107 param: &alef_core::ir::ParamDef,
1108 bridge_param_names: &HashSet<String>,
1109 bridge_type_aliases: &HashSet<String>,
1110) -> bool {
1111 if bridge_param_names.contains(param.name.as_str()) {
1112 return true;
1113 }
1114 let type_name = match ¶m.ty {
1115 TypeRef::Named(n) => Some(n.as_str()),
1116 TypeRef::Optional(inner) => {
1117 if let TypeRef::Named(n) = inner.as_ref() {
1118 Some(n.as_str())
1119 } else {
1120 None
1121 }
1122 }
1123 _ => None,
1124 };
1125 type_name.is_some_and(|n| bridge_type_aliases.contains(n))
1126}
1127
1128fn gen_sync_function_method(
1129 out: &mut String,
1130 func: &FunctionDef,
1131 prefix: &str,
1132 class_name: &str,
1133 opaque_types: &AHashSet<String>,
1134 bridge_param_names: &HashSet<String>,
1135 bridge_type_aliases: &HashSet<String>,
1136) {
1137 let params: Vec<String> = func
1139 .params
1140 .iter()
1141 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1142 .map(|p| {
1143 let ptype = java_type(&p.ty);
1144 format!("{} {}", ptype, to_java_name(&p.name))
1145 })
1146 .collect();
1147
1148 let return_type = java_type(&func.return_type);
1149
1150 writeln!(
1151 out,
1152 " public static {} {}({}) throws {}Exception {{",
1153 return_type,
1154 to_java_name(&func.name),
1155 params.join(", "),
1156 class_name
1157 )
1158 .ok();
1159
1160 writeln!(out, " try (var arena = Arena.ofConfined()) {{").ok();
1161
1162 let ffi_ptr_params: Vec<(String, String)> = func
1166 .params
1167 .iter()
1168 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1169 .filter_map(|p| {
1170 let inner_name = match &p.ty {
1171 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => Some(n.clone()),
1172 TypeRef::Optional(inner) => {
1173 if let TypeRef::Named(n) = inner.as_ref() {
1174 if !opaque_types.contains(n.as_str()) {
1175 Some(n.clone())
1176 } else {
1177 None
1178 }
1179 } else {
1180 None
1181 }
1182 }
1183 _ => None,
1184 };
1185 inner_name.map(|type_name| {
1186 let cname = "c".to_string() + &to_java_name(&p.name);
1187 let type_snake = type_name.to_snake_case();
1188 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1189 (cname, free_handle)
1190 })
1191 })
1192 .collect();
1193
1194 for param in &func.params {
1196 if is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
1197 continue;
1198 }
1199 marshal_param_to_ffi(out, &to_java_name(¶m.name), ¶m.ty, opaque_types, prefix);
1200 }
1201
1202 let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
1204
1205 let call_args: Vec<String> = func
1207 .params
1208 .iter()
1209 .map(|p| {
1210 if is_bridge_param_java(p, bridge_param_names, bridge_type_aliases) {
1211 "MemorySegment.NULL".to_string()
1212 } else {
1213 ffi_param_name(&to_java_name(&p.name), &p.ty, opaque_types)
1214 }
1215 })
1216 .collect();
1217
1218 let emit_ffi_ptr_cleanup = |out: &mut String| {
1220 for (cname, free_handle) in &ffi_ptr_params {
1221 writeln!(out, " if (!{}.equals(MemorySegment.NULL)) {{", cname).ok();
1222 writeln!(out, " {}.invoke({});", free_handle, cname).ok();
1223 writeln!(out, " }}").ok();
1224 }
1225 };
1226
1227 if matches!(func.return_type, TypeRef::Unit) {
1228 writeln!(out, " {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
1229 emit_ffi_ptr_cleanup(out);
1230 writeln!(out, " }} catch (Throwable e) {{").ok();
1231 writeln!(
1232 out,
1233 " throw new {}Exception(\"FFI call failed\", e);",
1234 class_name
1235 )
1236 .ok();
1237 writeln!(out, " }}").ok();
1238 } else if is_ffi_string_return(&func.return_type) {
1239 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
1240 writeln!(
1241 out,
1242 " var resultPtr = (MemorySegment) {}.invoke({});",
1243 ffi_handle,
1244 call_args.join(", ")
1245 )
1246 .ok();
1247 emit_ffi_ptr_cleanup(out);
1248 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1249 writeln!(out, " checkLastError();").ok();
1250 writeln!(out, " return null;").ok();
1251 writeln!(out, " }}").ok();
1252 writeln!(
1253 out,
1254 " String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1255 )
1256 .ok();
1257 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1258 writeln!(out, " return result;").ok();
1259 writeln!(out, " }} catch (Throwable e) {{").ok();
1260 writeln!(
1261 out,
1262 " throw new {}Exception(\"FFI call failed\", e);",
1263 class_name
1264 )
1265 .ok();
1266 writeln!(out, " }}").ok();
1267 } else if matches!(func.return_type, TypeRef::Named(_)) {
1268 let return_type_name = match &func.return_type {
1270 TypeRef::Named(name) => name,
1271 _ => unreachable!(),
1272 };
1273 let is_opaque = opaque_types.contains(return_type_name.as_str());
1274
1275 writeln!(
1276 out,
1277 " var resultPtr = (MemorySegment) {}.invoke({});",
1278 ffi_handle,
1279 call_args.join(", ")
1280 )
1281 .ok();
1282 emit_ffi_ptr_cleanup(out);
1283 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1284 writeln!(out, " checkLastError();").ok();
1285 writeln!(out, " return null;").ok();
1286 writeln!(out, " }}").ok();
1287
1288 if is_opaque {
1289 writeln!(out, " return new {}(resultPtr);", return_type_name).ok();
1291 } else {
1292 let type_snake = return_type_name.to_snake_case();
1295 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1296 let to_json_handle = format!(
1297 "NativeLib.{}_{}_TO_JSON",
1298 prefix.to_uppercase(),
1299 type_snake.to_uppercase()
1300 );
1301 writeln!(
1302 out,
1303 " var jsonPtr = (MemorySegment) {}.invoke(resultPtr);",
1304 to_json_handle
1305 )
1306 .ok();
1307 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1308 writeln!(out, " if (jsonPtr.equals(MemorySegment.NULL)) {{").ok();
1309 writeln!(out, " checkLastError();").ok();
1310 writeln!(out, " return null;").ok();
1311 writeln!(out, " }}").ok();
1312 writeln!(
1313 out,
1314 " String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1315 )
1316 .ok();
1317 writeln!(
1318 out,
1319 " NativeLib.{}_FREE_STRING.invoke(jsonPtr);",
1320 prefix.to_uppercase()
1321 )
1322 .ok();
1323 writeln!(
1324 out,
1325 " return createObjectMapper().readValue(json, {}.class);",
1326 return_type_name
1327 )
1328 .ok();
1329 }
1330
1331 writeln!(out, " }} catch (Throwable e) {{").ok();
1332 writeln!(
1333 out,
1334 " throw new {}Exception(\"FFI call failed\", e);",
1335 class_name
1336 )
1337 .ok();
1338 writeln!(out, " }}").ok();
1339 } else if matches!(func.return_type, TypeRef::Vec(_)) {
1340 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
1342 writeln!(
1343 out,
1344 " var resultPtr = (MemorySegment) {}.invoke({});",
1345 ffi_handle,
1346 call_args.join(", ")
1347 )
1348 .ok();
1349 emit_ffi_ptr_cleanup(out);
1350 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1351 writeln!(out, " return java.util.List.of();").ok();
1352 writeln!(out, " }}").ok();
1353 writeln!(
1354 out,
1355 " String json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1356 )
1357 .ok();
1358 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1359 let element_type = match &func.return_type {
1361 TypeRef::Vec(inner) => java_type(inner),
1362 _ => unreachable!(),
1363 };
1364 writeln!(
1365 out,
1366 " return createObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<java.util.List<{}>>() {{ }});",
1367 element_type
1368 )
1369 .ok();
1370 writeln!(out, " }} catch (Throwable e) {{").ok();
1371 writeln!(
1372 out,
1373 " throw new {}Exception(\"FFI call failed\", e);",
1374 class_name
1375 )
1376 .ok();
1377 writeln!(out, " }}").ok();
1378 } else {
1379 writeln!(
1380 out,
1381 " var primitiveResult = ({}) {}.invoke({});",
1382 java_ffi_return_cast(&func.return_type),
1383 ffi_handle,
1384 call_args.join(", ")
1385 )
1386 .ok();
1387 emit_ffi_ptr_cleanup(out);
1388 writeln!(out, " return primitiveResult;").ok();
1389 writeln!(out, " }} catch (Throwable e) {{").ok();
1390 writeln!(
1391 out,
1392 " throw new {}Exception(\"FFI call failed\", e);",
1393 class_name
1394 )
1395 .ok();
1396 writeln!(out, " }}").ok();
1397 }
1398
1399 writeln!(out, " }}").ok();
1400}
1401
1402fn gen_async_wrapper_method(
1403 out: &mut String,
1404 func: &FunctionDef,
1405 bridge_param_names: &HashSet<String>,
1406 bridge_type_aliases: &HashSet<String>,
1407) {
1408 let params: Vec<String> = func
1409 .params
1410 .iter()
1411 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1412 .map(|p| {
1413 let ptype = java_type(&p.ty);
1414 format!("{} {}", ptype, to_java_name(&p.name))
1415 })
1416 .collect();
1417
1418 let return_type = match &func.return_type {
1419 TypeRef::Unit => "Void".to_string(),
1420 other => java_boxed_type(other).to_string(),
1421 };
1422
1423 let sync_method_name = to_java_name(&func.name);
1424 let async_method_name = format!("{}Async", sync_method_name);
1425 let param_names: Vec<String> = func
1426 .params
1427 .iter()
1428 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1429 .map(|p| to_java_name(&p.name))
1430 .collect();
1431
1432 writeln!(
1433 out,
1434 " public static CompletableFuture<{}> {}({}) {{",
1435 return_type,
1436 async_method_name,
1437 params.join(", ")
1438 )
1439 .ok();
1440 writeln!(out, " return CompletableFuture.supplyAsync(() -> {{").ok();
1441 writeln!(out, " try {{").ok();
1442 writeln!(
1443 out,
1444 " return {}({});",
1445 sync_method_name,
1446 param_names.join(", ")
1447 )
1448 .ok();
1449 writeln!(out, " }} catch (Throwable e) {{").ok();
1450 writeln!(out, " throw new CompletionException(e);").ok();
1451 writeln!(out, " }}").ok();
1452 writeln!(out, " }});").ok();
1453 writeln!(out, " }}").ok();
1454}
1455
1456fn gen_exception_class(package: &str, class_name: &str) -> String {
1461 let mut out = String::with_capacity(512);
1462
1463 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1464 writeln!(out, "package {};", package).ok();
1465 writeln!(out).ok();
1466
1467 writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
1468 writeln!(out, " private final int code;").ok();
1469 writeln!(out).ok();
1470 writeln!(out, " public {}Exception(int code, String message) {{", class_name).ok();
1471 writeln!(out, " super(message);").ok();
1472 writeln!(out, " this.code = code;").ok();
1473 writeln!(out, " }}").ok();
1474 writeln!(out).ok();
1475 writeln!(
1476 out,
1477 " public {}Exception(String message, Throwable cause) {{",
1478 class_name
1479 )
1480 .ok();
1481 writeln!(out, " super(message, cause);").ok();
1482 writeln!(out, " this.code = -1;").ok();
1483 writeln!(out, " }}").ok();
1484 writeln!(out).ok();
1485 writeln!(out, " public int getCode() {{").ok();
1486 writeln!(out, " return code;").ok();
1487 writeln!(out, " }}").ok();
1488 writeln!(out, "}}").ok();
1489
1490 out
1491}
1492
1493#[allow(clippy::too_many_arguments)]
1498fn gen_facade_class(
1499 api: &ApiSurface,
1500 package: &str,
1501 public_class: &str,
1502 raw_class: &str,
1503 _prefix: &str,
1504 bridge_param_names: &HashSet<String>,
1505 bridge_type_aliases: &HashSet<String>,
1506 has_visitor_bridge: bool,
1507) -> String {
1508 let mut body = String::with_capacity(4096);
1509
1510 writeln!(body, "public final class {} {{", public_class).ok();
1511 writeln!(body, " private {}() {{ }}", public_class).ok();
1512 writeln!(body).ok();
1513
1514 for func in &api.functions {
1516 let params: Vec<String> = func
1518 .params
1519 .iter()
1520 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1521 .map(|p| {
1522 let ptype = java_type(&p.ty);
1523 format!("{} {}", ptype, to_java_name(&p.name))
1524 })
1525 .collect();
1526
1527 let return_type = java_type(&func.return_type);
1528
1529 if !func.doc.is_empty() {
1530 writeln!(body, " /**").ok();
1531 for line in func.doc.lines() {
1532 writeln!(body, " * {}", line).ok();
1533 }
1534 writeln!(body, " */").ok();
1535 }
1536
1537 writeln!(
1538 body,
1539 " public static {} {}({}) throws {}Exception {{",
1540 return_type,
1541 to_java_name(&func.name),
1542 params.join(", "),
1543 raw_class
1544 )
1545 .ok();
1546
1547 for param in &func.params {
1549 if !param.optional && !is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
1550 let pname = to_java_name(¶m.name);
1551 writeln!(
1552 body,
1553 " java.util.Objects.requireNonNull({}, \"{} must not be null\");",
1554 pname, pname
1555 )
1556 .ok();
1557 }
1558 }
1559
1560 let call_args: Vec<String> = func
1564 .params
1565 .iter()
1566 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1567 .map(|p| to_java_name(&p.name))
1568 .collect();
1569
1570 if matches!(func.return_type, TypeRef::Unit) {
1571 writeln!(
1572 body,
1573 " {}.{}({});",
1574 raw_class,
1575 to_java_name(&func.name),
1576 call_args.join(", ")
1577 )
1578 .ok();
1579 } else {
1580 writeln!(
1581 body,
1582 " return {}.{}({});",
1583 raw_class,
1584 to_java_name(&func.name),
1585 call_args.join(", ")
1586 )
1587 .ok();
1588 }
1589
1590 writeln!(body, " }}").ok();
1591 writeln!(body).ok();
1592
1593 let has_optional = func
1596 .params
1597 .iter()
1598 .any(|p| p.optional && !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases));
1599 if has_optional {
1600 let required_params: Vec<String> = func
1601 .params
1602 .iter()
1603 .filter(|p| !p.optional && !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1604 .map(|p| {
1605 let ptype = java_type(&p.ty);
1606 format!("{} {}", ptype, to_java_name(&p.name))
1607 })
1608 .collect();
1609
1610 writeln!(
1611 body,
1612 " public static {} {}({}) throws {}Exception {{",
1613 return_type,
1614 to_java_name(&func.name),
1615 required_params.join(", "),
1616 raw_class
1617 )
1618 .ok();
1619
1620 let full_args: Vec<String> = func
1623 .params
1624 .iter()
1625 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1626 .map(|p| {
1627 if p.optional {
1628 "null".to_string()
1629 } else {
1630 to_java_name(&p.name)
1631 }
1632 })
1633 .collect();
1634
1635 if matches!(func.return_type, TypeRef::Unit) {
1636 writeln!(
1637 body,
1638 " {}.{}({});",
1639 raw_class,
1640 to_java_name(&func.name),
1641 full_args.join(", ")
1642 )
1643 .ok();
1644 } else {
1645 writeln!(
1646 body,
1647 " return {}.{}({});",
1648 raw_class,
1649 to_java_name(&func.name),
1650 full_args.join(", ")
1651 )
1652 .ok();
1653 }
1654
1655 writeln!(body, " }}").ok();
1656 writeln!(body).ok();
1657 }
1658 }
1659
1660 if has_visitor_bridge {
1662 writeln!(body, " /**").ok();
1663 writeln!(
1664 body,
1665 " * Convert HTML to Markdown, invoking visitor callbacks during processing."
1666 )
1667 .ok();
1668 writeln!(body, " */").ok();
1669 writeln!(
1670 body,
1671 " public static ConversionResult convertWithVisitor(String html, ConversionOptions options, Visitor visitor)"
1672 )
1673 .ok();
1674 writeln!(body, " throws {}Exception {{", raw_class).ok();
1675 writeln!(
1676 body,
1677 " return {}.convertWithVisitor(html, options, visitor);",
1678 raw_class
1679 )
1680 .ok();
1681 writeln!(body, " }}").ok();
1682 writeln!(body).ok();
1683 }
1684
1685 writeln!(body, "}}").ok();
1686
1687 let mut out = String::with_capacity(body.len() + 512);
1689
1690 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1691 writeln!(out, "package {};", package).ok();
1692
1693 let has_list = body.contains("List<");
1695 let has_map = body.contains("Map<");
1696 let has_optional = body.contains("Optional<");
1697 let has_imports = has_list || has_map || has_optional;
1698
1699 if has_imports {
1700 writeln!(out).ok();
1701 if has_list {
1702 writeln!(out, "import java.util.List;").ok();
1703 }
1704 if has_map {
1705 writeln!(out, "import java.util.Map;").ok();
1706 }
1707 if has_optional {
1708 writeln!(out, "import java.util.Optional;").ok();
1709 }
1710 }
1711
1712 writeln!(out).ok();
1713 out.push_str(&body);
1714
1715 out
1716}
1717
1718fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
1723 let mut out = String::with_capacity(1024);
1724 let class_name = &typ.name;
1725 let type_snake = class_name.to_snake_case();
1726
1727 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1728 writeln!(out, "package {};", package).ok();
1729 writeln!(out).ok();
1730 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1731 writeln!(out).ok();
1732
1733 if !typ.doc.is_empty() {
1734 writeln!(out, "/**").ok();
1735 for line in typ.doc.lines() {
1736 writeln!(out, " * {}", line).ok();
1737 }
1738 writeln!(out, " */").ok();
1739 }
1740
1741 writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
1742 writeln!(out, " private final MemorySegment handle;").ok();
1743 writeln!(out).ok();
1744 writeln!(out, " {}(MemorySegment handle) {{", class_name).ok();
1745 writeln!(out, " this.handle = handle;").ok();
1746 writeln!(out, " }}").ok();
1747 writeln!(out).ok();
1748 writeln!(out, " MemorySegment handle() {{").ok();
1749 writeln!(out, " return this.handle;").ok();
1750 writeln!(out, " }}").ok();
1751 writeln!(out).ok();
1752 writeln!(out, " @Override").ok();
1753 writeln!(out, " public void close() {{").ok();
1754 writeln!(
1755 out,
1756 " if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
1757 )
1758 .ok();
1759 writeln!(out, " try {{").ok();
1760 writeln!(
1761 out,
1762 " NativeLib.{}_{}_FREE.invoke(handle);",
1763 prefix.to_uppercase(),
1764 type_snake.to_uppercase()
1765 )
1766 .ok();
1767 writeln!(out, " }} catch (Throwable e) {{").ok();
1768 writeln!(
1769 out,
1770 " throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
1771 class_name
1772 )
1773 .ok();
1774 writeln!(out, " }}").ok();
1775 writeln!(out, " }}").ok();
1776 writeln!(out, " }}").ok();
1777 writeln!(out, "}}").ok();
1778
1779 out
1780}
1781
1782fn emit_javadoc(out: &mut String, doc: &str, indent: &str) {
1792 if doc.is_empty() {
1793 return;
1794 }
1795 writeln!(out, "{indent}/**").ok();
1796 for line in doc.lines() {
1797 if line.is_empty() {
1798 writeln!(out, "{indent} *").ok();
1799 } else {
1800 let escaped = escape_javadoc_line(line);
1801 writeln!(out, "{indent} * {escaped}").ok();
1802 }
1803 }
1804 writeln!(out, "{indent} */").ok();
1805}
1806
1807const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
1810
1811fn gen_record_type(package: &str, typ: &TypeDef, complex_enums: &AHashSet<String>, lang_rename_all: &str) -> String {
1812 let (field_list, field_docs): (Vec<String>, Vec<String>) = typ
1817 .fields
1818 .iter()
1819 .map(|f| {
1820 let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
1823 let ftype = if is_complex {
1824 "Object".to_string()
1825 } else if f.optional {
1826 format!("Optional<{}>", java_boxed_type(&f.ty))
1827 } else {
1828 java_type(&f.ty).to_string()
1829 };
1830 let jname = safe_java_field_name(&f.name);
1831 let decl = if lang_rename_all == "camelCase" && f.name.contains('_') {
1835 format!("@JsonProperty(\"{}\") {} {}", f.name, ftype, jname)
1836 } else {
1837 format!("{} {}", ftype, jname)
1838 };
1839 (decl, f.doc.clone())
1840 })
1841 .unzip();
1842
1843 let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
1847
1848 let mut record_block = String::new();
1850 emit_javadoc(&mut record_block, &typ.doc, "");
1851 if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
1852 writeln!(record_block, "public record {}(", typ.name).ok();
1853 for (i, (field, doc)) in field_list.iter().zip(field_docs.iter()).enumerate() {
1854 let comma = if i < field_list.len() - 1 { "," } else { "" };
1855 if !doc.is_empty() {
1856 let doc_summary = escape_javadoc_line(doc.lines().next().unwrap_or("").trim());
1858 writeln!(record_block, " /** {doc_summary} */").ok();
1859 }
1860 writeln!(record_block, " {}{}", field, comma).ok();
1861 }
1862 writeln!(record_block, ") {{").ok();
1863 } else {
1864 writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
1865 }
1866
1867 if typ.has_default {
1869 writeln!(record_block, " public static {}Builder builder() {{", typ.name).ok();
1870 writeln!(record_block, " return new {}Builder();", typ.name).ok();
1871 writeln!(record_block, " }}").ok();
1872 }
1873
1874 writeln!(record_block, "}}").ok();
1875
1876 let needs_json_property = field_list.iter().any(|f| f.contains("@JsonProperty("));
1878 let mut out = String::with_capacity(record_block.len() + 512);
1879 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1880 writeln!(out, "package {};", package).ok();
1881 writeln!(out).ok();
1882 if single_line.contains("List<") {
1883 writeln!(out, "import java.util.List;").ok();
1884 }
1885 if single_line.contains("Map<") {
1886 writeln!(out, "import java.util.Map;").ok();
1887 }
1888 if single_line.contains("Optional<") {
1889 writeln!(out, "import java.util.Optional;").ok();
1890 }
1891 if needs_json_property {
1892 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1893 }
1894 writeln!(out).ok();
1895 write!(out, "{}", record_block).ok();
1896
1897 out
1898}
1899
1900fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
1906 match rename_all {
1907 Some("snake_case") => name.to_snake_case(),
1908 Some("camelCase") => name.to_lower_camel_case(),
1909 Some("PascalCase") => name.to_pascal_case(),
1910 Some("SCREAMING_SNAKE_CASE") => name.to_snake_case().to_uppercase(),
1911 Some("lowercase") => name.to_lowercase(),
1912 Some("UPPERCASE") => name.to_uppercase(),
1913 _ => name.to_lowercase(),
1914 }
1915}
1916
1917fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
1918 let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
1919
1920 if enum_def.serde_tag.is_some() && has_data_variants {
1922 return gen_java_tagged_union(package, enum_def);
1923 }
1924
1925 let mut out = String::with_capacity(1024);
1926
1927 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1928 writeln!(out, "package {};", package).ok();
1929 writeln!(out).ok();
1930 writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
1931 writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
1932 writeln!(out).ok();
1933
1934 emit_javadoc(&mut out, &enum_def.doc, "");
1935 writeln!(out, "public enum {} {{", enum_def.name).ok();
1936
1937 for (i, variant) in enum_def.variants.iter().enumerate() {
1938 let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
1939 let json_name = variant
1941 .serde_rename
1942 .clone()
1943 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1944 if !variant.doc.is_empty() {
1945 let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
1946 writeln!(out, " /** {doc_summary} */").ok();
1947 }
1948 writeln!(out, " {}(\"{}\"){}", variant.name, json_name, comma).ok();
1949 }
1950
1951 writeln!(out).ok();
1952 writeln!(out, " private final String value;").ok();
1953 writeln!(out).ok();
1954 writeln!(out, " {}(String value) {{", enum_def.name).ok();
1955 writeln!(out, " this.value = value;").ok();
1956 writeln!(out, " }}").ok();
1957 writeln!(out).ok();
1958 writeln!(out, " @JsonValue").ok();
1959 writeln!(out, " public String getValue() {{").ok();
1960 writeln!(out, " return value;").ok();
1961 writeln!(out, " }}").ok();
1962 writeln!(out).ok();
1963 writeln!(out, " @JsonCreator").ok();
1964 writeln!(out, " public static {} fromValue(String value) {{", enum_def.name).ok();
1965 writeln!(out, " for ({} e : values()) {{", enum_def.name).ok();
1966 writeln!(out, " if (e.value.equalsIgnoreCase(value)) {{").ok();
1967 writeln!(out, " return e;").ok();
1968 writeln!(out, " }}").ok();
1969 writeln!(out, " }}").ok();
1970 writeln!(
1971 out,
1972 " throw new IllegalArgumentException(\"Unknown value: \" + value);"
1973 )
1974 .ok();
1975 writeln!(out, " }}").ok();
1976
1977 writeln!(out, "}}").ok();
1978
1979 out
1980}
1981
1982fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
1987 let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
1988
1989 let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
1994 let optional_type = if variant_names.contains("Optional") {
1995 "java.util.Optional"
1996 } else {
1997 "Optional"
1998 };
1999
2000 let needs_json_property = enum_def
2002 .variants
2003 .iter()
2004 .any(|v| v.fields.iter().any(|f| !is_tuple_field_name(&f.name)));
2005
2006 let mut out = String::with_capacity(2048);
2007 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2008 writeln!(out, "package {};", package).ok();
2009 writeln!(out).ok();
2010 if needs_json_property {
2011 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
2012 }
2013 writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
2014 writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
2015
2016 let needs_list = !variant_names.contains("List")
2018 && enum_def
2019 .variants
2020 .iter()
2021 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
2022 let needs_map = !variant_names.contains("Map")
2023 && enum_def
2024 .variants
2025 .iter()
2026 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
2027 let needs_optional =
2028 !variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
2029 let needs_unwrapped = enum_def
2032 .variants
2033 .iter()
2034 .any(|v| v.fields.len() == 1 && is_tuple_field_name(&v.fields[0].name));
2035 if needs_list {
2036 writeln!(out, "import java.util.List;").ok();
2037 }
2038 if needs_map {
2039 writeln!(out, "import java.util.Map;").ok();
2040 }
2041 if needs_optional {
2042 writeln!(out, "import java.util.Optional;").ok();
2043 }
2044 if needs_unwrapped {
2045 writeln!(out, "import com.fasterxml.jackson.annotation.JsonUnwrapped;").ok();
2046 }
2047 writeln!(out).ok();
2048
2049 emit_javadoc(&mut out, &enum_def.doc, "");
2050 writeln!(
2052 out,
2053 "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
2054 )
2055 .ok();
2056 writeln!(out, "@JsonSubTypes({{").ok();
2057 for (i, variant) in enum_def.variants.iter().enumerate() {
2058 let discriminator = variant
2059 .serde_rename
2060 .clone()
2061 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
2062 let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
2063 writeln!(
2064 out,
2065 " @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
2066 enum_def.name, variant.name, discriminator, comma
2067 )
2068 .ok();
2069 }
2070 writeln!(out, "}})").ok();
2071 writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
2072
2073 for variant in &enum_def.variants {
2075 writeln!(out).ok();
2076 if variant.fields.is_empty() {
2077 if !variant.doc.is_empty() {
2079 let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
2080 writeln!(out, " /** {doc_summary} */").ok();
2081 }
2082 writeln!(out, " record {}() implements {} {{", variant.name, enum_def.name).ok();
2083 writeln!(out, " }}").ok();
2084 } else {
2085 let field_parts: Vec<String> = variant
2087 .fields
2088 .iter()
2089 .map(|f| {
2090 let ftype = if f.optional {
2091 let inner = java_boxed_type(&f.ty);
2092 let inner_str = inner.as_ref();
2093 let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
2095 inner_str.replacen("List<", "java.util.List<", 1)
2096 } else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
2097 inner_str.replacen("Map<", "java.util.Map<", 1)
2098 } else {
2099 inner_str.to_string()
2100 };
2101 format!("{optional_type}<{inner_qualified}>")
2102 } else {
2103 let t = java_type(&f.ty);
2104 let t_str = t.as_ref();
2105 if t_str.starts_with("List<") && variant_names.contains("List") {
2106 t_str.replacen("List<", "java.util.List<", 1)
2107 } else if t_str.starts_with("Map<") && variant_names.contains("Map") {
2108 t_str.replacen("Map<", "java.util.Map<", 1)
2109 } else {
2110 t_str.to_string()
2111 }
2112 };
2113 if is_tuple_field_name(&f.name) {
2117 format!("@JsonUnwrapped {ftype} value")
2118 } else {
2119 let json_name = f.name.trim_start_matches('_');
2120 let jname = safe_java_field_name(json_name);
2121 format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
2122 }
2123 })
2124 .collect();
2125
2126 let single = format!(
2127 " record {}({}) implements {} {{ }}",
2128 variant.name,
2129 field_parts.join(", "),
2130 enum_def.name
2131 );
2132
2133 if !variant.doc.is_empty() {
2134 let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
2135 writeln!(out, " /** {doc_summary} */").ok();
2136 }
2137 if single.len() > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
2138 writeln!(out, " record {}(", variant.name).ok();
2139 for (i, fp) in field_parts.iter().enumerate() {
2140 let comma = if i < field_parts.len() - 1 { "," } else { "" };
2141 writeln!(out, " {}{}", fp, comma).ok();
2142 }
2143 writeln!(out, " ) implements {} {{", enum_def.name).ok();
2144 writeln!(out, " }}").ok();
2145 } else {
2146 writeln!(
2147 out,
2148 " record {}({}) implements {} {{ }}",
2149 variant.name,
2150 field_parts.join(", "),
2151 enum_def.name
2152 )
2153 .ok();
2154 }
2155 }
2156 }
2157
2158 writeln!(out).ok();
2159 writeln!(out, "}}").ok();
2160 out
2161}
2162
2163fn gen_ffi_layout(ty: &TypeRef) -> String {
2168 match ty {
2169 TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
2170 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
2171 TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
2172 TypeRef::Optional(inner) => gen_ffi_layout(inner),
2173 TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
2174 TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
2175 TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
2176 TypeRef::Unit => "".to_string(),
2177 TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
2178 }
2179}
2180
2181fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef, opaque_types: &AHashSet<String>, prefix: &str) {
2182 match ty {
2183 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
2184 let cname = "c".to_string() + name;
2185 writeln!(out, " var {} = arena.allocateFrom({});", cname, name).ok();
2186 }
2187 TypeRef::Named(type_name) => {
2188 let cname = "c".to_string() + name;
2189 if opaque_types.contains(type_name.as_str()) {
2190 writeln!(out, " var {} = {}.handle();", cname, name).ok();
2192 } else {
2193 let type_snake = type_name.to_snake_case();
2196 let from_json_handle = format!(
2197 "NativeLib.{}_{}_FROM_JSON",
2198 prefix.to_uppercase(),
2199 type_snake.to_uppercase()
2200 );
2201 let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
2202 writeln!(
2203 out,
2204 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
2205 cname, name, name
2206 )
2207 .ok();
2208 writeln!(
2209 out,
2210 " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
2211 cname, cname, cname
2212 )
2213 .ok();
2214 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
2215 writeln!(
2216 out,
2217 " ? (MemorySegment) {}.invoke({}JsonSeg)",
2218 from_json_handle, cname
2219 )
2220 .ok();
2221 writeln!(out, " : MemorySegment.NULL;").ok();
2222 }
2223 }
2224 TypeRef::Optional(inner) => {
2225 match inner.as_ref() {
2227 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
2228 let cname = "c".to_string() + name;
2229 writeln!(
2230 out,
2231 " var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
2232 cname, name, name
2233 )
2234 .ok();
2235 }
2236 TypeRef::Named(type_name) => {
2237 let cname = "c".to_string() + name;
2238 if opaque_types.contains(type_name.as_str()) {
2239 writeln!(
2240 out,
2241 " var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
2242 cname, name, name
2243 )
2244 .ok();
2245 } else {
2246 let type_snake = type_name.to_snake_case();
2248 let from_json_handle = format!(
2249 "NativeLib.{}_{}_FROM_JSON",
2250 prefix.to_uppercase(),
2251 type_snake.to_uppercase()
2252 );
2253 writeln!(
2254 out,
2255 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
2256 cname, name, name
2257 )
2258 .ok();
2259 writeln!(out, " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
2260 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
2261 writeln!(
2262 out,
2263 " ? (MemorySegment) {}.invoke({}JsonSeg)",
2264 from_json_handle, cname
2265 )
2266 .ok();
2267 writeln!(out, " : MemorySegment.NULL;").ok();
2268 }
2269 }
2270 _ => {
2271 }
2273 }
2274 }
2275 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
2276 let cname = "c".to_string() + name;
2278 writeln!(
2279 out,
2280 " var {}Json = createObjectMapper().writeValueAsString({});",
2281 cname, name
2282 )
2283 .ok();
2284 writeln!(out, " var {} = arena.allocateFrom({}Json);", cname, cname).ok();
2285 }
2286 _ => {
2287 }
2289 }
2290}
2291
2292fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
2293 match ty {
2294 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
2295 TypeRef::Named(_) => "c".to_string() + name,
2296 TypeRef::Vec(_) | TypeRef::Map(_, _) => "c".to_string() + name,
2297 TypeRef::Optional(inner) => match inner.as_ref() {
2298 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
2299 "c".to_string() + name
2300 }
2301 _ => name.to_string(),
2302 },
2303 _ => name.to_string(),
2304 }
2305}
2306
2307fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
2310 if return_layout.is_empty() {
2311 if param_layouts.is_empty() {
2313 "FunctionDescriptor.ofVoid()".to_string()
2314 } else {
2315 format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
2316 }
2317 } else {
2318 if param_layouts.is_empty() {
2320 format!("FunctionDescriptor.of({})", return_layout)
2321 } else {
2322 format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
2323 }
2324 }
2325}
2326
2327fn is_ffi_string_return(ty: &TypeRef) -> bool {
2330 match ty {
2331 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
2332 TypeRef::Optional(inner) => is_ffi_string_return(inner),
2333 _ => false,
2334 }
2335}
2336
2337fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
2339 match ty {
2340 TypeRef::Primitive(prim) => match prim {
2341 PrimitiveType::Bool => "boolean",
2342 PrimitiveType::U8 | PrimitiveType::I8 => "byte",
2343 PrimitiveType::U16 | PrimitiveType::I16 => "short",
2344 PrimitiveType::U32 | PrimitiveType::I32 => "int",
2345 PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
2346 PrimitiveType::F32 => "float",
2347 PrimitiveType::F64 => "double",
2348 },
2349 TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
2350 _ => "MemorySegment",
2351 }
2352}
2353
2354fn gen_helper_methods(out: &mut String, prefix: &str, class_name: &str) {
2355 let needs_check_last_error = out.contains("checkLastError()");
2357 let needs_read_cstring = out.contains("readCString(");
2358 let needs_read_bytes = out.contains("readBytes(");
2359 let needs_create_object_mapper = out.contains("createObjectMapper()");
2360
2361 if !needs_check_last_error && !needs_read_cstring && !needs_read_bytes && !needs_create_object_mapper {
2362 return;
2363 }
2364
2365 writeln!(out, " // Helper methods for FFI marshalling").ok();
2366 writeln!(out).ok();
2367
2368 if needs_check_last_error {
2369 writeln!(out, " private static void checkLastError() throws Throwable {{").ok();
2372 writeln!(
2373 out,
2374 " int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
2375 prefix.to_uppercase()
2376 )
2377 .ok();
2378 writeln!(out, " if (errCode != 0) {{").ok();
2379 writeln!(
2380 out,
2381 " var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
2382 prefix.to_uppercase()
2383 )
2384 .ok();
2385 writeln!(
2386 out,
2387 " String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
2388 )
2389 .ok();
2390 writeln!(out, " throw new {}Exception(errCode, msg);", class_name).ok();
2391 writeln!(out, " }}").ok();
2392 writeln!(out, " }}").ok();
2393 writeln!(out).ok();
2394 }
2395
2396 if needs_create_object_mapper {
2397 writeln!(
2403 out,
2404 " private static com.fasterxml.jackson.databind.ObjectMapper createObjectMapper() {{"
2405 )
2406 .ok();
2407 writeln!(out, " return new com.fasterxml.jackson.databind.ObjectMapper()").ok();
2408 writeln!(
2409 out,
2410 " .registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module())"
2411 )
2412 .ok();
2413 writeln!(out, " .findAndRegisterModules()").ok();
2414 writeln!(
2415 out,
2416 " .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
2417 )
2418 .ok();
2419 writeln!(
2420 out,
2421 " .configure(com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);"
2422 )
2423 .ok();
2424 writeln!(out, " }}").ok();
2425 writeln!(out).ok();
2426 }
2427
2428 if needs_read_cstring {
2429 writeln!(out, " private static String readCString(MemorySegment ptr) {{").ok();
2430 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
2431 writeln!(out, " return null;").ok();
2432 writeln!(out, " }}").ok();
2433 writeln!(out, " return ptr.getUtf8String(0);").ok();
2434 writeln!(out, " }}").ok();
2435 writeln!(out).ok();
2436 }
2437
2438 if needs_read_bytes {
2439 writeln!(
2440 out,
2441 " private static byte[] readBytes(MemorySegment ptr, long len) {{"
2442 )
2443 .ok();
2444 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
2445 writeln!(out, " return new byte[0];").ok();
2446 writeln!(out, " }}").ok();
2447 writeln!(out, " byte[] bytes = new byte[(int) len];").ok();
2448 writeln!(
2449 out,
2450 " MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
2451 )
2452 .ok();
2453 writeln!(out, " return bytes;").ok();
2454 writeln!(out, " }}").ok();
2455 }
2456}
2457
2458fn format_optional_value(ty: &TypeRef, default: &str) -> String {
2465 if default.contains("Optional.") {
2467 return default.to_string();
2468 }
2469
2470 let inner_ty = match ty {
2472 TypeRef::Optional(inner) => inner.as_ref(),
2473 other => other,
2474 };
2475
2476 let formatted_value = match inner_ty {
2478 TypeRef::Primitive(p) => match p {
2479 PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
2480 if default.ends_with('L') || default.ends_with('l') {
2482 default.to_string()
2483 } else if default.parse::<i64>().is_ok() {
2484 format!("{}L", default)
2485 } else {
2486 default.to_string()
2487 }
2488 }
2489 PrimitiveType::F32 => {
2490 if default.ends_with('f') || default.ends_with('F') {
2492 default.to_string()
2493 } else if default.parse::<f32>().is_ok() {
2494 format!("{}f", default)
2495 } else {
2496 default.to_string()
2497 }
2498 }
2499 PrimitiveType::F64 => {
2500 default.to_string()
2502 }
2503 _ => default.to_string(),
2504 },
2505 _ => default.to_string(),
2506 };
2507
2508 format!("Optional.of({})", formatted_value)
2509}
2510
2511fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
2512 let mut body = String::with_capacity(2048);
2513
2514 emit_javadoc(&mut body, &typ.doc, "");
2515 writeln!(body, "public class {}Builder {{", typ.name).ok();
2516 writeln!(body).ok();
2517
2518 for field in &typ.fields {
2520 let field_name = safe_java_field_name(&field.name);
2521
2522 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
2524 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
2525 {
2526 continue;
2527 }
2528
2529 let field_type = if field.optional {
2532 format!("Optional<{}>", java_boxed_type(&field.ty))
2533 } else if matches!(field.ty, TypeRef::Duration) {
2534 java_boxed_type(&field.ty).to_string()
2535 } else {
2536 java_type(&field.ty).to_string()
2537 };
2538
2539 let default_value = if field.optional {
2540 if let Some(default) = &field.default {
2542 format_optional_value(&field.ty, default)
2544 } else {
2545 "Optional.empty()".to_string()
2547 }
2548 } else {
2549 if let Some(default) = &field.default {
2551 default.clone()
2552 } else {
2553 match &field.ty {
2554 TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
2555 TypeRef::Json => "null".to_string(),
2556 TypeRef::Bytes => "new byte[0]".to_string(),
2557 TypeRef::Primitive(p) => match p {
2558 PrimitiveType::Bool => "false".to_string(),
2559 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
2560 _ => "0".to_string(),
2561 },
2562 TypeRef::Vec(_) => "List.of()".to_string(),
2563 TypeRef::Map(_, _) => "Map.of()".to_string(),
2564 TypeRef::Optional(_) => "Optional.empty()".to_string(),
2565 TypeRef::Duration => "null".to_string(),
2566 _ => "null".to_string(),
2567 }
2568 }
2569 };
2570
2571 writeln!(body, " private {} {} = {};", field_type, field_name, default_value).ok();
2572 }
2573
2574 writeln!(body).ok();
2575
2576 for field in &typ.fields {
2578 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
2580 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
2581 {
2582 continue;
2583 }
2584
2585 let field_name = safe_java_field_name(&field.name);
2586 let field_name_pascal = to_class_name(&field.name);
2587 let field_type = if field.optional {
2588 format!("Optional<{}>", java_boxed_type(&field.ty))
2589 } else if matches!(field.ty, TypeRef::Duration) {
2590 java_boxed_type(&field.ty).to_string()
2591 } else {
2592 java_type(&field.ty).to_string()
2593 };
2594
2595 writeln!(
2596 body,
2597 " public {}Builder with{}({} value) {{",
2598 typ.name, field_name_pascal, field_type
2599 )
2600 .ok();
2601 writeln!(body, " this.{} = value;", field_name).ok();
2602 writeln!(body, " return this;").ok();
2603 writeln!(body, " }}").ok();
2604 writeln!(body).ok();
2605 }
2606
2607 writeln!(body, " public {} build() {{", typ.name).ok();
2609 writeln!(body, " return new {}(", typ.name).ok();
2610 let non_tuple_fields: Vec<_> = typ
2611 .fields
2612 .iter()
2613 .filter(|f| {
2614 !(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
2616 || f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
2617 })
2618 .collect();
2619 for (i, field) in non_tuple_fields.iter().enumerate() {
2620 let field_name = safe_java_field_name(&field.name);
2621 let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
2622 writeln!(body, " {}{}", field_name, comma).ok();
2623 }
2624 writeln!(body, " );").ok();
2625 writeln!(body, " }}").ok();
2626
2627 writeln!(body, "}}").ok();
2628
2629 let mut out = String::with_capacity(body.len() + 512);
2631
2632 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2633 writeln!(out, "package {};", package).ok();
2634 writeln!(out).ok();
2635
2636 if body.contains("List<") {
2637 writeln!(out, "import java.util.List;").ok();
2638 }
2639 if body.contains("Map<") {
2640 writeln!(out, "import java.util.Map;").ok();
2641 }
2642 if body.contains("Optional<") {
2643 writeln!(out, "import java.util.Optional;").ok();
2644 }
2645
2646 writeln!(out).ok();
2647 out.push_str(&body);
2648
2649 out
2650}