alef_backend_java/gen_bindings/
mod.rs1use ahash::AHashSet;
2use alef_codegen::naming::to_class_name;
3use alef_core::backend::{Backend, BuildConfig, BuildDependency, 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 trait_bridge;
15mod types;
16
17use facade::gen_facade_class;
18use ffi_class::gen_main_class;
19use helpers::gen_exception_class;
20use native_lib::gen_native_lib;
21use types::{gen_builder_class, gen_enum_class, gen_opaque_handle_class, gen_record_type};
22
23pub struct JavaBackend;
24
25impl JavaBackend {
26 fn resolve_main_class(api: &ApiSurface) -> String {
32 let base = to_class_name(&api.crate_name.replace('-', "_"));
33 if base.ends_with("Rs") {
34 base
35 } else {
36 format!("{}Rs", base)
37 }
38 }
39}
40
41impl Backend for JavaBackend {
42 fn name(&self) -> &str {
43 "java"
44 }
45
46 fn language(&self) -> Language {
47 Language::Java
48 }
49
50 fn capabilities(&self) -> Capabilities {
51 Capabilities {
52 supports_async: true,
53 supports_classes: true,
54 supports_enums: true,
55 supports_option: true,
56 supports_result: true,
57 ..Capabilities::default()
58 }
59 }
60
61 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
62 let package = config.java_package();
63 let prefix = config.ffi_prefix();
64 let main_class = Self::resolve_main_class(api);
65 let package_path = package.replace('.', "/");
66
67 let output_dir = resolve_output_dir(
68 config.output.java.as_ref(),
69 &config.crate_config.name,
70 "packages/java/src/main/java/",
71 );
72
73 let base_path = if output_dir.ends_with(&package_path) || output_dir.ends_with(&format!("{}/", package_path)) {
76 PathBuf::from(&output_dir)
77 } else {
78 PathBuf::from(&output_dir).join(&package_path)
79 };
80
81 let bridge_param_names: HashSet<String> = config
84 .trait_bridges
85 .iter()
86 .filter_map(|b| b.param_name.clone())
87 .collect();
88 let bridge_type_aliases: HashSet<String> = config
89 .trait_bridges
90 .iter()
91 .filter_map(|b| b.type_alias.clone())
92 .collect();
93 let has_visitor_pattern = config.ffi.as_ref().map(|f| f.visitor_callbacks).unwrap_or(false);
95
96 let mut files = Vec::new();
97
98 let description = config
100 .scaffold
101 .as_ref()
102 .and_then(|s| s.description.as_deref())
103 .unwrap_or("High-performance HTML to Markdown converter.");
104 files.push(GeneratedFile {
105 path: base_path.join("package-info.java"),
106 content: format!(
107 "/**\n * {description}\n */\npackage {package};\n",
108 description = description,
109 package = package,
110 ),
111 generated_header: true,
112 });
113
114 files.push(GeneratedFile {
116 path: base_path.join("NativeLib.java"),
117 content: gen_native_lib(api, config, &package, &prefix, has_visitor_pattern),
118 generated_header: true,
119 });
120
121 files.push(GeneratedFile {
123 path: base_path.join(format!("{}.java", main_class)),
124 content: gen_main_class(
125 api,
126 config,
127 &package,
128 &main_class,
129 &prefix,
130 &bridge_param_names,
131 &bridge_type_aliases,
132 has_visitor_pattern,
133 ),
134 generated_header: true,
135 });
136
137 files.push(GeneratedFile {
139 path: base_path.join(format!("{}Exception.java", main_class)),
140 content: gen_exception_class(&package, &main_class),
141 generated_header: true,
142 });
143
144 let complex_enums: AHashSet<String> = api
148 .enums
149 .iter()
150 .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
151 .map(|e| e.name.clone())
152 .collect();
153
154 let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
156
157 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
163 let is_unit_serde = !typ.is_opaque && typ.fields.is_empty() && typ.has_serde;
164 if !typ.is_opaque && (!typ.fields.is_empty() || is_unit_serde) {
165 if has_visitor_pattern && (typ.name == "NodeContext" || typ.name == "VisitResult") {
167 continue;
168 }
169 files.push(GeneratedFile {
170 path: base_path.join(format!("{}.java", typ.name)),
171 content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
172 generated_header: true,
173 });
174 if typ.has_default {
176 files.push(GeneratedFile {
177 path: base_path.join(format!("{}Builder.java", typ.name)),
178 content: gen_builder_class(&package, typ),
179 generated_header: true,
180 });
181 }
182 }
183 }
184
185 let builder_class_names: AHashSet<String> = api
188 .types
189 .iter()
190 .filter(|t| !t.is_opaque && (!t.fields.is_empty() || (t.has_serde && t.fields.is_empty())) && t.has_default)
191 .map(|t| format!("{}Builder", t.name))
192 .collect();
193
194 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
196 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
197 files.push(GeneratedFile {
198 path: base_path.join(format!("{}.java", typ.name)),
199 content: gen_opaque_handle_class(&package, typ, &prefix),
200 generated_header: true,
201 });
202 }
203 }
204
205 for enum_def in &api.enums {
207 if has_visitor_pattern && enum_def.name == "VisitResult" {
209 continue;
210 }
211 files.push(GeneratedFile {
212 path: base_path.join(format!("{}.java", enum_def.name)),
213 content: gen_enum_class(&package, enum_def),
214 generated_header: true,
215 });
216 }
217
218 for error in &api.errors {
220 for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
221 files.push(GeneratedFile {
222 path: base_path.join(format!("{}.java", class_name)),
223 content,
224 generated_header: true,
225 });
226 }
227 }
228
229 if has_visitor_pattern {
231 for (filename, content) in crate::gen_visitor::gen_visitor_files(&package, &main_class) {
232 files.push(GeneratedFile {
233 path: base_path.join(filename),
234 content,
235 generated_header: false, });
237 }
238 }
239
240 for bridge_cfg in &config.trait_bridges {
244 if bridge_cfg.exclude_languages.contains(&Language::Java.to_string()) {
245 continue;
246 }
247
248 if let Some(trait_def) = api.types.iter().find(|t| t.name == bridge_cfg.trait_name && t.is_trait) {
249 let has_super_trait = bridge_cfg.super_trait.is_some();
250 let trait_bridge::BridgeFiles {
251 interface_content,
252 bridge_content,
253 } = trait_bridge::gen_trait_bridge_files(trait_def, &prefix, &package, has_super_trait);
254
255 files.push(GeneratedFile {
256 path: base_path.join(format!("I{}.java", trait_def.name)),
257 content: interface_content,
258 generated_header: true,
259 });
260 files.push(GeneratedFile {
261 path: base_path.join(format!("{}Bridge.java", trait_def.name)),
262 content: bridge_content,
263 generated_header: true,
264 });
265 }
266 }
267
268 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
270
271 Ok(files)
272 }
273
274 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
275 let package = config.java_package();
276 let prefix = config.ffi_prefix();
277 let main_class = Self::resolve_main_class(api);
278 let package_path = package.replace('.', "/");
279
280 let output_dir = resolve_output_dir(
281 config.output.java.as_ref(),
282 &config.crate_config.name,
283 "packages/java/src/main/java/",
284 );
285
286 let base_path = if output_dir.ends_with(&package_path) || output_dir.ends_with(&format!("{}/", package_path)) {
289 PathBuf::from(&output_dir)
290 } else {
291 PathBuf::from(&output_dir).join(&package_path)
292 };
293
294 let bridge_param_names: HashSet<String> = config
296 .trait_bridges
297 .iter()
298 .filter_map(|b| b.param_name.clone())
299 .collect();
300 let bridge_type_aliases: HashSet<String> = config
301 .trait_bridges
302 .iter()
303 .filter_map(|b| b.type_alias.clone())
304 .collect();
305
306 let public_class = main_class.trim_end_matches("Rs").to_string();
309 let facade_content = gen_facade_class(
310 api,
311 &package,
312 &public_class,
313 &main_class,
314 &prefix,
315 &bridge_param_names,
316 &bridge_type_aliases,
317 );
318
319 Ok(vec![GeneratedFile {
320 path: base_path.join(format!("{}.java", public_class)),
321 content: facade_content,
322 generated_header: true,
323 }])
324 }
325
326 fn build_config(&self) -> Option<BuildConfig> {
327 Some(BuildConfig {
328 tool: "mvn",
329 crate_suffix: "",
330 build_dep: BuildDependency::Ffi,
331 post_build: vec![],
332 })
333 }
334}