alef_backend_java/gen_bindings/
mod.rs1use ahash::AHashSet;
2use alef_codegen::naming::to_class_name;
3use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
4use alef_core::config::{AlefConfig, Language, resolve_output_dir};
5use alef_core::ir::ApiSurface;
6use std::collections::HashSet;
7use std::path::PathBuf;
8
9mod facade;
10mod ffi_class;
11mod helpers;
12mod marshal;
13mod native_lib;
14mod types;
15
16use facade::gen_facade_class;
17use ffi_class::gen_main_class;
18use helpers::gen_exception_class;
19use native_lib::gen_native_lib;
20use types::{gen_builder_class, gen_enum_class, gen_opaque_handle_class, gen_record_type};
21
22pub struct JavaBackend;
23
24impl JavaBackend {
25 fn resolve_main_class(api: &ApiSurface) -> String {
31 let base = to_class_name(&api.crate_name.replace('-', "_"));
32 if base.ends_with("Rs") {
33 base
34 } else {
35 format!("{}Rs", base)
36 }
37 }
38}
39
40impl Backend for JavaBackend {
41 fn name(&self) -> &str {
42 "java"
43 }
44
45 fn language(&self) -> Language {
46 Language::Java
47 }
48
49 fn capabilities(&self) -> Capabilities {
50 Capabilities {
51 supports_async: true,
52 supports_classes: true,
53 supports_enums: true,
54 supports_option: true,
55 supports_result: true,
56 ..Capabilities::default()
57 }
58 }
59
60 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
61 let package = config.java_package();
62 let prefix = config.ffi_prefix();
63 let main_class = Self::resolve_main_class(api);
64 let package_path = package.replace('.', "/");
65
66 let output_dir = resolve_output_dir(
67 config.output.java.as_ref(),
68 &config.crate_config.name,
69 "packages/java/src/main/java/",
70 );
71
72 let base_path = PathBuf::from(&output_dir).join(&package_path);
73
74 let bridge_param_names: HashSet<String> = config
77 .trait_bridges
78 .iter()
79 .filter_map(|b| b.param_name.clone())
80 .collect();
81 let bridge_type_aliases: HashSet<String> = config
82 .trait_bridges
83 .iter()
84 .filter_map(|b| b.type_alias.clone())
85 .collect();
86 let has_visitor_bridge = !config.trait_bridges.is_empty();
87
88 let mut files = Vec::new();
89
90 let description = config
92 .scaffold
93 .as_ref()
94 .and_then(|s| s.description.as_deref())
95 .unwrap_or("High-performance HTML to Markdown converter.");
96 files.push(GeneratedFile {
97 path: base_path.join("package-info.java"),
98 content: format!(
99 "/**\n * {description}\n */\npackage {package};\n",
100 description = description,
101 package = package,
102 ),
103 generated_header: true,
104 });
105
106 files.push(GeneratedFile {
108 path: base_path.join("NativeLib.java"),
109 content: gen_native_lib(api, config, &package, &prefix, has_visitor_bridge),
110 generated_header: true,
111 });
112
113 files.push(GeneratedFile {
115 path: base_path.join(format!("{}.java", main_class)),
116 content: gen_main_class(
117 api,
118 config,
119 &package,
120 &main_class,
121 &prefix,
122 &bridge_param_names,
123 &bridge_type_aliases,
124 has_visitor_bridge,
125 ),
126 generated_header: true,
127 });
128
129 files.push(GeneratedFile {
131 path: base_path.join(format!("{}Exception.java", main_class)),
132 content: gen_exception_class(&package, &main_class),
133 generated_header: true,
134 });
135
136 let complex_enums: AHashSet<String> = api
140 .enums
141 .iter()
142 .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
143 .map(|e| e.name.clone())
144 .collect();
145
146 let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
148
149 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
151 if !typ.is_opaque && !typ.fields.is_empty() {
152 if has_visitor_bridge && (typ.name == "NodeContext" || typ.name == "VisitResult") {
154 continue;
155 }
156 files.push(GeneratedFile {
157 path: base_path.join(format!("{}.java", typ.name)),
158 content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
159 generated_header: true,
160 });
161 if typ.has_default {
163 files.push(GeneratedFile {
164 path: base_path.join(format!("{}Builder.java", typ.name)),
165 content: gen_builder_class(&package, typ),
166 generated_header: true,
167 });
168 }
169 }
170 }
171
172 let builder_class_names: AHashSet<String> = api
175 .types
176 .iter()
177 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
178 .map(|t| format!("{}Builder", t.name))
179 .collect();
180
181 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
183 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
184 files.push(GeneratedFile {
185 path: base_path.join(format!("{}.java", typ.name)),
186 content: gen_opaque_handle_class(&package, typ, &prefix),
187 generated_header: true,
188 });
189 }
190 }
191
192 for enum_def in &api.enums {
194 if has_visitor_bridge && enum_def.name == "VisitResult" {
196 continue;
197 }
198 files.push(GeneratedFile {
199 path: base_path.join(format!("{}.java", enum_def.name)),
200 content: gen_enum_class(&package, enum_def),
201 generated_header: true,
202 });
203 }
204
205 for error in &api.errors {
207 for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
208 files.push(GeneratedFile {
209 path: base_path.join(format!("{}.java", class_name)),
210 content,
211 generated_header: true,
212 });
213 }
214 }
215
216 if has_visitor_bridge {
218 for (filename, content) in crate::gen_visitor::gen_visitor_files(&package, &main_class) {
219 files.push(GeneratedFile {
220 path: base_path.join(filename),
221 content,
222 generated_header: false, });
224 }
225 }
226
227 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
229
230 Ok(files)
231 }
232
233 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
234 let package = config.java_package();
235 let prefix = config.ffi_prefix();
236 let main_class = Self::resolve_main_class(api);
237 let package_path = package.replace('.', "/");
238
239 let output_dir = resolve_output_dir(
240 config.output.java.as_ref(),
241 &config.crate_config.name,
242 "packages/java/src/main/java/",
243 );
244
245 let base_path = PathBuf::from(&output_dir).join(&package_path);
246
247 let bridge_param_names: HashSet<String> = config
249 .trait_bridges
250 .iter()
251 .filter_map(|b| b.param_name.clone())
252 .collect();
253 let bridge_type_aliases: HashSet<String> = config
254 .trait_bridges
255 .iter()
256 .filter_map(|b| b.type_alias.clone())
257 .collect();
258 let has_visitor_bridge = !config.trait_bridges.is_empty();
259
260 let public_class = main_class.trim_end_matches("Rs").to_string();
263 let facade_content = gen_facade_class(
264 api,
265 &package,
266 &public_class,
267 &main_class,
268 &prefix,
269 &bridge_param_names,
270 &bridge_type_aliases,
271 has_visitor_bridge,
272 );
273
274 Ok(vec![GeneratedFile {
275 path: base_path.join(format!("{}.java", public_class)),
276 content: facade_content,
277 generated_header: true,
278 }])
279 }
280
281 fn build_config(&self) -> Option<BuildConfig> {
282 Some(BuildConfig {
283 tool: "mvn",
284 crate_suffix: "",
285 depends_on_ffi: true,
286 post_build: vec![],
287 })
288 }
289}