Skip to main content

zerodds_dlrl_codegen/
java.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Java-Codegen — DDS 1.4 §B.4 Annex Java-PSM.
5
6use alloc::format;
7use alloc::string::String;
8
9use crate::DlrlTypeInfo;
10
11/// Erzeugt einen Java-Class-Source fuer einen DLRL-Object-Type.
12/// Spec §B.6 Java-PSM: extends `ObjectRoot`.
13#[allow(clippy::format_collect)]
14#[must_use]
15pub fn generate_java_object(info: &DlrlTypeInfo) -> String {
16    let cls = simple_name(&info.name);
17    let pkg = package(&info.name);
18    let key_lines: String = info
19        .keys
20        .iter()
21        .map(|k| format!("    @DlrlKey\n    public long {k};\n"))
22        .collect();
23    let rel_lines: String = info
24        .relations
25        .iter()
26        .map(|(rel, target)| {
27            let t = simple_name(target);
28            format!(
29                "    @DlrlRelation(target = {t}.class)\n    public RefList<{t}> {rel} = new RefList<>();\n"
30            )
31        })
32        .collect();
33    let header = if pkg.is_empty() {
34        String::new()
35    } else {
36        format!("package {pkg};\n\n")
37    };
38    format!(
39        "{header}// Generated DLRL Object — Spec §B.6 Java-PSM\n\
40@DlrlObject\npublic class {cls} extends ObjectRoot {{\n{key_lines}{rel_lines}}}\n"
41    )
42}
43
44/// Erzeugt einen `ObjectListener<T>`-Interface-Stub. Spec §B.6.7.
45#[must_use]
46pub fn generate_java_object_listener(info: &DlrlTypeInfo) -> String {
47    let cls = simple_name(&info.name);
48    let pkg = package(&info.name);
49    let header = if pkg.is_empty() {
50        String::new()
51    } else {
52        format!("package {pkg};\n\n")
53    };
54    format!(
55        "{header}// Generated DLRL ObjectListener — Spec §B.6.7\n\
56public interface {cls}Listener extends ObjectListener<{cls}> {{\n\
57    void on{cls}Created({cls} obj);\n\
58    void on{cls}Modified({cls} obj);\n\
59    void on{cls}Deleted({cls} obj);\n\
60}}\n"
61    )
62}
63
64fn simple_name(scoped: &str) -> &str {
65    scoped.rsplit("::").next().unwrap_or(scoped)
66}
67
68fn package(scoped: &str) -> String {
69    let parts: alloc::vec::Vec<&str> = scoped.split("::").collect();
70    if parts.len() <= 1 {
71        return String::new();
72    }
73    parts[..parts.len() - 1].join(".")
74}
75
76#[cfg(test)]
77#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
78mod tests {
79    use super::*;
80
81    fn trade_info() -> DlrlTypeInfo {
82        DlrlTypeInfo {
83            name: "demo::Trade".into(),
84            keys: alloc::vec!["symbol".into()],
85            relations: alloc::vec![("quotes".into(), "demo::Quote".into())],
86        }
87    }
88
89    #[test]
90    fn java_object_emits_package_and_annotations() {
91        let s = generate_java_object(&trade_info());
92        assert!(s.contains("package demo;"));
93        assert!(s.contains("@DlrlObject"));
94        assert!(s.contains("class Trade extends ObjectRoot"));
95        assert!(s.contains("@DlrlKey"));
96        assert!(s.contains("public long symbol;"));
97        assert!(s.contains("@DlrlRelation(target = Quote.class)"));
98        assert!(s.contains("RefList<Quote> quotes"));
99    }
100
101    #[test]
102    fn java_object_no_package_for_unscoped() {
103        let info = DlrlTypeInfo {
104            name: "Foo".into(),
105            ..DlrlTypeInfo::default()
106        };
107        let s = generate_java_object(&info);
108        assert!(!s.contains("package "));
109        assert!(s.contains("class Foo extends ObjectRoot"));
110    }
111
112    #[test]
113    fn listener_interface_emits_three_callbacks() {
114        let s = generate_java_object_listener(&trade_info());
115        assert!(s.contains("interface TradeListener extends ObjectListener<Trade>"));
116        assert!(s.contains("void onTradeCreated(Trade obj);"));
117        assert!(s.contains("void onTradeModified(Trade obj);"));
118        assert!(s.contains("void onTradeDeleted(Trade obj);"));
119    }
120}