use std::fmt::Write;
use zerodds_idl::ast::{
ConstrTypeDecl, Definition, Specification, StructDcl, StructDef, TypeDecl, TypeSpec, UnionDcl,
UnionDef,
};
use crate::JavaGenOptions;
use crate::emitter::JavaFile;
use crate::error::JavaGenError;
pub(crate) fn emit_corba_traits_files(
spec: &Specification,
opts: &JavaGenOptions,
) -> Result<Vec<JavaFile>, JavaGenError> {
let pkg = sanitize_package(&opts.root_package);
let mut emitted = Vec::new();
collect_top_level(&spec.definitions, &pkg, &mut Vec::new(), &mut emitted);
let mut files = Vec::new();
for entry in emitted {
files.push(emit_one(&entry)?);
}
Ok(files)
}
#[derive(Debug)]
struct TraitEntry {
package_path: String,
class_name: String,
full_name: String,
variable_size: bool,
}
fn sanitize_package(p: &str) -> String {
p.split('.')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(".")
}
fn lower(s: &str) -> String {
s.to_lowercase()
}
fn collect_top_level(
defs: &[Definition],
pkg: &str,
scope: &mut Vec<String>,
out: &mut Vec<TraitEntry>,
) {
for d in defs {
match d {
Definition::Module(m) => {
let inner_pkg = if pkg.is_empty() {
lower(&m.name.text)
} else {
format!("{}.{}", pkg, lower(&m.name.text))
};
scope.push(m.name.text.clone());
collect_top_level(&m.definitions, &inner_pkg, scope, out);
scope.pop();
}
Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
out.push(TraitEntry {
package_path: pkg.to_string(),
class_name: s.name.text.clone(),
full_name: full_name(scope, &s.name.text),
variable_size: struct_is_variable(s),
});
}
Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Union(UnionDcl::Def(u)))) => {
out.push(TraitEntry {
package_path: pkg.to_string(),
class_name: u.name.text.clone(),
full_name: full_name(scope, &u.name.text),
variable_size: union_is_variable(u),
});
}
Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Enum(e))) => {
out.push(TraitEntry {
package_path: pkg.to_string(),
class_name: e.name.text.clone(),
full_name: full_name(scope, &e.name.text),
variable_size: false,
});
}
_ => {}
}
}
}
fn full_name(scope: &[String], name: &str) -> String {
if scope.is_empty() {
name.to_string()
} else {
format!("{}::{name}", scope.join("::"))
}
}
fn struct_is_variable(s: &StructDef) -> bool {
s.members.iter().any(|m| type_is_variable(&m.type_spec))
}
fn union_is_variable(u: &UnionDef) -> bool {
u.cases
.iter()
.any(|c| type_is_variable(&c.element.type_spec))
}
fn type_is_variable(ts: &TypeSpec) -> bool {
match ts {
TypeSpec::Primitive(_) => false,
TypeSpec::Fixed(_) => false,
TypeSpec::String(_) => true,
TypeSpec::Sequence(_) => true,
TypeSpec::Map(_) => true,
TypeSpec::Scoped(_) => true,
TypeSpec::Any => true,
}
}
fn emit_one(entry: &TraitEntry) -> Result<JavaFile, JavaGenError> {
let class_name = format!("{}CorbaTraits", entry.class_name);
let mut src = String::new();
writeln!(&mut src, "// Generated by zerodds idl-java. Do not edit.").map_err(fmt_err)?;
writeln!(&mut src, "// Annex A.1 — CORBA-spezifische Type-Mappings.").map_err(fmt_err)?;
if !entry.package_path.is_empty() {
writeln!(&mut src, "package {};", entry.package_path).map_err(fmt_err)?;
writeln!(&mut src).map_err(fmt_err)?;
}
writeln!(&mut src, "/**").map_err(fmt_err)?;
writeln!(
&mut src,
" * CORBA-Annex-A.1 Trait-Konstanten fuer Type {}.",
entry.full_name
)
.map_err(fmt_err)?;
writeln!(
&mut src,
" * Aequivalent zum C++-`CORBA::traits<T>` Template."
)
.map_err(fmt_err)?;
writeln!(&mut src, " */").map_err(fmt_err)?;
writeln!(&mut src, "public final class {class_name} {{").map_err(fmt_err)?;
writeln!(
&mut src,
" public static final String FULL_NAME = \"{}\";",
entry.full_name
)
.map_err(fmt_err)?;
writeln!(
&mut src,
" public static final boolean IS_VARIABLE_SIZE = {};",
if entry.variable_size { "true" } else { "false" }
)
.map_err(fmt_err)?;
writeln!(
&mut src,
" public static final boolean IS_LOCAL = false;"
)
.map_err(fmt_err)?;
writeln!(&mut src).map_err(fmt_err)?;
writeln!(&mut src, " private {class_name}() {{}}").map_err(fmt_err)?;
writeln!(&mut src, "}}").map_err(fmt_err)?;
Ok(JavaFile {
package_path: entry.package_path.clone(),
class_name,
source: src,
})
}
fn fmt_err(_: std::fmt::Error) -> JavaGenError {
JavaGenError::Internal("string formatting failed".into())
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use crate::{JavaGenOptions, generate_java_files_with_corba_traits};
use zerodds_idl::config::ParserConfig;
fn render(src: &str) -> Vec<crate::emitter::JavaFile> {
let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse");
let opts = JavaGenOptions::default();
generate_java_files_with_corba_traits(&ast, &opts).expect("gen")
}
fn find_traits<'a>(
files: &'a [crate::emitter::JavaFile],
class: &str,
) -> Option<&'a crate::emitter::JavaFile> {
files.iter().find(|f| f.class_name == class)
}
#[test]
fn empty_source_emits_no_traits_files() {
let files = render("");
assert!(files.iter().all(|f| !f.class_name.contains("CorbaTraits")));
}
#[test]
fn struct_emits_companion_traits_file() {
let files = render("struct S { long x; };");
let f = find_traits(&files, "SCorbaTraits").expect("traits file");
assert!(f.source.contains("public final class SCorbaTraits"));
assert!(
f.source
.contains("public static final String FULL_NAME = \"S\";")
);
assert!(
f.source
.contains("public static final boolean IS_VARIABLE_SIZE = false;")
);
assert!(
f.source
.contains("public static final boolean IS_LOCAL = false;")
);
}
#[test]
fn variable_size_struct_marked_correctly() {
let files = render("struct S { string name; };");
let f = find_traits(&files, "SCorbaTraits").expect("traits file");
assert!(f.source.contains("IS_VARIABLE_SIZE = true;"));
}
#[test]
fn enum_emits_traits_file() {
let files = render("enum Color { RED, GREEN, BLUE };");
let f = find_traits(&files, "ColorCorbaTraits").expect("traits");
assert!(f.source.contains("FULL_NAME = \"Color\";"));
assert!(f.source.contains("IS_VARIABLE_SIZE = false;"));
}
#[test]
fn nested_module_yields_correct_package_path() {
let files = render("module A { module B { struct S { long x; }; }; };");
let f = find_traits(&files, "SCorbaTraits").expect("traits");
assert_eq!(f.package_path, "a.b");
assert!(f.source.contains("package a.b;"));
assert!(f.source.contains("FULL_NAME = \"A::B::S\";"));
}
#[test]
fn union_with_string_branch_is_variable() {
let files = render("union U switch (long) { case 1: string s; case 2: long n; };");
let f = find_traits(&files, "UCorbaTraits").expect("traits");
assert!(f.source.contains("IS_VARIABLE_SIZE = true;"));
}
#[test]
fn sequence_member_marks_struct_variable() {
let files = render("struct S { sequence<long> data; };");
let f = find_traits(&files, "SCorbaTraits").expect("traits");
assert!(f.source.contains("IS_VARIABLE_SIZE = true;"));
}
#[test]
fn private_constructor_prevents_instantiation() {
let files = render("struct S { long x; };");
let f = find_traits(&files, "SCorbaTraits").expect("traits");
assert!(f.source.contains("private SCorbaTraits() {}"));
}
#[test]
fn no_local_default_set_to_false() {
let files = render("struct S { long x; };");
let f = find_traits(&files, "SCorbaTraits").expect("traits");
assert!(f.source.contains("IS_LOCAL = false;"));
}
}