1extern crate alloc;
75
76use alloc::format;
77use alloc::string::{String, ToString};
78use alloc::vec::Vec;
79use core::fmt::Write;
80
81use zerodds_idl::ast::{IntegerType, PrimitiveType, ScopedName, TypeSpec};
82use zerodds_rpc::service_mapping::{MethodDef, ParamDef, ParamDirection, ServiceDef};
83
84use crate::JavaGenOptions;
85use crate::emitter::{JavaFile, fmt_err, indent_unit, wrap_compilation_unit_default};
86use crate::error::JavaGenError;
87use crate::keywords::sanitize_identifier;
88use crate::type_map::{
89 floating_to_java, floating_to_java_boxed, integer_to_java, integer_to_java_boxed,
90 primitive_to_java, primitive_to_java_boxed,
91};
92
93pub fn emit_service_interface(
102 svc: &ServiceDef,
103 pkg: &str,
104 opts: &JavaGenOptions,
105) -> Result<JavaFile, JavaGenError> {
106 let class = sanitize_identifier(&svc.name)?;
107 let ind = indent_unit(opts);
108 let mut body = String::new();
109 writeln!(body, "/** Synchronous service interface for {class}. */").map_err(fmt_err)?;
110 writeln!(body, "@org.zerodds.rpc.Service(\"{}\")", svc.name).map_err(fmt_err)?;
111 writeln!(body, "public interface {class} {{").map_err(fmt_err)?;
112 for m in &svc.methods {
113 emit_sync_method_signature(&mut body, m, &ind)?;
114 }
115 writeln!(body, "}}").map_err(fmt_err)?;
116 let source = wrap_compilation_unit_default(pkg, &body);
117 Ok(JavaFile {
118 package_path: pkg.to_string(),
119 class_name: class,
120 source,
121 })
122}
123
124pub fn emit_service_interface_async(
129 svc: &ServiceDef,
130 pkg: &str,
131 opts: &JavaGenOptions,
132) -> Result<JavaFile, JavaGenError> {
133 let svc_class = sanitize_identifier(&svc.name)?;
134 let class = format!("{svc_class}Async");
135 let ind = indent_unit(opts);
136 let mut body = String::new();
137 writeln!(
138 body,
139 "/** Asynchronous service interface for {svc_class}. */"
140 )
141 .map_err(fmt_err)?;
142 writeln!(body, "public interface {class} {{").map_err(fmt_err)?;
143 for m in &svc.methods {
144 emit_async_method_signature(&mut body, m, &ind)?;
145 }
146 writeln!(body, "}}").map_err(fmt_err)?;
147 let source = wrap_compilation_unit_default(pkg, &body);
148 Ok(JavaFile {
149 package_path: pkg.to_string(),
150 class_name: class,
151 source,
152 })
153}
154
155pub fn emit_requester_class(
160 svc: &ServiceDef,
161 pkg: &str,
162 opts: &JavaGenOptions,
163) -> Result<JavaFile, JavaGenError> {
164 let svc_class = sanitize_identifier(&svc.name)?;
165 let class = format!("{svc_class}Requester");
166 let ind = indent_unit(opts);
167 let mut body = String::new();
168 writeln!(
169 body,
170 "/** Client-side proxy for {svc_class}. Implements both the \
171 synchronous {svc_class} interface and the {svc_class}Async \
172 interface. */",
173 )
174 .map_err(fmt_err)?;
175 writeln!(
176 body,
177 "public final class {class} implements {svc_class}, {svc_class}Async {{",
178 )
179 .map_err(fmt_err)?;
180 writeln!(
181 body,
182 "{ind}private final org.zerodds.rpc.Requester<Object, Object> requester;",
183 )
184 .map_err(fmt_err)?;
185 writeln!(body).map_err(fmt_err)?;
186 writeln!(
187 body,
188 "{ind}public {class}(org.zerodds.rpc.Requester<Object, Object> requester) {{",
189 )
190 .map_err(fmt_err)?;
191 writeln!(body, "{ind}{ind}this.requester = requester;").map_err(fmt_err)?;
192 writeln!(body, "{ind}}}").map_err(fmt_err)?;
193 writeln!(body).map_err(fmt_err)?;
194
195 for m in &svc.methods {
197 emit_requester_sync_impl(&mut body, m, &ind)?;
198 }
199 writeln!(body).map_err(fmt_err)?;
200 for m in &svc.methods {
202 emit_requester_async_impl(&mut body, m, &ind)?;
203 }
204
205 writeln!(body, "}}").map_err(fmt_err)?;
206 let source = wrap_compilation_unit_default(pkg, &body);
207 Ok(JavaFile {
208 package_path: pkg.to_string(),
209 class_name: class,
210 source,
211 })
212}
213
214pub fn emit_replier_class(
219 svc: &ServiceDef,
220 pkg: &str,
221 opts: &JavaGenOptions,
222) -> Result<JavaFile, JavaGenError> {
223 let svc_class = sanitize_identifier(&svc.name)?;
224 let class = format!("{svc_class}Replier");
225 let handler_iface = format!("{svc_class}Service");
226 let ind = indent_unit(opts);
227 let mut body = String::new();
228 writeln!(
229 body,
230 "/** Server-side replier for {svc_class}. Wires a {handler_iface} \
231 implementation to the underlying RPC runtime. */",
232 )
233 .map_err(fmt_err)?;
234 writeln!(body, "public final class {class} {{").map_err(fmt_err)?;
235 writeln!(
236 body,
237 "{ind}private final org.zerodds.rpc.Replier<Object, Object> replier;",
238 )
239 .map_err(fmt_err)?;
240 writeln!(body, "{ind}private final {handler_iface} handler;").map_err(fmt_err)?;
241 writeln!(body).map_err(fmt_err)?;
242 writeln!(
243 body,
244 "{ind}public {class}(org.zerodds.rpc.Replier<Object, Object> replier, {handler_iface} handler) {{",
245 )
246 .map_err(fmt_err)?;
247 writeln!(body, "{ind}{ind}this.replier = replier;").map_err(fmt_err)?;
248 writeln!(body, "{ind}{ind}this.handler = handler;").map_err(fmt_err)?;
249 writeln!(body, "{ind}}}").map_err(fmt_err)?;
250 writeln!(body).map_err(fmt_err)?;
251
252 writeln!(
255 body,
256 "{ind}/** Dispatches an incoming request by method id. */",
257 )
258 .map_err(fmt_err)?;
259 writeln!(
260 body,
261 "{ind}public Object dispatch(int methodId, Object args) {{",
262 )
263 .map_err(fmt_err)?;
264 writeln!(body, "{ind}{ind}switch (methodId) {{").map_err(fmt_err)?;
265 for (idx, m) in svc.methods.iter().enumerate() {
266 let mname = sanitize_identifier(&m.name)?;
267 let case_id = idx + 1;
268 writeln!(body, "{ind}{ind}{ind}case {case_id}: {{").map_err(fmt_err)?;
273 writeln!(body, "{ind}{ind}{ind}{ind}Object[] __a = (Object[]) args;").map_err(fmt_err)?;
274 let mut req_idx = 0usize;
275 let mut call_args: Vec<String> = Vec::new();
276 for p in &m.params {
277 let pname = sanitize_identifier(&p.name)?;
278 let boxed = typespec_to_java_boxed(&p.type_ref)?;
279 match p.direction {
280 ParamDirection::In => {
281 call_args.push(format!("({boxed}) __a[{req_idx}]"));
282 req_idx += 1;
283 }
284 ParamDirection::InOut => {
285 writeln!(
286 body,
287 "{ind}{ind}{ind}{ind}org.zerodds.rpc.Holder<{boxed}> {pname} = \
288 new org.zerodds.rpc.Holder<>(({boxed}) __a[{req_idx}]);",
289 )
290 .map_err(fmt_err)?;
291 call_args.push(pname);
292 req_idx += 1;
293 }
294 ParamDirection::Out => {
295 writeln!(
296 body,
297 "{ind}{ind}{ind}{ind}org.zerodds.rpc.Holder<{boxed}> {pname} = \
298 new org.zerodds.rpc.Holder<>();",
299 )
300 .map_err(fmt_err)?;
301 call_args.push(pname);
302 }
303 }
304 }
305 let call = format!("handler.{mname}({})", call_args.join(", "));
306 if m.return_type.is_some() {
307 let ret_unboxed = sync_return_type(m)?;
308 writeln!(body, "{ind}{ind}{ind}{ind}{ret_unboxed} __ret = {call};").map_err(fmt_err)?;
309 } else {
310 writeln!(body, "{ind}{ind}{ind}{ind}{call};").map_err(fmt_err)?;
311 }
312 let mut reply_parts: Vec<String> = Vec::new();
314 reply_parts.push(if m.return_type.is_some() {
315 "__ret".to_string()
316 } else {
317 "null".to_string()
318 });
319 for p in &m.params {
320 if p.direction != ParamDirection::In {
321 reply_parts.push(format!("{}.value", sanitize_identifier(&p.name)?));
322 }
323 }
324 writeln!(
325 body,
326 "{ind}{ind}{ind}{ind}return new Object[] {{ {} }};",
327 reply_parts.join(", ")
328 )
329 .map_err(fmt_err)?;
330 writeln!(body, "{ind}{ind}{ind}}}").map_err(fmt_err)?;
331 }
332 writeln!(
333 body,
334 "{ind}{ind}{ind}default: throw new org.zerodds.rpc.RemoteException(\
335 \"unknown method id: \" + methodId, \
336 org.zerodds.rpc.RemoteExceptionCode.UNKNOWN_OPERATION);",
337 )
338 .map_err(fmt_err)?;
339 writeln!(body, "{ind}{ind}}}").map_err(fmt_err)?;
340 writeln!(body, "{ind}}}").map_err(fmt_err)?;
341
342 writeln!(body, "}}").map_err(fmt_err)?;
343 let source = wrap_compilation_unit_default(pkg, &body);
344 Ok(JavaFile {
345 package_path: pkg.to_string(),
346 class_name: class,
347 source,
348 })
349}
350
351pub fn emit_service_handler_interface(
359 svc: &ServiceDef,
360 pkg: &str,
361 opts: &JavaGenOptions,
362) -> Result<JavaFile, JavaGenError> {
363 let svc_class = sanitize_identifier(&svc.name)?;
364 let class = format!("{svc_class}Service");
365 let ind = indent_unit(opts);
366 let mut body = String::new();
367 writeln!(
368 body,
369 "/** Server-side handler interface for {svc_class}. Implementors \
370 provide the actual business logic; a {svc_class}Replier wires \
371 them to the RPC runtime. */",
372 )
373 .map_err(fmt_err)?;
374 writeln!(body, "public interface {class} {{").map_err(fmt_err)?;
375 for m in &svc.methods {
376 emit_handler_method_signature(&mut body, m, &ind)?;
377 }
378 writeln!(body, "}}").map_err(fmt_err)?;
379 let source = wrap_compilation_unit_default(pkg, &body);
380 Ok(JavaFile {
381 package_path: pkg.to_string(),
382 class_name: class,
383 source,
384 })
385}
386
387pub fn emit_service_files(
395 svc: &ServiceDef,
396 pkg: &str,
397 opts: &JavaGenOptions,
398) -> Result<Vec<JavaFile>, JavaGenError> {
399 Ok(alloc::vec![
400 emit_service_interface(svc, pkg, opts)?,
401 emit_service_interface_async(svc, pkg, opts)?,
402 emit_service_handler_interface(svc, pkg, opts)?,
403 emit_requester_class(svc, pkg, opts)?,
404 emit_replier_class(svc, pkg, opts)?,
405 ])
406}
407
408fn emit_sync_method_signature(
413 out: &mut String,
414 m: &MethodDef,
415 ind: &str,
416) -> Result<(), JavaGenError> {
417 let name = sanitize_identifier(&m.name)?;
418 let ret = sync_return_type(m)?;
419 let params = render_method_params(m)?;
420 let throws = String::new();
421 if m.oneway {
422 writeln!(out, "{ind}@org.zerodds.rpc.Oneway").map_err(fmt_err)?;
423 }
424 writeln!(out, "{ind}{ret} {name}({params}){throws};").map_err(fmt_err)?;
425 Ok(())
426}
427
428fn emit_async_method_signature(
429 out: &mut String,
430 m: &MethodDef,
431 ind: &str,
432) -> Result<(), JavaGenError> {
433 let name = sanitize_identifier(&m.name)?;
434 let async_name = format!("{name}Async");
435 let ret = async_return_type(m)?;
436 let params = render_method_params_async(m)?;
437 if m.oneway {
438 writeln!(out, "{ind}@org.zerodds.rpc.Oneway").map_err(fmt_err)?;
439 }
440 writeln!(out, "{ind}{ret} {async_name}({params});").map_err(fmt_err)?;
441 Ok(())
442}
443
444fn emit_handler_method_signature(
445 out: &mut String,
446 m: &MethodDef,
447 ind: &str,
448) -> Result<(), JavaGenError> {
449 emit_sync_method_signature(out, m, ind)
452}
453
454fn emit_requester_sync_impl(
455 out: &mut String,
456 m: &MethodDef,
457 ind: &str,
458) -> Result<(), JavaGenError> {
459 let name = sanitize_identifier(&m.name)?;
460 let async_name = format!("{name}Async");
461 let ret_ty = sync_return_type(m)?;
462 let params = render_method_params(m)?;
463 let arg_list = render_call_arglist(m)?;
464 writeln!(out, "{ind}@Override").map_err(fmt_err)?;
465 writeln!(out, "{ind}public {ret_ty} {name}({params}) {{").map_err(fmt_err)?;
466 if m.oneway {
467 writeln!(out, "{ind}{ind}{async_name}({arg_list});").map_err(fmt_err)?;
468 writeln!(out, "{ind}}}").map_err(fmt_err)?;
469 return Ok(());
470 }
471 if m.return_type.is_none() {
472 writeln!(
475 out,
476 "{ind}{ind}try {{ {async_name}({arg_list}).get(); }} catch (Exception e) {{ \
477 throw new org.zerodds.rpc.RemoteException(e); }}",
478 )
479 .map_err(fmt_err)?;
480 } else {
481 writeln!(
482 out,
483 "{ind}{ind}try {{ return {async_name}({arg_list}).get(); }} catch (Exception e) {{ \
484 throw new org.zerodds.rpc.RemoteException(e); }}",
485 )
486 .map_err(fmt_err)?;
487 }
488 writeln!(out, "{ind}}}").map_err(fmt_err)?;
489 Ok(())
490}
491
492fn emit_requester_async_impl(
493 out: &mut String,
494 m: &MethodDef,
495 ind: &str,
496) -> Result<(), JavaGenError> {
497 let name = sanitize_identifier(&m.name)?;
498 let async_name = format!("{name}Async");
499 let ret_ty = async_return_type(m)?;
500 let params = render_method_params_async(m)?;
501 writeln!(out, "{ind}@Override").map_err(fmt_err)?;
502 writeln!(out, "{ind}public {ret_ty} {async_name}({params}) {{").map_err(fmt_err)?;
503
504 let req_args = request_args_array(m)?;
508 if m.oneway {
509 writeln!(out, "{ind}{ind}requester.sendOneway({req_args});").map_err(fmt_err)?;
510 writeln!(
511 out,
512 "{ind}{ind}return java.util.concurrent.CompletableFuture.completedFuture(null);",
513 )
514 .map_err(fmt_err)?;
515 } else {
516 let has_writeback = m.params.iter().any(|p| p.direction != ParamDirection::In);
517 if m.return_type.is_none() && !has_writeback {
518 writeln!(
520 out,
521 "{ind}{ind}return requester.sendRequest({req_args}).thenApply(__reply -> null);",
522 )
523 .map_err(fmt_err)?;
524 } else {
525 writeln!(
526 out,
527 "{ind}{ind}return requester.sendRequest({req_args}).thenApply(__reply -> {{",
528 )
529 .map_err(fmt_err)?;
530 writeln!(out, "{ind}{ind}{ind}Object[] __out = (Object[]) __reply;",)
531 .map_err(fmt_err)?;
532 let mut k = 1usize;
534 for p in &m.params {
535 if p.direction == ParamDirection::In {
536 continue;
537 }
538 let pname = sanitize_identifier(&p.name)?;
539 let boxed = typespec_to_java_boxed(&p.type_ref)?;
540 writeln!(out, "{ind}{ind}{ind}{pname}.value = ({boxed}) __out[{k}];",)
541 .map_err(fmt_err)?;
542 k += 1;
543 }
544 match &m.return_type {
545 None => {
546 writeln!(out, "{ind}{ind}{ind}return null;").map_err(fmt_err)?;
547 }
548 Some(ts) => {
549 let boxed = typespec_to_java_boxed(ts)?;
550 writeln!(out, "{ind}{ind}{ind}return ({boxed}) __out[0];").map_err(fmt_err)?;
551 }
552 }
553 writeln!(out, "{ind}{ind}}});").map_err(fmt_err)?;
554 }
555 }
556 writeln!(out, "{ind}}}").map_err(fmt_err)?;
557 Ok(())
558}
559
560fn sync_return_type(m: &MethodDef) -> Result<String, JavaGenError> {
568 if m.oneway {
569 return Ok("void".to_string());
570 }
571 match &m.return_type {
572 None => Ok("void".to_string()),
573 Some(ts) => typespec_to_java_unboxed(ts),
574 }
575}
576
577fn async_return_type(m: &MethodDef) -> Result<String, JavaGenError> {
582 if m.oneway {
583 return Ok("java.util.concurrent.CompletableFuture<Void>".to_string());
584 }
585 match &m.return_type {
586 None => Ok("java.util.concurrent.CompletableFuture<Void>".to_string()),
587 Some(ts) => Ok(format!(
588 "java.util.concurrent.CompletableFuture<{}>",
589 typespec_to_java_boxed(ts)?
590 )),
591 }
592}
593
594fn render_method_params(m: &MethodDef) -> Result<String, JavaGenError> {
597 let mut parts: Vec<String> = Vec::new();
598 for p in &m.params {
599 parts.push(render_param(p)?);
600 }
601 Ok(parts.join(", "))
602}
603
604fn render_method_params_async(m: &MethodDef) -> Result<String, JavaGenError> {
606 render_method_params(m)
607}
608
609fn render_param(p: &ParamDef) -> Result<String, JavaGenError> {
610 let name = sanitize_identifier(&p.name)?;
611 let ty = match p.direction {
612 ParamDirection::In => typespec_to_java_unboxed(&p.type_ref)?,
613 ParamDirection::Out | ParamDirection::InOut => {
614 format!(
616 "org.zerodds.rpc.Holder<{}>",
617 typespec_to_java_boxed(&p.type_ref)?
618 )
619 }
620 };
621 Ok(format!("{ty} {name}"))
622}
623
624fn render_call_arglist(m: &MethodDef) -> Result<String, JavaGenError> {
625 let mut parts: Vec<String> = Vec::new();
626 for p in &m.params {
627 parts.push(sanitize_identifier(&p.name)?);
628 }
629 Ok(parts.join(", "))
630}
631
632fn request_args_array(m: &MethodDef) -> Result<String, JavaGenError> {
636 let mut parts: Vec<String> = Vec::new();
637 for p in &m.params {
638 let pname = sanitize_identifier(&p.name)?;
639 match p.direction {
640 ParamDirection::In => parts.push(pname),
641 ParamDirection::InOut => parts.push(format!("{pname}.value")),
642 ParamDirection::Out => {}
643 }
644 }
645 Ok(format!("new Object[] {{ {} }}", parts.join(", ")))
646}
647
648fn typespec_to_java_unboxed(ts: &TypeSpec) -> Result<String, JavaGenError> {
653 match ts {
654 TypeSpec::Primitive(p) => Ok(primitive_to_java(*p).to_string()),
655 TypeSpec::Scoped(s) => Ok(scoped_to_java(s)),
656 TypeSpec::String(_) => Ok("String".to_string()),
657 TypeSpec::Sequence(s) => Ok(format!(
658 "java.util.List<{}>",
659 typespec_to_java_boxed(&s.elem)?
660 )),
661 TypeSpec::Map(mm) => Ok(format!(
662 "java.util.Map<{}, {}>",
663 typespec_to_java_boxed(&mm.key)?,
664 typespec_to_java_boxed(&mm.value)?,
665 )),
666 TypeSpec::Fixed(_) => Err(JavaGenError::UnsupportedConstruct {
667 construct: "fixed".into(),
668 context: None,
669 }),
670 TypeSpec::Any => Err(JavaGenError::UnsupportedConstruct {
671 construct: "any".into(),
672 context: None,
673 }),
674 }
675}
676
677fn typespec_to_java_boxed(ts: &TypeSpec) -> Result<String, JavaGenError> {
678 match ts {
679 TypeSpec::Primitive(p) => Ok(primitive_to_java_boxed(*p).to_string()),
680 TypeSpec::Scoped(s) => Ok(scoped_to_java(s)),
681 TypeSpec::String(_) => Ok("String".to_string()),
682 TypeSpec::Sequence(s) => Ok(format!(
683 "java.util.List<{}>",
684 typespec_to_java_boxed(&s.elem)?
685 )),
686 TypeSpec::Map(mm) => Ok(format!(
687 "java.util.Map<{}, {}>",
688 typespec_to_java_boxed(&mm.key)?,
689 typespec_to_java_boxed(&mm.value)?,
690 )),
691 TypeSpec::Fixed(_) => Err(JavaGenError::UnsupportedConstruct {
692 construct: "fixed".into(),
693 context: None,
694 }),
695 TypeSpec::Any => Err(JavaGenError::UnsupportedConstruct {
696 construct: "any".into(),
697 context: None,
698 }),
699 }
700}
701
702fn scoped_to_java(s: &ScopedName) -> String {
703 s.parts
704 .iter()
705 .map(|p| p.text.clone())
706 .collect::<Vec<_>>()
707 .join(".")
708}
709
710#[allow(dead_code)]
713fn _unused_marker(_i: IntegerType) {
714 let _ = integer_to_java;
715 let _ = floating_to_java;
716 let _ = integer_to_java_boxed;
717 let _ = floating_to_java_boxed;
718 let _ = PrimitiveType::Boolean;
719}