Skip to main content

zerodds_lang_rules/emit/
interface_java.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! An IDL `interface`, as the Java language binding DDS-RPC 1.0 §7.11.2
4//! specifies.
5//!
6//! §7.11.2 is short on purpose: "The following sub clauses describe the
7//! entities that are either different or not described in the C++
8//! language bindings", and §7.11.2.3 then says the function-call style
9//! "shall define the same entities as that of C++". So this column is
10//! the C++ one of `emit::interface_cpp` read through two substitutions —
11//! `org.omg.dds.rpc.Future<T>` for `dds::rpc::future<T>` (§7.11.2.2.4)
12//! and the DDS-Java-PSM type mapping for the DDS-Cxx-PSM one — and one
13//! difference the substitution cannot absorb.
14//!
15//! ## The difference: `out` and `inout`
16//!
17//! C++ passes them by reference; §7.11.1.1.2 rule 6.e sends that to
18//! DDS-Cxx-PSM §7.4.5, whose answer is `T&`. Java has no `T&`. The
19//! \[IDL2Java\] mapping's answer is a `Holder` class per type, and
20//! §7.11.2 does not take it — it sends the type mapping to
21//! [DDS-Java-PSM], which has no holders.
22//!
23//! What it does name is `${interface}_${operation}_Out`: rule 7.i makes
24//! that the payload of the `future` an *asynchronous* operation with
25//! `out` or `inout` parameters returns. The same type is what the
26//! synchronous operation returns here. So the multiple return travels in
27//! a type the specification itself names for exactly this call shape,
28//! rather than in `n` holder classes it does not name at all.
29//!
30//! ## Exceptions
31//!
32//! No `throws` clause, on any generated method. Rule 6.c: "The functions
33//! shall not have any exception specification even if the IDL operation
34//! has an exception specification. The implementation may still throw
35//! the exceptions specified by the IDL operation." §7.11.2 overrides
36//! nothing about it, and §7.11.2.1 — Java's own "Mapping of Exceptions"
37//! — has no body in the published document, which under §7.11.2's
38//! preamble means the C++ clause stands. The C++ hierarchy of Figure 7.6
39//! puts every remote exception under `std::runtime_error` or
40//! `std::logic_error`, both unchecked; the Java counterpart of an
41//! unchecked exception needs no `throws`.
42//!
43//! The `raises` clause is still visible: it is written into the method's
44//! documentation, so a reader of the generated file sees what the IDL
45//! declared rather than nothing at all. The same line goes into the C++
46//! output for the same reason.
47//!
48//! ## Where it lands
49//!
50//! One outer class, as `adapter::java`'s [`crate::adapter::java::OUTER`]
51//! is one outer class and for the same reason: Java allows one public
52//! top-level type per file. The data types are written from `ZdTypes`
53//! down and the services from [`OUTER`] down, which is what
54//! `java_member_type` already spells for every type a signature names.
55
56mod calls;
57mod enhanced;
58mod enhanced_calls;
59
60use crate::adapter::interface::{
61    self, Attribute, BindingError, Operation, Parameter, RETURN_MEMBER, Service, ServiceMapping,
62};
63use crate::adapter::java::java_member_type;
64use zerodds_ast::Ast;
65use zerodds_lang_cst::common::{Doc, Ident, Path};
66use zerodds_lang_cst::java;
67
68/// The class every generated service nests inside.
69///
70/// `ZdTypes` holds the data types; this holds the services. Two outer
71/// classes and not one, because they are two generated artefacts: a
72/// model with no `interface` in it produces no service file at all, and
73/// a `ZdTypes` that sometimes carried services and sometimes did not
74/// would be a class whose shape depended on what else was in the IDL.
75pub const OUTER: &str = "ZdServices";
76
77/// The package DDS-RPC 1.0 §7.11.2.2.1 puts the request-reply binding
78/// in.
79const RPC: &str = "org.omg.dds.rpc";
80/// The implementation classes of the RPC runtime (not in a package of the prefix `org.omg.dds`,
81/// DDS-Java-PSM 1.0 §7.2.6).
82const RPC_IMPL: &str = "org.zerodds.ddsrpc";
83
84/// The Java declarations every interface of `ast` produces.
85///
86/// # Errors
87/// [`BindingError`] — the interface did not reach the seam, or a type in
88/// it has no Java spelling.
89pub fn java_interface_items(ast: &Ast) -> Result<Vec<java::TypeDecl>, BindingError> {
90    java_interface_items_with(ast, ServiceMapping::Basic)
91}
92
93/// [`java_interface_items`] for the given service mapping.
94///
95/// [`ServiceMapping::Enhanced`] writes the topic types of DDS-RPC 1.0
96/// §7.5.1.2 — the `@Choice @Autoid` `Request` and `Reply` holders and the
97/// `In`, `Out` and `Result` holders — and the interface types that do not
98/// depend on the shape of the topic types; the Basic mapping's `Call`
99/// union, its codec and its function-call classes are not written.
100///
101/// # Errors
102/// As [`java_interface_items`].
103pub fn java_interface_items_with(
104    ast: &Ast,
105    mapping: ServiceMapping,
106) -> Result<Vec<java::TypeDecl>, BindingError> {
107    let mut pending: Vec<(Vec<String>, java::Member)> = Vec::new();
108    // §7.5.1.1.5 rule 4a names the constant after the exception alone, so
109    // two interfaces of one module that raise it share the one constant.
110    let mut declared_hashes: Vec<(Vec<String>, String)> = Vec::new();
111    for service in interface::services(ast)? {
112        let chain: Vec<String> = interface::module_chain(ast, service.module)?
113            .into_iter()
114            .map(|(_, name)| java::escape_keyword(&name))
115            .collect();
116        let members = if mapping.is_enhanced() {
117            enhanced::members(ast, &Service { mapping, ..service })?
118        } else {
119            members_of(ast, &service, &chain, &mut declared_hashes)?
120        };
121        for member in members {
122            pending.push((chain.clone(), member));
123        }
124    }
125    // A service defined by a pair of types (DDS-RPC 1.0 §7.3.1.4).
126    pending.extend(
127        crate::emit::type_pair_java::java_type_pair_members(ast).map_err(BindingError::Codec)?,
128    );
129    if pending.is_empty() {
130        return Ok(Vec::new());
131    }
132    Ok(vec![java::TypeDecl::Class(java::Class {
133        doc: Doc::lines(&[
134            "The Java shape of every service one IDL file declares.",
135            "",
136            "DDS-RPC 1.0 §7.11.2. Each IDL module nests as a class, exactly as",
137            "the data types nest inside `ZdTypes`.",
138        ]),
139        annotations: Vec::new(),
140        modifiers: vec![java::Modifier::Public, java::Modifier::Final],
141        name: Ident::new(OUTER),
142        generics: Vec::new(),
143        extends: None,
144        implements: Vec::new(),
145        members: nest(&pending, 0),
146    })])
147}
148
149/// One level of the module fold.
150///
151/// zerodds-lint: recursion-depth 64 (module nesting; every chain is
152/// built by `interface::module_chain`, which is bounded by
153/// `interface::MAX_MODULE_DEPTH`)
154fn nest(items: &[(Vec<String>, java::Member)], depth: usize) -> Vec<java::Member> {
155    let mut out = Vec::new();
156    let mut index = 0;
157    while index < items.len() {
158        let Some((chain, member)) = items.get(index) else {
159            break;
160        };
161        let Some(name) = chain.get(depth) else {
162            out.push(member.clone());
163            index += 1;
164            continue;
165        };
166        let mut end = index;
167        while items
168            .get(end)
169            .is_some_and(|(other, _)| other.get(depth) == Some(name))
170        {
171            end += 1;
172        }
173        let Some(run) = items.get(index..end) else {
174            break;
175        };
176        out.push(java::Member::Nested(Box::new(java::TypeDecl::Class(
177            java::Class {
178                doc: Doc::line("An IDL module, as the class its services nest in."),
179                annotations: Vec::new(),
180                modifiers: vec![
181                    java::Modifier::Public,
182                    java::Modifier::Static,
183                    java::Modifier::Final,
184                ],
185                name: Ident::new(name),
186                generics: Vec::new(),
187                extends: None,
188                implements: Vec::new(),
189                members: nest(run, depth + 1),
190            },
191        ))));
192        index = end;
193    }
194    out
195}
196
197fn members_of(
198    ast: &Ast,
199    service: &Service,
200    chain: &[String],
201    declared_hashes: &mut Vec<(Vec<String>, String)>,
202) -> Result<Vec<java::Member>, BindingError> {
203    let name = java::escape_keyword(&service.name);
204    let operations = service.all_operations();
205    let mut out = Vec::new();
206
207    // The topic types of §7.5.1.1.4-7, the same shape and the same names
208    // `emit::interface_cpp` writes and `crates/rpc/src/codegen.rs`
209    // derives.
210    for operation in &operations {
211        out.push(holder(
212            &interface::in_struct_name(&name, &java::escape_keyword(&operation.name)),
213            request_fields(ast, service, operation)?,
214            Doc::line("The `in` and `inout` parameters of one call. DDS-RPC 1.0 §7.5.1.1.4."),
215        ));
216        out.push(holder(
217            &interface::out_struct_name(&name, &java::escape_keyword(&operation.name)),
218            reply_fields(ast, service, operation)?,
219            Doc::line(
220                "The return value and the `out` and `inout` parameters of one call. DDS-RPC 1.0 \
221                 §7.5.1.1.5.",
222            ),
223        ));
224    }
225    // §7.5.1.1.6 rule 4 and §7.5.1.1.5 rule 4a: one label constant per
226    // operation and one per exception, beside the unions.
227    for operation in &operations {
228        let spelled = java::escape_keyword(&operation.name);
229        out.push(hash_constant(
230            &interface::operation_hash_constant(&name, &spelled),
231            interface::operation_hash(&operation.name),
232            "The case label of one operation. DDS-RPC 1.0 §7.5.1.1.6 rules 4 and 5a.",
233        ));
234    }
235    for operation in &operations {
236        for raised in &operation.raises {
237            let key = (chain.to_vec(), raised.scoped_name.clone());
238            if declared_hashes.contains(&key) {
239                continue;
240            }
241            declared_hashes.push(key);
242            out.push(hash_constant(
243                &interface::exception_hash_constant(&java::escape_keyword(&raised.name)),
244                interface::exception_hash(&raised.scoped_name),
245                "The case label of one raised exception. DDS-RPC 1.0 §7.5.1.1.5 rule 4.",
246            ));
247        }
248    }
249    for operation in &operations {
250        out.push(result_union_of(ast, &name, operation)?);
251    }
252    out.push(union_of(
253        &interface::call_union_name(&name),
254        &operations,
255        &name,
256        false,
257    ));
258    out.push(union_of(
259        &interface::return_union_name(&name),
260        &operations,
261        &name,
262        true,
263    ));
264    out.push(holder(
265        &interface::request_type_name(&name),
266        vec![
267            field(qualified(RPC, "RequestHeader"), "header"),
268            field(
269                local(&interface::call_union_name(&name)),
270                interface::DATA_MEMBER,
271            ),
272        ],
273        Doc::line("The request topic type. DDS-RPC 1.0 §7.5.1.1.6."),
274    ));
275    out.push(holder(
276        &interface::reply_type_name(&name),
277        vec![
278            field(qualified(RPC, "ReplyHeader"), "header"),
279            field(
280                local(&interface::return_union_name(&name)),
281                interface::DATA_MEMBER,
282            ),
283        ],
284        Doc::line("The reply topic type. DDS-RPC 1.0 §7.5.1.1.7."),
285    ));
286    // §7.5.1.1.6 and §7.5.1.1.7 declare the two topic types in the module
287    // of the interface, so the DDS type name is module-qualified, as the
288    // C++ column announces it.
289    let scope: Vec<String> = interface::module_chain(ast, service.module)?
290        .into_iter()
291        .map(|(_, module)| module)
292        .collect();
293    out.push(support_class(service, &name, &scope));
294
295    out.push(sync_interface(ast, service, &name)?);
296    out.push(async_interface(ast, service, &name)?);
297
298    if service.is_remote() {
299        // §7.9, §7.11.1.5, §7.11.2.3: the codec, the client and the
300        // service that run.
301        out.extend(calls::members(ast, service, &name)?);
302        out.push(endpoint(
303            &format!("{name}Requester"),
304            "Requester",
305            &name,
306            "The service's `Requester`, over its two topic types. DDS-RPC 1.0 §7.11.2.2.1.",
307        ));
308        out.push(endpoint(
309            &format!("{name}Replier"),
310            "Replier",
311            &name,
312            "The service's `Replier`, over its two topic types. DDS-RPC 1.0 §7.11.2.2.1.",
313        ));
314        out.push(client_interface(&name));
315        out.push(service_interface(&name));
316    }
317    Ok(out)
318}
319
320// -- the topic types ---------------------------------------------------
321
322fn request_fields(
323    ast: &Ast,
324    service: &Service,
325    operation: &Operation,
326) -> Result<Vec<java::Member>, BindingError> {
327    let mut out = Vec::new();
328    for param in operation.inputs() {
329        out.push(field(
330            java_member_type(ast, param.type_, &at(service, operation, &param.name))?,
331            &java::escape_keyword(&param.name),
332        ));
333    }
334    // §7.5.1.1.4 rule 3c: an operation with no `in`/`inout` parameters
335    // carries a single `dummy` of type `dds::rpc::UnusedMember`, not an
336    // empty structure — XCDR gives an empty structure no octets.
337    if out.is_empty() {
338        out.push(field(
339            qualified(RPC, "UnusedMember"),
340            interface::DUMMY_MEMBER,
341        ));
342    }
343    Ok(out)
344}
345
346fn reply_fields(
347    ast: &Ast,
348    service: &Service,
349    operation: &Operation,
350) -> Result<Vec<java::Member>, BindingError> {
351    // §7.5.1.1.5 rule 3a: the `out` and `inout` parameters first, in
352    // declaration order and under their formal names; rule 3b: the
353    // return value *last*.
354    let mut out = Vec::new();
355    for param in operation.outputs() {
356        out.push(field(
357            java_member_type(ast, param.type_, &at(service, operation, &param.name))?,
358            &java::escape_keyword(&param.name),
359        ));
360    }
361    if let Some(returns) = operation.returns {
362        let taken: Vec<String> = operation
363            .outputs()
364            .map(|param| java::escape_keyword(&param.name))
365            .collect();
366        out.push(field(
367            java_member_type(ast, returns, &at(service, operation, "return"))?,
368            &return_member_name(&taken),
369        ));
370    }
371    // Rule 3c: nothing at all becomes the `dummy` member.
372    if out.is_empty() {
373        out.push(field(
374            qualified(RPC, "UnusedMember"),
375            interface::DUMMY_MEMBER,
376        ));
377    }
378    Ok(out)
379}
380
381/// The name §7.5.1.1.5 rule 3b gives the return value, with the
382/// collision rule: "If an operation has an out argument named `return_`
383/// and the operation has a regular return value, the regular return
384/// value in the Out structure shall be represented as `return_N`."
385fn return_member_name(taken: &[String]) -> String {
386    if !taken.iter().any(|name| name == RETURN_MEMBER) {
387        return RETURN_MEMBER.to_string();
388    }
389    for n in 1..=i32::MAX {
390        let candidate = format!("{RETURN_MEMBER}{n}");
391        if !taken.iter().any(|name| name == &candidate) {
392            return candidate;
393        }
394    }
395    // Unreachable below 2^31 parameters; a name no IDL identifier can
396    // take is still better than two members sharing one.
397    format!("{RETURN_MEMBER} ")
398}
399
400fn union_of(name: &str, operations: &[Operation], service: &str, reply: bool) -> java::Member {
401    let mut members = vec![
402        discriminator_field(),
403        // §7.5.1.1.6 rule 3 / §7.5.1.1.7 rule 3: the `default` case.
404        case_field(
405            qualified(RPC, "UnknownOperation"),
406            interface::UNKNOWN_OP_MEMBER,
407            "Live on the `default` case. DDS-RPC 1.0 §7.5.1.1.6 rule 3.",
408        ),
409    ];
410    for operation in operations {
411        let spelled = java::escape_keyword(&operation.name);
412        let case = if reply {
413            interface::result_union_name(service, &spelled)
414        } else {
415            interface::in_struct_name(service, &spelled)
416        };
417        members.push(case_field(
418            local(&case),
419            &spelled,
420            &format!(
421                "Live when `_d` is `{}`.",
422                interface::operation_hash_constant(service, &spelled)
423            ),
424        ));
425    }
426    holder(
427        name,
428        members,
429        Doc::line(if reply {
430            "The reply union, without the `oneway` operations. DDS-RPC 1.0 §7.5.1.1.7."
431        } else {
432            "The request union over every operation. DDS-RPC 1.0 §7.5.1.1.6."
433        }),
434    )
435}
436
437/// The `_d` member every synthesized union carries. §7.5.1.1.6 rule 2.
438fn discriminator_field() -> java::Member {
439    java::Member::Field(java::Field {
440        doc: Doc::line("Which case this sample carries. DDS-RPC 1.0 §7.5.1.1.6 rule 2."),
441        annotations: Vec::new(),
442        modifiers: vec![java::Modifier::Public],
443        type_: java::Type::Primitive(java::Primitive::Int),
444        name: Ident::new("_d"),
445        value: None,
446    })
447}
448
449/// One case member of a synthesized union.
450fn case_field(type_: java::Type, name: &str, doc: &str) -> java::Member {
451    java::Member::Field(java::Field {
452        doc: Doc::line(doc),
453        annotations: Vec::new(),
454        modifiers: vec![java::Modifier::Public],
455        type_,
456        name: Ident::new(name),
457        value: None,
458    })
459}
460
461/// One `${I}_${op}_Hash` or `${exception}_Ex_Hash` constant.
462/// §7.5.1.1.6 rule 4, §7.5.1.1.5 rule 4.
463fn hash_constant(name: &str, value: i32, doc: &str) -> java::Member {
464    java::Member::Field(java::Field {
465        doc: Doc::line(doc),
466        annotations: Vec::new(),
467        modifiers: vec![
468            java::Modifier::Public,
469            java::Modifier::Static,
470            java::Modifier::Final,
471        ],
472        type_: java::Type::Primitive(java::Primitive::Int),
473        name: Ident::new(name),
474        value: Some(java::Expr::Literal(zerodds_lang_cst::common::Literal::Int(
475            value.to_string(),
476        ))),
477    })
478}
479
480/// `${I}_${op}_Result` — §7.5.1.1.5.
481fn result_union_of(
482    ast: &Ast,
483    name: &str,
484    operation: &Operation,
485) -> Result<java::Member, BindingError> {
486    let spelled = java::escape_keyword(&operation.name);
487    let mut members = vec![
488        discriminator_field(),
489        case_field(
490            local(&interface::out_struct_name(name, &spelled)),
491            interface::RESULT_MEMBER,
492            "Live when `_d` is `dds::RETCODE_OK` (0). DDS-RPC 1.0 §7.5.1.1.5 rule 3a.",
493        ),
494    ];
495    // §7.5.1.1.5: the `default` case carries `dds::rpc::UnknownException`.
496    members.push(case_field(
497        qualified(RPC, "UnknownException"),
498        "unknownEx",
499        "Live on the `default` case. DDS-RPC 1.0 §7.5.1.1.5.",
500    ));
501    for raised in &operation.raises {
502        let exception = java::escape_keyword(&raised.name);
503        members.push(case_field(
504            exception_type(ast, raised)?,
505            &interface::exception_member_name(&exception),
506            &format!(
507                "Live when `_d` is `{}`. DDS-RPC 1.0 §7.5.1.1.5 rule 5.",
508                interface::exception_hash_constant(&exception)
509            ),
510        ));
511    }
512    Ok(holder(
513        &interface::result_union_name(name, &spelled),
514        members,
515        Doc::line("The outcome of one operation. DDS-RPC 1.0 §7.5.1.1.5."),
516    ))
517}
518
519/// The data holder an exception is declared as, reachable from any module:
520/// `ZdTypes.<modules>.<Name>`.
521fn exception_type(ast: &Ast, raised: &interface::Raise) -> Result<java::Type, BindingError> {
522    let node = ast
523        .exception(raised.id)
524        .map_err(|_| interface::InterfaceError::NoNode {
525            kind: "exception",
526            at: raised.name.clone(),
527        })?;
528    calls::data_type(ast, node.module, &raised.name)
529}
530
531/// `ISupport` — the two topic names of §7.4.1 and the two type names of
532/// §7.5.1.1.6-7, which are different strings.
533fn support_class(service: &Service, name: &str, scope: &[String]) -> java::Member {
534    support_class_with(service, name, scope, true, None)
535}
536
537/// [`support_class`], with the factory methods that name the codec
538/// (`factories`) or without them.
539fn support_class_with(
540    service: &Service,
541    name: &str,
542    scope: &[String],
543    factories: bool,
544    descriptions: Option<&(
545        crate::emit::interface_rust::type_information::JavaDescription,
546        crate::emit::interface_rust::type_information::JavaDescription,
547    )>,
548) -> java::Member {
549    let style = interface::BindingStyle::FunctionCall;
550    let mut members = vec![
551        topic_constant("REQUEST_TOPIC_NAME", &service.request_topic_name(style)),
552        topic_constant("REPLY_TOPIC_NAME", &service.reply_topic_name(style)),
553        topic_constant("SERVICE_NAME", &service.service_name),
554        // §7.4.1: the `<interface_name>` — the module names joined by
555        // underscores, then the interface name. A run-time service name
556        // (§7.4.2.3) composes the topic names from it.
557        topic_constant("INTERFACE_NAME", &service.qualified_name),
558        topic_constant(
559            "REQUEST_TYPE_NAME",
560            &scoped(scope, &interface::request_type_name(name)),
561        ),
562        topic_constant(
563            "REPLY_TYPE_NAME",
564            &scoped(scope, &interface::reply_type_name(name)),
565        ),
566    ];
567    // §7.4.3: one alias per parent interface.
568    for (at, alias) in service.topic_aliases(style, false).into_iter().enumerate() {
569        members.push(topic_constant(&format!("REQUEST_TOPIC_ALIAS_{at}"), &alias));
570    }
571    for (at, alias) in service.topic_aliases(style, true).into_iter().enumerate() {
572        members.push(topic_constant(&format!("REPLY_TOPIC_ALIAS_{at}"), &alias));
573    }
574    if factories {
575        members.extend(calls::support_methods(service, name, descriptions));
576    }
577    holder(
578        &format!("{name}Support"),
579        members,
580        Doc::line("The service's type support: the names of its two topics and its two types."),
581    )
582}
583
584/// `module::Name`: the DDS type name of a type declared in `scope`.
585fn scoped(scope: &[String], name: &str) -> String {
586    let mut out = String::new();
587    for module in scope {
588        out.push_str(module);
589        out.push_str("::");
590    }
591    out.push_str(name);
592    out
593}
594
595fn topic_constant(name: &str, topic: &str) -> java::Member {
596    java::Member::Field(java::Field {
597        doc: Doc::line("A topic name. DDS-RPC 1.0 §7.4.1.1."),
598        annotations: Vec::new(),
599        modifiers: vec![
600            java::Modifier::Public,
601            java::Modifier::Static,
602            java::Modifier::Final,
603        ],
604        type_: java::Type::named(Path::single("String")),
605        name: Ident::new(name),
606        value: Some(java::Expr::Literal(zerodds_lang_cst::common::Literal::Str(
607            topic.to_string(),
608        ))),
609    })
610}
611
612// -- the two interfaces ------------------------------------------------
613
614fn sync_interface(ast: &Ast, service: &Service, name: &str) -> Result<java::Member, BindingError> {
615    let mut members = Vec::new();
616    for operation in &service.operations {
617        members.push(java::Member::Method(sync_method(
618            ast, service, name, operation,
619        )?));
620    }
621    for attribute in &service.attributes {
622        members.extend(sync_accessors(ast, service, attribute)?);
623    }
624    Ok(java::Member::Nested(Box::new(java::TypeDecl::Interface(
625        java::Interface {
626            doc: Doc::line(
627                "The service, as the interface DDS-RPC 1.0 §7.11.1.1.2 rule 1 requires and \
628                 §7.11.2.3 carries into Java.",
629            ),
630            annotations: Vec::new(),
631            modifiers: vec![java::Modifier::Public],
632            name: Ident::new(name),
633            generics: Vec::new(),
634            extends: base_types(ast, service, "")?,
635            members,
636        },
637    ))))
638}
639
640fn sync_method(
641    ast: &Ast,
642    service: &Service,
643    name: &str,
644    operation: &Operation,
645) -> Result<java::Method, BindingError> {
646    // The multiple return of an `out`/`inout` call travels in the type
647    // rule 7.i names for the same call shape.
648    let returns = if operation.has_outputs() {
649        local(&interface::out_struct_name(
650            name,
651            &java::escape_keyword(&operation.name),
652        ))
653    } else if let Some(target) = operation.returns {
654        java_member_type(ast, target, &at(service, operation, "return"))?
655    } else {
656        java::Type::Void
657    };
658    let mut params = Vec::new();
659    for param in operation.inputs() {
660        params.push(java_param(ast, service, operation, param)?);
661    }
662    Ok(method(
663        &java::escape_keyword(&operation.name),
664        returns,
665        params,
666        raises_doc(operation),
667    ))
668}
669
670fn sync_accessors(
671    ast: &Ast,
672    service: &Service,
673    attribute: &Attribute,
674) -> Result<Vec<java::Member>, BindingError> {
675    let at = format!("{}::{}", service.name, attribute.name);
676    let type_ = java_member_type(ast, attribute.type_, &at)?;
677    let spelled = java::escape_keyword(&attribute.name);
678    let mut out = vec![java::Member::Method(method(
679        &spelled,
680        type_.clone(),
681        Vec::new(),
682        Doc::line("An attribute accessor. DDS-RPC 1.0 §7.11.1.1.2 rule 6.f."),
683    ))];
684    if !attribute.readonly {
685        out.push(java::Member::Method(method(
686            &spelled,
687            java::Type::Void,
688            vec![java::Param {
689                annotations: Vec::new(),
690                final_: false,
691                type_,
692                name: Ident::new(&spelled),
693                varargs: false,
694            }],
695            Doc::line("An attribute accessor. DDS-RPC 1.0 §7.11.1.1.2 rule 6.f."),
696        )));
697    }
698    Ok(out)
699}
700
701fn async_interface(ast: &Ast, service: &Service, name: &str) -> Result<java::Member, BindingError> {
702    let mut members = Vec::new();
703    for operation in &service.operations {
704        members.push(java::Member::Method(async_method(
705            ast, service, name, operation,
706        )?));
707    }
708    for attribute in &service.attributes {
709        let at = format!("{}::{}", service.name, attribute.name);
710        let type_ = java_member_type(ast, attribute.type_, &at)?;
711        let spelled = java::escape_keyword(&attribute.name);
712        members.push(java::Member::Method(method(
713            &format!("{spelled}_async"),
714            future_of(boxed(type_.clone())),
715            Vec::new(),
716            Doc::line("An attribute accessor. DDS-RPC 1.0 §7.11.1.1.2 rule 7.f."),
717        )));
718        if !attribute.readonly {
719            members.push(java::Member::Method(method(
720                &format!("{spelled}_async"),
721                future_of(java::Type::named(Path::single("Void"))),
722                vec![java::Param {
723                    annotations: Vec::new(),
724                    final_: false,
725                    type_,
726                    name: Ident::new(&spelled),
727                    varargs: false,
728                }],
729                Doc::line("An attribute accessor. DDS-RPC 1.0 §7.11.1.1.2 rule 7.g."),
730            )));
731        }
732    }
733    Ok(java::Member::Nested(Box::new(java::TypeDecl::Interface(
734        java::Interface {
735            doc: Doc::line("The service's asynchronous half. DDS-RPC 1.0 §7.11.1.1.2 rule 1."),
736            annotations: Vec::new(),
737            modifiers: vec![java::Modifier::Public],
738            name: Ident::new(&format!("{name}Async")),
739            generics: Vec::new(),
740            extends: base_types(ast, service, "Async")?,
741            members,
742        },
743    ))))
744}
745
746fn async_method(
747    ast: &Ast,
748    service: &Service,
749    name: &str,
750    operation: &Operation,
751) -> Result<java::Method, BindingError> {
752    // Rules 7.h and 7.i, with `Void` where C++ writes `void`: a Java
753    // type argument cannot be a primitive and cannot be `void`.
754    let payload = if operation.has_outputs() {
755        local(&interface::out_struct_name(
756            name,
757            &java::escape_keyword(&operation.name),
758        ))
759    } else if let Some(target) = operation.returns {
760        boxed(java_member_type(
761            ast,
762            target,
763            &at(service, operation, "return"),
764        )?)
765    } else {
766        java::Type::named(Path::single("Void"))
767    };
768    let mut params = Vec::new();
769    for param in operation.inputs() {
770        params.push(java_param(ast, service, operation, param)?);
771    }
772    Ok(method(
773        &format!("{}_async", java::escape_keyword(&operation.name)),
774        future_of(payload),
775        params,
776        raises_doc(operation),
777    ))
778}
779
780// -- the endpoints -----------------------------------------------------
781
782/// `class <I>Requester extends org.zerodds.ddsrpc.RequesterImpl<TReq, TRep>`.
783///
784/// A class, unlike every other type this file emits except the topic
785/// types: `org.omg.dds.rpc.Requester` and `Replier` are interfaces
786/// (§7.11.2.2.1), `RequesterImpl` and `ReplierImpl` their implementations,
787/// and the typed name is the generic one with its two arguments fixed —
788/// what the C++ column says with a `using`. §7.11.1.4.3 calls a `Requester`
789/// "a reference type … configured at the time of construction using
790/// RequesterParams", which is a constructor and not a method, so the
791/// constructor forwards.
792fn endpoint(name: &str, template: &str, service: &str, doc: &str) -> java::Member {
793    // DDS-RPC 1.0 §7.11.2: the params of the machine-readable Java files take no type
794    // arguments.
795    let params = java::Type::named(path(RPC, &format!("{template}Params")));
796    java::Member::Nested(Box::new(java::TypeDecl::Class(java::Class {
797        doc: Doc::line(doc),
798        annotations: Vec::new(),
799        modifiers: vec![java::Modifier::Public, java::Modifier::Static],
800        name: Ident::new(name),
801        generics: Vec::new(),
802        extends: Some(java::Type::generic(
803            path(RPC_IMPL, &format!("{template}Impl")),
804            vec![
805                local(&interface::request_type_name(service)),
806                local(&interface::reply_type_name(service)),
807            ],
808        )),
809        implements: Vec::new(),
810        members: vec![java::Member::Constructor(java::Constructor {
811            doc: Doc::line("The generic endpoint, over this service's two topic types."),
812            annotations: Vec::new(),
813            modifiers: vec![java::Modifier::Public],
814            name: Ident::new(name),
815            params: vec![java::Param {
816                annotations: Vec::new(),
817                final_: false,
818                type_: params,
819                name: Ident::new("params"),
820                varargs: false,
821            }],
822            throws: Vec::new(),
823            body: java::Block {
824                statements: vec![java::Statement::Expr(java::Expr::MethodCall {
825                    receiver: None,
826                    name: Ident::new("super"),
827                    args: vec![java::Expr::Path(Path::single("params"))],
828                })],
829            },
830        })],
831    })))
832}
833
834/// `IClient` — §7.11.1.5.4 through §7.11.2.3.
835///
836/// "A `Client` inherits publicly from `${interface}`,
837/// `${interface}Async`, and `ClientEndpoint`." Three supertypes, which
838/// a Java *class* could not have and a Java interface can.
839fn client_interface(name: &str) -> java::Member {
840    java::Member::Nested(Box::new(java::TypeDecl::Interface(java::Interface {
841        doc: Doc::line("The function-call client. DDS-RPC 1.0 §7.11.1.5.4, §7.11.2.3."),
842        annotations: Vec::new(),
843        modifiers: vec![java::Modifier::Public],
844        name: Ident::new(&format!("{name}Client")),
845        generics: Vec::new(),
846        extends: vec![
847            local(name),
848            local(&format!("{name}Async")),
849            java::Type::named(path(RPC, "ClientEndpoint")),
850        ],
851        members: Vec::new(),
852    })))
853}
854
855/// `IService` — §7.11.1.5.1 through §7.11.2.3.
856fn service_interface(name: &str) -> java::Member {
857    java::Member::Nested(Box::new(java::TypeDecl::Interface(java::Interface {
858        doc: Doc::line("The function-call service. DDS-RPC 1.0 §7.11.1.5.1, §7.11.2.3."),
859        annotations: Vec::new(),
860        modifiers: vec![java::Modifier::Public],
861        name: Ident::new(&format!("{name}Service")),
862        generics: Vec::new(),
863        extends: vec![java::Type::named(path(RPC, "ServiceEndpoint"))],
864        members: vec![java::Member::Method(method(
865            "implementation",
866            local(name),
867            Vec::new(),
868            Doc::line("The implementation this service dispatches into."),
869        ))],
870    })))
871}
872
873// -- the shapes ---------------------------------------------------------
874
875fn java_param(
876    ast: &Ast,
877    service: &Service,
878    operation: &Operation,
879    param: &Parameter,
880) -> Result<java::Param, BindingError> {
881    Ok(java::Param {
882        annotations: Vec::new(),
883        final_: false,
884        type_: java_member_type(ast, param.type_, &at(service, operation, &param.name))?,
885        name: Ident::new(&java::escape_keyword(&param.name)),
886        varargs: false,
887    })
888}
889
890fn method(name: &str, returns: java::Type, params: Vec<java::Param>, doc: Doc) -> java::Method {
891    java::Method {
892        doc,
893        annotations: Vec::new(),
894        // No modifier: an interface method is public and abstract
895        // already, and `-Xlint:all -Werror` warns about writing either.
896        modifiers: Vec::new(),
897        generics: Vec::new(),
898        returns,
899        name: Ident::new(name),
900        params,
901        // Rule 6.c: no exception specification.
902        throws: Vec::new(),
903        body: None,
904    }
905}
906
907/// `org.omg.dds.rpc.Future<T>` — §7.11.2.2.4.
908fn future_of(payload: java::Type) -> java::Type {
909    java::Type::generic(path(RPC, "Future"), vec![payload])
910}
911
912/// The wrapper class a primitive takes as a type argument.
913fn boxed(type_: java::Type) -> java::Type {
914    match type_ {
915        java::Type::Primitive(primitive) => java::Type::named(Path::single(primitive.boxed())),
916        java::Type::Void => java::Type::named(Path::single("Void")),
917        other => other,
918    }
919}
920
921/// The base interfaces of `service`, spelled from its module: a base may be
922/// declared in another one.
923fn base_types(ast: &Ast, service: &Service, suffix: &str) -> Result<Vec<java::Type>, BindingError> {
924    service
925        .bases
926        .iter()
927        .map(|base| {
928            let module = interface::service(ast, base.id)?.module;
929            enhanced::holder_type(
930                ast,
931                service.module,
932                module,
933                &format!("{}{suffix}", java::escape_keyword(&base.name)),
934            )
935        })
936        .collect()
937}
938
939fn holder(name: &str, members: Vec<java::Member>, doc: Doc) -> java::Member {
940    java::Member::Nested(Box::new(java::TypeDecl::Class(java::Class {
941        doc,
942        annotations: Vec::new(),
943        modifiers: vec![
944            java::Modifier::Public,
945            java::Modifier::Static,
946            java::Modifier::Final,
947        ],
948        name: Ident::new(name),
949        generics: Vec::new(),
950        extends: None,
951        implements: Vec::new(),
952        members,
953    })))
954}
955
956fn field(type_: java::Type, name: &str) -> java::Member {
957    java::Member::Field(java::Field {
958        doc: Doc::none(),
959        annotations: Vec::new(),
960        modifiers: vec![java::Modifier::Public],
961        type_,
962        name: Ident::new(name),
963        value: None,
964    })
965}
966
967fn local(name: &str) -> java::Type {
968    java::Type::named(Path::single(name))
969}
970
971fn path(package: &str, name: &str) -> Path {
972    let mut segments: Vec<&str> = package.split('.').collect();
973    segments.push(name);
974    Path::new(&segments)
975}
976
977fn qualified(package: &str, name: &str) -> java::Type {
978    java::Type::named(path(package, name))
979}
980
981/// The `raises` clause, as documentation. See
982/// [`Operation::raises_line`].
983fn raises_doc(operation: &Operation) -> Doc {
984    operation
985        .raises_line()
986        .map_or_else(Doc::none, |line| Doc::line(&line))
987}
988
989/// Where an operation sits, for a diagnostic.
990fn at(service: &Service, operation: &Operation, what: &str) -> String {
991    format!("{}::{}({what})", service.name, operation.name)
992}