use alef_codegen::naming::to_java_name;
use alef_core::hash::{self, CommentStyle};
use alef_core::ir::{PrimitiveType, TypeRef};
use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase};
use std::collections::HashSet;
const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
"wait",
"notify",
"notifyAll",
"getClass",
"hashCode",
"equals",
"toString",
"clone",
"finalize",
];
pub(crate) fn escape_javadoc_line(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '`' {
let mut code = String::new();
for c in chars.by_ref() {
if c == '`' {
break;
}
code.push(c);
}
result.push_str("{@code ");
for code_ch in code.chars() {
match code_ch {
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'&' => result.push_str("&"),
other => result.push(other),
}
}
result.push('}');
} else if ch == '<' {
result.push_str("<");
} else if ch == '>' {
result.push_str(">");
} else if ch == '&' {
result.push_str("&");
} else if ch == '*' && chars.peek() == Some(&'/') {
chars.next();
result.push_str("* /");
} else if ch == '@' {
result.push_str("{@literal @}");
} else {
result.push(ch);
}
}
result
}
pub(crate) fn is_tuple_field_name(name: &str) -> bool {
let stripped = name.trim_start_matches('_');
!stripped.is_empty() && stripped.chars().all(|c| c.is_ascii_digit())
}
pub(crate) fn safe_java_field_name(name: &str) -> String {
let java_name = to_java_name(name);
if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
format!("{}Value", java_name)
} else {
java_name
}
}
pub(crate) fn is_bridge_param_java(
param: &alef_core::ir::ParamDef,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) -> bool {
if bridge_param_names.contains(param.name.as_str()) {
return true;
}
let type_name = match ¶m.ty {
TypeRef::Named(n) => Some(n.as_str()),
TypeRef::Optional(inner) => {
if let TypeRef::Named(n) = inner.as_ref() {
Some(n.as_str())
} else {
None
}
}
_ => None,
};
type_name.is_some_and(|n| bridge_type_aliases.contains(n))
}
pub(crate) fn gen_infrastructure_exception_class(
package: &str,
main_class: &str,
class_name: &str,
code: i32,
doc: &str,
) -> String {
let header = hash::header(CommentStyle::DoubleSlash);
crate::template_env::render(
"infrastructure_exception.jinja",
minijinja::context! {
header => header,
package => package,
class_name => class_name,
main_class => main_class,
code => code,
doc => doc,
},
)
}
pub(crate) fn gen_exception_class(package: &str, class_name: &str) -> String {
let header = hash::header(CommentStyle::DoubleSlash);
crate::template_env::render(
"exception_class.jinja",
minijinja::context! {
header => header,
package => package,
class_name => class_name,
},
)
}
fn transform_rustdoc_for_java(doc: &str) -> String {
let sections = alef_codegen::doc_emission::parse_rustdoc_sections(doc);
let rendered = alef_codegen::doc_emission::render_javadoc_sections(§ions, "KreuzbergRsException");
if rendered.trim().is_empty() {
return doc.replace("[`", "").replace("`]", "").trim().to_string();
}
rendered.replace("[`", "").replace("`]", "")
}
pub(crate) fn emit_javadoc(out: &mut String, doc: &str, indent: &str) {
if doc.is_empty() {
return;
}
let transformed = transform_rustdoc_for_java(doc);
if transformed.is_empty() {
return;
}
out.push_str(indent);
out.push_str("/**\n");
let lines: Vec<String> = transformed
.lines()
.map(|line| escape_javadoc_line(line).trim_end().to_string())
.collect();
out.push_str(&crate::template_env::render(
"javadoc_lines.jinja",
minijinja::context! {
indent => indent,
lines => lines,
},
));
out.push_str(indent);
out.push_str(" */\n");
}
pub(crate) const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
pub(crate) fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
match rename_all {
Some("snake_case") => alef_codegen::naming::pascal_to_snake(name),
Some("camelCase") => name.to_lower_camel_case(),
Some("PascalCase") => name.to_pascal_case(),
Some("SCREAMING_SNAKE_CASE") => alef_codegen::naming::pascal_to_screaming_snake(name),
Some("kebab-case") => name.to_kebab_case(),
Some("SCREAMING-KEBAB-CASE") => name.to_kebab_case().to_uppercase(),
Some("lowercase") => name.to_lowercase(),
Some("UPPERCASE") => name.to_uppercase(),
_ => name.to_lowercase(),
}
}
pub(crate) fn format_optional_value(ty: &TypeRef, default: &str) -> String {
if default.contains("Optional.") {
return default.to_string();
}
let inner_ty = match ty {
TypeRef::Optional(inner) => inner.as_ref(),
other => other,
};
let formatted_value = match inner_ty {
TypeRef::Primitive(p) => match p {
PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
if default.ends_with('L') || default.ends_with('l') {
default.to_string()
} else if default.parse::<i64>().is_ok() {
format!("{}L", default)
} else {
default.to_string()
}
}
PrimitiveType::F32 => {
if default.ends_with('f') || default.ends_with('F') {
default.to_string()
} else if default.parse::<f32>().is_ok() {
format!("{}f", default)
} else {
default.to_string()
}
}
PrimitiveType::F64 => {
default.to_string()
}
_ => default.to_string(),
},
_ => default.to_string(),
};
format!("Optional.of({})", formatted_value)
}