Skip to main content

ri/java/classes/
module_rpc.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! you may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Module RPC JNI Bindings
19//!
20//! JNI bindings for Ri module RPC classes.
21
22use std::sync::Arc;
23
24use jni::JNIEnv;
25use jni::objects::{JClass, JString};
26use jni::sys::{jlong, jboolean, jbyteArray, jstring, jobject};
27use jni::objects::JValue;
28use crate::module_rpc::{RiModuleRPC, RiModuleClient, RiModuleEndpoint, RiMethodCall, RiMethodResponse};
29use crate::java::exception::{check_not_null, throw_ri_error};
30
31/// Converts a Java string argument into a `String`, throwing a RiError and
32/// returning `None` when the argument cannot be read.
33fn read_string(env: &mut JNIEnv, value: &JString, arg: &str) -> Option<String> {
34    match env.get_string(value) {
35        Ok(s) => Some(s.into()),
36        Err(e) => {
37            throw_ri_error(env, &format!("Failed to read {arg}: {e}"));
38            None
39        }
40    }
41}
42
43/// Converts a Java byte array argument into a `Vec<u8>`, returning an empty
44/// vector for a null array.
45fn read_byte_array(env: &mut JNIEnv, array: jbyteArray, arg: &str) -> Option<Vec<u8>> {
46    if array.is_null() {
47        return Some(Vec::new());
48    }
49    match env.convert_byte_array(unsafe { jni::objects::JByteArray::from_raw(array) }) {
50        Ok(v) => Some(v),
51        Err(e) => {
52            throw_ri_error(env, &format!("Failed to read {arg}: {e}"));
53            None
54        }
55    }
56}
57
58/// Copies a `Vec<u8>` into a fresh Java byte array, returning a null pointer
59/// when allocation fails.
60fn write_byte_array(env: &mut JNIEnv, data: &[u8]) -> jbyteArray {
61    match env.byte_array_from_slice(data) {
62        Ok(array) => array.into_raw(),
63        Err(_) => std::ptr::null_mut(),
64    }
65}
66
67/// Clones the `Arc<RiModuleRPC>` behind a raw `Arc::into_raw` pointer without
68/// consuming the original reference.
69///
70/// # Safety
71///
72/// `ptr` must have been produced by `Arc::into_raw` over an `Arc<RiModuleRPC>`
73/// that is still alive.
74unsafe fn clone_rpc_arc(ptr: jlong) -> Option<Arc<RiModuleRPC>> {
75    if ptr == 0 {
76        return None;
77    }
78    let arc = Arc::from_raw(ptr as *const RiModuleRPC);
79    let cloned = arc.clone();
80    let _ = Arc::into_raw(arc);
81    Some(cloned)
82}
83
84// =============================================================================
85// RiModuleRPC JNI Bindings
86// =============================================================================
87
88#[no_mangle]
89pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleRPC_new0(
90    _env: JNIEnv,
91    _class: JClass,
92) -> jlong {
93    Arc::into_raw(Arc::new(RiModuleRPC::new())) as jlong
94}
95
96#[no_mangle]
97pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleRPC_free0(
98    _env: JNIEnv,
99    _class: JClass,
100    ptr: jlong,
101) {
102    if ptr != 0 {
103        unsafe {
104            let _ = Arc::from_raw(ptr as *const RiModuleRPC);
105        }
106    }
107}
108
109// =============================================================================
110// RiModuleClient JNI Bindings
111// =============================================================================
112
113#[no_mangle]
114pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleClient_new0(
115    mut env: JNIEnv,
116    _class: JClass,
117    rpc_ptr: jlong,
118) -> jlong {
119    match unsafe { clone_rpc_arc(rpc_ptr) } {
120        Some(arc) => Box::into_raw(Box::new(RiModuleClient::new(arc))) as jlong,
121        None => {
122            throw_ri_error(&mut env, "RiModuleRPC pointer is null");
123            0
124        }
125    }
126}
127
128#[no_mangle]
129pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleClient_call0(
130    mut env: JNIEnv,
131    _class: JClass,
132    ptr: jlong,
133    module_name: JString,
134    method_name: JString,
135    params: jbyteArray,
136) -> jlong {
137    if !check_not_null(&mut env, ptr, "RiModuleClient") {
138        return 0;
139    }
140    let (Some(module), Some(method), Some(params_vec)) = (
141        read_string(&mut env, &module_name, "module name"),
142        read_string(&mut env, &method_name, "method name"),
143        read_byte_array(&mut env, params, "params"),
144    ) else {
145        return 0;
146    };
147
148    let client = unsafe { &*(ptr as *const RiModuleClient) };
149    crate::java::runtime::block_on_jni(&mut env, "RiModuleClient::call", async move {
150        client.call(&module, &method, params_vec).await
151    })
152    .map(|response| Box::into_raw(Box::new(response)) as jlong)
153    .unwrap_or(0)
154}
155
156#[no_mangle]
157pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleClient_callWithTimeout0(
158    mut env: JNIEnv,
159    _class: JClass,
160    ptr: jlong,
161    module_name: JString,
162    method_name: JString,
163    params: jbyteArray,
164    timeout_ms: jlong,
165) -> jlong {
166    if !check_not_null(&mut env, ptr, "RiModuleClient") {
167        return 0;
168    }
169    let (Some(module), Some(method), Some(params_vec)) = (
170        read_string(&mut env, &module_name, "module name"),
171        read_string(&mut env, &method_name, "method name"),
172        read_byte_array(&mut env, params, "params"),
173    ) else {
174        return 0;
175    };
176    let timeout = crate::java::runtime::ttl_from_jlong(timeout_ms).unwrap_or(5000);
177
178    let client = unsafe { &*(ptr as *const RiModuleClient) };
179    crate::java::runtime::block_on_jni(&mut env, "RiModuleClient::call_with_timeout", async move {
180        client
181            .call_with_timeout(&module, &method, params_vec, timeout)
182            .await
183    })
184    .map(|response| Box::into_raw(Box::new(response)) as jlong)
185    .unwrap_or(0)
186}
187
188#[no_mangle]
189pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleClient_free0(
190    _env: JNIEnv,
191    _class: JClass,
192    ptr: jlong,
193) {
194    if ptr != 0 {
195        unsafe {
196            let _ = Box::from_raw(ptr as *mut RiModuleClient);
197        }
198    }
199}
200
201// =============================================================================
202// RiModuleEndpoint JNI Bindings
203// =============================================================================
204
205#[no_mangle]
206pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleEndpoint_new0(
207    mut env: JNIEnv,
208    _class: JClass,
209    module_name: JString,
210) -> jlong {
211    let Some(module) = read_string(&mut env, &module_name, "module name") else {
212        return 0;
213    };
214
215    let endpoint = Box::new(RiModuleEndpoint::new(&module));
216    Box::into_raw(endpoint) as jlong
217}
218
219#[no_mangle]
220pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleEndpoint_getModuleName0<'local>(
221    mut env: JNIEnv<'local>,
222    _class: JClass<'local>,
223    ptr: jlong,
224) -> jstring {
225    if !check_not_null(&mut env, ptr, "RiModuleEndpoint") {
226        return std::ptr::null_mut();
227    }
228
229    let endpoint = unsafe { &*(ptr as *const RiModuleEndpoint) };
230    match env.new_string(endpoint.module_name()) {
231        Ok(s) => s.into_raw(),
232        Err(_) => std::ptr::null_mut(),
233    }
234}
235
236#[no_mangle]
237pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleEndpoint_listMethods0<'local>(
238    mut env: JNIEnv<'local>,
239    _class: JClass<'local>,
240    ptr: jlong,
241) -> jobject {
242    if !check_not_null(&mut env, ptr, "RiModuleEndpoint") {
243        return std::ptr::null_mut();
244    }
245
246    let endpoint = unsafe { &*(ptr as *const RiModuleEndpoint) };
247    let methods = futures::executor::block_on(endpoint.list_methods());
248
249    // Java declares `List<String>` — must return an ArrayList, not a String[].
250    let Ok(list) = env.new_object("java/util/ArrayList", "()V", &[]) else {
251        return std::ptr::null_mut();
252    };
253    for method in methods.iter() {
254        let Ok(s) = env.new_string(method) else {
255            continue;
256        };
257        let _ = env.call_method(
258            &list,
259            "add",
260            "(Ljava/lang/Object;)Z",
261            &[JValue::Object(&s)],
262        );
263    }
264    list.into_raw()
265}
266
267#[no_mangle]
268pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiModuleEndpoint_free0(
269    _env: JNIEnv,
270    _class: JClass,
271    ptr: jlong,
272) {
273    if ptr != 0 {
274        unsafe {
275            let _ = Box::from_raw(ptr as *mut RiModuleEndpoint);
276        }
277    }
278}
279
280// =============================================================================
281// RiMethodCall JNI Bindings
282// =============================================================================
283
284#[no_mangle]
285pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodCall_new0(
286    mut env: JNIEnv,
287    _class: JClass,
288    method_name: JString,
289    params: jbyteArray,
290) -> jlong {
291    let (Some(method), Some(params_vec)) = (
292        read_string(&mut env, &method_name, "method name"),
293        read_byte_array(&mut env, params, "params"),
294    ) else {
295        return 0;
296    };
297
298    let call = Box::new(RiMethodCall::new(method, params_vec));
299    Box::into_raw(call) as jlong
300}
301
302#[no_mangle]
303pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodCall_getMethodName0<'local>(
304    mut env: JNIEnv<'local>,
305    _class: JClass<'local>,
306    ptr: jlong,
307) -> jstring {
308    if !check_not_null(&mut env, ptr, "RiMethodCall") {
309        return std::ptr::null_mut();
310    }
311
312    let call = unsafe { &*(ptr as *const RiMethodCall) };
313    match env.new_string(&call.method_name) {
314        Ok(s) => s.into_raw(),
315        Err(_) => std::ptr::null_mut(),
316    }
317}
318
319#[no_mangle]
320pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodCall_getParams0(
321    mut env: JNIEnv,
322    _class: JClass,
323    ptr: jlong,
324) -> jbyteArray {
325    if !check_not_null(&mut env, ptr, "RiMethodCall") {
326        return std::ptr::null_mut();
327    }
328
329    let call = unsafe { &*(ptr as *const RiMethodCall) };
330    write_byte_array(&mut env, &call.params)
331}
332
333#[no_mangle]
334pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodCall_getTimeoutMs0(
335    mut env: JNIEnv,
336    _class: JClass,
337    ptr: jlong,
338) -> jlong {
339    if !check_not_null(&mut env, ptr, "RiMethodCall") {
340        return 0;
341    }
342
343    let call = unsafe { &*(ptr as *const RiMethodCall) };
344    call.timeout_ms as jlong
345}
346
347#[no_mangle]
348pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodCall_setTimeoutMs0(
349    mut env: JNIEnv,
350    _class: JClass,
351    ptr: jlong,
352    timeout_ms: jlong,
353) {
354    if !check_not_null(&mut env, ptr, "RiMethodCall") {
355        return;
356    }
357
358    let call = unsafe { &mut *(ptr as *mut RiMethodCall) };
359    call.timeout_ms = timeout_ms.max(0) as u64;
360}
361
362#[no_mangle]
363pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodCall_free0(
364    _env: JNIEnv,
365    _class: JClass,
366    ptr: jlong,
367) {
368    if ptr != 0 {
369        unsafe {
370            let _ = Box::from_raw(ptr as *mut RiMethodCall);
371        }
372    }
373}
374
375// =============================================================================
376// RiMethodResponse JNI Bindings
377// =============================================================================
378
379#[no_mangle]
380pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodResponse_isSuccess0(
381    mut env: JNIEnv,
382    _class: JClass,
383    ptr: jlong,
384) -> jboolean {
385    if !check_not_null(&mut env, ptr, "RiMethodResponse") {
386        return 0;
387    }
388
389    let response = unsafe { &*(ptr as *const RiMethodResponse) };
390    response.success as jboolean
391}
392
393#[no_mangle]
394pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodResponse_getData0(
395    mut env: JNIEnv,
396    _class: JClass,
397    ptr: jlong,
398) -> jbyteArray {
399    if !check_not_null(&mut env, ptr, "RiMethodResponse") {
400        return std::ptr::null_mut();
401    }
402
403    let response = unsafe { &*(ptr as *const RiMethodResponse) };
404    write_byte_array(&mut env, &response.data)
405}
406
407#[no_mangle]
408pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodResponse_getError0<'local>(
409    mut env: JNIEnv<'local>,
410    _class: JClass<'local>,
411    ptr: jlong,
412) -> jstring {
413    if !check_not_null(&mut env, ptr, "RiMethodResponse") {
414        return std::ptr::null_mut();
415    }
416
417    let response = unsafe { &*(ptr as *const RiMethodResponse) };
418    match env.new_string(&response.error) {
419        Ok(s) => s.into_raw(),
420        Err(_) => std::ptr::null_mut(),
421    }
422}
423
424#[no_mangle]
425pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodResponse_isTimeout0(
426    mut env: JNIEnv,
427    _class: JClass,
428    ptr: jlong,
429) -> jboolean {
430    if !check_not_null(&mut env, ptr, "RiMethodResponse") {
431        return 0;
432    }
433
434    let response = unsafe { &*(ptr as *const RiMethodResponse) };
435    response.is_timeout as jboolean
436}
437
438#[no_mangle]
439pub extern "system" fn Java_com_dunimd_ri_modulerpc_RiMethodResponse_free0(
440    _env: JNIEnv,
441    _class: JClass,
442    ptr: jlong,
443) {
444    if ptr != 0 {
445        unsafe {
446            let _ = Box::from_raw(ptr as *mut RiMethodResponse);
447        }
448    }
449}