use ahash::AHashSet;
use alef_codegen::naming::to_class_name;
use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
use alef_core::config::{AlefConfig, Language, resolve_output_dir};
use alef_core::ir::ApiSurface;
use std::collections::HashSet;
use std::path::PathBuf;
mod facade;
mod ffi_class;
mod helpers;
mod marshal;
mod native_lib;
mod types;
use facade::gen_facade_class;
use ffi_class::gen_main_class;
use helpers::gen_exception_class;
use native_lib::gen_native_lib;
use types::{gen_builder_class, gen_enum_class, gen_opaque_handle_class, gen_record_type};
pub struct JavaBackend;
impl JavaBackend {
fn resolve_main_class(api: &ApiSurface) -> String {
let base = to_class_name(&api.crate_name.replace('-', "_"));
if base.ends_with("Rs") {
base
} else {
format!("{}Rs", base)
}
}
}
impl Backend for JavaBackend {
fn name(&self) -> &str {
"java"
}
fn language(&self) -> Language {
Language::Java
}
fn capabilities(&self) -> Capabilities {
Capabilities {
supports_async: true,
supports_classes: true,
supports_enums: true,
supports_option: true,
supports_result: true,
..Capabilities::default()
}
}
fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let package = config.java_package();
let prefix = config.ffi_prefix();
let main_class = Self::resolve_main_class(api);
let package_path = package.replace('.', "/");
let output_dir = resolve_output_dir(
config.output.java.as_ref(),
&config.crate_config.name,
"packages/java/src/main/java/",
);
let base_path = PathBuf::from(&output_dir).join(&package_path);
let bridge_param_names: HashSet<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.param_name.clone())
.collect();
let bridge_type_aliases: HashSet<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.type_alias.clone())
.collect();
let has_visitor_bridge = !config.trait_bridges.is_empty();
let mut files = Vec::new();
let description = config
.scaffold
.as_ref()
.and_then(|s| s.description.as_deref())
.unwrap_or("High-performance HTML to Markdown converter.");
files.push(GeneratedFile {
path: base_path.join("package-info.java"),
content: format!(
"/**\n * {description}\n */\npackage {package};\n",
description = description,
package = package,
),
generated_header: true,
});
files.push(GeneratedFile {
path: base_path.join("NativeLib.java"),
content: gen_native_lib(api, config, &package, &prefix, has_visitor_bridge),
generated_header: true,
});
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", main_class)),
content: gen_main_class(
api,
config,
&package,
&main_class,
&prefix,
&bridge_param_names,
&bridge_type_aliases,
has_visitor_bridge,
),
generated_header: true,
});
files.push(GeneratedFile {
path: base_path.join(format!("{}Exception.java", main_class)),
content: gen_exception_class(&package, &main_class),
generated_header: true,
});
let complex_enums: AHashSet<String> = api
.enums
.iter()
.filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
.map(|e| e.name.clone())
.collect();
let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if !typ.is_opaque && !typ.fields.is_empty() {
if has_visitor_bridge && (typ.name == "NodeContext" || typ.name == "VisitResult") {
continue;
}
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", typ.name)),
content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
generated_header: true,
});
if typ.has_default {
files.push(GeneratedFile {
path: base_path.join(format!("{}Builder.java", typ.name)),
content: gen_builder_class(&package, typ),
generated_header: true,
});
}
}
}
let builder_class_names: AHashSet<String> = api
.types
.iter()
.filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
.map(|t| format!("{}Builder", t.name))
.collect();
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if typ.is_opaque && !builder_class_names.contains(&typ.name) {
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", typ.name)),
content: gen_opaque_handle_class(&package, typ, &prefix),
generated_header: true,
});
}
}
for enum_def in &api.enums {
if has_visitor_bridge && enum_def.name == "VisitResult" {
continue;
}
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", enum_def.name)),
content: gen_enum_class(&package, enum_def),
generated_header: true,
});
}
for error in &api.errors {
for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", class_name)),
content,
generated_header: true,
});
}
}
if has_visitor_bridge {
for (filename, content) in crate::gen_visitor::gen_visitor_files(&package, &main_class) {
files.push(GeneratedFile {
path: base_path.join(filename),
content,
generated_header: false, });
}
}
let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
Ok(files)
}
fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let package = config.java_package();
let prefix = config.ffi_prefix();
let main_class = Self::resolve_main_class(api);
let package_path = package.replace('.', "/");
let output_dir = resolve_output_dir(
config.output.java.as_ref(),
&config.crate_config.name,
"packages/java/src/main/java/",
);
let base_path = PathBuf::from(&output_dir).join(&package_path);
let bridge_param_names: HashSet<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.param_name.clone())
.collect();
let bridge_type_aliases: HashSet<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.type_alias.clone())
.collect();
let has_visitor_bridge = !config.trait_bridges.is_empty();
let public_class = main_class.trim_end_matches("Rs").to_string();
let facade_content = gen_facade_class(
api,
&package,
&public_class,
&main_class,
&prefix,
&bridge_param_names,
&bridge_type_aliases,
has_visitor_bridge,
);
Ok(vec![GeneratedFile {
path: base_path.join(format!("{}.java", public_class)),
content: facade_content,
generated_header: true,
}])
}
fn build_config(&self) -> Option<BuildConfig> {
Some(BuildConfig {
tool: "mvn",
crate_suffix: "",
depends_on_ffi: true,
post_build: vec![],
})
}
}