1extern crate alloc;
66
67use alloc::format;
68use alloc::string::{String, ToString};
69use alloc::vec::Vec;
70use core::fmt::Write;
71
72use zerodds_idl::ast::{IntegerType, PrimitiveType, ScopedName, TypeSpec};
73use zerodds_rpc::service_mapping::{MethodDef, ParamDef, ParamDirection, ServiceDef};
74
75use crate::JavaGenOptions;
76use crate::emitter::{JavaFile, fmt_err, indent_unit, wrap_compilation_unit_default};
77use crate::error::JavaGenError;
78use crate::keywords::sanitize_identifier;
79use crate::type_map::{
80 floating_to_java, floating_to_java_boxed, integer_to_java, integer_to_java_boxed,
81 primitive_to_java, primitive_to_java_boxed,
82};
83
84pub fn emit_service_interface(
93 svc: &ServiceDef,
94 pkg: &str,
95 opts: &JavaGenOptions,
96) -> Result<JavaFile, JavaGenError> {
97 let class = sanitize_identifier(&svc.name)?;
98 let ind = indent_unit(opts);
99 let mut body = String::new();
100 writeln!(body, "/** Synchronous service interface for {class}. */").map_err(fmt_err)?;
101 writeln!(body, "@org.zerodds.rpc.Service(\"{}\")", svc.name).map_err(fmt_err)?;
102 writeln!(body, "public interface {class} {{").map_err(fmt_err)?;
103 for m in &svc.methods {
104 emit_sync_method_signature(&mut body, m, &ind)?;
105 }
106 writeln!(body, "}}").map_err(fmt_err)?;
107 let source = wrap_compilation_unit_default(pkg, &body);
108 Ok(JavaFile {
109 package_path: pkg.to_string(),
110 class_name: class,
111 source,
112 })
113}
114
115pub fn emit_service_interface_async(
120 svc: &ServiceDef,
121 pkg: &str,
122 opts: &JavaGenOptions,
123) -> Result<JavaFile, JavaGenError> {
124 let svc_class = sanitize_identifier(&svc.name)?;
125 let class = format!("{svc_class}Async");
126 let ind = indent_unit(opts);
127 let mut body = String::new();
128 writeln!(
129 body,
130 "/** Asynchronous service interface for {svc_class}. */"
131 )
132 .map_err(fmt_err)?;
133 writeln!(body, "public interface {class} {{").map_err(fmt_err)?;
134 for m in &svc.methods {
135 emit_async_method_signature(&mut body, m, &ind)?;
136 }
137 writeln!(body, "}}").map_err(fmt_err)?;
138 let source = wrap_compilation_unit_default(pkg, &body);
139 Ok(JavaFile {
140 package_path: pkg.to_string(),
141 class_name: class,
142 source,
143 })
144}
145
146pub fn emit_requester_class(
151 svc: &ServiceDef,
152 pkg: &str,
153 opts: &JavaGenOptions,
154) -> Result<JavaFile, JavaGenError> {
155 let svc_class = sanitize_identifier(&svc.name)?;
156 let class = format!("{svc_class}Requester");
157 let ind = indent_unit(opts);
158 let mut body = String::new();
159 writeln!(
160 body,
161 "/** Client-side proxy for {svc_class}. Implements both the \
162 synchronous {svc_class} interface and the {svc_class}Async \
163 interface. */",
164 )
165 .map_err(fmt_err)?;
166 writeln!(
167 body,
168 "public final class {class} implements {svc_class}, {svc_class}Async {{",
169 )
170 .map_err(fmt_err)?;
171 if cfg!(feature = "jni") {
172 writeln!(
175 body,
176 "{ind}private final org.zerodds.rpc.RustRequesterFFI requesterFfi;",
177 )
178 .map_err(fmt_err)?;
179 writeln!(body).map_err(fmt_err)?;
180 writeln!(
181 body,
182 "{ind}public {class}(org.zerodds.rpc.RustRequesterFFI requesterFfi) {{",
183 )
184 .map_err(fmt_err)?;
185 writeln!(body, "{ind}{ind}this.requesterFfi = requesterFfi;").map_err(fmt_err)?;
186 writeln!(body, "{ind}}}").map_err(fmt_err)?;
187 writeln!(body).map_err(fmt_err)?;
188 } else {
189 writeln!(
190 body,
191 "{ind}private final org.zerodds.rpc.Requester<Object, Object> requester;",
192 )
193 .map_err(fmt_err)?;
194 writeln!(body).map_err(fmt_err)?;
195 writeln!(
196 body,
197 "{ind}public {class}(org.zerodds.rpc.Requester<Object, Object> requester) {{",
198 )
199 .map_err(fmt_err)?;
200 writeln!(body, "{ind}{ind}this.requester = requester;").map_err(fmt_err)?;
201 writeln!(body, "{ind}}}").map_err(fmt_err)?;
202 writeln!(body).map_err(fmt_err)?;
203 }
204
205 for m in &svc.methods {
207 emit_requester_sync_impl(&mut body, m, &ind)?;
208 }
209 writeln!(body).map_err(fmt_err)?;
210 for m in &svc.methods {
212 emit_requester_async_impl(&mut body, m, &ind)?;
213 }
214
215 writeln!(body, "}}").map_err(fmt_err)?;
216 let source = wrap_compilation_unit_default(pkg, &body);
217 Ok(JavaFile {
218 package_path: pkg.to_string(),
219 class_name: class,
220 source,
221 })
222}
223
224pub fn emit_replier_class(
229 svc: &ServiceDef,
230 pkg: &str,
231 opts: &JavaGenOptions,
232) -> Result<JavaFile, JavaGenError> {
233 let svc_class = sanitize_identifier(&svc.name)?;
234 let class = format!("{svc_class}Replier");
235 let handler_iface = format!("{svc_class}Service");
236 let ind = indent_unit(opts);
237 let mut body = String::new();
238 writeln!(
239 body,
240 "/** Server-side replier for {svc_class}. Wires a {handler_iface} \
241 implementation to the underlying RPC runtime. */",
242 )
243 .map_err(fmt_err)?;
244 writeln!(body, "public final class {class} {{").map_err(fmt_err)?;
245 writeln!(
246 body,
247 "{ind}private final org.zerodds.rpc.Replier<Object, Object> replier;",
248 )
249 .map_err(fmt_err)?;
250 writeln!(body, "{ind}private final {handler_iface} handler;").map_err(fmt_err)?;
251 writeln!(body).map_err(fmt_err)?;
252 writeln!(
253 body,
254 "{ind}public {class}(org.zerodds.rpc.Replier<Object, Object> replier, {handler_iface} handler) {{",
255 )
256 .map_err(fmt_err)?;
257 writeln!(body, "{ind}{ind}this.replier = replier;").map_err(fmt_err)?;
258 writeln!(body, "{ind}{ind}this.handler = handler;").map_err(fmt_err)?;
259 writeln!(body, "{ind}}}").map_err(fmt_err)?;
260 writeln!(body).map_err(fmt_err)?;
261
262 writeln!(
265 body,
266 "{ind}/** Dispatches an incoming request by method id. */",
267 )
268 .map_err(fmt_err)?;
269 writeln!(
270 body,
271 "{ind}public Object dispatch(int methodId, Object args) {{",
272 )
273 .map_err(fmt_err)?;
274 writeln!(body, "{ind}{ind}switch (methodId) {{").map_err(fmt_err)?;
275 for (idx, m) in svc.methods.iter().enumerate() {
276 let mname = sanitize_identifier(&m.name)?;
277 let case_id = idx + 1;
278 let void_like = m.oneway
281 || (m.return_type.is_none()
282 && m.params.iter().all(|p| p.direction == ParamDirection::In));
283 let stub = if void_like {
284 format!("{ind}{ind}{ind}case {case_id}: handler.{mname}(/* args */); return null;")
285 } else {
286 format!("{ind}{ind}{ind}case {case_id}: return handler.{mname}(/* args */);")
287 };
288 writeln!(body, "{stub}").map_err(fmt_err)?;
289 }
290 writeln!(
291 body,
292 "{ind}{ind}{ind}default: throw new org.zerodds.rpc.RemoteException(\
293 \"unknown method id: \" + methodId, \
294 org.zerodds.rpc.RemoteExceptionCode.UNKNOWN_OPERATION);",
295 )
296 .map_err(fmt_err)?;
297 writeln!(body, "{ind}{ind}}}").map_err(fmt_err)?;
298 writeln!(body, "{ind}}}").map_err(fmt_err)?;
299
300 writeln!(body, "}}").map_err(fmt_err)?;
301 let source = wrap_compilation_unit_default(pkg, &body);
302 Ok(JavaFile {
303 package_path: pkg.to_string(),
304 class_name: class,
305 source,
306 })
307}
308
309pub fn emit_service_handler_interface(
317 svc: &ServiceDef,
318 pkg: &str,
319 opts: &JavaGenOptions,
320) -> Result<JavaFile, JavaGenError> {
321 let svc_class = sanitize_identifier(&svc.name)?;
322 let class = format!("{svc_class}Service");
323 let ind = indent_unit(opts);
324 let mut body = String::new();
325 writeln!(
326 body,
327 "/** Server-side handler interface for {svc_class}. Implementors \
328 provide the actual business logic; a {svc_class}Replier wires \
329 them to the RPC runtime. */",
330 )
331 .map_err(fmt_err)?;
332 writeln!(body, "public interface {class} {{").map_err(fmt_err)?;
333 for m in &svc.methods {
334 emit_handler_method_signature(&mut body, m, &ind)?;
335 }
336 writeln!(body, "}}").map_err(fmt_err)?;
337 let source = wrap_compilation_unit_default(pkg, &body);
338 Ok(JavaFile {
339 package_path: pkg.to_string(),
340 class_name: class,
341 source,
342 })
343}
344
345pub fn emit_service_files(
353 svc: &ServiceDef,
354 pkg: &str,
355 opts: &JavaGenOptions,
356) -> Result<Vec<JavaFile>, JavaGenError> {
357 Ok(alloc::vec![
358 emit_service_interface(svc, pkg, opts)?,
359 emit_service_interface_async(svc, pkg, opts)?,
360 emit_service_handler_interface(svc, pkg, opts)?,
361 emit_requester_class(svc, pkg, opts)?,
362 emit_replier_class(svc, pkg, opts)?,
363 ])
364}
365
366fn emit_sync_method_signature(
371 out: &mut String,
372 m: &MethodDef,
373 ind: &str,
374) -> Result<(), JavaGenError> {
375 let name = sanitize_identifier(&m.name)?;
376 let ret = sync_return_type(m)?;
377 let params = render_method_params(m)?;
378 let throws = String::new();
379 if m.oneway {
380 writeln!(out, "{ind}@org.zerodds.rpc.Oneway").map_err(fmt_err)?;
381 }
382 writeln!(out, "{ind}{ret} {name}({params}){throws};").map_err(fmt_err)?;
383 Ok(())
384}
385
386fn emit_async_method_signature(
387 out: &mut String,
388 m: &MethodDef,
389 ind: &str,
390) -> Result<(), JavaGenError> {
391 let name = sanitize_identifier(&m.name)?;
392 let async_name = format!("{name}Async");
393 let ret = async_return_type(m)?;
394 let params = render_method_params_async(m)?;
395 if m.oneway {
396 writeln!(out, "{ind}@org.zerodds.rpc.Oneway").map_err(fmt_err)?;
397 }
398 writeln!(out, "{ind}{ret} {async_name}({params});").map_err(fmt_err)?;
399 Ok(())
400}
401
402fn emit_handler_method_signature(
403 out: &mut String,
404 m: &MethodDef,
405 ind: &str,
406) -> Result<(), JavaGenError> {
407 emit_sync_method_signature(out, m, ind)
410}
411
412fn emit_requester_sync_impl(
413 out: &mut String,
414 m: &MethodDef,
415 ind: &str,
416) -> Result<(), JavaGenError> {
417 let name = sanitize_identifier(&m.name)?;
418 let async_name = format!("{name}Async");
419 let ret_ty = sync_return_type(m)?;
420 let params = render_method_params(m)?;
421 let arg_list = render_call_arglist(m)?;
422 writeln!(out, "{ind}@Override").map_err(fmt_err)?;
423 writeln!(out, "{ind}public {ret_ty} {name}({params}) {{").map_err(fmt_err)?;
424 if m.oneway {
425 writeln!(out, "{ind}{ind}{async_name}({arg_list});").map_err(fmt_err)?;
426 writeln!(out, "{ind}}}").map_err(fmt_err)?;
427 return Ok(());
428 }
429 if m.return_type.is_none() && m.params.iter().all(|p| p.direction == ParamDirection::In) {
430 writeln!(
431 out,
432 "{ind}{ind}try {{ {async_name}({arg_list}).get(); }} catch (Exception e) {{ \
433 throw new org.zerodds.rpc.RemoteException(e); }}",
434 )
435 .map_err(fmt_err)?;
436 } else {
437 writeln!(
438 out,
439 "{ind}{ind}try {{ return {async_name}({arg_list}).get(); }} catch (Exception e) {{ \
440 throw new org.zerodds.rpc.RemoteException(e); }}",
441 )
442 .map_err(fmt_err)?;
443 }
444 writeln!(out, "{ind}}}").map_err(fmt_err)?;
445 Ok(())
446}
447
448fn emit_requester_async_impl(
449 out: &mut String,
450 m: &MethodDef,
451 ind: &str,
452) -> Result<(), JavaGenError> {
453 let name = sanitize_identifier(&m.name)?;
454 let async_name = format!("{name}Async");
455 let ret_ty = async_return_type(m)?;
456 let params = render_method_params_async(m)?;
457 writeln!(out, "{ind}@Override").map_err(fmt_err)?;
458 writeln!(out, "{ind}public {ret_ty} {async_name}({params}) {{").map_err(fmt_err)?;
459
460 if cfg!(feature = "jni") {
466 if m.oneway {
467 writeln!(
468 out,
469 "{ind}{ind}// JNI: oneway -> Requester.sendRequest blocking + ignore reply.",
470 )
471 .map_err(fmt_err)?;
472 writeln!(
473 out,
474 "{ind}{ind}requesterFfi.sendRequest(/* xcdr2_encode(args) */ new byte[0], 0L);",
475 )
476 .map_err(fmt_err)?;
477 writeln!(
478 out,
479 "{ind}{ind}return java.util.concurrent.CompletableFuture.completedFuture(null);",
480 )
481 .map_err(fmt_err)?;
482 } else {
483 writeln!(
484 out,
485 "{ind}{ind}// JNI: async-Path via RustRequesterFFI.sendRequestAsync.",
486 )
487 .map_err(fmt_err)?;
488 writeln!(
489 out,
490 "{ind}{ind}return requesterFfi.sendRequestAsync(/* xcdr2_encode(args) */ new byte[0])",
491 )
492 .map_err(fmt_err)?;
493 writeln!(
494 out,
495 "{ind}{ind}{ind}.thenApply(reply -> /* xcdr2_decode<TOut>(reply) */ null);",
496 )
497 .map_err(fmt_err)?;
498 }
499 writeln!(out, "{ind}}}").map_err(fmt_err)?;
500 return Ok(());
501 }
502
503 if m.oneway {
504 writeln!(
505 out,
506 "{ind}{ind}requester.sendOneway(new Object[] {{ /* args */ }});",
507 )
508 .map_err(fmt_err)?;
509 writeln!(
510 out,
511 "{ind}{ind}return java.util.concurrent.CompletableFuture.completedFuture(null);",
512 )
513 .map_err(fmt_err)?;
514 } else {
515 writeln!(
516 out,
517 "{ind}{ind}return requester.sendRequest(new Object[] {{ /* args */ }});",
518 )
519 .map_err(fmt_err)?;
520 }
521 writeln!(out, "{ind}}}").map_err(fmt_err)?;
522 Ok(())
523}
524
525fn sync_return_type(m: &MethodDef) -> Result<String, JavaGenError> {
533 if m.oneway {
534 return Ok("void".to_string());
535 }
536 match &m.return_type {
537 None => Ok("void".to_string()),
538 Some(ts) => typespec_to_java_unboxed(ts),
539 }
540}
541
542fn async_return_type(m: &MethodDef) -> Result<String, JavaGenError> {
547 if m.oneway {
548 return Ok("java.util.concurrent.CompletableFuture<Void>".to_string());
549 }
550 match &m.return_type {
551 None => Ok("java.util.concurrent.CompletableFuture<Void>".to_string()),
552 Some(ts) => Ok(format!(
553 "java.util.concurrent.CompletableFuture<{}>",
554 typespec_to_java_boxed(ts)?
555 )),
556 }
557}
558
559fn render_method_params(m: &MethodDef) -> Result<String, JavaGenError> {
562 let mut parts: Vec<String> = Vec::new();
563 for p in &m.params {
564 parts.push(render_param(p)?);
565 }
566 Ok(parts.join(", "))
567}
568
569fn render_method_params_async(m: &MethodDef) -> Result<String, JavaGenError> {
571 render_method_params(m)
572}
573
574fn render_param(p: &ParamDef) -> Result<String, JavaGenError> {
575 let name = sanitize_identifier(&p.name)?;
576 let ty = match p.direction {
577 ParamDirection::In => typespec_to_java_unboxed(&p.type_ref)?,
578 ParamDirection::Out | ParamDirection::InOut => {
579 format!(
581 "org.zerodds.rpc.Holder<{}>",
582 typespec_to_java_boxed(&p.type_ref)?
583 )
584 }
585 };
586 Ok(format!("{ty} {name}"))
587}
588
589fn render_call_arglist(m: &MethodDef) -> Result<String, JavaGenError> {
590 let mut parts: Vec<String> = Vec::new();
591 for p in &m.params {
592 parts.push(sanitize_identifier(&p.name)?);
593 }
594 Ok(parts.join(", "))
595}
596
597fn typespec_to_java_unboxed(ts: &TypeSpec) -> Result<String, JavaGenError> {
602 match ts {
603 TypeSpec::Primitive(p) => Ok(primitive_to_java(*p).to_string()),
604 TypeSpec::Scoped(s) => Ok(scoped_to_java(s)),
605 TypeSpec::String(_) => Ok("String".to_string()),
606 TypeSpec::Sequence(s) => Ok(format!(
607 "java.util.List<{}>",
608 typespec_to_java_boxed(&s.elem)?
609 )),
610 TypeSpec::Map(mm) => Ok(format!(
611 "java.util.Map<{}, {}>",
612 typespec_to_java_boxed(&mm.key)?,
613 typespec_to_java_boxed(&mm.value)?,
614 )),
615 TypeSpec::Fixed(_) => Err(JavaGenError::UnsupportedConstruct {
616 construct: "fixed".into(),
617 context: None,
618 }),
619 TypeSpec::Any => Err(JavaGenError::UnsupportedConstruct {
620 construct: "any".into(),
621 context: None,
622 }),
623 }
624}
625
626fn typespec_to_java_boxed(ts: &TypeSpec) -> Result<String, JavaGenError> {
627 match ts {
628 TypeSpec::Primitive(p) => Ok(primitive_to_java_boxed(*p).to_string()),
629 TypeSpec::Scoped(s) => Ok(scoped_to_java(s)),
630 TypeSpec::String(_) => Ok("String".to_string()),
631 TypeSpec::Sequence(s) => Ok(format!(
632 "java.util.List<{}>",
633 typespec_to_java_boxed(&s.elem)?
634 )),
635 TypeSpec::Map(mm) => Ok(format!(
636 "java.util.Map<{}, {}>",
637 typespec_to_java_boxed(&mm.key)?,
638 typespec_to_java_boxed(&mm.value)?,
639 )),
640 TypeSpec::Fixed(_) => Err(JavaGenError::UnsupportedConstruct {
641 construct: "fixed".into(),
642 context: None,
643 }),
644 TypeSpec::Any => Err(JavaGenError::UnsupportedConstruct {
645 construct: "any".into(),
646 context: None,
647 }),
648 }
649}
650
651fn scoped_to_java(s: &ScopedName) -> String {
652 s.parts
653 .iter()
654 .map(|p| p.text.clone())
655 .collect::<Vec<_>>()
656 .join(".")
657}
658
659#[allow(dead_code)]
662fn _unused_marker(_i: IntegerType) {
663 let _ = integer_to_java;
664 let _ = floating_to_java;
665 let _ = integer_to_java_boxed;
666 let _ = floating_to_java_boxed;
667 let _ = PrimitiveType::Boolean;
668}