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