From 6c254c31c4bd7a481f3e15eaaff6eef153a9eb64 Mon Sep 17 00:00:00 2001 From: milyin Date: Tue, 14 Apr 2026 15:32:10 +0200 Subject: [PATCH 01/52] chore: add branch placeholder zbobr_fix-68-adjust-zenoh-java-functionality-to-zenoh-kotlin From d58386cf525221d869e866d6b37e6ca3e349ba91 Mon Sep 17 00:00:00 2001 From: milyin Date: Tue, 14 Apr 2026 16:24:09 +0200 Subject: [PATCH 02/52] feat: add JNI exports for zenoh-kotlin compatibility (advanced pub/sub) - Fix zenoh-ext Cargo.toml to include "unstable" feature (required for AdvancedPublisher/AdvancedSubscriber) - Add owned_object.rs: safe Arc accessor to prevent early drops - Add sample_callback.rs: SetJniSampleCallback trait for JNI callback wiring - Add ext/ module with JNI exports for AdvancedPublisher, AdvancedSubscriber, MatchingListener, SampleMissListener - Add openSessionViaJNI instance-method variant for zenoh-kotlin compatibility - Add prepare_publisher_builder and prepare_subscriber_builder helpers - Add declareAdvancedPublisherViaJNI and declareAdvancedSubscriberViaJNI Co-Authored-By: Claude Sonnet 4.6 --- zenoh-jni/Cargo.toml | 2 +- zenoh-jni/src/ext/advanced_publisher.rs | 339 ++++++++++++++++++++ zenoh-jni/src/ext/advanced_subscriber.rs | 359 ++++++++++++++++++++++ zenoh-jni/src/ext/matching_listener.rs | 41 +++ zenoh-jni/src/ext/mod.rs | 18 ++ zenoh-jni/src/ext/sample_miss_listener.rs | 41 +++ zenoh-jni/src/lib.rs | 4 + zenoh-jni/src/owned_object.rs | 46 +++ zenoh-jni/src/sample_callback.rs | 138 +++++++++ zenoh-jni/src/session.rs | 331 +++++++++++++++++++- 10 files changed, 1317 insertions(+), 2 deletions(-) create mode 100644 zenoh-jni/src/ext/advanced_publisher.rs create mode 100644 zenoh-jni/src/ext/advanced_subscriber.rs create mode 100644 zenoh-jni/src/ext/matching_listener.rs create mode 100644 zenoh-jni/src/ext/mod.rs create mode 100644 zenoh-jni/src/ext/sample_miss_listener.rs create mode 100644 zenoh-jni/src/owned_object.rs create mode 100644 zenoh-jni/src/sample_callback.rs diff --git a/zenoh-jni/Cargo.toml b/zenoh-jni/Cargo.toml index 975fc0fc..a7ff42ab 100644 --- a/zenoh-jni/Cargo.toml +++ b/zenoh-jni/Cargo.toml @@ -37,7 +37,7 @@ uhlc = "0.8.0" json5 = "0.4.1" serde_yaml = "0.9.19" zenoh = { version = "1.8.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false } -zenoh-ext = { version = "1.8.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["internal"], default-features = false, optional = true } +zenoh-ext = { version = "1.8.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false, optional = true } tracing = { version = "0.1" , features = ["log"] } [lib] name = "zenoh_jni" diff --git a/zenoh-jni/src/ext/advanced_publisher.rs b/zenoh-jni/src/ext/advanced_publisher.rs new file mode 100644 index 00000000..c3d0c7c1 --- /dev/null +++ b/zenoh-jni/src/ext/advanced_publisher.rs @@ -0,0 +1,339 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::sync::Arc; + +use jni::objects::JValue; +use jni::{ + objects::{JByteArray, JClass, JString}, + sys::jint, + JNIEnv, +}; +use zenoh::handlers::{Callback, DefaultHandler}; +use zenoh::Wait; +use zenoh_ext::AdvancedPublisher; + +use crate::owned_object::OwnedObject; +use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; + +use crate::throw_exception; +use crate::{ + errors::ZResult, + utils::{decode_byte_array, decode_encoding}, + zerror, +}; +use jni::sys::jboolean; +use std::ptr::null; + +use jni::objects::JObject; +use zenoh::matching::{MatchingListener, MatchingListenerBuilder, MatchingStatus}; + +trait SetJniMatchingStatusCallback { + type WithCallback; + + unsafe fn set_jni_matching_status_callback( + self, + env: &mut JNIEnv, + callback: JObject, + on_close: JObject, + ) -> ZResult; +} + +impl<'a> SetJniMatchingStatusCallback for MatchingListenerBuilder<'a, DefaultHandler> { + type WithCallback = MatchingListenerBuilder<'a, Callback>; + + unsafe fn set_jni_matching_status_callback( + self, + env: &mut JNIEnv, + callback: JObject, + on_close: JObject, + ) -> ZResult { + let java_vm = Arc::new(get_java_vm(env)?); + let callback_global_ref = get_callback_global_ref(env, callback)?; + let on_close_global_ref = get_callback_global_ref(env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + + let builder = self.callback(move |matching_status| { + on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure + let _ = || -> ZResult<()> { + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!("Unable to attach thread for matching listener: {}", err) + })?; + + env.call_method( + &callback_global_ref, + "run", + "(Z)V", + &[JValue::from(matching_status.matching())], + ) + .map_err(|err| zerror!(err))?; + Ok(()) + }() + .map_err(|err| tracing::error!("On matching listener callback error: {err}")); + }); + Ok(builder) + } +} + +/// Declare a MatchingListener for [AdvancedPublisher] via JNI. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_publisher_ptr`: The raw pointer to an [AdvancedPublisher]. +/// - `callback`: The callback function as an instance of the `JNIMatchingListenerCallback` interface in Java/Kotlin. +/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon undeclaring the [MatchingListener]. +/// +/// Returns: +/// - A raw pointer to the declared [MatchingListener]. In case of failure, an exception is thrown and null is returned. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. +/// - The [AdvancedPublisher] pointer remains valid and the ownership of the [AdvancedPublisher] is not transferred, +/// allowing safe usage of the [AdvancedPublisher] after this function call. +/// - The callback function passed as `callback` must be a valid instance of the `JNIMatchingListenerCallback` interface +/// in Java/Kotlin, matching the specified signature. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_declareMatchingListenerViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_publisher_ptr: *const AdvancedPublisher, + + callback: JObject, + on_close: JObject, +) -> *const MatchingListener<()> { + let advanced_publisher = OwnedObject::from_raw(advanced_publisher_ptr); + + || -> ZResult<*const MatchingListener<()>> { + tracing::debug!( + "Declaring matching listener on '{}'...", + advanced_publisher.key_expr() + ); + + let matching_listener = advanced_publisher + .matching_listener() + .set_jni_matching_status_callback(&mut env, callback, on_close)? + .wait() + .map_err(|err| zerror!("Unable to declare matching listener: {}", err))?; + + tracing::debug!( + "Matching listener declared on '{}'...", + advanced_publisher.key_expr() + ); + Ok(Arc::into_raw(Arc::new(matching_listener))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Declare a background matching listener for [AdvancedPublisher] via JNI. +/// Register the listener callback to be run in background until the [AdvancedPublisher] is undeclared. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_publisher_ptr`: The raw pointer to an [AdvancedPublisher]. +/// - `callback`: The callback function as an instance of the `JNIMatchingListenerCallback` interface in Java/Kotlin. +/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon undeclaring the [AdvancedPublisher]. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. +/// - The [AdvancedPublisher] pointer remains valid and the ownership of the [AdvancedPublisher] is not transferred, +/// allowing safe usage of the [AdvancedPublisher] after this function call. +/// - The callback function passed as `callback` must be a valid instance of the `JNIMatchingListenerCallback` interface +/// in Java/Kotlin, matching the specified signature. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_declareBackgroundMatchingListenerViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_publisher_ptr: *const AdvancedPublisher, + + callback: JObject, + on_close: JObject, +) { + let advanced_publisher = OwnedObject::from_raw(advanced_publisher_ptr); + + || -> ZResult<()> { + tracing::debug!( + "Declaring background matching listener on '{}'...", + advanced_publisher.key_expr() + ); + + advanced_publisher + .matching_listener() + .set_jni_matching_status_callback(&mut env, callback, on_close)? + .background() + .wait() + .map_err(|err| zerror!("Unable to declare background matching listener: {}", err))?; + + tracing::debug!( + "Background matching listener declared on '{}'...", + advanced_publisher.key_expr() + ); + Ok(()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + }); +} + +/// Return the matching status of the [AdvancedPublisher]. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_publisher_ptr`: The raw pointer to an [AdvancedPublisher]. +/// +/// Returns: +/// - will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. +/// - The [AdvancedPublisher] pointer remains valid and the ownership of the [AdvancedPublisher] is not transferred, +/// allowing safe usage of the [AdvancedPublisher] after this function call. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_getMatchingStatusViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_publisher_ptr: *const AdvancedPublisher, +) -> jboolean { + use crate::errors::ZError; + + let advanced_publisher = OwnedObject::from_raw(advanced_publisher_ptr); + advanced_publisher + .matching_status() + .wait() + .map(|val| val.matching() as jboolean) + .map_err(|e| zerror!(e.to_string())) + .unwrap_or_else(|err: ZError| { + throw_exception!(env, err); + false as jboolean + }) +} + +/// Performs a PUT operation on an [AdvancedPublisher] via JNI. +/// +/// # Parameters +/// - `env`: The JNI environment pointer. +/// - `_class`: The Java class reference (unused). +/// - `payload`: The byte array to be published. +/// - `encoding_id`: The encoding ID of the payload. +/// - `encoding_schema`: Nullable encoding schema string of the payload. +/// - `attachment`: Nullble byte array for the attachment. +/// - `publisher_ptr`: The raw pointer to the [AdvancedPublisher]. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - Assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. +/// - The [AdvancedPublisher] pointer remains valid after this function call. +/// - May throw an exception in case of failure, which must be handled by the caller. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_putViaJNI( + mut env: JNIEnv, + _class: JClass, + payload: JByteArray, + encoding_id: jint, + encoding_schema: /*nullable*/ JString, + attachment: /*nullable*/ JByteArray, + publisher_ptr: *const AdvancedPublisher<'static>, +) { + let publisher = OwnedObject::from_raw(publisher_ptr); + let _ = || -> ZResult<()> { + let payload = decode_byte_array(&env, payload)?; + let mut publication = publisher.put(payload); + let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; + publication = publication.encoding(encoding); + if !attachment.is_null() { + let attachment = decode_byte_array(&env, attachment)?; + publication = publication.attachment::>(attachment) + }; + publication.wait().map_err(|err| zerror!(err)) + }() + .map_err(|err| throw_exception!(env, err)); +} + +/// Performs a DELETE operation on an [AdvancedPublisher] via JNI. +/// +/// # Parameters +/// - `env`: The JNI environment pointer. +/// - `_class`: The Java class reference (unused). +/// - `attachment`: Nullble byte array for the attachment. +/// - `publisher_ptr`: The raw pointer to the [AdvancedPublisher]. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - Assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. +/// - The [AdvancedPublisher] pointer remains valid after this function call. +/// - May throw an exception in case of failure, which must be handled by the caller. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_deleteViaJNI( + mut env: JNIEnv, + _class: JClass, + attachment: /*nullable*/ JByteArray, + publisher_ptr: *const AdvancedPublisher<'static>, +) { + let publisher = OwnedObject::from_raw(publisher_ptr); + let _ = || -> ZResult<()> { + let mut delete = publisher.delete(); + if !attachment.is_null() { + let attachment = decode_byte_array(&env, attachment)?; + delete = delete.attachment::>(attachment) + }; + delete.wait().map_err(|err| zerror!(err)) + }() + .map_err(|err| throw_exception!(env, err)); +} + +/// Frees the [AdvancedPublisher]. +/// +/// # Parameters: +/// - `_env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `publisher_ptr`: The raw pointer to the [AdvancedPublisher]. +/// +/// # Safety: +/// - The function is marked as unsafe due to raw pointer manipulation. +/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. +/// - After calling this function, the [AdvancedPublisher] pointer becomes invalid and should not be used anymore. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + publisher_ptr: *const AdvancedPublisher, +) { + Arc::from_raw(publisher_ptr); +} diff --git a/zenoh-jni/src/ext/advanced_subscriber.rs b/zenoh-jni/src/ext/advanced_subscriber.rs new file mode 100644 index 00000000..4fbc624a --- /dev/null +++ b/zenoh-jni/src/ext/advanced_subscriber.rs @@ -0,0 +1,359 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::sync::Arc; + +use jni::sys::jboolean; +use jni::{objects::JClass, JNIEnv}; +use zenoh::handlers::{Callback, DefaultHandler}; +use zenoh::pubsub::Subscriber; +use zenoh_ext::SampleMissListener; +use zenoh_ext::{AdvancedSubscriber, Miss, SampleMissListenerBuilder}; + +use crate::sample_callback::SetJniSampleCallback; +use jni::objects::JObject; + +use crate::errors::ZResult; +use jni::objects::JValue; +use zenoh::Wait; + +use crate::owned_object::OwnedObject; + +use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; +use crate::zerror; +use std::ptr::null; + +use crate::throw_exception; + +trait SetJniSampleMissListenerCallback { + type WithCallback; + + unsafe fn set_jni_sample_miss_callback( + self, + env: &mut JNIEnv, + callback: JObject, + on_close: JObject, + ) -> ZResult; +} + +impl<'a> SetJniSampleMissListenerCallback for SampleMissListenerBuilder<'a, DefaultHandler> { + type WithCallback = SampleMissListenerBuilder<'a, Callback>; + + unsafe fn set_jni_sample_miss_callback( + self, + env: &mut JNIEnv, + callback: JObject, + on_close: JObject, + ) -> ZResult { + let java_vm = Arc::new(get_java_vm(env)?); + let callback_global_ref = get_callback_global_ref(env, callback)?; + let on_close_global_ref = get_callback_global_ref(env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + + let builder = self.callback(move |miss| { + on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure + let _ = || -> ZResult<()> { + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!("Unable to attach thread for sample miss listener: {}", err) + })?; + + let (zid_lower, zid_upper, eid) = { + let id = miss.source(); + + let zid = id.zid().to_le_bytes(); + let zid_lower = i64::from_le_bytes(zid[0..8].try_into().unwrap()); + let zid_upper = i64::from_le_bytes(zid[8..16].try_into().unwrap()); + + (zid_lower, zid_upper, id.eid()) + }; + let missed_count = miss.nb(); + + env.call_method( + &callback_global_ref, + "run", + "(JJJJ)V", + &[ + JValue::from(zid_lower), + JValue::from(zid_upper), + JValue::from(eid as i64), + JValue::from(missed_count as i64), + ], + ) + .map_err(|err| zerror!(err))?; + Ok(()) + }() + .map_err(|err| tracing::error!("On sample miss listener callback error: {err}")); + }); + Ok(builder) + } +} + +/// Declares a subscriber to detect matching publishers for an [AdvancedSubscriber] via JNI. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. +/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. +/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. +/// +/// Returns: +/// - A raw pointer to the declared [Subscriber]. In case of failure, an exception is thrown and null is returned. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. +/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, +/// allowing safe usage of the [AdvancedSubscriber] after this function call. +/// - The callback function passed as `callback` must be a valid instance of the `JNISubscriberCallback` interface +/// in Java/Kotlin, matching the specified signature. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareDetectPublishersSubscriberViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_subscriber_ptr: *const AdvancedSubscriber<()>, + history: jboolean, + callback: JObject, + on_close: JObject, +) -> *const Subscriber<()> { + let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); + + || -> ZResult<*const Subscriber<()>> { + tracing::debug!( + "Declaring detect publishers subscriber on '{}'...", + advanced_subscriber.key_expr() + ); + + let detect_publishers_subscriber = advanced_subscriber + .detect_publishers() + .history(history != 0) + .set_jni_sample_callback(&mut env, callback, on_close)? + .wait() + .map_err(|err| zerror!("Unable to declare detect publishers subscriber: {}", err))?; + + tracing::debug!( + "Detect publishers subscriber declared on '{}'...", + advanced_subscriber.key_expr() + ); + Ok(Arc::into_raw(Arc::new(detect_publishers_subscriber))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Declares a background subscriber to detect matching publishers for an [AdvancedSubscriber] via JNI. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. +/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. +/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. +/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, +/// allowing safe usage of the [AdvancedSubscriber] after this function call. +/// - The callback function passed as `callback` must be a valid instance of the `JNISubscriberCallback` interface +/// in Java/Kotlin, matching the specified signature. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareBackgroundDetectPublishersSubscriberViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_subscriber_ptr: *const AdvancedSubscriber<()>, + history: jboolean, + callback: JObject, + on_close: JObject, +) { + let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); + + || -> ZResult<()> { + tracing::debug!( + "Declaring background detect publishers subscriber on '{}'...", + advanced_subscriber.key_expr() + ); + + advanced_subscriber + .detect_publishers() + .history(history != 0) + .set_jni_sample_callback(&mut env, callback, on_close)? + .background() + .wait() + .map_err(|err| { + zerror!( + "Unable to declare background detect publishers subscriber: {}", + err + ) + })?; + + tracing::debug!( + "Background detect publishers subscriber declared on '{}'...", + advanced_subscriber.key_expr() + ); + Ok(()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + }); +} + +/// Declares a [SampleMissListener] to detect missed samples for an [AdvancedSubscriber] via JNI. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. +/// - `callback`: The callback function as an instance of the `JNISampleMissListenerCallback` interface in Java/Kotlin. +/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. +/// +/// Returns: +/// - A raw pointer to the declared [SampleMissListener]. In case of failure, an exception is thrown and null is returned. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. +/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, +/// allowing safe usage of the [AdvancedSubscriber] after this function call. +/// - The callback function passed as `callback` must be a valid instance of the `JNISampleMissListenerCallback` interface +/// in Java/Kotlin, matching the specified signature. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareSampleMissListenerViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_subscriber_ptr: *const AdvancedSubscriber<()>, + + callback: JObject, + on_close: JObject, +) -> *const SampleMissListener<()> { + let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); + + || -> ZResult<*const SampleMissListener<()>> { + tracing::debug!( + "Declaring sample miss listener on '{}'...", + advanced_subscriber.key_expr() + ); + + let result = advanced_subscriber + .sample_miss_listener() + .set_jni_sample_miss_callback(&mut env, callback, on_close)? + .wait(); + + let sample_miss_listener = + result.map_err(|err| zerror!("Unable to declare sample miss listener: {}", err))?; + + tracing::debug!( + "Matching listener declared on '{}'...", + advanced_subscriber.key_expr() + ); + Ok(Arc::into_raw(Arc::new(sample_miss_listener))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Declare a background sample miss listener for [AdvancedSubscriber] via JNI. +/// Register the listener callback to be run in background until the [AdvancedSubscriber] is undeclared. +/// +/// Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `advanced_subscriber_ptr`: The raw pointer to an [AdvancedSubscriber]. +/// - `callback`: The callback function as an instance of the `JNISampleMissListenerCallback` interface in Java/Kotlin. +/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon undeclaring the [AdvancedSubscriber]. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. +/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, +/// allowing safe usage of the [AdvancedSubscriber] after this function call. +/// - The callback function passed as `callback` must be a valid instance of the `JNISampleMissListenerCallback` interface +/// in Java/Kotlin, matching the specified signature. +/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareBackgroundSampleMissListenerViaJNI( + mut env: JNIEnv, + _class: JClass, + advanced_subscriber_ptr: *const AdvancedSubscriber<()>, + + callback: JObject, + on_close: JObject, +) { + let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); + + || -> ZResult<()> { + tracing::debug!( + "Declaring background sample miss listener on '{}'...", + advanced_subscriber.key_expr() + ); + + advanced_subscriber + .sample_miss_listener() + .set_jni_sample_miss_callback(&mut env, callback, on_close)? + .background() + .wait() + .map_err(|err| zerror!("Unable to declare background sample miss listener: {}", err))?; + + tracing::debug!( + "Background sample miss listener declared on '{}'...", + advanced_subscriber.key_expr() + ); + Ok(()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + }) +} + +/// Frees the [AdvancedSubscriber]. +/// +/// # Parameters: +/// - `_env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. +/// +/// # Safety: +/// - The function is marked as unsafe due to raw pointer manipulation. +/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. +/// - The function takes ownership of the raw pointer and releases the associated memory. +/// - After calling this function, the [AdvancedSubscriber] pointer becomes invalid and should not be used anymore. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + subscriber_ptr: *const AdvancedSubscriber<()>, +) { + Arc::from_raw(subscriber_ptr); +} diff --git a/zenoh-jni/src/ext/matching_listener.rs b/zenoh-jni/src/ext/matching_listener.rs new file mode 100644 index 00000000..737d0203 --- /dev/null +++ b/zenoh-jni/src/ext/matching_listener.rs @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::sync::Arc; + +use jni::{objects::JClass, JNIEnv}; +use zenoh::matching::MatchingListener; + +/// Frees the [MatchingListener]. +/// +/// # Parameters: +/// - `_env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `matching_listener_ptr`: The raw pointer to the [MatchingListener]. +/// +/// # Safety: +/// - The function is marked as unsafe due to raw pointer manipulation. +/// - It assumes that the provided [MatchingListener] pointer is valid and has not been modified or freed. +/// - The function takes ownership of the raw pointer and releases the associated memory. +/// - After calling this function, the [MatchingListener] pointer becomes invalid and should not be used anymore. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIMatchingListener_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + matching_listener_ptr: *const MatchingListener<()>, +) { + Arc::from_raw(matching_listener_ptr); +} diff --git a/zenoh-jni/src/ext/mod.rs b/zenoh-jni/src/ext/mod.rs new file mode 100644 index 00000000..8a4a30b9 --- /dev/null +++ b/zenoh-jni/src/ext/mod.rs @@ -0,0 +1,18 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +mod advanced_publisher; +mod advanced_subscriber; +mod matching_listener; +mod sample_miss_listener; diff --git a/zenoh-jni/src/ext/sample_miss_listener.rs b/zenoh-jni/src/ext/sample_miss_listener.rs new file mode 100644 index 00000000..5fc3ddbb --- /dev/null +++ b/zenoh-jni/src/ext/sample_miss_listener.rs @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::sync::Arc; + +use jni::{objects::JClass, JNIEnv}; +use zenoh_ext::SampleMissListener; + +/// Frees the [SampleMissListener]. +/// +/// # Parameters: +/// - `_env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `sample_miss_listener_ptr`: The raw pointer to the [SampleMissListener]. +/// +/// # Safety: +/// - The function is marked as unsafe due to raw pointer manipulation. +/// - It assumes that the provided [SampleMissListener] pointer is valid and has not been modified or freed. +/// - The function takes ownership of the raw pointer and releases the associated memory. +/// - After calling this function, the [SampleMissListener] pointer becomes invalid and should not be used anymore. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNISampleMissListener_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + sample_miss_listener_ptr: *const SampleMissListener<()>, +) { + Arc::from_raw(sample_miss_listener_ptr); +} diff --git a/zenoh-jni/src/lib.rs b/zenoh-jni/src/lib.rs index ff3981a4..507fa54f 100644 --- a/zenoh-jni/src/lib.rs +++ b/zenoh-jni/src/lib.rs @@ -17,10 +17,12 @@ mod errors; mod key_expr; mod liveliness; mod logger; +pub(crate) mod owned_object; mod publisher; mod querier; mod query; mod queryable; +pub(crate) mod sample_callback; mod scouting; mod session; mod subscriber; @@ -28,6 +30,8 @@ mod utils; #[cfg(feature = "zenoh-ext")] mod zbytes; mod zenoh_id; +#[cfg(feature = "zenoh-ext")] +mod ext; // Test should be runned with `cargo test --no-default-features` #[test] diff --git a/zenoh-jni/src/owned_object.rs b/zenoh-jni/src/owned_object.rs new file mode 100644 index 00000000..326230f6 --- /dev/null +++ b/zenoh-jni/src/owned_object.rs @@ -0,0 +1,46 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ops::Deref, sync::Arc}; + +/// Safe accessor to refocounted ([Arc]) owned objects. +/// Helps to avoid early drop by offloading [std::mem::forget] from user +pub(crate) struct OwnedObject { + inner: Option>, +} + +impl Deref for OwnedObject { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: inner is always initialized + unsafe { self.inner.as_ref().unwrap_unchecked() } + } +} + +impl Drop for OwnedObject { + fn drop(&mut self) { + // SAFETY: inner is always initialized + let inner = unsafe { self.inner.take().unwrap_unchecked() }; + std::mem::forget(inner); + } +} + +impl OwnedObject { + pub(crate) unsafe fn from_raw(ptr: *const T) -> Self { + Self { + inner: Some(Arc::from_raw(ptr)), + } + } +} diff --git a/zenoh-jni/src/sample_callback.rs b/zenoh-jni/src/sample_callback.rs new file mode 100644 index 00000000..fdf677b8 --- /dev/null +++ b/zenoh-jni/src/sample_callback.rs @@ -0,0 +1,138 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::sync::Arc; + +use jni::{ + objects::{JByteArray, JObject, JString, JValue}, + sys::jint, + JNIEnv, +}; +use zenoh::{ + handlers::{Callback, DefaultHandler}, + liveliness::LivelinessSubscriberBuilder, + pubsub::SubscriberBuilder, + sample::Sample, +}; + +use crate::{errors::ZResult, utils::*, zerror}; + +pub(crate) trait SetJniSampleCallback: Sized + HasSampleCallbackSetter { + unsafe fn set_jni_sample_callback( + self, + env: &mut JNIEnv, + callback: JObject, + on_close: JObject, + ) -> ZResult { + let java_vm = Arc::new(get_java_vm(env)?); + let callback_global_ref = get_callback_global_ref(env, callback)?; + let on_close_global_ref = get_callback_global_ref(env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + + let builder = self.set_callback(move |sample: Sample| { + on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure + let _ = || -> ZResult<()> { + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!("Unable to attach thread for sample callback: {}", err) + })?; + let byte_array = bytes_to_java_array(&env, sample.payload()) + .map(|array| env.auto_local(array))?; + + let encoding_id: jint = sample.encoding().id() as jint; + let encoding_schema = match sample.encoding().schema() { + Some(schema) => slice_to_java_string(&env, schema)?, + None => JString::default(), + }; + let kind = sample.kind() as jint; + let (timestamp, is_valid) = sample + .timestamp() + .map(|timestamp| (timestamp.get_time().as_u64(), true)) + .unwrap_or((0, false)); + + let attachment_bytes = sample + .attachment() + .map_or_else( + || Ok(JByteArray::default()), + |attachment| bytes_to_java_array(&env, attachment), + ) + .map(|array| env.auto_local(array)) + .map_err(|err| zerror!("Error processing attachment: {}", err))?; + + let key_expr_str = env.auto_local( + env.new_string(sample.key_expr().to_string()) + .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, + ); + + let express = sample.express(); + let priority = sample.priority() as jint; + let cc = sample.congestion_control() as jint; + + env.call_method( + &callback_global_ref, + "run", + "(Ljava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + &[ + JValue::from(&key_expr_str), + JValue::from(&byte_array), + JValue::from(encoding_id), + JValue::from(&encoding_schema), + JValue::from(kind), + JValue::from(timestamp as i64), + JValue::from(is_valid), + JValue::from(&attachment_bytes), + JValue::from(express), + JValue::from(priority), + JValue::from(cc), + ], + ) + .map_err(|err| zerror!(err))?; + Ok(()) + }() + .map_err(|err| tracing::error!("On sample callback error: {err}")); + }); + Ok(builder) + } +} + +impl SetJniSampleCallback for T {} + +pub(crate) trait HasSampleCallbackSetter { + type BuilderWithCallback; + + fn set_callback(self, callback: F) -> Self::BuilderWithCallback + where + F: Fn(Sample) + Send + Sync + 'static; +} + +impl<'a, 'b> HasSampleCallbackSetter for SubscriberBuilder<'a, 'b, DefaultHandler> { + type BuilderWithCallback = SubscriberBuilder<'a, 'b, Callback>; + + fn set_callback(self, callback: F) -> Self::BuilderWithCallback + where + F: Fn(Sample) + Send + Sync + 'static, + { + self.callback(callback) + } +} + +impl<'a, 'b> HasSampleCallbackSetter for LivelinessSubscriberBuilder<'a, 'b, DefaultHandler> { + type BuilderWithCallback = LivelinessSubscriberBuilder<'a, 'b, Callback>; + + fn set_callback(self, callback: F) -> Self::BuilderWithCallback + where + F: Fn(Sample) + Send + Sync + 'static, + { + self.callback(callback) + } +} diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 5f0848b2..1950cc06 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -21,14 +21,26 @@ use jni::{ }; use zenoh::{ config::Config, + handlers::Callback, key_expr::KeyExpr, - pubsub::{Publisher, Subscriber}, + pubsub::{Publisher, PublisherBuilder, Subscriber, SubscriberBuilder}, query::{Querier, Query, Queryable, ReplyError, ReplyKeyExpr, Selector}, sample::Sample, session::{EntityGlobalId, Session, ZenohId}, Wait, }; +use crate::owned_object::OwnedObject; +use crate::sample_callback::SetJniSampleCallback; +#[cfg(feature = "zenoh-ext")] +use jni::sys::jdouble; +#[cfg(feature = "zenoh-ext")] +use zenoh_ext::{ + AdvancedPublisher, AdvancedPublisherBuilderExt, AdvancedSubscriber, + AdvancedSubscriberBuilderExt, CacheConfig, HistoryConfig, MissDetectionConfig, RecoveryConfig, + RepliesConfig, +}; + use crate::{ errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception, utils::*, zerror, }; @@ -1199,3 +1211,320 @@ fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result< } Ok(array_list.as_raw()) } + +/// Open a Zenoh session via JNI (instance-method variant for zenoh-kotlin compatibility). +/// +/// This is an alias for the companion-object variant, allowing zenoh-kotlin to call +/// `openSessionViaJNI` as an instance method on `JNISession`. +/// +/// # Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class (parameter required by the JNI interface but unused). +/// - `config_ptr`: Pointer to the Zenoh config. If null, the default configuration will be loaded. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( + mut env: JNIEnv, + _class: JClass, + config_ptr: *const Config, +) -> *const Session { + let session = open_session(config_ptr); + match session { + Ok(session) => Arc::into_raw(Arc::new(session)), + Err(err) => { + tracing::error!("Unable to open session: {}", err); + throw_exception!(env, zerror!(err)); + null() + } + } +} + +#[allow(clippy::too_many_arguments)] +unsafe fn prepare_publisher_builder<'a, 'b>( + env: &mut JNIEnv, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: &JString, + session: &'a Session, + congestion_control: jint, + priority: jint, + is_express: jboolean, + reliability: jint, +) -> ZResult> { + let key_expr = process_kotlin_key_expr(env, key_expr_str, key_expr_ptr)?; + let congestion_control = decode_congestion_control(congestion_control)?; + let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; + let builder = session + .declare_publisher(key_expr) + .congestion_control(congestion_control) + .priority(priority) + .express(is_express != 0) + .reliability(reliability); + Ok(builder) +} + +unsafe fn prepare_subscriber_builder<'a, 'b>( + env: &mut JNIEnv, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: &JString, + session: &'a Session, + callback: JObject, + on_close: JObject, + entity_name: &str, +) -> ZResult>> { + let key_expr = process_kotlin_key_expr(env, key_expr_str, key_expr_ptr)?; + tracing::debug!("Declaring {entity_name} on '{}'...", key_expr); + + let builder = session + .declare_subscriber(key_expr.to_owned()) + .set_jni_sample_callback(env, callback, on_close)?; + + tracing::debug!("{entity_name} declared on '{}'.", key_expr); + Ok(builder) +} + +/// Declare an advanced Zenoh subscriber via JNI. +/// +/// # Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `key_expr_ptr`: Raw pointer to the [KeyExpr], may be null. +/// - `key_expr_str`: String representation of the [KeyExpr]. +/// - `history_config_enabled`: Whether history config is enabled. +/// - `history_detect_late_publishers`: Whether to detect late publishers in history. +/// - `history_max_samples`: Max samples in history (<=0 means unlimited). +/// - `history_max_age_seconds`: Max age in seconds for history (<=0.0 means unlimited). +/// - `recovery_config_enabled`: Whether recovery config is enabled. +/// - `recovery_config_is_heartbeat`: Whether to use heartbeat recovery. +/// - `recovery_query_period_ms`: Query period for periodic recovery in milliseconds. +/// - `subscriber_detection`: Allow this subscriber to be detected through liveliness. +/// - `session_ptr`: The raw pointer to the Zenoh session. +/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface. +/// - `on_close`: A `JNIOnCloseCallback` to be called upon closing the subscriber. +/// +/// Returns: +/// - A raw pointer to the declared [AdvancedSubscriber]. In case of failure, an exception is thrown and null is returned. +/// +/// Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberViaJNI( + mut env: JNIEnv, + _class: JClass, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, + session_ptr: *const Session, + // HistoryConfig + history_config_enabled: jboolean, + history_detect_late_publishers: jboolean, + history_max_samples: jlong, + history_max_age_seconds: jdouble, + // RecoveryConfig + recovery_config_enabled: jboolean, + recovery_config_is_heartbeat: jboolean, + recovery_query_period_ms: jlong, + + subscriber_detection: jboolean, + + callback: JObject, + on_close: JObject, +) -> *const AdvancedSubscriber<()> { + let session = Arc::from_raw(session_ptr); + let subscriber_ptr = || -> ZResult<*const AdvancedSubscriber<()>> { + let mut builder = prepare_subscriber_builder( + &mut env, + key_expr_ptr, + &key_expr_str, + &session, + callback, + on_close, + "advanced subscriber", + )? + .advanced(); + + if history_config_enabled != 0 { + let mut history = match history_detect_late_publishers != 0 { + true => HistoryConfig::default().detect_late_publishers(), + false => HistoryConfig::default(), + }; + + if history_max_samples > 0 { + history = history.max_samples( + history_max_samples + .try_into() + .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, + ); + } + + if history_max_age_seconds > 0.0 { + history = history.max_age(history_max_age_seconds); + } + + builder = builder.history(history); + } + + if recovery_config_enabled != 0 { + let recovery = if recovery_config_is_heartbeat != 0 { + RecoveryConfig::default().heartbeat() + } else { + let dur = Duration::from_millis( + recovery_query_period_ms + .try_into() + .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, + ); + RecoveryConfig::default().periodic_queries(dur) + }; + builder = builder.recovery(recovery); + } + + if subscriber_detection != 0 { + builder = builder.subscriber_detection(); + } + + builder + .wait() + .map(|s| Arc::into_raw(Arc::new(s))) + .map_err(|err| zerror!("Unable to declare advanced subscriber: {}", err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }); + std::mem::forget(session); + subscriber_ptr +} + +/// Declare an advanced Zenoh publisher via JNI. +/// +/// # Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class. +/// - `key_expr_ptr`: Raw pointer to the [KeyExpr] to be used for the publisher, may be null. +/// - `key_expr_str`: String representation of the [KeyExpr]. +/// - `session_ptr`: Raw pointer to the Zenoh [Session] to be used for the publisher. +/// - `congestion_control`: The [CongestionControl] configuration as an ordinal. +/// - `priority`: The [Priority] configuration as an ordinal. +/// - `is_express`: The express config of the publisher. +/// - `reliability`: The reliability value as an ordinal. +/// - `cache_enabled`: If true, attach a cache to the [AdvancedPublisher]. +/// - `cache_max_samples`: How many samples to keep for each resource. +/// - `cache_replies_priority`: The [Priority] for cache replies as an ordinal. +/// - `cache_replies_congestion_control`: The [CongestionControl] for cache replies as an ordinal. +/// - `cache_replies_is_express`: The express config for cache replies. +/// - `sample_miss_detection_enabled`: Enables sample miss detection functionality. +/// - `sample_miss_detection_enable_heartbeat`: Use heartbeat for miss detection. +/// - `sample_miss_detection_heartbeat_ms`: Heartbeat period in milliseconds. +/// - `sample_miss_detection_heartbeat_is_sporadic`: Whether heartbeat is sporadic. +/// - `publisher_detection`: Enables publisher detection. +/// +/// # Returns: +/// - A raw pointer to the declared [AdvancedPublisher] or null in case of failure. +/// +/// # Safety: +/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. +/// +#[cfg(feature = "zenoh-ext")] +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedPublisherViaJNI( + mut env: JNIEnv, + _class: JClass, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, + session_ptr: *const Session, + congestion_control: jint, + priority: jint, + is_express: jboolean, + reliability: jint, + // CacheConfig + cache_enabled: jboolean, + cache_max_samples: jlong, + cache_replies_priority: jint, + cache_replies_congestion_control: jint, + cache_replies_is_express: jboolean, + // MissDetectionConfig + sample_miss_detection_enabled: jboolean, + sample_miss_detection_enable_heartbeat: jboolean, + sample_miss_detection_heartbeat_ms: jlong, + sample_miss_detection_heartbeat_is_sporadic: jboolean, + + publisher_detection: jboolean, +) -> *const AdvancedPublisher<'static> { + let session = OwnedObject::from_raw(session_ptr); + let publisher_ptr = || -> ZResult<*const AdvancedPublisher<'static>> { + let mut builder = prepare_publisher_builder( + &mut env, + key_expr_ptr, + &key_expr_str, + &session, + congestion_control, + priority, + is_express, + reliability, + )? + .advanced(); + + // fill CacheConfig + if cache_enabled != 0 { + let cache_congestion_control = + decode_congestion_control(cache_replies_congestion_control)?; + + let cache_priority = decode_priority(cache_replies_priority)?; + + let replies_config = RepliesConfig::default() + .priority(cache_priority) + .congestion_control(cache_congestion_control) + .express(cache_replies_is_express != 0); + + let cache_config = CacheConfig::default() + .max_samples( + cache_max_samples + .try_into() + .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, + ) + .replies_config(replies_config); + + builder = builder.cache(cache_config); + } + + // fill MissDetectionConfig + if sample_miss_detection_enabled != 0 { + let miss_detection_config = { + let mut result = MissDetectionConfig::default(); + if sample_miss_detection_enable_heartbeat != 0 { + let duration = Duration::from_millis( + sample_miss_detection_heartbeat_ms + .try_into() + .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, + ); + + result = match sample_miss_detection_heartbeat_is_sporadic != 0 { + true => result.sporadic_heartbeat(duration), + false => result.heartbeat(duration), + }; + } + result + }; + builder = builder.sample_miss_detection(miss_detection_config); + } + + if publisher_detection != 0 { + builder = builder.publisher_detection(); + } + + let result = builder.wait(); + match result { + Ok(publisher) => Ok(Arc::into_raw(Arc::new(publisher))), + Err(err) => Err(zerror!(err)), + } + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }); + publisher_ptr +} From 3919692d01a4bbcd46e31e7b33aa8166572adb5f Mon Sep 17 00:00:00 2001 From: milyin Date: Tue, 14 Apr 2026 17:04:12 +0200 Subject: [PATCH 03/52] fix: restore unstable feature for zenoh-ext and reorder modules The ext module requires unstable features from zenoh-ext. The merge removed this feature but the ext module still uses it, causing compilation errors. Also reordered module declarations to group feature-gated modules together per cargo fmt output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- zenoh-jni/Cargo.toml | 2 +- zenoh-jni/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zenoh-jni/Cargo.toml b/zenoh-jni/Cargo.toml index a88e31ec..1afcceee 100644 --- a/zenoh-jni/Cargo.toml +++ b/zenoh-jni/Cargo.toml @@ -37,7 +37,7 @@ uhlc = "0.8.0" json5 = "0.4.1" serde_yaml = "0.9.19" zenoh = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false } -zenoh-ext = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["internal"], default-features = false, optional = true } +zenoh-ext = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false, optional = true } tracing = { version = "0.1" , features = ["log"] } [lib] name = "zenoh_jni" diff --git a/zenoh-jni/src/lib.rs b/zenoh-jni/src/lib.rs index 507fa54f..93765edb 100644 --- a/zenoh-jni/src/lib.rs +++ b/zenoh-jni/src/lib.rs @@ -14,6 +14,8 @@ mod config; mod errors; +#[cfg(feature = "zenoh-ext")] +mod ext; mod key_expr; mod liveliness; mod logger; @@ -30,8 +32,6 @@ mod utils; #[cfg(feature = "zenoh-ext")] mod zbytes; mod zenoh_id; -#[cfg(feature = "zenoh-ext")] -mod ext; // Test should be runned with `cargo test --no-default-features` #[test] From eba165a6c8a9ca830a4519c5e9ea06ccc2b753e4 Mon Sep 17 00:00:00 2001 From: milyin Date: Tue, 14 Apr 2026 17:44:00 +0200 Subject: [PATCH 04/52] fix: unify openSessionViaJNI JNI symbol between zenoh-java and zenoh-kotlin Add @JvmStatic to openSessionViaJNI in JNISession companion object so Kotlin emits Java_io_zenoh_jni_JNISession_openSessionViaJNI (outer-class form) instead of the $Companion-suffixed variant. Remove the now-redundant Java_io_zenoh_jni_JNISession_00024Companion_openSessionViaJNI from session.rs, leaving the single canonical JNI binding that both zenoh-java and zenoh-kotlin share. Co-Authored-By: Claude Sonnet 4.6 --- .../kotlin/io/zenoh/jni/JNISession.kt | 1 + zenoh-jni/src/session.rs | 40 +++---------------- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 096a6c59..7026ebd6 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -53,6 +53,7 @@ internal class JNISession(val sessionPtr: Long) { return JNISession(sessionPtr) } + @JvmStatic @Throws(ZError::class) private external fun openSessionViaJNI(configPtr: Long): Long } diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 1950cc06..0b5ae57f 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -45,37 +45,6 @@ use crate::{ errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception, utils::*, zerror, }; -/// Open a Zenoh session via JNI. -/// -/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute -/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require -/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. -/// -/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `config_path`: Nullable path to the Zenoh config file. If null, the default configuration will be loaded. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_00024Companion_openSessionViaJNI( - mut env: JNIEnv, - _class: JClass, - config_ptr: *const Config, -) -> *const Session { - let session = open_session(config_ptr); - match session { - Ok(session) => Arc::into_raw(Arc::new(session)), - Err(err) => { - tracing::error!("Unable to open session: {}", err); - throw_exception!(env, zerror!(err)); - null() - } - } -} - /// Open a Zenoh session with the configuration pointed out by `config_path`. /// /// If the config path provided is null then the default configuration is loaded. @@ -1212,10 +1181,13 @@ fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result< Ok(array_list.as_raw()) } -/// Open a Zenoh session via JNI (instance-method variant for zenoh-kotlin compatibility). +/// Open a Zenoh session via JNI. +/// +/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute +/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require +/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. /// -/// This is an alias for the companion-object variant, allowing zenoh-kotlin to call -/// `openSessionViaJNI` as an instance method on `JNISession`. +/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. /// /// # Parameters: /// - `env`: The JNI environment. From f0cd490162fc0a254c855c3731fe5c06362d40b7 Mon Sep 17 00:00:00 2001 From: milyin Date: Wed, 15 Apr 2026 14:27:46 +0200 Subject: [PATCH 05/52] fix: use OwnedObject::from_raw in declareAdvancedSubscriberViaJNI for consistency --- zenoh-jni/src/session.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 0b5ae57f..c07c4dea 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -1305,7 +1305,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberV callback: JObject, on_close: JObject, ) -> *const AdvancedSubscriber<()> { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); let subscriber_ptr = || -> ZResult<*const AdvancedSubscriber<()>> { let mut builder = prepare_subscriber_builder( &mut env, @@ -1365,9 +1365,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberV .unwrap_or_else(|err| { throw_exception!(env, err); null() - }); - std::mem::forget(session); - subscriber_ptr + }) } /// Declare an advanced Zenoh publisher via JNI. From b619f14ebd5ccf17db7c8fa8142df52cb83af886 Mon Sep 17 00:00:00 2001 From: milyin Date: Wed, 15 Apr 2026 15:08:05 +0200 Subject: [PATCH 06/52] feat: create zenoh-jni-runtime module and migrate JNI adapters from zenoh-java - Create zenoh-jni-runtime Gradle subproject with public primitive-only JNI adapters - Move JNI adapter classes (JNIConfig, JNIKeyExpr, JNISession, JNIPublisher, JNIQuery, JNIQuerier, JNIScout, JNILiveliness, JNISubscriber, JNIQueryable, JNILivelinessToken, JNIZenohId) from zenoh-java to zenoh-jni-runtime with public visibility and primitive APIs - Add new JNIAdvancedPublisher, JNIAdvancedSubscriber, JNIMatchingListener, JNISampleMissListener - Add new callback interfaces including JNIMatchingListenerCallback and JNISampleMissListenerCallback - Add public ZenohLoad expect/actual objects in zenoh-jni-runtime (jvm and android) - Add public Target enum in zenoh-jni-runtime jvmMain - Update zenoh-java/build.gradle.kts: depend on zenoh-jni-runtime, remove duplicate native build - Update Session.kt to inline callback assembly using runtime JNI adapters - Update Publisher.kt, Query.kt, Querier.kt, Liveliness.kt, Zenoh.kt to use primitive APIs - Fix Rust bug in session.rs: remove spurious let binding in declareAdvancedSubscriberViaJNI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- settings.gradle.kts | 1 + zenoh-java/build.gradle.kts | 59 +- .../src/commonMain/kotlin/io/zenoh/Config.kt | 12 +- .../src/commonMain/kotlin/io/zenoh/Session.kt | 284 ++++++++- .../src/commonMain/kotlin/io/zenoh/Zenoh.kt | 38 +- .../kotlin/io/zenoh/jni/JNILiveliness.kt | 188 ------ .../kotlin/io/zenoh/jni/JNILivelinessToken.kt | 12 - .../kotlin/io/zenoh/jni/JNIPublisher.kt | 70 --- .../kotlin/io/zenoh/jni/JNIQuerier.kt | 137 ----- .../kotlin/io/zenoh/jni/JNIQuery.kt | 106 ---- .../kotlin/io/zenoh/jni/JNIScout.kt | 87 --- .../kotlin/io/zenoh/jni/JNISession.kt | 549 ------------------ .../kotlin/io/zenoh/keyexpr/KeyExpr.kt | 14 +- .../kotlin/io/zenoh/liveliness/Liveliness.kt | 130 ++++- .../kotlin/io/zenoh/pubsub/Publisher.kt | 7 +- .../kotlin/io/zenoh/query/Querier.kt | 76 ++- .../commonMain/kotlin/io/zenoh/query/Query.kt | 41 +- zenoh-jni-runtime/build.gradle.kts | 240 ++++++++ .../androidMain/kotlin/io/zenoh/ZenohLoad.kt | 7 +- .../commonMain/kotlin/io/zenoh/ZenohLoad.kt | 20 + .../kotlin/io/zenoh/exceptions/ZError.kt | 20 + .../io/zenoh/jni/JNIAdvancedPublisher.kt | 75 +++ .../io/zenoh/jni/JNIAdvancedSubscriber.kt | 80 +++ .../kotlin/io/zenoh/jni/JNIConfig.kt | 38 +- .../kotlin/io/zenoh/jni/JNIKeyExpr.kt | 51 +- .../kotlin/io/zenoh/jni/JNILiveliness.kt | 47 ++ .../kotlin/io/zenoh/jni/JNILivelinessToken.kt | 27 + .../io/zenoh/jni/JNIMatchingListener.kt | 25 + .../kotlin/io/zenoh/jni/JNIPublisher.kt | 49 ++ .../kotlin/io/zenoh/jni/JNIQuerier.kt | 43 ++ .../kotlin/io/zenoh/jni/JNIQuery.kt | 96 +++ .../kotlin/io/zenoh/jni/JNIQueryable.kt | 7 +- .../io/zenoh/jni/JNISampleMissListener.kt | 25 + .../kotlin/io/zenoh/jni/JNIScout.kt | 48 ++ .../kotlin/io/zenoh/jni/JNISession.kt | 194 +++++++ .../kotlin/io/zenoh/jni/JNISubscriber.kt | 8 +- .../kotlin/io/zenoh/jni/JNIZenohId.kt | 6 +- .../io/zenoh/jni/callbacks/JNIGetCallback.kt | 4 +- .../callbacks/JNIMatchingListenerCallback.kt | 20 + .../zenoh/jni/callbacks/JNIOnCloseCallback.kt | 4 +- .../jni/callbacks/JNIQueryableCallback.kt | 22 +- .../JNISampleMissListenerCallback.kt | 20 + .../zenoh/jni/callbacks/JNIScoutCallback.kt | 4 +- .../jni/callbacks/JNISubscriberCallback.kt | 4 +- .../src/jvmMain/kotlin/io/zenoh/Target.kt | 4 +- .../src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 39 +- zenoh-jni/src/session.rs | 2 +- 47 files changed, 1599 insertions(+), 1441 deletions(-) delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt create mode 100644 zenoh-jni-runtime/build.gradle.kts rename zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt => zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt (79%) create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt (63%) rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt (55%) create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt (79%) create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt (78%) rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt (80%) rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt (91%) create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt (84%) rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt (56%) create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt (85%) rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt (89%) rename {zenoh-java => zenoh-jni-runtime}/src/jvmMain/kotlin/io/zenoh/Target.kt (93%) rename zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt => zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt (81%) diff --git a/settings.gradle.kts b/settings.gradle.kts index 2d17a6a4..776fcd77 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ pluginManagement { rootProject.name = "zenoh-java" include(":zenoh-java") +include(":zenoh-jni-runtime") include(":examples") include(":zenoh-jni") diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index 123d7499..74bd48af 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -30,7 +30,7 @@ val release = project.findProperty("release")?.toString()?.toBoolean() == true // Modifying this property will affect the release workflows! val isRemotePublication = project.findProperty("remotePublication")?.toString()?.toBoolean() == true -var buildMode = if (release) BuildMode.RELEASE else BuildMode.DEBUG +var buildMode = if (release) "release" else "debug" if (androidEnabled) { apply(plugin = "com.android.library") @@ -66,6 +66,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { + implementation(project(":zenoh-jni-runtime")) implementation("commons-net:commons-net:3.9.0") implementation("com.google.guava:guava:33.3.1-jre") } @@ -82,18 +83,9 @@ kotlin { } } } - val jvmMain by getting { - if (isRemotePublication) { - // The line below is intended to load the native libraries that are crosscompiled on GitHub actions when publishing a JVM package. - resources.srcDir("../jni-libs").include("*/**") - } else { - resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) - } - } + val jvmMain by getting {} - val jvmTest by getting { - resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) - } + val jvmTest by getting {} } val javadocJar by tasks.registering(Jar::class) { @@ -168,48 +160,7 @@ tasks.whenObjectAdded { } tasks.named("compileKotlinJvm") { - dependsOn("buildZenohJni") -} - -tasks.register("buildZenohJni") { - doLast { - if (!isRemotePublication) { - // This is intended for local publications. For publications done through GitHub workflows, - // the zenoh-jni build is achieved and loaded differently from the CI - buildZenohJNI(buildMode) - } - } -} - -fun buildZenohJNI(mode: BuildMode = BuildMode.DEBUG) { - val cargoCommand = mutableListOf("cargo", "build") - - if (mode == BuildMode.RELEASE) { - cargoCommand.add("--release") - } - - val result = project.exec { - commandLine(*(cargoCommand.toTypedArray()), "--manifest-path", "../zenoh-jni/Cargo.toml") - } - - if (result.exitValue != 0) { - throw GradleException("Failed to build Zenoh-JNI.") - } - - Thread.sleep(1000) -} - -enum class BuildMode { - DEBUG { - override fun toString(): String { - return "debug" - } - }, - RELEASE { - override fun toString(): String { - return "release" - } - } + dependsOn(":zenoh-jni-runtime:buildZenohJni") } fun Project.configureAndroid() { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index 91bc0e01..9443c731 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -46,7 +46,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic fun loadDefault(): Config { - return JNIConfig.loadDefaultConfig() + return Config(JNIConfig(JNIConfig.loadDefaultConfig())) } /** @@ -59,7 +59,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromFile(file: File): Config { - return JNIConfig.loadConfigFile(file) + return Config(JNIConfig(JNIConfig.loadConfigFile(file.toString()))) } /** @@ -72,7 +72,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromFile(path: Path): Config { - return JNIConfig.loadConfigFile(path) + return Config(JNIConfig(JNIConfig.loadConfigFile(path.toString()))) } /** @@ -87,7 +87,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromJson(config: String): Config { - return JNIConfig.loadJsonConfig(config) + return Config(JNIConfig(JNIConfig.loadJsonConfig(config))) } /** @@ -102,7 +102,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromJson5(config: String): Config { - return JNIConfig.loadJson5Config(config) + return Config(JNIConfig(JNIConfig.loadJson5Config(config))) } /** @@ -117,7 +117,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromYaml(config: String): Config { - return JNIConfig.loadYamlConfig(config) + return Config(JNIConfig(JNIConfig.loadYamlConfig(config))) } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index d3b125f9..440d80b4 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -15,23 +15,40 @@ package io.zenoh import io.zenoh.annotations.Unstable +import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes +import io.zenoh.bytes.into +import io.zenoh.config.EntityGlobalId import io.zenoh.config.ZenohId import io.zenoh.exceptions.ZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler +import io.zenoh.jni.JNIKeyExpr +import io.zenoh.jni.JNIPublisher +import io.zenoh.jni.JNIQuery +import io.zenoh.jni.JNIQuerier +import io.zenoh.jni.JNIQueryable import io.zenoh.jni.JNISession +import io.zenoh.jni.JNISubscriber +import io.zenoh.jni.callbacks.JNIGetCallback +import io.zenoh.jni.callbacks.JNIQueryableCallback +import io.zenoh.jni.callbacks.JNISubscriberCallback import io.zenoh.keyexpr.KeyExpr import io.zenoh.liveliness.Liveliness import io.zenoh.pubsub.* +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS import io.zenoh.query.* import io.zenoh.query.Query import io.zenoh.query.Queryable import io.zenoh.sample.Sample +import io.zenoh.sample.SampleKind import io.zenoh.session.SessionDeclaration import io.zenoh.session.SessionInfo +import org.apache.commons.net.ntp.TimeStamp import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.BlockingQueue @@ -383,9 +400,10 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) fun declareKeyExpr(keyExpr: String): KeyExpr { return jniSession?.run { - val keyexpr = declareKeyExpr(keyExpr) - strongDeclarations.add(keyexpr) - keyexpr + val ptr = declareKeyExprViaJNI(sessionPtr, keyExpr) + val ke = KeyExpr(keyExpr, JNIKeyExpr(ptr)) + strongDeclarations.add(ke) + ke } ?: throw sessionClosedException } @@ -400,8 +418,11 @@ class Session private constructor(private val config: Config) : AutoCloseable { */ @Throws(ZError::class) fun undeclare(keyExpr: KeyExpr) { - return jniSession?.run { - undeclareKeyExpr(keyExpr) + jniSession?.run { + keyExpr.jniKeyExpr?.run { + undeclareKeyExprViaJNI(sessionPtr, ptr) + keyExpr.jniKeyExpr = null + } ?: throw ZError("Attempting to undeclare a non declared key expression.") } ?: throw (sessionClosedException) } @@ -576,7 +597,22 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) internal fun resolvePublisher(keyExpr: KeyExpr, options: PublisherOptions): Publisher { return jniSession?.run { - val publisher = declarePublisher(keyExpr, options) + val publisherRawPtr = declarePublisherViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr, + options.congestionControl.value, + options.priority.value, + options.express, + options.reliability.ordinal + ) + val publisher = Publisher( + keyExpr, + options.congestionControl, + options.priority, + options.encoding, + JNIPublisher(publisherRawPtr) + ) weakDeclarations.add(WeakReference(publisher)) publisher } ?: throw (sessionClosedException) @@ -587,7 +623,25 @@ class Session private constructor(private val config: Config) : AutoCloseable { keyExpr: KeyExpr, handler: Handler ): HandlerSubscriber { return jniSession?.run { - val subscriber = declareSubscriberWithHandler(keyExpr, handler) + val subCallback = + JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + handler.handle( + Sample( + KeyExpr(keyExpr1, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } + val subscriberRawPtr = declareSubscriberViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, subCallback, handler::onClose + ) + val subscriber = HandlerSubscriber(keyExpr, JNISubscriber(subscriberRawPtr), handler.receiver()) strongDeclarations.add(subscriber) subscriber } ?: throw (sessionClosedException) @@ -598,7 +652,25 @@ class Session private constructor(private val config: Config) : AutoCloseable { keyExpr: KeyExpr, callback: Callback ): CallbackSubscriber { return jniSession?.run { - val subscriber = declareSubscriberWithCallback(keyExpr, callback) + val subCallback = + JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + callback.run( + Sample( + KeyExpr(keyExpr1, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } + val subscriberRawPtr = declareSubscriberViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, subCallback, fun() {} + ) + val subscriber = CallbackSubscriber(keyExpr, JNISubscriber(subscriberRawPtr)) strongDeclarations.add(subscriber) subscriber } ?: throw (sessionClosedException) @@ -609,7 +681,27 @@ class Session private constructor(private val config: Config) : AutoCloseable { keyExpr: KeyExpr, handler: Handler, options: QueryableOptions ): HandlerQueryable { return jniSession?.run { - val queryable = declareQueryableWithHandler(keyExpr, handler, options) + val queryCallback = + JNIQueryableCallback { keyExpr1, selectorParams, payload, encodingId, encodingSchema, attachmentBytes, queryPtr, acceptReplies -> + val jniQuery = JNIQuery(queryPtr) + val keyExpr2 = KeyExpr(keyExpr1, null) + val selector = if (selectorParams.isEmpty()) Selector(keyExpr2) else Selector(keyExpr2, Parameters.from(selectorParams)) + handler.handle( + Query( + keyExpr2, + selector, + payload?.into(), + payload?.let { Encoding(encodingId, schema = encodingSchema) }, + attachmentBytes?.into(), + ReplyKeyExpr.entries[acceptReplies], + jniQuery + ) + ) + } + val queryableRawPtr = declareQueryableViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, queryCallback, handler::onClose, options.complete + ) + val queryable = HandlerQueryable(keyExpr, JNIQueryable(queryableRawPtr), handler.receiver()) strongDeclarations.add(queryable) queryable } ?: throw (sessionClosedException) @@ -620,18 +712,55 @@ class Session private constructor(private val config: Config) : AutoCloseable { keyExpr: KeyExpr, callback: Callback, options: QueryableOptions ): CallbackQueryable { return jniSession?.run { - val queryable = declareQueryableWithCallback(keyExpr, callback, options) + val queryCallback = + JNIQueryableCallback { keyExpr1, selectorParams, payload, encodingId, encodingSchema, attachmentBytes, queryPtr, acceptReplies -> + val jniQuery = JNIQuery(queryPtr) + val keyExpr2 = KeyExpr(keyExpr1, null) + val selector = if (selectorParams.isEmpty()) Selector(keyExpr2) else Selector(keyExpr2, Parameters.from(selectorParams)) + callback.run( + Query( + keyExpr2, + selector, + payload?.into(), + payload?.let { Encoding(encodingId, schema = encodingSchema) }, + attachmentBytes?.into(), + ReplyKeyExpr.entries[acceptReplies], + jniQuery + ) + ) + } + val queryableRawPtr = declareQueryableViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, queryCallback, fun() {}, options.complete + ) + val queryable = CallbackQueryable(keyExpr, JNIQueryable(queryableRawPtr)) strongDeclarations.add(queryable) queryable } ?: throw (sessionClosedException) } + @OptIn(Unstable::class) private fun resolveQuerier( keyExpr: KeyExpr, options: QuerierOptions ): Querier { return jniSession?.run { - val querier = declareQuerier(keyExpr, options) + val querierRawPtr = declareQuerierViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr, + options.target.ordinal, + options.consolidationMode.ordinal, + options.congestionControl.value, + options.priority.value, + options.express, + options.timeout.toMillis(), + options.acceptReplies.ordinal + ) + val querier = Querier( + keyExpr, + QoS(congestionControl = options.congestionControl, priority = options.priority, express = options.express), + JNIQuerier(querierRawPtr) + ) weakDeclarations.add(WeakReference(querier)) querier } ?: throw sessionClosedException @@ -643,11 +772,49 @@ class Session private constructor(private val config: Config) : AutoCloseable { handler: Handler, options: GetOptions ): R { - return jniSession?.performGetWithHandler( - selector, - handler, - options - ) ?: throw sessionClosedException + return jniSession?.run { + val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val reply: Reply = if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + Reply.Success( + replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, + Sample( + KeyExpr(keyExpr!!, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } else { + Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema)) + } + handler.handle(reply) + } + val sel = selector.into() + getViaJNI( + sel.keyExpr.jniKeyExpr?.ptr ?: 0, + sel.keyExpr.keyExpr, + sel.parameters?.toString(), + sessionPtr, + getCallback, + handler::onClose, + options.timeout.toMillis(), + options.target.ordinal, + options.consolidation.ordinal, + options.attachment?.into()?.bytes, + options.payload?.into()?.bytes, + options.encoding?.id ?: Encoding.defaultEncoding().id, + options.encoding?.schema, + options.qos.congestionControl.value, + options.qos.priority.value, + options.qos.express, + options.acceptReplies.ordinal + ) + handler.receiver() + } ?: throw sessionClosedException } @Throws(ZError::class) @@ -656,42 +823,105 @@ class Session private constructor(private val config: Config) : AutoCloseable { callback: Callback, options: GetOptions ) { - return jniSession?.performGetWithCallback( - selector, - callback, - options - ) ?: throw sessionClosedException + jniSession?.run { + val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val reply: Reply = if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + Reply.Success( + replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, + Sample( + KeyExpr(keyExpr!!, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } else { + Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema)) + } + callback.run(reply) + } + val sel = selector.into() + getViaJNI( + sel.keyExpr.jniKeyExpr?.ptr ?: 0, + sel.keyExpr.keyExpr, + sel.parameters?.toString(), + sessionPtr, + getCallback, + fun() {}, + options.timeout.toMillis(), + options.target.ordinal, + options.consolidation.ordinal, + options.attachment?.into()?.bytes, + options.payload?.into()?.bytes, + options.encoding?.id ?: Encoding.defaultEncoding().id, + options.encoding?.schema, + options.qos.congestionControl.value, + options.qos.priority.value, + options.qos.express, + options.acceptReplies.ordinal + ) + } ?: throw sessionClosedException } @Throws(ZError::class) internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) { - jniSession?.run { performPut(keyExpr, payload, putOptions) } + jniSession?.run { + val encoding = putOptions.encoding ?: Encoding.defaultEncoding() + putViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr, + payload.into().bytes, + encoding.id, + encoding.schema, + putOptions.congestionControl.value, + putOptions.priority.value, + putOptions.express, + putOptions.attachment?.into()?.bytes, + putOptions.reliability.ordinal + ) + } } @Throws(ZError::class) internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) { - jniSession?.run { performDelete(keyExpr, deleteOptions) } + jniSession?.run { + deleteViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr, + deleteOptions.congestionControl.value, + deleteOptions.priority.value, + deleteOptions.express, + deleteOptions.attachment?.into()?.bytes, + deleteOptions.reliability.ordinal + ) + } } @Throws(ZError::class) internal fun zid(): ZenohId { - return jniSession?.zid() ?: throw sessionClosedException + return jniSession?.run { ZenohId(getZidViaJNI(sessionPtr)) } ?: throw sessionClosedException } @Throws(ZError::class) internal fun getPeersId(): List { - return jniSession?.peersZid() ?: throw sessionClosedException + return jniSession?.run { getPeersZidViaJNI(sessionPtr).map { ZenohId(it) } } ?: throw sessionClosedException } @Throws(ZError::class) internal fun getRoutersId(): List { - return jniSession?.routersZid() ?: throw sessionClosedException + return jniSession?.run { getRoutersZidViaJNI(sessionPtr).map { ZenohId(it) } } ?: throw sessionClosedException } /** Launches the session through the jni session, returning the [Session] on success. */ @Throws(ZError::class) private fun launch(): Session { - this.jniSession = JNISession.open(config) + this.jniSession = JNISession.open(config.jniConfig.ptr) return this } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt index 7a5931f8..7a5500d6 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -15,11 +15,14 @@ package io.zenoh import io.zenoh.Logger.Companion.LOG_ENV +import io.zenoh.config.WhatAmI +import io.zenoh.config.ZenohId import io.zenoh.exceptions.ZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler import io.zenoh.jni.JNIScout +import io.zenoh.jni.callbacks.JNIScoutCallback import io.zenoh.scouting.* import java.util.* import java.util.concurrent.BlockingQueue @@ -53,10 +56,12 @@ object Zenoh { @Throws(ZError::class) fun scout(scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout>> { val handler = BlockingQueueHandler(LinkedBlockingDeque>()) - return JNIScout.scoutWithHandler( - scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() }, - receiver = handler.receiver(), config = scoutOptions.config - ) + val scoutCallback = JNIScoutCallback { whatAmI, id, locators -> + handler.handle(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators)) + } + val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it } + val ptr = JNIScout.scoutViaJNI(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig?.ptr ?: 0) + return HandlerScout(JNIScout(ptr), handler.receiver()) } /** @@ -74,10 +79,12 @@ object Zenoh { @JvmStatic @Throws(ZError::class) fun scout(handler: Handler, scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout { - return JNIScout.scoutWithHandler( - scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() }, - receiver = handler.receiver(), config = scoutOptions.config - ) + val scoutCallback = JNIScoutCallback { whatAmI, id, locators -> + handler.handle(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators)) + } + val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it } + val ptr = JNIScout.scoutViaJNI(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig?.ptr ?: 0) + return HandlerScout(JNIScout(ptr), handler.receiver()) } /** @@ -94,9 +101,12 @@ object Zenoh { @JvmStatic @Throws(ZError::class) fun scout(callback: Callback, scoutOptions: ScoutOptions = ScoutOptions()): CallbackScout { - return JNIScout.scoutWithCallback( - scoutOptions.whatAmI, callback, config = scoutOptions.config - ) + val scoutCallback = JNIScoutCallback { whatAmI, id, locators -> + callback.run(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators)) + } + val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it } + val ptr = JNIScout.scoutViaJNI(binaryWhatAmI, scoutCallback, fun() {}, scoutOptions.config?.jniConfig?.ptr ?: 0) + return CallbackScout(JNIScout(ptr)) } /** @@ -136,9 +146,3 @@ object Zenoh { logLevelProp?.let { Logger.start(it) } ?: Logger.start(fallbackFilter) } } - -/** - * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the - * log level configuration. - */ -internal expect object ZenohLoad diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt deleted file mode 100644 index 0225c32b..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt +++ /dev/null @@ -1,188 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.into -import io.zenoh.config.EntityGlobalId -import io.zenoh.config.ZenohId -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.jni.callbacks.JNISubscriberCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.liveliness.LivelinessToken -import io.zenoh.pubsub.CallbackSubscriber -import io.zenoh.pubsub.HandlerSubscriber -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.Reply -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration - -internal object JNILiveliness { - - @Throws(ZError::class) - fun get( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - receiver: R, - timeout: Duration, - onClose: Runnable - ): R { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr2: String?, - payload: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr2!!, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - callback.run(reply) - } - getViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - getCallback, - timeout.toMillis(), - onClose::run - ) - return receiver - } - - fun declareToken(jniSession: JNISession, keyExpr: KeyExpr): LivelinessToken { - val ptr = declareTokenViaJNI(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr) - return LivelinessToken(JNILivelinessToken(ptr)) - } - - fun declareSubscriber( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - history: Boolean, - onClose: () -> Unit - ): CallbackSubscriber { - val subCallback = - JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr2, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val ptr = declareSubscriberViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - history, - onClose - ) - return CallbackSubscriber(keyExpr, JNISubscriber(ptr)) - } - - fun declareSubscriber( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - receiver: R, - history: Boolean, - onClose: () -> Unit - ): HandlerSubscriber { - val subCallback = - JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr2, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val ptr = declareSubscriberViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - history, - onClose - ) - return HandlerSubscriber(keyExpr, JNISubscriber(ptr), receiver) - } - - private external fun getViaJNI( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNIGetCallback, - timeoutMs: Long, - onClose: JNIOnCloseCallback - ) - - private external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long - - private external fun declareSubscriberViaJNI( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNISubscriberCallback, - history: Boolean, - onClose: JNIOnCloseCallback - ): Long -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt deleted file mode 100644 index 991c860a..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.zenoh.jni - -internal class JNILivelinessToken(val ptr: Long) { - - fun undeclare() { - undeclareViaJNI(this.ptr) - } - - companion object { - external fun undeclareViaJNI(ptr: Long) - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt deleted file mode 100644 index 6e694271..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.exceptions.ZError -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.IntoZBytes - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.Publisher]. - * - * @property ptr: raw pointer to the underlying native Publisher. - */ -internal class JNIPublisher(private val ptr: Long) { - - /** - * Put operation. - * - * @param payload Payload of the put. - * @param encoding Encoding of the payload. - * @param attachment Optional attachment. - */ - @Throws(ZError::class) - fun put(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?) { - val resolvedEncoding = encoding ?: Encoding.defaultEncoding() - putViaJNI(payload.into().bytes, resolvedEncoding.id, resolvedEncoding.schema, attachment?.into()?.bytes, ptr) - } - - /** - * Delete operation. - * - * @param attachment Optional attachment. - */ - @Throws(ZError::class) - fun delete(attachment: IntoZBytes?) { - deleteViaJNI(attachment?.into()?.bytes, ptr) - } - - /** - * Close and free the underlying publisher pointer. - * - * Further operations with this publisher should not be performed anymore. - */ - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun putViaJNI( - valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long - ) - - @Throws(ZError::class) - private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) - - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt deleted file mode 100644 index c026ed1c..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.annotations.Unstable -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.IntoZBytes -import io.zenoh.bytes.into -import io.zenoh.config.EntityGlobalId -import io.zenoh.config.ZenohId -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.handlers.Handler -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.Parameters -import io.zenoh.query.Querier -import io.zenoh.query.Reply -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp - -internal class JNIQuerier(val ptr: Long) { - - @OptIn(Unstable::class) - @Throws(ZError::class) - fun performGetWithCallback(keyExpr: KeyExpr, callback: Callback, options: Querier.GetOptions) { - performGet(keyExpr, options.parameters, callback, fun() {}, Unit, options.attachment, options.payload, options.encoding) - } - - @OptIn(Unstable::class) - @Throws(ZError::class) - fun performGetWithHandler(keyExpr: KeyExpr, handler: Handler, options: Querier.GetOptions): R { - return performGet(keyExpr, options.parameters, handler::handle, handler::onClose, handler.receiver(), options.attachment, options.payload, options.encoding) - } - - @Throws(ZError::class) - private fun performGet( - keyExpr: KeyExpr, - parameters: Parameters?, - callback: Callback, - onClose: () -> Unit, - receiver: R, - attachment: IntoZBytes?, - payload: IntoZBytes?, - encoding: Encoding? - ): R { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr2: String?, - payload2: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr2!!, null), - payload2.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload2.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - callback.run(reply) - } - - getViaJNI(this.ptr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - parameters?.toString(), - getCallback, - onClose, - attachment?.into()?.bytes, - payload?.into()?.bytes, - encoding?.id ?: Encoding.defaultEncoding().id, - encoding?.schema - ) - return receiver - } - - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun getViaJNI( - querierPtr: Long, - keyExprPtr: Long, - keyExprString: String, - parameters: String?, - callback: JNIGetCallback, - onClose: JNIOnCloseCallback, - attachmentBytes: ByteArray?, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - ) - - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt deleted file mode 100644 index 0b1683f0..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt +++ /dev/null @@ -1,106 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.exceptions.ZError -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.bytes.Encoding -import io.zenoh.qos.QoS -import io.zenoh.bytes.IntoZBytes -import io.zenoh.sample.Sample -import org.apache.commons.net.ntp.TimeStamp - -/** - * Adapter class for interacting with a Query using JNI. - * - * This class serves as an adapter for interacting with a Query through JNI (Java Native Interface). - * - * @property ptr The raw pointer to the underlying native query. - */ -internal class JNIQuery(private val ptr: Long) { - - fun replySuccess(sample: Sample) { - val timestampEnabled = sample.timestamp != null - replySuccessViaJNI( - ptr, - sample.keyExpr.jniKeyExpr?.ptr ?: 0, - sample.keyExpr.keyExpr, - sample.payload.bytes, - sample.encoding.id, - sample.encoding.schema, - timestampEnabled, - if (timestampEnabled) sample.timestamp!!.ntpValue() else 0, - sample.attachment?.bytes, - sample.qos.express - ) - } - - fun replyError(error: IntoZBytes, encoding: Encoding) { - replyErrorViaJNI(ptr, error.into().bytes, encoding.id, encoding.schema) - } - - fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: IntoZBytes?, qos: QoS) { - val timestampEnabled = timestamp != null - replyDeleteViaJNI( - ptr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - timestampEnabled, - if (timestampEnabled) timestamp!!.ntpValue() else 0, - attachment?.into()?.bytes, - qos.express - ) - } - - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun replySuccessViaJNI( - queryPtr: Long, - keyExprPtr: Long, - keyExprString: String, - valuePayload: ByteArray, - valueEncodingId: Int, - valueEncodingSchema: String?, - timestampEnabled: Boolean, - timestampNtp64: Long, - attachment: ByteArray?, - qosExpress: Boolean, - ) - - @Throws(ZError::class) - private external fun replyErrorViaJNI( - queryPtr: Long, - errorValuePayload: ByteArray, - errorValueEncoding: Int, - encodingSchema: String?, - ) - - @Throws(ZError::class) - private external fun replyDeleteViaJNI( - queryPtr: Long, - keyExprPtr: Long, - keyExprString: String, - timestampEnabled: Boolean, - timestampNtp64: Long, - attachment: ByteArray?, - qosExpress: Boolean, - ) - - /** Frees the underlying native Query. */ - private external fun freePtrViaJNI(ptr: Long) -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt deleted file mode 100644 index f2e1c49c..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.Config -import io.zenoh.ZenohLoad -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIScoutCallback -import io.zenoh.config.ZenohId -import io.zenoh.scouting.Hello -import io.zenoh.config.WhatAmI -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.scouting.CallbackScout -import io.zenoh.scouting.HandlerScout - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.scouting.Scout] - * - * @property ptr: raw pointer to the underlying native scout. - */ -internal class JNIScout(private val ptr: Long) { - - companion object { - - init { - ZenohLoad - } - - @Throws(ZError::class) - fun scoutWithHandler( - whatAmI: Set, - callback: Callback, - onClose: () -> Unit, - config: Config?, - receiver: R - ): HandlerScout { - val scoutCallback = JNIScoutCallback { whatAmI2: Int, id: ByteArray, locators: List -> - callback.run(Hello(WhatAmI.fromInt(whatAmI2), ZenohId(id), locators)) - } - val binaryWhatAmI: Int = whatAmI.map { it.value }.reduce { acc, it -> acc or it } - val ptr = scoutViaJNI(binaryWhatAmI, scoutCallback, onClose,config?.jniConfig?.ptr ?: 0) - return HandlerScout(JNIScout(ptr), receiver) - } - - @Throws(ZError::class) - fun scoutWithCallback( - whatAmI: Set, - callback: Callback, - config: Config?, - ): CallbackScout { - val scoutCallback = JNIScoutCallback { whatAmI2: Int, id: ByteArray, locators: List -> - callback.run(Hello(WhatAmI.fromInt(whatAmI2), ZenohId(id), locators)) - } - val binaryWhatAmI: Int = whatAmI.map { it.value }.reduce { acc, it -> acc or it } - val ptr = scoutViaJNI(binaryWhatAmI, scoutCallback, fun() {},config?.jniConfig?.ptr ?: 0) - return CallbackScout(JNIScout(ptr)) - } - - @Throws(ZError::class) - private external fun scoutViaJNI( - whatAmI: Int, - callback: JNIScoutCallback, - onClose: JNIOnCloseCallback, - configPtr: Long, - ): Long - - @Throws(ZError::class) - external fun freePtrViaJNI(ptr: Long) - } - - fun close() { - freePtrViaJNI(ptr) - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt deleted file mode 100644 index 7026ebd6..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ /dev/null @@ -1,549 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.* -import io.zenoh.bytes.Encoding -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIQueryableCallback -import io.zenoh.jni.callbacks.JNISubscriberCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.bytes.IntoZBytes -import io.zenoh.config.ZenohId -import io.zenoh.bytes.into -import io.zenoh.Config -import io.zenoh.annotations.Unstable -import io.zenoh.config.EntityGlobalId -import io.zenoh.handlers.Handler -import io.zenoh.pubsub.* -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.* -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp - -/** Adapter class to handle the communication with the Zenoh JNI code for a [Session]. */ -internal class JNISession(val sessionPtr: Long) { - - companion object { - init { - ZenohLoad - } - - @Throws(ZError::class) - fun open(config: Config): JNISession { - val sessionPtr = openSessionViaJNI(config.jniConfig.ptr) - return JNISession(sessionPtr) - } - - @JvmStatic - @Throws(ZError::class) - private external fun openSessionViaJNI(configPtr: Long): Long - } - - fun close() { - closeSessionViaJNI(sessionPtr) - } - - @Throws(ZError::class) - fun declarePublisher(keyExpr: KeyExpr, publisherOptions: PublisherOptions): Publisher { - val publisherRawPtr = declarePublisherViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - publisherOptions.congestionControl.value, - publisherOptions.priority.value, - publisherOptions.express, - publisherOptions.reliability.ordinal - ) - return Publisher( - keyExpr, - publisherOptions.congestionControl, - publisherOptions.priority, - publisherOptions.encoding, - JNIPublisher(publisherRawPtr), - ) - } - - @Throws(ZError::class) - fun declareSubscriberWithHandler( - keyExpr: KeyExpr, handler: Handler - ): HandlerSubscriber { - val subCallback = - JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr1, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - handler.handle(sample) - } - val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, subCallback, handler::onClose - ) - return HandlerSubscriber(keyExpr, JNISubscriber(subscriberRawPtr), handler.receiver()) - } - - @Throws(ZError::class) - fun declareSubscriberWithCallback( - keyExpr: KeyExpr, callback: Callback - ): CallbackSubscriber { - val subCallback = - JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr1, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - subCallback, - fun() {} - ) - return CallbackSubscriber(keyExpr, JNISubscriber(subscriberRawPtr)) - } - - @Throws(ZError::class) - fun declareQueryableWithCallback( - keyExpr: KeyExpr, callback: Callback, config: QueryableOptions - ): CallbackQueryable { - val queryCallback = - JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long, acceptReplies: Int -> - val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExpr1, null) - val selector = if (selectorParams.isEmpty()) { - Selector(keyExpr2) - } else { - Selector(keyExpr2, Parameters.from(selectorParams)) - } - val replyKeyExpr = ReplyKeyExpr.entries[acceptReplies] - val query = Query( - keyExpr2, - selector, - payload?.into(), - payload?.let { Encoding(encodingId, schema = encodingSchema) }, - attachmentBytes?.into(), - replyKeyExpr, - jniQuery - ) - callback.run(query) - } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - queryCallback, - fun() {}, - config.complete - ) - return CallbackQueryable(keyExpr, JNIQueryable(queryableRawPtr)) - } - - @Throws(ZError::class) - fun declareQueryableWithHandler( - keyExpr: KeyExpr, handler: Handler, config: QueryableOptions - ): HandlerQueryable { - val queryCallback = - JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long, acceptReplies: Int -> - val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExpr1, null) - val selector = if (selectorParams.isEmpty()) { - Selector(keyExpr2) - } else { - Selector(keyExpr2, Parameters.from(selectorParams)) - } - val replyKeyExpr = ReplyKeyExpr.entries[acceptReplies] - val query = Query( - keyExpr2, - selector, - payload?.into(), - payload?.let { Encoding(encodingId, schema = encodingSchema) }, - attachmentBytes?.into(), - replyKeyExpr, - jniQuery - ) - handler.handle(query) - } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - queryCallback, - handler::onClose, - config.complete - ) - return HandlerQueryable(keyExpr, JNIQueryable(queryableRawPtr), handler.receiver()) - } - - @OptIn(Unstable::class) - fun declareQuerier( - keyExpr: KeyExpr, - options: QuerierOptions - ): Querier { - val querierRawPtr = declareQuerierViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - options.target.ordinal, - options.consolidationMode.ordinal, - options.congestionControl.value, - options.priority.value, - options.express, - options.timeout.toMillis(), - options.acceptReplies.ordinal - ) - return Querier( - keyExpr, - QoS( - congestionControl = options.congestionControl, - priority = options.priority, - express = options.express - ), - JNIQuerier(querierRawPtr) - ) - } - - @Throws(ZError::class) - fun performGetWithCallback( - intoSelector: IntoSelector, - callback: Callback, - options: GetOptions - ) { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload1: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr!!, null), - payload1.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload1.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - callback.run(reply) - } - - val selector = intoSelector.into() - getViaJNI( - selector.keyExpr.jniKeyExpr?.ptr ?: 0, - selector.keyExpr.keyExpr, - selector.parameters?.toString(), - sessionPtr, - getCallback, - fun() {}, - options.timeout.toMillis(), - options.target.ordinal, - options.consolidation.ordinal, - options.attachment?.into()?.bytes, - options.payload?.into()?.bytes, - options.encoding?.id ?: Encoding.defaultEncoding().id, - options.encoding?.schema, - options.qos.congestionControl.value, - options.qos.priority.value, - options.qos.express, - options.acceptReplies.ordinal - ) - } - - @Throws(ZError::class) - fun performGetWithHandler( - intoSelector: IntoSelector, - handler: Handler, - options: GetOptions - ): R { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload1: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr!!, null), - payload1.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload1.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - handler.handle(reply) - } - - val selector = intoSelector.into() - getViaJNI( - selector.keyExpr.jniKeyExpr?.ptr ?: 0, - selector.keyExpr.keyExpr, - selector.parameters?.toString(), - sessionPtr, - getCallback, - handler::onClose, - options.timeout.toMillis(), - options.target.ordinal, - options.consolidation.ordinal, - options.attachment?.into()?.bytes, - options.payload?.into()?.bytes, - options.encoding?.id ?: Encoding.defaultEncoding().id, - options.encoding?.schema, - options.qos.congestionControl.value, - options.qos.priority.value, - options.qos.express, - options.acceptReplies.ordinal - ) - return handler.receiver() - } - - @Throws(ZError::class) - fun declareKeyExpr(keyExpr: String): KeyExpr { - val ptr = declareKeyExprViaJNI(sessionPtr, keyExpr) - return KeyExpr(keyExpr, JNIKeyExpr(ptr)) - } - - @Throws(ZError::class) - fun undeclareKeyExpr(keyExpr: KeyExpr) { - keyExpr.jniKeyExpr?.run { - undeclareKeyExprViaJNI(sessionPtr, this.ptr) - keyExpr.jniKeyExpr = null - } ?: throw ZError("Attempting to undeclare a non declared key expression.") - } - - @Throws(ZError::class) - fun performPut( - keyExpr: KeyExpr, - payload: IntoZBytes, - options: PutOptions, - ) { - val encoding = options.encoding ?: Encoding.defaultEncoding() - putViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - payload.into().bytes, - encoding.id, - encoding.schema, - options.congestionControl.value, - options.priority.value, - options.express, - options.attachment?.into()?.bytes, - options.reliability.ordinal - ) - } - - @Throws(ZError::class) - fun performDelete( - keyExpr: KeyExpr, - options: DeleteOptions, - ) { - deleteViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - options.congestionControl.value, - options.priority.value, - options.express, - options.attachment?.into()?.bytes, - options.reliability.ordinal - ) - } - - @Throws(ZError::class) - fun zid(): ZenohId { - return ZenohId(getZidViaJNI(sessionPtr)) - } - - @Throws(ZError::class) - fun peersZid(): List { - return getPeersZidViaJNI(sessionPtr).map { ZenohId(it) } - } - - @Throws(ZError::class) - fun routersZid(): List { - return getRoutersZidViaJNI(sessionPtr).map { ZenohId(it) } - } - - @Throws(ZError::class) - private external fun getZidViaJNI(ptr: Long): ByteArray - - @Throws(ZError::class) - private external fun getPeersZidViaJNI(ptr: Long): List - - @Throws(ZError::class) - private external fun getRoutersZidViaJNI(ptr: Long): List - - @Throws(ZError::class) - private external fun closeSessionViaJNI(ptr: Long) - - @Throws(ZError::class) - private external fun declarePublisherViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - congestionControl: Int, - priority: Int, - express: Boolean, - reliability: Int - ): Long - - @Throws(ZError::class) - private external fun declareSubscriberViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - callback: JNISubscriberCallback, - onClose: JNIOnCloseCallback, - ): Long - - @Throws(ZError::class) - private external fun declareQueryableViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - callback: JNIQueryableCallback, - onClose: JNIOnCloseCallback, - complete: Boolean - ): Long - - @Throws(ZError::class) - private external fun declareQuerierViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - target: Int, - consolidation: Int, - congestionControl: Int, - priority: Int, - express: Boolean, - timeoutMs: Long, - acceptReplies: Int - ): Long - - @Throws(ZError::class) - private external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long - - @Throws(ZError::class) - private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) - - @Throws(ZError::class) - private external fun getViaJNI( - keyExprPtr: Long, - keyExprString: String, - selectorParams: String?, - sessionPtr: Long, - callback: JNIGetCallback, - onClose: JNIOnCloseCallback, - timeoutMs: Long, - target: Int, - consolidation: Int, - attachmentBytes: ByteArray?, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - congestionControl: Int, - priority: Int, - express: Boolean, - acceptReplies: Int, - ) - - @Throws(ZError::class) - private external fun putViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - valuePayload: ByteArray, - valueEncoding: Int, - valueEncodingSchema: String?, - congestionControl: Int, - priority: Int, - express: Boolean, - attachmentBytes: ByteArray?, - reliability: Int - ) - - @Throws(ZError::class) - private external fun deleteViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - congestionControl: Int, - priority: Int, - express: Boolean, - attachmentBytes: ByteArray?, - reliability: Int - ) -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index b5fe6110..fda83d39 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -79,7 +79,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn @JvmStatic @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { - return JNIKeyExpr.tryFrom(keyExpr) + return KeyExpr(JNIKeyExpr.tryFrom(keyExpr)) } /** @@ -95,7 +95,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn @JvmStatic @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { - return JNIKeyExpr.autocanonize(keyExpr) + return KeyExpr(JNIKeyExpr.autocanonize(keyExpr)) } } @@ -106,7 +106,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun intersects(other: KeyExpr): Boolean { - return JNIKeyExpr.intersects(this, other) + return JNIKeyExpr.intersects(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) } /** @@ -116,7 +116,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun includes(other: KeyExpr): Boolean { - return JNIKeyExpr.includes(this, other) + return JNIKeyExpr.includes(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) } /** @@ -126,7 +126,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun relationTo(other: KeyExpr): SetIntersectionLevel { - return JNIKeyExpr.relationTo(this, other) + return SetIntersectionLevel.fromInt(JNIKeyExpr.relationTo(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr)) } /** @@ -135,7 +135,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun join(other: String): KeyExpr { - return JNIKeyExpr.joinViaJNI(this, other) + return KeyExpr(JNIKeyExpr.join(jniKeyExpr?.ptr ?: 0, keyExpr, other)) } /** @@ -144,7 +144,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun concat(other: String): KeyExpr { - return JNIKeyExpr.concatViaJNI(this, other) + return KeyExpr(JNIKeyExpr.concat(jniKeyExpr?.ptr ?: 0, keyExpr, other)) } override fun toString(): String { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index 96711557..be811bba 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -15,17 +15,30 @@ package io.zenoh.liveliness import io.zenoh.Session +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.into +import io.zenoh.config.EntityGlobalId +import io.zenoh.config.ZenohId import io.zenoh.exceptions.ZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler import io.zenoh.jni.JNILiveliness +import io.zenoh.jni.JNILivelinessToken +import io.zenoh.jni.JNISubscriber +import io.zenoh.jni.callbacks.JNIGetCallback +import io.zenoh.jni.callbacks.JNISubscriberCallback import io.zenoh.keyexpr.KeyExpr import io.zenoh.pubsub.CallbackSubscriber import io.zenoh.pubsub.HandlerSubscriber import io.zenoh.pubsub.Subscriber +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS import io.zenoh.query.Reply import io.zenoh.sample.Sample +import io.zenoh.sample.SampleKind +import org.apache.commons.net.ntp.TimeStamp import java.time.Duration import java.util.* import java.util.concurrent.BlockingQueue @@ -49,7 +62,8 @@ class Liveliness internal constructor(private val session: Session) { @Throws(ZError::class) fun declareToken(keyExpr: KeyExpr): LivelinessToken { val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareToken(jniSession, keyExpr) + val ptr = JNILiveliness.declareTokenViaJNI(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr) + return LivelinessToken(JNILivelinessToken(ptr)) } /** @@ -66,14 +80,16 @@ class Liveliness internal constructor(private val session: Session) { ): BlockingQueue> { val jniSession = session.jniSession ?: throw Session.sessionClosedException val handler = BlockingQueueHandler(LinkedBlockingDeque()) - return JNILiveliness.get( - jniSession, - keyExpr, - handler::handle, - receiver = handler.receiver(), - timeout, - onClose = handler::onClose + val getCallback = buildGetCallback(handler::handle) + JNILiveliness.getViaJNI( + jniSession.sessionPtr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + getCallback, + timeout.toMillis(), + handler::onClose ) + return handler.receiver() } /** @@ -89,7 +105,14 @@ class Liveliness internal constructor(private val session: Session) { keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000) ) { val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.get(jniSession, keyExpr, callback, Unit, timeout, {}) + JNILiveliness.getViaJNI( + jniSession.sessionPtr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + buildGetCallback(callback), + timeout.toMillis(), + fun() {} + ) } /** @@ -106,17 +129,39 @@ class Liveliness internal constructor(private val session: Session) { keyExpr: KeyExpr, handler: Handler, timeout: Duration = Duration.ofMillis(10000) ): R { val jniSession = session.jniSession ?: throw Session.sessionClosedException - val callback = handler::handle - return JNILiveliness.get( - jniSession, - keyExpr, - callback, - handler.receiver(), - timeout, - onClose = handler::onClose + JNILiveliness.getViaJNI( + jniSession.sessionPtr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + buildGetCallback(handler::handle), + timeout.toMillis(), + handler::onClose ) + return handler.receiver() } + private fun buildGetCallback(callback: Callback): JNIGetCallback = + JNIGetCallback { replierZid, replierEid, success, keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val reply: Reply = if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + Reply.Success( + replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, + Sample( + KeyExpr(keyExpr2!!, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } else { + Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema)) + } + callback.run(reply) + } + /** * Create a [Subscriber] for liveliness changes matching the given key expression. * @@ -131,14 +176,16 @@ class Liveliness internal constructor(private val session: Session) { ): HandlerSubscriber>> { val handler = BlockingQueueHandler(LinkedBlockingDeque()) val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - handler::handle, - handler.receiver(), + val subCallback = buildSubscriberCallback(handler::handle) + val ptr = JNILiveliness.declareSubscriberViaJNI( + jniSession.sessionPtr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + subCallback, options.history, handler::onClose ) + return HandlerSubscriber(keyExpr, JNISubscriber(ptr), handler.receiver()) } /** @@ -156,13 +203,16 @@ class Liveliness internal constructor(private val session: Session) { options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() ): CallbackSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - callback, + val subCallback = buildSubscriberCallback(callback) + val ptr = JNILiveliness.declareSubscriberViaJNI( + jniSession.sessionPtr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + subCallback, options.history, fun() {} ) + return CallbackSubscriber(keyExpr, JNISubscriber(ptr)) } /** @@ -181,15 +231,33 @@ class Liveliness internal constructor(private val session: Session) { options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() ): HandlerSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - handler::handle, - handler.receiver(), + val subCallback = buildSubscriberCallback(handler::handle) + val ptr = JNILiveliness.declareSubscriberViaJNI( + jniSession.sessionPtr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + subCallback, options.history, handler::onClose ) + return HandlerSubscriber(keyExpr, JNISubscriber(ptr), handler.receiver()) } + + private fun buildSubscriberCallback(callback: Callback): JNISubscriberCallback = + JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + callback.run( + Sample( + KeyExpr(keyExpr2, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index d0a9de2e..9ec8641a 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -79,13 +79,14 @@ class Publisher internal constructor( /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @Throws(ZError::class) fun put(payload: IntoZBytes) { - jniPublisher?.put(payload, encoding, null) ?: throw publisherNotValid + jniPublisher?.put(payload.into().bytes, encoding.id, encoding.schema, null) ?: throw publisherNotValid } /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @Throws(ZError::class) fun put(payload: IntoZBytes, options: PutOptions) { - jniPublisher?.put(payload, options.encoding ?: this.encoding, options.attachment) ?: throw publisherNotValid + val enc = options.encoding ?: this.encoding + jniPublisher?.put(payload.into().bytes, enc.id, enc.schema, options.attachment?.into()?.bytes) ?: throw publisherNotValid } /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @@ -102,7 +103,7 @@ class Publisher internal constructor( @JvmOverloads @Throws(ZError::class) fun delete(options: DeleteOptions = DeleteOptions()) { - jniPublisher?.delete(options.attachment) ?: throw(publisherNotValid) + jniPublisher?.delete(options.attachment?.into()?.bytes) ?: throw(publisherNotValid) } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 541d056b..27cc6acb 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -18,16 +18,23 @@ import io.zenoh.annotations.Unstable import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes +import io.zenoh.bytes.into +import io.zenoh.config.EntityGlobalId +import io.zenoh.config.ZenohId import io.zenoh.exceptions.ZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler import io.zenoh.jni.JNIQuerier +import io.zenoh.jni.callbacks.JNIGetCallback import io.zenoh.keyexpr.KeyExpr import io.zenoh.qos.CongestionControl import io.zenoh.qos.Priority import io.zenoh.qos.QoS +import io.zenoh.sample.Sample +import io.zenoh.sample.SampleKind import io.zenoh.session.SessionDeclaration +import org.apache.commons.net.ntp.TimeStamp import java.time.Duration import java.util.Optional import java.util.concurrent.BlockingQueue @@ -143,11 +150,76 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v } private fun resolveGetWithCallback(keyExpr: KeyExpr, callback: Callback, options: GetOptions) { - jniQuerier?.performGetWithCallback(keyExpr, callback, options) ?: throw ZError("Querier is not valid.") + val jni = jniQuerier ?: throw ZError("Querier is not valid.") + val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val reply: Reply = if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + Reply.Success( + replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, + Sample( + KeyExpr(keyExpr2!!, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } else { + Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema)) + } + callback.run(reply) + } + jni.getViaJNI( + jni.ptr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + options.parameters?.toString(), + getCallback, + fun() {}, + options.attachment?.into()?.bytes, + options.payload?.into()?.bytes, + options.encoding?.id ?: Encoding.defaultEncoding().id, + options.encoding?.schema + ) } private fun resolveGetWithHandler(keyExpr: KeyExpr, handler: Handler, options: GetOptions): R { - return jniQuerier?.performGetWithHandler(keyExpr, handler, options) ?: throw ZError("Querier is not valid.") + val jni = jniQuerier ?: throw ZError("Querier is not valid.") + val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl -> + val reply: Reply = if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + Reply.Success( + replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, + Sample( + KeyExpr(keyExpr2!!, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + ) + } else { + Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema)) + } + handler.handle(reply) + } + jni.getViaJNI( + jni.ptr, + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + options.parameters?.toString(), + getCallback, + handler::onClose, + options.attachment?.into()?.bytes, + options.payload?.into()?.bytes, + options.encoding?.id ?: Encoding.defaultEncoding().id, + options.encoding?.schema + ) + return handler.receiver() } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index 3a8184da..9731983d 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -24,6 +24,7 @@ import io.zenoh.keyexpr.KeyExpr import io.zenoh.qos.QoS import io.zenoh.sample.Sample import io.zenoh.sample.SampleKind +import org.apache.commons.net.ntp.TimeStamp /** * Represents a Zenoh Query in Kotlin. @@ -61,17 +62,22 @@ class Query internal constructor( @Throws(ZError::class) @JvmOverloads fun reply(keyExpr: KeyExpr, payload: IntoZBytes, options: ReplyOptions = ReplyOptions()) { - val sample = Sample( - keyExpr, - payload.into(), - options.encoding, - SampleKind.PUT, - options.timeStamp, - QoS(options.congestionControl, options.priority, options.express), - options.attachment?.into() - ) + val zbytes = payload.into() + val encoding = options.encoding + val timestamp = options.timeStamp + val timestampEnabled = timestamp != null jniQuery?.apply { - replySuccess(sample) + replySuccess( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + zbytes.bytes, + encoding?.id ?: Encoding.defaultEncoding().id, + encoding?.schema, + timestampEnabled, + if (timestampEnabled) timestamp!!.ntpValue() else 0, + options.attachment?.into()?.bytes, + QoS(options.congestionControl, options.priority, options.express).express + ) jniQuery = null } ?: throw (ZError("Query is invalid")) } @@ -98,12 +104,16 @@ class Query internal constructor( @JvmOverloads @Throws(ZError::class) fun replyDel(keyExpr: KeyExpr, options: ReplyDelOptions = ReplyDelOptions()) { + val timestamp = options.timeStamp + val timestampEnabled = timestamp != null jniQuery?.apply { replyDelete( - keyExpr, - options.timeStamp, - options.attachment, - QoS(options.congestionControl, options.priority, options.express), + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + timestampEnabled, + if (timestampEnabled) timestamp!!.ntpValue() else 0, + options.attachment?.into()?.bytes, + QoS(options.congestionControl, options.priority, options.express).express ) jniQuery = null } ?: throw (ZError("Query is invalid")) @@ -118,8 +128,9 @@ class Query internal constructor( @JvmOverloads @Throws(ZError::class) fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) { + val encoding = options.encoding jniQuery?.apply { - replyError(message.into(), options.encoding) + replyError(message.into().bytes, encoding?.id ?: Encoding.defaultEncoding().id, encoding?.schema) jniQuery = null } ?: throw (ZError("Query is invalid")) } diff --git a/zenoh-jni-runtime/build.gradle.kts b/zenoh-jni-runtime/build.gradle.kts new file mode 100644 index 00000000..dc997ce5 --- /dev/null +++ b/zenoh-jni-runtime/build.gradle.kts @@ -0,0 +1,240 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +import com.nishtahir.CargoExtension + +plugins { + kotlin("multiplatform") + `maven-publish` + signing +} + +val androidEnabled = project.findProperty("android")?.toString()?.toBoolean() == true +val release = project.findProperty("release")?.toString()?.toBoolean() == true + +// If the publication is meant to be done on a remote repository (Maven central). +// Modifying this property will affect the release workflows! +val isRemotePublication = project.findProperty("remotePublication")?.toString()?.toBoolean() == true + +var buildMode = if (release) BuildMode.RELEASE else BuildMode.DEBUG + +if (androidEnabled) { + apply(plugin = "com.android.library") + apply(plugin = "org.mozilla.rust-android-gradle.rust-android") + + configureCargo() + configureAndroid() +} + +kotlin { + jvmToolchain(11) + jvm { + compilations.all { + kotlinOptions.jvmTarget = "11" + } + testRuns["test"].executionTask.configure { + val zenohPaths = "../zenoh-jni/target/$buildMode" + jvmArgs("-Djava.library.path=$zenohPaths") + } + if (!androidEnabled) { + withJava() + } + } + if (androidEnabled) { + androidTarget { + publishLibraryVariants("release") + } + } + + @Suppress("Unused") + sourceSets { + val commonMain by getting {} + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + if (androidEnabled) { + val androidUnitTest by getting { + dependencies { + implementation(kotlin("test-junit")) + } + } + } + val jvmMain by getting { + if (isRemotePublication) { + resources.srcDir("../jni-libs").include("*/**") + } else { + resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) + } + } + + val jvmTest by getting { + resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) + } + } + + publishing { + publications.withType { + groupId = "org.eclipse.zenoh" + artifactId = "zenoh-jni-runtime" + version = rootProject.version.toString() + + pom { + name.set("Zenoh JNI Runtime") + description.set("The Eclipse Zenoh JNI runtime layer for zenoh-java and zenoh-kotlin.") + url.set("https://zenoh.io/") + + licenses { + license { + name.set("Eclipse Public License 2.0 OR Apache License 2.0") + url.set("http://www.eclipse.org/legal/epl-2.0") + } + } + developers { + developer { + id.set("ZettaScale") + name.set("ZettaScale Zenoh Team") + email.set("zenoh@zettascale.tech") + } + } + scm { + connection.set("scm:git:https://github.com/eclipse-zenoh/zenoh-java.git") + developerConnection.set("scm:git:https://github.com/eclipse-zenoh/zenoh-java.git") + url.set("https://github.com/eclipse-zenoh/zenoh-java") + } + } + } + } +} + +signing { + isRequired = isRemotePublication + useInMemoryPgpKeys(System.getenv("ORG_GPG_SUBKEY_ID"), System.getenv("ORG_GPG_PRIVATE_KEY"), System.getenv("ORG_GPG_PASSPHRASE")) + sign(publishing.publications) +} + +tasks.withType().configureEach { + dependsOn(tasks.withType()) +} + +tasks.withType { + doFirst { + systemProperty("java.library.path", "../zenoh-jni/target/$buildMode") + } +} + +tasks.whenObjectAdded { + if ((this.name == "mergeDebugJniLibFolders" || this.name == "mergeReleaseJniLibFolders")) { + this.dependsOn("cargoBuild") + } +} + +tasks.named("compileKotlinJvm") { + dependsOn("buildZenohJni") +} + +tasks.register("buildZenohJni") { + doLast { + if (!isRemotePublication) { + buildZenohJNI(buildMode) + } + } +} + +fun buildZenohJNI(mode: BuildMode = BuildMode.DEBUG) { + val cargoCommand = mutableListOf("cargo", "build") + + if (mode == BuildMode.RELEASE) { + cargoCommand.add("--release") + } + + val result = project.exec { + commandLine(*(cargoCommand.toTypedArray()), "--manifest-path", "../zenoh-jni/Cargo.toml") + } + + if (result.exitValue != 0) { + throw GradleException("Failed to build Zenoh-JNI.") + } + + Thread.sleep(1000) +} + +enum class BuildMode { + DEBUG { + override fun toString(): String { + return "debug" + } + }, + RELEASE { + override fun toString(): String { + return "release" + } + } +} + +fun Project.configureAndroid() { + extensions.configure("android") { + namespace = "io.zenoh.jni" + compileSdk = 30 + + ndkVersion = "26.0.10792818" + + defaultConfig { + minSdk = 30 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + getByName("debug") { + isMinifyEnabled = false + } + } + sourceSets { + getByName("main") { + manifest.srcFile("src/androidMain/AndroidManifest.xml") + } + } + publishing { + singleVariant("release") { + withSourcesJar() + withJavadocJar() + } + } + } +} + +fun Project.configureCargo() { + extensions.configure("cargo") { + pythonCommand = "python3" + module = "../zenoh-jni" + libname = "zenoh-jni" + targetIncludes = arrayOf("libzenoh_jni.so") + targetDirectory = "../zenoh-jni/target/" + profile = "release" + targets = arrayListOf( + "arm", + "arm64", + "x86", + "x86_64", + ) + } +} diff --git a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt similarity index 79% rename from zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt rename to zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt index 74d42e51..15de9e38 100644 --- a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt +++ b/zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -15,10 +15,9 @@ package io.zenoh /** - * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the - * log level configuration. + * Static singleton class to load the Zenoh native library once and only once. */ -internal actual object ZenohLoad { +public actual object ZenohLoad { private const val ZENOH_LIB_NAME = "zenoh_jni" init { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt new file mode 100644 index 00000000..a2d21403 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt @@ -0,0 +1,20 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +/** + * Static singleton object to load the Zenoh native library once and only once. + */ +public expect object ZenohLoad diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt new file mode 100644 index 00000000..1984bd23 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt @@ -0,0 +1,20 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.exceptions + +/** + * A Zenoh Error. + */ +class ZError(override val message: String? = null): Exception() diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt new file mode 100644 index 00000000..d27b0826 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt @@ -0,0 +1,75 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.exceptions.ZError +import io.zenoh.jni.callbacks.JNIMatchingListenerCallback +import io.zenoh.jni.callbacks.JNIOnCloseCallback + +/** + * Adapter class for a native Zenoh AdvancedPublisher. + * + * @property ptr Raw pointer to the underlying native AdvancedPublisher. + */ +public class JNIAdvancedPublisher(public val ptr: Long) { + + @Throws(ZError::class) + fun put(payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?) { + putViaJNI(payload, encodingId, encodingSchema, attachment, ptr) + } + + @Throws(ZError::class) + fun delete(attachment: ByteArray?) { + deleteViaJNI(attachment, ptr) + } + + @Throws(ZError::class) + fun declareMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback): Long = + declareMatchingListenerViaJNI(ptr, callback, onClose) + + @Throws(ZError::class) + fun declareBackgroundMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback) = + declareBackgroundMatchingListenerViaJNI(ptr, callback, onClose) + + @Throws(ZError::class) + fun getMatchingStatus(): Boolean = getMatchingStatusViaJNI(ptr) + + fun close() { + freePtrViaJNI(ptr) + } + + @Throws(ZError::class) + private external fun putViaJNI( + payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long + ) + + @Throws(ZError::class) + private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) + + @Throws(ZError::class) + private external fun declareMatchingListenerViaJNI( + ptr: Long, callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback + ): Long + + @Throws(ZError::class) + private external fun declareBackgroundMatchingListenerViaJNI( + ptr: Long, callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback + ) + + @Throws(ZError::class) + private external fun getMatchingStatusViaJNI(ptr: Long): Boolean + + private external fun freePtrViaJNI(ptr: Long) +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt new file mode 100644 index 00000000..c76d1dbb --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt @@ -0,0 +1,80 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.exceptions.ZError +import io.zenoh.jni.callbacks.JNIOnCloseCallback +import io.zenoh.jni.callbacks.JNISampleMissListenerCallback +import io.zenoh.jni.callbacks.JNISubscriberCallback + +/** + * Adapter class for a native Zenoh AdvancedSubscriber. + * + * @property ptr Raw pointer to the underlying native AdvancedSubscriber. + */ +public class JNIAdvancedSubscriber(public val ptr: Long) { + + @Throws(ZError::class) + fun declareDetectPublishersSubscriber( + history: Boolean, + callback: JNISubscriberCallback, + onClose: JNIOnCloseCallback, + ): Long = declareDetectPublishersSubscriberViaJNI(ptr, history, callback, onClose) + + @Throws(ZError::class) + fun declareBackgroundDetectPublishersSubscriber( + history: Boolean, + callback: JNISubscriberCallback, + onClose: JNIOnCloseCallback, + ) = declareBackgroundDetectPublishersSubscriberViaJNI(ptr, history, callback, onClose) + + @Throws(ZError::class) + fun declareSampleMissListener( + callback: JNISampleMissListenerCallback, + onClose: JNIOnCloseCallback, + ): Long = declareSampleMissListenerViaJNI(ptr, callback, onClose) + + @Throws(ZError::class) + fun declareBackgroundSampleMissListener( + callback: JNISampleMissListenerCallback, + onClose: JNIOnCloseCallback, + ) = declareBackgroundSampleMissListenerViaJNI(ptr, callback, onClose) + + fun close() { + freePtrViaJNI(ptr) + } + + @Throws(ZError::class) + private external fun declareDetectPublishersSubscriberViaJNI( + ptr: Long, history: Boolean, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback + ): Long + + @Throws(ZError::class) + private external fun declareBackgroundDetectPublishersSubscriberViaJNI( + ptr: Long, history: Boolean, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback + ) + + @Throws(ZError::class) + private external fun declareSampleMissListenerViaJNI( + ptr: Long, callback: JNISampleMissListenerCallback, onClose: JNIOnCloseCallback + ): Long + + @Throws(ZError::class) + private external fun declareBackgroundSampleMissListenerViaJNI( + ptr: Long, callback: JNISampleMissListenerCallback, onClose: JNIOnCloseCallback + ) + + private external fun freePtrViaJNI(ptr: Long) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt similarity index 63% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt index ea278988..39a15756 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,13 +14,11 @@ package io.zenoh.jni -import io.zenoh.Config import io.zenoh.ZenohLoad import io.zenoh.exceptions.ZError -import java.io.File -import java.nio.file.Path -internal class JNIConfig(internal val ptr: Long) { +/** Adapter for the native Zenoh config. Factory methods return raw pointers for use by facade classes. */ +public class JNIConfig(public val ptr: Long) { companion object { @@ -28,36 +26,29 @@ internal class JNIConfig(internal val ptr: Long) { ZenohLoad } - fun loadDefaultConfig(): Config { - val cfgPtr = loadDefaultConfigViaJNI() - return Config(JNIConfig(cfgPtr)) - } - @Throws(ZError::class) - fun loadConfigFile(path: Path): Config { - val cfgPtr = loadConfigFileViaJNI(path.toString()) - return Config(JNIConfig(cfgPtr)) + fun loadDefaultConfig(): Long { + return loadDefaultConfigViaJNI() } @Throws(ZError::class) - fun loadConfigFile(file: File): Config = loadConfigFile(file.toPath()) + fun loadConfigFile(path: String): Long { + return loadConfigFileViaJNI(path) + } @Throws(ZError::class) - fun loadJsonConfig(rawConfig: String): Config { - val cfgPtr = loadJsonConfigViaJNI(rawConfig) - return Config(JNIConfig(cfgPtr)) + fun loadJsonConfig(rawConfig: String): Long { + return loadJsonConfigViaJNI(rawConfig) } @Throws(ZError::class) - fun loadJson5Config(rawConfig: String): Config { - val cfgPtr = loadJsonConfigViaJNI(rawConfig) - return Config(JNIConfig(cfgPtr)) + fun loadYamlConfig(rawConfig: String): Long { + return loadYamlConfigViaJNI(rawConfig) } @Throws(ZError::class) - fun loadYamlConfig(rawConfig: String): Config { - val cfgPtr = loadYamlConfigViaJNI(rawConfig) - return Config(JNIConfig(cfgPtr)) + fun loadJson5Config(rawConfig: String): Long { + return loadJsonConfigViaJNI(rawConfig) } @Throws(ZError::class) @@ -78,7 +69,6 @@ internal class JNIConfig(internal val ptr: Long) { @Throws(ZError::class) private external fun insertJson5ViaJNI(ptr: Long, key: String, value: String): Long - /** Frees the underlying native config. */ private external fun freePtrViaJNI(ptr: Long) @Throws(ZError::class) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt similarity index 55% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index 29e419e3..ce5f7126 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -16,10 +16,9 @@ package io.zenoh.jni import io.zenoh.ZenohLoad import io.zenoh.exceptions.ZError -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.SetIntersectionLevel -internal class JNIKeyExpr(internal val ptr: Long) { +/** Adapter for native Zenoh key expressions. Factory methods return raw primitives. */ +public class JNIKeyExpr(public val ptr: Long) { companion object { init { @@ -27,50 +26,37 @@ internal class JNIKeyExpr(internal val ptr: Long) { } @Throws(ZError::class) - fun tryFrom(keyExpr: String): KeyExpr { - return KeyExpr(tryFromViaJNI(keyExpr)) + fun tryFrom(keyExpr: String): String { + return tryFromViaJNI(keyExpr) } @Throws(ZError::class) - fun autocanonize(keyExpr: String): KeyExpr { - return KeyExpr(autocanonizeViaJNI(keyExpr)) + fun autocanonize(keyExpr: String): String { + return autocanonizeViaJNI(keyExpr) } @Throws(ZError::class) - fun intersects(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = intersectsViaJNI( - keyExprA.jniKeyExpr?.ptr ?: 0, - keyExprA.keyExpr, - keyExprB.jniKeyExpr?.ptr ?: 0, - keyExprB.keyExpr - ) + fun intersects(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean = + intersectsViaJNI(ptrA, keyExprA, ptrB, keyExprB) @Throws(ZError::class) - fun includes(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = includesViaJNI( - keyExprA.jniKeyExpr?.ptr ?: 0, - keyExprA.keyExpr, - keyExprB.jniKeyExpr?.ptr ?: 0, - keyExprB.keyExpr - ) + fun includes(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean = + includesViaJNI(ptrA, keyExprA, ptrB, keyExprB) + /** Returns SetIntersectionLevel ordinal as Int. Callers convert to SetIntersectionLevel. */ @Throws(ZError::class) - fun relationTo(keyExpr: KeyExpr, other: KeyExpr): SetIntersectionLevel { - val intersection = relationToViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - other.jniKeyExpr?.ptr ?: 0, - other.keyExpr - ) - return SetIntersectionLevel.fromInt(intersection) + fun relationTo(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int { + return relationToViaJNI(ptrA, keyExprA, ptrB, keyExprB) } @Throws(ZError::class) - fun joinViaJNI(keyExpr: KeyExpr, other: String): KeyExpr { - return KeyExpr(joinViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) + fun join(ptrA: Long, keyExprA: String, other: String): String { + return joinViaJNI(ptrA, keyExprA, other) } @Throws(ZError::class) - fun concatViaJNI(keyExpr: KeyExpr, other: String): KeyExpr { - return KeyExpr(concatViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) + fun concat(ptrA: Long, keyExprA: String, other: String): String { + return concatViaJNI(ptrA, keyExprA, other) } @Throws(ZError::class) @@ -99,6 +85,5 @@ internal class JNIKeyExpr(internal val ptr: Long) { freePtrViaJNI(ptr) } - /** Frees the underlying native KeyExpr. */ private external fun freePtrViaJNI(ptr: Long) } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt new file mode 100644 index 00000000..580ffcc1 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt @@ -0,0 +1,47 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.exceptions.ZError +import io.zenoh.jni.callbacks.JNIGetCallback +import io.zenoh.jni.callbacks.JNIOnCloseCallback +import io.zenoh.jni.callbacks.JNISubscriberCallback + +/** Adapter object for interacting with Zenoh Liveliness through JNI. */ +public object JNILiveliness { + + @Throws(ZError::class) + external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long + + @Throws(ZError::class) + external fun declareSubscriberViaJNI( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNISubscriberCallback, + history: Boolean, + onClose: JNIOnCloseCallback, + ): Long + + @Throws(ZError::class) + external fun getViaJNI( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNIGetCallback, + timeoutMs: Long, + onClose: JNIOnCloseCallback, + ) +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt new file mode 100644 index 00000000..b9d8dbeb --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt @@ -0,0 +1,27 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +/** Adapter class for a native Zenoh LivelinessToken. */ +public class JNILivelinessToken(public val ptr: Long) { + + fun undeclare() { + undeclareViaJNI(ptr) + } + + companion object { + external fun undeclareViaJNI(ptr: Long) + } +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt new file mode 100644 index 00000000..d41814ac --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt @@ -0,0 +1,25 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +/** Adapter class for a native Zenoh MatchingListener. */ +public class JNIMatchingListener(public val ptr: Long) { + + fun close() { + freePtrViaJNI(ptr) + } + + private external fun freePtrViaJNI(ptr: Long) +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt new file mode 100644 index 00000000..edfddc97 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt @@ -0,0 +1,49 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.exceptions.ZError + +/** + * Adapter class for a native Zenoh publisher. Uses primitive types for put/delete. + * + * @property ptr Raw pointer to the underlying native Publisher. + */ +public class JNIPublisher(public val ptr: Long) { + + @Throws(ZError::class) + fun put(payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?) { + putViaJNI(payload, encodingId, encodingSchema, attachment, ptr) + } + + @Throws(ZError::class) + fun delete(attachment: ByteArray?) { + deleteViaJNI(attachment, ptr) + } + + fun close() { + freePtrViaJNI(ptr) + } + + @Throws(ZError::class) + private external fun putViaJNI( + valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long + ) + + @Throws(ZError::class) + private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) + + private external fun freePtrViaJNI(ptr: Long) +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt new file mode 100644 index 00000000..07baadeb --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt @@ -0,0 +1,43 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.exceptions.ZError +import io.zenoh.jni.callbacks.JNIGetCallback +import io.zenoh.jni.callbacks.JNIOnCloseCallback + +/** Adapter class for a native Zenoh querier. */ +public class JNIQuerier(public val ptr: Long) { + + @Throws(ZError::class) + external fun getViaJNI( + querierPtr: Long, + keyExprPtr: Long, + keyExprString: String, + parameters: String?, + callback: JNIGetCallback, + onClose: JNIOnCloseCallback, + attachmentBytes: ByteArray?, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + ) + + external fun freePtrViaJNI(ptr: Long) + + fun close() { + freePtrViaJNI(ptr) + } +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt new file mode 100644 index 00000000..d2711638 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt @@ -0,0 +1,96 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.exceptions.ZError + +/** + * Adapter class for interacting with a native Zenoh Query using JNI. + * + * @property ptr The raw pointer to the underlying native query. + */ +public class JNIQuery(private val ptr: Long) { + + @Throws(ZError::class) + fun replySuccess( + keyExprPtr: Long, + keyExprString: String, + payload: ByteArray, + encodingId: Int, + encodingSchema: String?, + timestampEnabled: Boolean, + timestampNtp64: Long, + attachment: ByteArray?, + qosExpress: Boolean, + ) { + replySuccessViaJNI(ptr, keyExprPtr, keyExprString, payload, encodingId, encodingSchema, timestampEnabled, timestampNtp64, attachment, qosExpress) + } + + @Throws(ZError::class) + fun replyError(errorPayload: ByteArray, encodingId: Int, encodingSchema: String?) { + replyErrorViaJNI(ptr, errorPayload, encodingId, encodingSchema) + } + + @Throws(ZError::class) + fun replyDelete( + keyExprPtr: Long, + keyExprString: String, + timestampEnabled: Boolean, + timestampNtp64: Long, + attachment: ByteArray?, + qosExpress: Boolean, + ) { + replyDeleteViaJNI(ptr, keyExprPtr, keyExprString, timestampEnabled, timestampNtp64, attachment, qosExpress) + } + + fun close() { + freePtrViaJNI(ptr) + } + + @Throws(ZError::class) + private external fun replySuccessViaJNI( + queryPtr: Long, + keyExprPtr: Long, + keyExprString: String, + valuePayload: ByteArray, + valueEncodingId: Int, + valueEncodingSchema: String?, + timestampEnabled: Boolean, + timestampNtp64: Long, + attachment: ByteArray?, + qosExpress: Boolean, + ) + + @Throws(ZError::class) + private external fun replyErrorViaJNI( + queryPtr: Long, + errorValuePayload: ByteArray, + errorValueEncoding: Int, + encodingSchema: String?, + ) + + @Throws(ZError::class) + private external fun replyDeleteViaJNI( + queryPtr: Long, + keyExprPtr: Long, + keyExprString: String, + timestampEnabled: Boolean, + timestampNtp64: Long, + attachment: ByteArray?, + qosExpress: Boolean, + ) + + private external fun freePtrViaJNI(ptr: Long) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt similarity index 79% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt index e5f7d3ce..7299b67e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -15,16 +15,15 @@ package io.zenoh.jni /** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.query.Queryable] + * Adapter class to handle the interactions with Zenoh through JNI for a Queryable. * * @property ptr: raw pointer to the underlying native Queryable. */ -internal class JNIQueryable(val ptr: Long) { +public class JNIQueryable(public val ptr: Long) { fun close() { freePtrViaJNI(ptr) } - /** Frees the underlying native Queryable. */ private external fun freePtrViaJNI(ptr: Long) } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt new file mode 100644 index 00000000..08edf3d3 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt @@ -0,0 +1,25 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +/** Adapter class for a native Zenoh SampleMissListener. */ +public class JNISampleMissListener(public val ptr: Long) { + + fun close() { + freePtrViaJNI(ptr) + } + + private external fun freePtrViaJNI(ptr: Long) +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt new file mode 100644 index 00000000..0f2db057 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt @@ -0,0 +1,48 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError +import io.zenoh.jni.callbacks.JNIOnCloseCallback +import io.zenoh.jni.callbacks.JNIScoutCallback + +/** + * Adapter class to handle the interactions with Zenoh through JNI for a Scout. + * + * @property ptr: raw pointer to the underlying native scout. + */ +public class JNIScout(private val ptr: Long) { + + companion object { + init { + ZenohLoad + } + + @Throws(ZError::class) + external fun scoutViaJNI( + whatAmI: Int, + callback: JNIScoutCallback, + onClose: JNIOnCloseCallback, + configPtr: Long, + ): Long + + external fun freePtrViaJNI(ptr: Long) + } + + fun close() { + freePtrViaJNI(ptr) + } +} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt new file mode 100644 index 00000000..9b3ad49b --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -0,0 +1,194 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError +import io.zenoh.jni.callbacks.JNIGetCallback +import io.zenoh.jni.callbacks.JNIOnCloseCallback +import io.zenoh.jni.callbacks.JNIQueryableCallback +import io.zenoh.jni.callbacks.JNISubscriberCallback + +/** Adapter class to handle communication with the Zenoh JNI code for a Session. */ +public class JNISession(public val sessionPtr: Long) { + + companion object { + init { + ZenohLoad + } + + @Throws(ZError::class) + fun open(configPtr: Long): JNISession { + val sessionPtr = openSessionViaJNI(configPtr) + return JNISession(sessionPtr) + } + + @JvmStatic + @Throws(ZError::class) + private external fun openSessionViaJNI(configPtr: Long): Long + } + + @Throws(ZError::class) + external fun closeSessionViaJNI(ptr: Long) + + @Throws(ZError::class) + external fun declarePublisherViaJNI( + keyExprPtr: Long, + keyExprString: String, + sessionPtr: Long, + congestionControl: Int, + priority: Int, + express: Boolean, + reliability: Int + ): Long + + @Throws(ZError::class) + external fun declareSubscriberViaJNI( + keyExprPtr: Long, + keyExprString: String, + sessionPtr: Long, + callback: JNISubscriberCallback, + onClose: JNIOnCloseCallback, + ): Long + + @Throws(ZError::class) + external fun declareQueryableViaJNI( + keyExprPtr: Long, + keyExprString: String, + sessionPtr: Long, + callback: JNIQueryableCallback, + onClose: JNIOnCloseCallback, + complete: Boolean + ): Long + + @Throws(ZError::class) + external fun declareQuerierViaJNI( + keyExprPtr: Long, + keyExprString: String, + sessionPtr: Long, + target: Int, + consolidation: Int, + congestionControl: Int, + priority: Int, + express: Boolean, + timeoutMs: Long, + acceptReplies: Int + ): Long + + @Throws(ZError::class) + external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long + + @Throws(ZError::class) + external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) + + @Throws(ZError::class) + external fun getViaJNI( + keyExprPtr: Long, + keyExprString: String, + selectorParams: String?, + sessionPtr: Long, + callback: JNIGetCallback, + onClose: JNIOnCloseCallback, + timeoutMs: Long, + target: Int, + consolidation: Int, + attachmentBytes: ByteArray?, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + congestionControl: Int, + priority: Int, + express: Boolean, + acceptReplies: Int, + ) + + @Throws(ZError::class) + external fun putViaJNI( + keyExprPtr: Long, + keyExprString: String, + sessionPtr: Long, + valuePayload: ByteArray, + valueEncoding: Int, + valueEncodingSchema: String?, + congestionControl: Int, + priority: Int, + express: Boolean, + attachmentBytes: ByteArray?, + reliability: Int + ) + + @Throws(ZError::class) + external fun deleteViaJNI( + keyExprPtr: Long, + keyExprString: String, + sessionPtr: Long, + congestionControl: Int, + priority: Int, + express: Boolean, + attachmentBytes: ByteArray?, + reliability: Int + ) + + @Throws(ZError::class) + external fun getZidViaJNI(ptr: Long): ByteArray + + @Throws(ZError::class) + external fun getPeersZidViaJNI(ptr: Long): List + + @Throws(ZError::class) + external fun getRoutersZidViaJNI(ptr: Long): List + + @Throws(ZError::class) + external fun declareAdvancedSubscriberViaJNI( + keyExprPtr: Long, + keyExprStr: String, + sessionPtr: Long, + historyConfigEnabled: Boolean, + historyDetectLatePublishers: Boolean, + historyMaxSamples: Long, + historyMaxAgeSeconds: Double, + recoveryConfigEnabled: Boolean, + recoveryConfigIsHeartbeat: Boolean, + recoveryQueryPeriodMs: Long, + subscriberDetection: Boolean, + callback: JNISubscriberCallback, + onClose: JNIOnCloseCallback, + ): Long + + @Throws(ZError::class) + external fun declareAdvancedPublisherViaJNI( + keyExprPtr: Long, + keyExprStr: String, + sessionPtr: Long, + congestionControl: Int, + priority: Int, + isExpress: Boolean, + reliability: Int, + cacheEnabled: Boolean, + cacheMaxSamples: Long, + cacheRepliesPriority: Int, + cacheRepliesCongestionControl: Int, + cacheRepliesIsExpress: Boolean, + sampleMissDetectionEnabled: Boolean, + sampleMissDetectionEnableHeartbeat: Boolean, + sampleMissDetectionHeartbeatMs: Long, + sampleMissDetectionHeartbeatIsSporadic: Boolean, + publisherDetection: Boolean, + ): Long + + fun close() { + closeSessionViaJNI(sessionPtr) + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt similarity index 78% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt index 1bb80543..7999854e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -15,17 +15,15 @@ package io.zenoh.jni /** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.Subscriber] + * Adapter class to handle the interactions with Zenoh through JNI for a Subscriber. * * @property ptr: raw pointer to the underlying native Subscriber. */ -internal class JNISubscriber(private val ptr: Long) { +public class JNISubscriber(public val ptr: Long) { fun close() { freePtrViaJNI(ptr) } - /** Frees the underlying native Subscriber. */ private external fun freePtrViaJNI(ptr: Long) - } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt similarity index 80% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt index 53ecb5dc..9bb02cb3 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -16,12 +16,12 @@ package io.zenoh.jni import io.zenoh.ZenohLoad -internal object JNIZenohId { +/** Adapter object for interacting with Zenoh IDs through JNI. */ +public object JNIZenohId { init { ZenohLoad } external fun toStringViaJNI(bytes: ByteArray): String - } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt similarity index 91% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt index 14f3c8e9..fe7da688 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,7 +14,7 @@ package io.zenoh.jni.callbacks -internal fun interface JNIGetCallback { +public fun interface JNIGetCallback { fun run( replierZid: ByteArray?, diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt new file mode 100644 index 00000000..4b133b08 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt @@ -0,0 +1,20 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni.callbacks + +/** Callback for matching listener notifications; receives true when matching subscribers exist. */ +public fun interface JNIMatchingListenerCallback { + fun run(matching: Boolean) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt similarity index 84% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt index b58fa23d..62760b54 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,7 +14,7 @@ package io.zenoh.jni.callbacks -internal fun interface JNIOnCloseCallback { +public fun interface JNIOnCloseCallback { fun run() diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt similarity index 56% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt index addf1430..8dc3bce8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,13 +14,15 @@ package io.zenoh.jni.callbacks -internal fun interface JNIQueryableCallback { - fun run(keyExpr: String, - selectorParams: String, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - attachmentBytes: ByteArray?, - queryPtr: Long, - acceptReplies: Int) +public fun interface JNIQueryableCallback { + fun run( + keyExpr: String, + selectorParams: String, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + attachmentBytes: ByteArray?, + queryPtr: Long, + acceptReplies: Int, + ) } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt new file mode 100644 index 00000000..bc5f09cc --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt @@ -0,0 +1,20 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni.callbacks + +/** Callback for sample miss listener notifications. Parameters encode the source entity global id and missed count. */ +public fun interface JNISampleMissListenerCallback { + fun run(zidLower: Long, zidUpper: Long, eid: Long, nb: Long) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt similarity index 85% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt index 0a8b20e9..57e2390f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,7 +14,7 @@ package io.zenoh.jni.callbacks -internal fun interface JNIScoutCallback { +public fun interface JNIScoutCallback { fun run(whatAmI: Int, zid: ByteArray, locators: List) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt similarity index 89% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt index 76373c72..013fc6ac 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,7 +14,7 @@ package io.zenoh.jni.callbacks -internal fun interface JNISubscriberCallback { +public fun interface JNISubscriberCallback { fun run( keyExpr: String, payload: ByteArray, diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt similarity index 93% rename from zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt rename to zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt index f3f28256..ad35d536 100644 --- a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -14,7 +14,7 @@ package io.zenoh -internal enum class Target { +public enum class Target { WINDOWS_X86_64_MSVC, WINDOWS_AARCH64_MSVC, LINUX_X86_64, diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt similarity index 81% rename from zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt rename to zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 8da47656..8e4fcdd3 100644 --- a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 ZettaScale Technology +// Copyright (c) 2026 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at @@ -21,10 +21,9 @@ import java.io.InputStream import java.util.zip.ZipInputStream /** - * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the - * log level configuration. + * Static singleton class to load the Zenoh native library once and only once. */ -internal actual object ZenohLoad { +public actual object ZenohLoad { private const val ZENOH_LIB_NAME = "zenoh_jni" init { @@ -36,13 +35,6 @@ internal actual object ZenohLoad { } } - /** - * Determine target - * - * Determines the [Target] corresponding to the machine on top of which the native code will run. - * - * @return A result with the target. - */ private fun determineTarget(): Result = runCatching { val osName = System.getProperty("os.name").lowercase() val osArch = System.getProperty("os.arch").lowercase() @@ -83,18 +75,6 @@ internal actual object ZenohLoad { return Result.success(target) } - /** - * Unzip library. - * - * The Zenoh libraries are stored within the JAR as compressed ZIP files. - * The location of the zipped files is expected to be under target/target.zip. - * It is expected that the zip file only contains the compressed library. - * - * The uncompressed library will be stored temporarily and deleted on exit. - * - * @param compressedLib Input stream pointing to the compressed library. - * @return A result with the uncompressed library file. - */ private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { val zipInputStream = ZipInputStream(compressedLib) val buffer = ByteArray(1024) @@ -140,24 +120,11 @@ internal actual object ZenohLoad { System.load(tempLib.absolutePath) } - /** - * Load library from jar package. - * - * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. - * - * @param target - */ private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { val lib: Result = loadLibraryAsInputStream(target) lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } } - /** - * Try loading local library. - * - * This function aims to load the default library that is usually included when building the zenoh kotlin library - * locally. - */ private fun tryLoadingLocalLibrary(): Result = runCatching { val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) ?: javaClass.classLoader.findLibraryStream( diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index c07c4dea..1cd4b7fd 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -1306,7 +1306,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberV on_close: JObject, ) -> *const AdvancedSubscriber<()> { let session = OwnedObject::from_raw(session_ptr); - let subscriber_ptr = || -> ZResult<*const AdvancedSubscriber<()>> { + || -> ZResult<*const AdvancedSubscriber<()>> { let mut builder = prepare_subscriber_builder( &mut env, key_expr_ptr, From f3ea4bc92a72a4777f471689c1217ee7170a2895 Mon Sep 17 00:00:00 2001 From: milyin Date: Wed, 15 Apr 2026 15:19:35 +0200 Subject: [PATCH 07/52] Add ZenohLoad safeguard to Logger.start() Ensure native library is loaded before calling startLogsViaJNI by touching ZenohLoad in Logger.start(). This makes Logger robust against direct internal calls that might bypass the usual Zenoh.kt initialization path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt index eba6681c..53a6caef 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt @@ -25,6 +25,7 @@ internal class Logger { @Throws(ZError::class) fun start(filter: String) { + ZenohLoad startLogsViaJNI(filter) } From 9e3770bef7b86fed1c15fc663ac3480d29fc75f8 Mon Sep 17 00:00:00 2001 From: milyin Date: Wed, 15 Apr 2026 15:31:14 +0200 Subject: [PATCH 08/52] Add missing AndroidManifest.xml to zenoh-jni-runtime Android source set Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- zenoh-jni-runtime/src/androidMain/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 zenoh-jni-runtime/src/androidMain/AndroidManifest.xml diff --git a/zenoh-jni-runtime/src/androidMain/AndroidManifest.xml b/zenoh-jni-runtime/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..4fb03756 --- /dev/null +++ b/zenoh-jni-runtime/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + From e881630de98a32b5d80eade147a680f6397bf188 Mon Sep 17 00:00:00 2001 From: milyin Date: Wed, 15 Apr 2026 16:26:08 +0200 Subject: [PATCH 09/52] fix: remove duplicate ZError from zenoh-java, expose via api() dep ZError was defined in both zenoh-jni-runtime and zenoh-java under the same FQCN (io.zenoh.exceptions.ZError). This caused duplicate-class packaging failures on JVM/Android when both artifacts were on the classpath. Fix: delete zenoh-java's copy so zenoh-jni-runtime is the single owner, and change the runtime dependency from implementation() to api() so that ZError (used in @Throws across zenoh-java's public API) is properly re-exported to consumers. Co-Authored-By: Claude Sonnet 4.6 --- zenoh-java/build.gradle.kts | 2 +- .../kotlin/io/zenoh/exceptions/ZError.kt | 20 ------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index 74bd48af..d752d8c8 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -66,7 +66,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation(project(":zenoh-jni-runtime")) + api(project(":zenoh-jni-runtime")) implementation("commons-net:commons-net:3.9.0") implementation("com.google.guava:guava:33.3.1-jre") } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt deleted file mode 100644 index c63a19ce..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.exceptions - -/** - * A Zenoh Error. - */ -class ZError(override val message: String? = null): Exception() From b26fba747a387c0bed9bdc49b0a726aa9ee314ba Mon Sep 17 00:00:00 2001 From: milyin Date: Wed, 15 Apr 2026 16:48:27 +0200 Subject: [PATCH 10/52] Remove Android JNI packaging from zenoh-java zenoh-jni-runtime is the single owner of Android Rust/JNI packaging. Remove the rust-android-gradle plugin, configureCargo() call, and mergeDebugJniLibFolders/mergeReleaseJniLibFolders task hooks from zenoh-java/build.gradle.kts so there is no duplication. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- zenoh-java/build.gradle.kts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index d752d8c8..81ea2335 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -12,8 +12,6 @@ // ZettaScale Zenoh Team, // -import com.nishtahir.CargoExtension - plugins { kotlin("multiplatform") kotlin("plugin.serialization") @@ -34,9 +32,7 @@ var buildMode = if (release) "release" else "debug" if (androidEnabled) { apply(plugin = "com.android.library") - apply(plugin = "org.mozilla.rust-android-gradle.rust-android") - configureCargo() configureAndroid() } @@ -153,12 +149,6 @@ tasks.withType { } } -tasks.whenObjectAdded { - if ((this.name == "mergeDebugJniLibFolders" || this.name == "mergeReleaseJniLibFolders")) { - this.dependsOn("cargoBuild") - } -} - tasks.named("compileKotlinJvm") { dependsOn(":zenoh-jni-runtime:buildZenohJni") } @@ -200,20 +190,3 @@ fun Project.configureAndroid() { } } } - -fun Project.configureCargo() { - extensions.configure("cargo") { - pythonCommand = "python3" - module = "../zenoh-jni" - libname = "zenoh-jni" - targetIncludes = arrayOf("libzenoh_jni.so") - targetDirectory = "../zenoh-jni/target/" - profile = "release" - targets = arrayListOf( - "arm", - "arm64", - "x86", - "x86_64", - ) - } -} From 57e4d927eee03dbda2451aeda45c6e4be05be33d Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 01:05:55 +0200 Subject: [PATCH 11/52] uniformly use OwnedObject --- zenoh-jni/src/config.rs | 9 ++--- zenoh-jni/src/key_expr.rs | 8 ++-- zenoh-jni/src/liveliness.rs | 15 +++----- zenoh-jni/src/publisher.rs | 7 ++-- zenoh-jni/src/querier.rs | 4 +- zenoh-jni/src/scouting.rs | 7 ++-- zenoh-jni/src/session.rs | 76 ++++++++++++++----------------------- 7 files changed, 49 insertions(+), 77 deletions(-) diff --git a/zenoh-jni/src/config.rs b/zenoh-jni/src/config.rs index 0ada1340..b64a78a4 100644 --- a/zenoh-jni/src/config.rs +++ b/zenoh-jni/src/config.rs @@ -21,6 +21,7 @@ use jni::{ }; use zenoh::Config; +use crate::owned_object::OwnedObject; use crate::{errors::ZResult, zerror}; use crate::{throw_exception, utils::decode_string}; @@ -130,8 +131,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_getJsonViaJN cfg_ptr: *const Config, key: JString, ) -> jstring { - let arc_cfg: Arc = Arc::from_raw(cfg_ptr); - let result = || -> ZResult { + let arc_cfg = OwnedObject::from_raw(cfg_ptr); + || -> ZResult { let key = decode_string(&mut env, &key)?; let json = arc_cfg.get_json(&key).map_err(|err| zerror!(err))?; let java_json = env.new_string(json).map_err(|err| zerror!(err))?; @@ -140,9 +141,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_getJsonViaJN .unwrap_or_else(|err| { throw_exception!(env, err); JString::default().as_raw() - }); - std::mem::forget(arc_cfg); - result + }) } /// Inserts a json5 value associated to the provided [key]. May throw an exception in case of failure, which must be handled diff --git a/zenoh-jni/src/key_expr.rs b/zenoh-jni/src/key_expr.rs index 11d39d47..dedbe565 100644 --- a/zenoh-jni/src/key_expr.rs +++ b/zenoh-jni/src/key_expr.rs @@ -12,7 +12,6 @@ // ZettaScale Zenoh Team, // -use std::ops::Deref; use std::sync::Arc; use jni::objects::JClass; @@ -21,6 +20,7 @@ use jni::{objects::JString, JNIEnv}; use zenoh::key_expr::KeyExpr; use crate::errors::ZResult; +use crate::owned_object::OwnedObject; use crate::utils::decode_string; use crate::{throw_exception, zerror}; @@ -325,9 +325,7 @@ pub(crate) unsafe fn process_kotlin_key_expr( .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; Ok(KeyExpr::from_string_unchecked(key_expr)) } else { - let key_expr = Arc::from_raw(key_expr_ptr); - let key_expr_clone = key_expr.deref().clone(); - std::mem::forget(key_expr); - Ok(key_expr_clone) + let key_expr = OwnedObject::from_raw(key_expr_ptr); + Ok((*key_expr).clone()) } } diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs index 8b05c925..4ad099d6 100644 --- a/zenoh-jni/src/liveliness.rs +++ b/zenoh-jni/src/liveliness.rs @@ -28,6 +28,7 @@ use zenoh::{ use crate::{ errors::ZResult, key_expr::process_kotlin_key_expr, + owned_object::OwnedObject, session::{on_reply_error, on_reply_success}, throw_exception, utils::{ @@ -49,7 +50,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( timeout_ms: jlong, on_close: JObject, ) { - let session = unsafe { Arc::from_raw(session_ptr) }; + let session = unsafe { OwnedObject::from_raw(session_ptr) }; let _ = || -> ZResult<()> { let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; let java_vm = Arc::new(get_java_vm(&mut env)?); @@ -98,7 +99,6 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( .map_err(|err| { throw_exception!(env, err); }); - std::mem::forget(session); } #[no_mangle] @@ -110,8 +110,8 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareTokenViaJNI( key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, ) -> *const LivelinessToken { - let session = unsafe { Arc::from_raw(session_ptr) }; - let ptr = || -> ZResult<*const LivelinessToken> { + let session = unsafe { OwnedObject::from_raw(session_ptr) }; + || -> ZResult<*const LivelinessToken> { let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; tracing::trace!("Declaring liveliness token on '{key_expr}'."); let token = session @@ -124,9 +124,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareTokenViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); null() - }); - std::mem::forget(session); - ptr + }) } #[no_mangle] @@ -151,7 +149,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( history: jboolean, on_close: JObject, ) -> *const Subscriber<()> { - let session = unsafe { Arc::from_raw(session_ptr) }; + let session = unsafe { OwnedObject::from_raw(session_ptr) }; || -> ZResult<*const Subscriber<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; @@ -232,7 +230,6 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( result.map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; tracing::debug!("Subscriber declared on '{}'.", key_expr); - std::mem::forget(session); Ok(Arc::into_raw(Arc::new(subscriber))) }() .unwrap_or_else(|err| { diff --git a/zenoh-jni/src/publisher.rs b/zenoh-jni/src/publisher.rs index ead60c3f..80738de2 100644 --- a/zenoh-jni/src/publisher.rs +++ b/zenoh-jni/src/publisher.rs @@ -21,6 +21,7 @@ use jni::{ }; use zenoh::{pubsub::Publisher, Wait}; +use crate::owned_object::OwnedObject; use crate::throw_exception; use crate::{ errors::ZResult, @@ -56,7 +57,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( attachment: /*nullable*/ JByteArray, publisher_ptr: *const Publisher<'static>, ) { - let publisher = Arc::from_raw(publisher_ptr); + let publisher = OwnedObject::from_raw(publisher_ptr); let _ = || -> ZResult<()> { let payload = decode_byte_array(&env, payload)?; let mut publication = publisher.put(payload); @@ -69,7 +70,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( publication.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); - std::mem::forget(publisher); } /// Performs a DELETE operation on a Zenoh publisher via JNI. @@ -94,7 +94,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_deleteViaJNI( attachment: /*nullable*/ JByteArray, publisher_ptr: *const Publisher<'static>, ) { - let publisher = Arc::from_raw(publisher_ptr); + let publisher = OwnedObject::from_raw(publisher_ptr); let _ = || -> ZResult<()> { let mut delete = publisher.delete(); if !attachment.is_null() { @@ -104,7 +104,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_deleteViaJNI( delete.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); - std::mem::forget(publisher) } /// Frees the publisher. diff --git a/zenoh-jni/src/querier.rs b/zenoh-jni/src/querier.rs index 8c239498..27bf7a63 100644 --- a/zenoh-jni/src/querier.rs +++ b/zenoh-jni/src/querier.rs @@ -24,6 +24,7 @@ use zenoh::{key_expr::KeyExpr, query::Querier, Wait}; use crate::{ errors::ZResult, key_expr::process_kotlin_key_expr, + owned_object::OwnedObject, session::{on_reply_error, on_reply_success}, throw_exception, utils::{ @@ -69,7 +70,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIQuerier_getViaJNI( encoding_id: jint, encoding_schema: /*nullable*/ JString, ) { - let querier = Arc::from_raw(querier_ptr); + let querier = OwnedObject::from_raw(querier_ptr); let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let java_vm = Arc::new(get_java_vm(&mut env)?); @@ -118,7 +119,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIQuerier_getViaJNI( .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); - std::mem::forget(querier); } /// diff --git a/zenoh-jni/src/scouting.rs b/zenoh-jni/src/scouting.rs index b0a665c1..cc45aee7 100644 --- a/zenoh-jni/src/scouting.rs +++ b/zenoh-jni/src/scouting.rs @@ -22,6 +22,7 @@ use jni::{ use zenoh::{config::WhatAmIMatcher, Wait}; use zenoh::{scouting::Scout, Config}; +use crate::owned_object::OwnedObject; use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; use crate::{errors::ZResult, throw_exception, zerror}; @@ -54,10 +55,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_scoutViaJNI( let config = if config_ptr.is_null() { Config::default() } else { - let arc_cfg = Arc::from_raw(config_ptr); - let config_clone = arc_cfg.as_ref().clone(); - std::mem::forget(arc_cfg); - config_clone + let arc_cfg = OwnedObject::from_raw(config_ptr); + (*arc_cfg).clone() }; zenoh::scout(whatAmIMatcher, config) .callback(move |hello| { diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 1cd4b7fd..13a082af 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -use std::{mem, ops::Deref, ptr::null, sync::Arc, time::Duration}; +use std::{ops::Deref, ptr::null, sync::Arc, time::Duration}; use jni::{ objects::{GlobalRef, JByteArray, JClass, JList, JObject, JString, JValue}, @@ -50,12 +50,10 @@ use crate::{ /// If the config path provided is null then the default configuration is loaded. /// unsafe fn open_session(config_ptr: *const Config) -> ZResult { - let config = Arc::from_raw(config_ptr); - let result = zenoh::open(config.as_ref().clone()) + let config = OwnedObject::from_raw(config_ptr); + zenoh::open((*config).clone()) .wait() - .map_err(|err| zerror!(err)); - mem::forget(config); - result + .map_err(|err: zenoh::Error| zerror!(err)) } /// Open a Zenoh session with a JSON configuration. @@ -206,8 +204,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( is_express: jboolean, reliability: jint, ) -> *const Publisher<'static> { - let session = Arc::from_raw(session_ptr); - let publisher_ptr = || -> ZResult<*const Publisher<'static>> { + let session = OwnedObject::from_raw(session_ptr); + || -> ZResult<*const Publisher<'static>> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; @@ -227,9 +225,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); null() - }); - std::mem::forget(session); - publisher_ptr + }) } /// Performs a `put` operation in the Zenoh session via JNI. @@ -275,7 +271,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( attachment: JByteArray, reliability: jint, ) { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let payload = decode_byte_array(&env, payload)?; @@ -303,7 +299,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); - std::mem::forget(session); } /// Performs a `delete` operation in the Zenoh session via JNI. @@ -344,7 +339,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( attachment: JByteArray, reliability: jint, ) { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let congestion_control = decode_congestion_control(congestion_control)?; @@ -369,7 +364,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); - std::mem::forget(session); } /// Declare a Zenoh subscriber via JNI. @@ -408,7 +402,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( callback: JObject, on_close: JObject, ) -> *const Subscriber<()> { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); || -> ZResult<*const Subscriber<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; @@ -486,7 +480,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( let subscriber = result.map_err(|err| zerror!("Unable to declare subscriber: {}", err))?; tracing::debug!("Subscriber declared on '{}'.", key_expr); - std::mem::forget(session); Ok(Arc::into_raw(Arc::new(subscriber))) }() .unwrap_or_else(|err| { @@ -528,7 +521,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQuerierViaJNI( timeout_ms: jlong, accept_replies: jint, ) -> *const Querier<'static> { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); || -> ZResult<*const Querier<'static>> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let query_target = decode_query_target(target)?; @@ -552,7 +545,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQuerierViaJNI( .map_err(|err| zerror!(err))?; tracing::debug!("Querier declared on '{}'.", key_expr); - std::mem::forget(session); Ok(Arc::into_raw(Arc::new(querier))) }() .unwrap_or_else(|err| { @@ -601,8 +593,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( on_close: JObject, complete: jboolean, ) -> *const Queryable<()> { - let session = Arc::from_raw(session_ptr); - let query_ptr = || -> ZResult<*const Queryable<()>> { + let session = OwnedObject::from_raw(session_ptr); + || -> ZResult<*const Queryable<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; @@ -638,9 +630,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); null() - }); - std::mem::forget(session); - query_ptr + }) } fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> ZResult<()> { @@ -757,8 +747,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareKeyExprViaJNI( session_ptr: *const Session, key_expr_str: JString, ) -> *const KeyExpr<'static> { - let session: Arc = Arc::from_raw(session_ptr); - let key_expr_ptr = || -> ZResult<*const KeyExpr<'static>> { + let session = OwnedObject::from_raw(session_ptr); + || -> ZResult<*const KeyExpr<'static>> { let key_expr_str = decode_string(&mut env, &key_expr_str)?; let key_expr = session .declare_keyexpr(key_expr_str.to_owned()) @@ -775,9 +765,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareKeyExprViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); null() - }); - mem::forget(session); - key_expr_ptr + }) } /// Undeclare a [KeyExpr] through a [Session] via JNI. @@ -808,7 +796,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( session_ptr: *const Session, key_expr_ptr: *const KeyExpr<'static>, ) { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); let key_expr = Arc::from_raw(key_expr_ptr); let key_expr_clone = key_expr.deref().clone(); match session.undeclare(key_expr_clone).wait() { @@ -820,7 +808,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( ); } } - std::mem::forget(session); // `key_expr` is intentionally left to be freed by Rust } @@ -881,7 +868,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( is_express: jboolean, accept_replies: jint, ) { - let session = Arc::from_raw(session_ptr); + let session = OwnedObject::from_raw(session_ptr); let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let java_vm = Arc::new(get_java_vm(&mut env)?); @@ -952,7 +939,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); - std::mem::forget(session); } pub(crate) fn on_reply_success( @@ -1111,8 +1097,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getPeersZidViaJNI( _class: JClass, session_ptr: *const Session, ) -> jobject { - let session = Arc::from_raw(session_ptr); - let ids = { + let session = OwnedObject::from_raw(session_ptr); + { let peers_zid = session.info().peers_zid().wait(); let ids = peers_zid.collect::>(); ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) @@ -1120,9 +1106,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getPeersZidViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); JObject::default().as_raw() - }); - std::mem::forget(session); - ids + }) } /// Returns a list of zenoh ids as byte arrays corresponding to the routers connected to the session provided. @@ -1134,8 +1118,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getRoutersZidViaJNI( _class: JClass, session_ptr: *const Session, ) -> jobject { - let session = Arc::from_raw(session_ptr); - let ids = { + let session = OwnedObject::from_raw(session_ptr); + { let peers_zid = session.info().routers_zid().wait(); let ids = peers_zid.collect::>(); ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) @@ -1143,9 +1127,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getRoutersZidViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); JObject::default().as_raw() - }); - std::mem::forget(session); - ids + }) } /// Returns the Zenoh ID as a byte array of the session. @@ -1156,8 +1138,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getZidViaJNI( _class: JClass, session_ptr: *const Session, ) -> jbyteArray { - let session = Arc::from_raw(session_ptr); - let ids = { + let session = OwnedObject::from_raw(session_ptr); + { let zid = session.info().zid().wait(); env.byte_array_from_slice(&zid.to_le_bytes()) .map(|x| x.as_raw()) @@ -1166,9 +1148,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getZidViaJNI( .unwrap_or_else(|err| { throw_exception!(env, err); JByteArray::default().as_raw() - }); - std::mem::forget(session); - ids + }) } fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result { From 0b8d396ca4521630f653db6bd403325d2fcbddfb Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 16:20:54 +0200 Subject: [PATCH 12/52] moved open funciton back to place --- zenoh-jni/src/session.rs | 62 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 13a082af..f36935f8 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -45,6 +45,37 @@ use crate::{ errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception, utils::*, zerror, }; +/// Open a Zenoh session via JNI. +/// +/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute +/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require +/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. +/// +/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. +/// +/// # Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class (parameter required by the JNI interface but unused). +/// - `config_ptr`: Pointer to the Zenoh config. If null, the default configuration will be loaded. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( + mut env: JNIEnv, + _class: JClass, + config_ptr: *const Config, +) -> *const Session { + let session = open_session(config_ptr); + match session { + Ok(session) => Arc::into_raw(Arc::new(session)), + Err(err) => { + tracing::error!("Unable to open session: {}", err); + throw_exception!(env, zerror!(err)); + null() + } + } +} + /// Open a Zenoh session with the configuration pointed out by `config_path`. /// /// If the config path provided is null then the default configuration is loaded. @@ -1161,37 +1192,6 @@ fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result< Ok(array_list.as_raw()) } -/// Open a Zenoh session via JNI. -/// -/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute -/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require -/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. -/// -/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `config_ptr`: Pointer to the Zenoh config. If null, the default configuration will be loaded. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( - mut env: JNIEnv, - _class: JClass, - config_ptr: *const Config, -) -> *const Session { - let session = open_session(config_ptr); - match session { - Ok(session) => Arc::into_raw(Arc::new(session)), - Err(err) => { - tracing::error!("Unable to open session: {}", err); - throw_exception!(env, zerror!(err)); - null() - } - } -} - #[allow(clippy::too_many_arguments)] unsafe fn prepare_publisher_builder<'a, 'b>( env: &mut JNIEnv, From a137fc2ebfadc0c65fbafca75ddd63bc1af457ed Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 16:37:49 +0200 Subject: [PATCH 13/52] advanced pub/sub creation simplified --- zenoh-jni/src/session.rs | 86 +++++++++------------------------------- 1 file changed, 19 insertions(+), 67 deletions(-) diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index f36935f8..9a7da616 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -21,9 +21,8 @@ use jni::{ }; use zenoh::{ config::Config, - handlers::Callback, key_expr::KeyExpr, - pubsub::{Publisher, PublisherBuilder, Subscriber, SubscriberBuilder}, + pubsub::{Publisher, Subscriber}, query::{Querier, Query, Queryable, ReplyError, ReplyKeyExpr, Selector}, sample::Sample, session::{EntityGlobalId, Session, ZenohId}, @@ -1192,50 +1191,6 @@ fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result< Ok(array_list.as_raw()) } -#[allow(clippy::too_many_arguments)] -unsafe fn prepare_publisher_builder<'a, 'b>( - env: &mut JNIEnv, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: &JString, - session: &'a Session, - congestion_control: jint, - priority: jint, - is_express: jboolean, - reliability: jint, -) -> ZResult> { - let key_expr = process_kotlin_key_expr(env, key_expr_str, key_expr_ptr)?; - let congestion_control = decode_congestion_control(congestion_control)?; - let priority = decode_priority(priority)?; - let reliability = decode_reliability(reliability)?; - let builder = session - .declare_publisher(key_expr) - .congestion_control(congestion_control) - .priority(priority) - .express(is_express != 0) - .reliability(reliability); - Ok(builder) -} - -unsafe fn prepare_subscriber_builder<'a, 'b>( - env: &mut JNIEnv, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: &JString, - session: &'a Session, - callback: JObject, - on_close: JObject, - entity_name: &str, -) -> ZResult>> { - let key_expr = process_kotlin_key_expr(env, key_expr_str, key_expr_ptr)?; - tracing::debug!("Declaring {entity_name} on '{}'...", key_expr); - - let builder = session - .declare_subscriber(key_expr.to_owned()) - .set_jni_sample_callback(env, callback, on_close)?; - - tracing::debug!("{entity_name} declared on '{}'.", key_expr); - Ok(builder) -} - /// Declare an advanced Zenoh subscriber via JNI. /// /// # Parameters: @@ -1287,16 +1242,13 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberV ) -> *const AdvancedSubscriber<()> { let session = OwnedObject::from_raw(session_ptr); || -> ZResult<*const AdvancedSubscriber<()>> { - let mut builder = prepare_subscriber_builder( - &mut env, - key_expr_ptr, - &key_expr_str, - &session, - callback, - on_close, - "advanced subscriber", - )? - .advanced(); + let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; + tracing::debug!("Declaring advanced subscriber on '{}'...", key_expr); + let mut builder = session + .declare_subscriber(key_expr.to_owned()) + .set_jni_sample_callback(&mut env, callback, on_close)? + .advanced(); + tracing::debug!("Advanced subscriber declared on '{}'.", key_expr); if history_config_enabled != 0 { let mut history = match history_detect_late_publishers != 0 { @@ -1406,17 +1358,17 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedPublisherVi ) -> *const AdvancedPublisher<'static> { let session = OwnedObject::from_raw(session_ptr); let publisher_ptr = || -> ZResult<*const AdvancedPublisher<'static>> { - let mut builder = prepare_publisher_builder( - &mut env, - key_expr_ptr, - &key_expr_str, - &session, - congestion_control, - priority, - is_express, - reliability, - )? - .advanced(); + let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; + let congestion_control = decode_congestion_control(congestion_control)?; + let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; + let mut builder = session + .declare_publisher(key_expr) + .congestion_control(congestion_control) + .priority(priority) + .express(is_express != 0) + .reliability(reliability) + .advanced(); // fill CacheConfig if cache_enabled != 0 { From 401a5ab22f36a1c20b807cf1b2e3202500a50d3c Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 16:43:31 +0200 Subject: [PATCH 14/52] reuse set_jni_sample_callback --- zenoh-jni/src/session.rs | 74 +++------------------------------------- 1 file changed, 4 insertions(+), 70 deletions(-) diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 9a7da616..cea91223 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -434,80 +434,14 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( ) -> *const Subscriber<()> { let session = OwnedObject::from_raw(session_ptr); || -> ZResult<*const Subscriber<()>> { - let java_vm = Arc::new(get_java_vm(&mut env)?); - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; tracing::debug!("Declaring subscriber on '{}'...", key_expr); - let result = session + let subscriber = session .declare_subscriber(key_expr.to_owned()) - .callback(move |sample: Sample| { - on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - let _ = || -> ZResult<()> { - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for subscriber: {}", err) - })?; - let byte_array = bytes_to_java_array(&env, sample.payload()) - .map(|array| env.auto_local(array))?; - - let encoding_id: jint = sample.encoding().id() as jint; - let encoding_schema = match sample.encoding().schema() { - Some(schema) => slice_to_java_string(&env, schema)?, - None => JString::default(), - }; - let kind = sample.kind() as jint; - let (timestamp, is_valid) = sample - .timestamp() - .map(|timestamp| (timestamp.get_time().as_u64(), true)) - .unwrap_or((0, false)); - - let attachment_bytes = sample - .attachment() - .map_or_else( - || Ok(JByteArray::default()), - |attachment| bytes_to_java_array(&env, attachment), - ) - .map(|array| env.auto_local(array)) - .map_err(|err| zerror!("Error processing attachment: {}", err))?; - - let key_expr_str = env.auto_local( - env.new_string(sample.key_expr().to_string()) - .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, - ); - - let express = sample.express(); - let priority = sample.priority() as jint; - let cc = sample.congestion_control() as jint; - - env.call_method( - &callback_global_ref, - "run", - "(Ljava/lang/String;[BILjava/lang/String;IJZ[BZII)V", - &[ - JValue::from(&key_expr_str), - JValue::from(&byte_array), - JValue::from(encoding_id), - JValue::from(&encoding_schema), - JValue::from(kind), - JValue::from(timestamp as i64), - JValue::from(is_valid), - JValue::from(&attachment_bytes), - JValue::from(express), - JValue::from(priority), - JValue::from(cc), - ], - ) - .map_err(|err| zerror!(err))?; - Ok(()) - }() - .map_err(|err| tracing::error!("On subscriber callback error: {err}")); - }) - .wait(); - - let subscriber = result.map_err(|err| zerror!("Unable to declare subscriber: {}", err))?; + .set_jni_sample_callback(&mut env, callback, on_close)? + .wait() + .map_err(|err| zerror!("Unable to declare subscriber: {}", err))?; tracing::debug!("Subscriber declared on '{}'.", key_expr); Ok(Arc::into_raw(Arc::new(subscriber))) From 11a38a7fd49fa6015398a80f6d91d867270aa315 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 16:48:01 +0200 Subject: [PATCH 15/52] reuse set_jni_sample_callback in liveliness --- zenoh-jni/src/liveliness.rs | 95 ++++++------------------------------- 1 file changed, 14 insertions(+), 81 deletions(-) diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs index 4ad099d6..650a4301 100644 --- a/zenoh-jni/src/liveliness.rs +++ b/zenoh-jni/src/liveliness.rs @@ -15,26 +15,24 @@ use std::{ptr::null, sync::Arc, time::Duration}; use jni::{ - objects::{JByteArray, JClass, JObject, JString, JValue}, - sys::{jboolean, jint, jlong}, + objects::{JClass, JObject, JString}, + sys::{jboolean, jlong}, JNIEnv, }; use zenoh::{ internal::runtime::ZRuntime, key_expr::KeyExpr, liveliness::LivelinessToken, - pubsub::Subscriber, sample::Sample, Session, Wait, + pubsub::Subscriber, Session, Wait, }; use crate::{ errors::ZResult, key_expr::process_kotlin_key_expr, owned_object::OwnedObject, + sample_callback::SetJniSampleCallback, session::{on_reply_error, on_reply_success}, throw_exception, - utils::{ - bytes_to_java_array, get_callback_global_ref, get_java_vm, load_on_close, - slice_to_java_string, - }, + utils::{get_callback_global_ref, get_java_vm, load_on_close}, zerror, }; @@ -151,83 +149,18 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( ) -> *const Subscriber<()> { let session = unsafe { OwnedObject::from_raw(session_ptr) }; || -> ZResult<*const Subscriber<()>> { - let java_vm = Arc::new(get_java_vm(&mut env)?); - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; tracing::debug!("Declaring liveliness subscriber on '{}'...", key_expr); - let result = session - .liveliness() - .declare_subscriber(key_expr.to_owned()) - .history(history != 0) - .callback(move |sample: Sample| { - let _ = || -> ZResult<()> { - on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for liveliness subscriber: {}", err) - })?; - let byte_array = bytes_to_java_array(&env, sample.payload()) - .map(|array| env.auto_local(array))?; - - let encoding_id: jint = sample.encoding().id() as jint; - let encoding_schema = match sample.encoding().schema() { - Some(schema) => slice_to_java_string(&env, schema)?, - None => JString::default(), - }; - let kind = sample.kind() as jint; - let (timestamp, is_valid) = sample - .timestamp() - .map(|timestamp| (timestamp.get_time().as_u64(), true)) - .unwrap_or((0, false)); - - let attachment_bytes = sample - .attachment() - .map_or_else( - || Ok(JByteArray::default()), - |attachment| bytes_to_java_array(&env, attachment), - ) - .map(|array| env.auto_local(array)) - .map_err(|err| zerror!("Error processing attachment: {}", err))?; - - let key_expr_str = env.auto_local( - env.new_string(sample.key_expr().to_string()) - .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, - ); - - let express = sample.express(); - let priority = sample.priority() as jint; - let cc = sample.congestion_control() as jint; - - env.call_method( - &callback_global_ref, - "run", - "(Ljava/lang/String;[BILjava/lang/String;IJZ[BZII)V", - &[ - JValue::from(&key_expr_str), - JValue::from(&byte_array), - JValue::from(encoding_id), - JValue::from(&encoding_schema), - JValue::from(kind), - JValue::from(timestamp as i64), - JValue::from(is_valid), - JValue::from(&attachment_bytes), - JValue::from(express), - JValue::from(priority), - JValue::from(cc), - ], - ) - .map_err(|err| zerror!(err))?; - Ok(()) - }() - .map_err(|err| tracing::error!("On liveliness subscriber callback error: {err}")); - }) - .wait(); - - let subscriber = - result.map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; + let subscriber = unsafe { + session + .liveliness() + .declare_subscriber(key_expr.to_owned()) + .history(history != 0) + .set_jni_sample_callback(&mut env, callback, on_close) + }? + .wait() + .map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; tracing::debug!("Subscriber declared on '{}'.", key_expr); Ok(Arc::into_raw(Arc::new(subscriber))) From ccc1463b36561c1874d9d9f5c6d9718c44b1fbcd Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 16:49:19 +0200 Subject: [PATCH 16/52] unsafe in function prototype --- zenoh-jni/src/liveliness.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs index 650a4301..3aaf1a98 100644 --- a/zenoh-jni/src/liveliness.rs +++ b/zenoh-jni/src/liveliness.rs @@ -137,7 +137,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_00024Companion_undeclareV #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( +pub unsafe extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( mut env: JNIEnv, _class: JClass, session_ptr: *const Session, @@ -147,19 +147,17 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( history: jboolean, on_close: JObject, ) -> *const Subscriber<()> { - let session = unsafe { OwnedObject::from_raw(session_ptr) }; + let session = OwnedObject::from_raw(session_ptr); || -> ZResult<*const Subscriber<()>> { - let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; tracing::debug!("Declaring liveliness subscriber on '{}'...", key_expr); - let subscriber = unsafe { - session - .liveliness() - .declare_subscriber(key_expr.to_owned()) - .history(history != 0) - .set_jni_sample_callback(&mut env, callback, on_close) - }? - .wait() + let subscriber = session + .liveliness() + .declare_subscriber(key_expr.to_owned()) + .history(history != 0) + .set_jni_sample_callback(&mut env, callback, on_close)? + .wait() .map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; tracing::debug!("Subscriber declared on '{}'.", key_expr); From 8861ded6eec4228b40ac3158637c4d791d49d146 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 18:26:41 +0200 Subject: [PATCH 17/52] useless config wrappers removed --- .../src/commonMain/kotlin/io/zenoh/Config.kt | 12 +++---- .../kotlin/io/zenoh/jni/JNIConfig.kt | 33 +++---------------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index 9443c731..e392c019 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -46,7 +46,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic fun loadDefault(): Config { - return Config(JNIConfig(JNIConfig.loadDefaultConfig())) + return Config(JNIConfig(JNIConfig.loadDefaultConfigViaJNI())) } /** @@ -59,7 +59,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromFile(file: File): Config { - return Config(JNIConfig(JNIConfig.loadConfigFile(file.toString()))) + return Config(JNIConfig(JNIConfig.loadConfigFileViaJNI(file.toString()))) } /** @@ -72,7 +72,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromFile(path: Path): Config { - return Config(JNIConfig(JNIConfig.loadConfigFile(path.toString()))) + return Config(JNIConfig(JNIConfig.loadConfigFileViaJNI(path.toString()))) } /** @@ -87,7 +87,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromJson(config: String): Config { - return Config(JNIConfig(JNIConfig.loadJsonConfig(config))) + return Config(JNIConfig(JNIConfig.loadJsonConfigViaJNI(config))) } /** @@ -102,7 +102,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromJson5(config: String): Config { - return Config(JNIConfig(JNIConfig.loadJson5Config(config))) + return Config(JNIConfig(JNIConfig.loadJsonConfigViaJNI(config))) } /** @@ -117,7 +117,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromYaml(config: String): Config { - return Config(JNIConfig(JNIConfig.loadYamlConfig(config))) + return Config(JNIConfig(JNIConfig.loadYamlConfigViaJNI(config))) } /** diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt index 39a15756..a12fff4a 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -27,41 +27,16 @@ public class JNIConfig(public val ptr: Long) { } @Throws(ZError::class) - fun loadDefaultConfig(): Long { - return loadDefaultConfigViaJNI() - } - - @Throws(ZError::class) - fun loadConfigFile(path: String): Long { - return loadConfigFileViaJNI(path) - } - - @Throws(ZError::class) - fun loadJsonConfig(rawConfig: String): Long { - return loadJsonConfigViaJNI(rawConfig) - } - - @Throws(ZError::class) - fun loadYamlConfig(rawConfig: String): Long { - return loadYamlConfigViaJNI(rawConfig) - } - - @Throws(ZError::class) - fun loadJson5Config(rawConfig: String): Long { - return loadJsonConfigViaJNI(rawConfig) - } - - @Throws(ZError::class) - private external fun loadDefaultConfigViaJNI(): Long + internal external fun loadDefaultConfigViaJNI(): Long @Throws(ZError::class) - private external fun loadConfigFileViaJNI(path: String): Long + internal external fun loadConfigFileViaJNI(path: String): Long @Throws(ZError::class) - private external fun loadJsonConfigViaJNI(rawConfig: String): Long + internal external fun loadJsonConfigViaJNI(rawConfig: String): Long @Throws(ZError::class) - private external fun loadYamlConfigViaJNI(rawConfig: String): Long + internal external fun loadYamlConfigViaJNI(rawConfig: String): Long @Throws(ZError::class) private external fun getIdViaJNI(ptr: Long): ByteArray From 823d698e1eb5adbc2c8b524ff5f38eb881e44dd2 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 18:34:58 +0200 Subject: [PATCH 18/52] useless methods removed --- .../kotlin/io/zenoh/keyexpr/KeyExpr.kt | 14 +++--- .../kotlin/io/zenoh/jni/JNIKeyExpr.kt | 47 +++---------------- 2 files changed, 14 insertions(+), 47 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index fda83d39..3e6ff24a 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -79,7 +79,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn @JvmStatic @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { - return KeyExpr(JNIKeyExpr.tryFrom(keyExpr)) + return KeyExpr(JNIKeyExpr.tryFromViaJNI(keyExpr)) } /** @@ -95,7 +95,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn @JvmStatic @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { - return KeyExpr(JNIKeyExpr.autocanonize(keyExpr)) + return KeyExpr(JNIKeyExpr.autocanonizeViaJNI(keyExpr)) } } @@ -106,7 +106,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun intersects(other: KeyExpr): Boolean { - return JNIKeyExpr.intersects(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) + return JNIKeyExpr.intersectsViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) } /** @@ -116,7 +116,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun includes(other: KeyExpr): Boolean { - return JNIKeyExpr.includes(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) + return JNIKeyExpr.includesViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) } /** @@ -126,7 +126,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun relationTo(other: KeyExpr): SetIntersectionLevel { - return SetIntersectionLevel.fromInt(JNIKeyExpr.relationTo(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr)) + return SetIntersectionLevel.fromInt(JNIKeyExpr.relationToViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr)) } /** @@ -135,7 +135,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun join(other: String): KeyExpr { - return KeyExpr(JNIKeyExpr.join(jniKeyExpr?.ptr ?: 0, keyExpr, other)) + return KeyExpr(JNIKeyExpr.joinViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other)) } /** @@ -144,7 +144,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun concat(other: String): KeyExpr { - return KeyExpr(JNIKeyExpr.concat(jniKeyExpr?.ptr ?: 0, keyExpr, other)) + return KeyExpr(JNIKeyExpr.concatViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other)) } override fun toString(): String { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index ce5f7126..2ccaa4ef 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -26,59 +26,26 @@ public class JNIKeyExpr(public val ptr: Long) { } @Throws(ZError::class) - fun tryFrom(keyExpr: String): String { - return tryFromViaJNI(keyExpr) - } + internal external fun tryFromViaJNI(keyExpr: String): String @Throws(ZError::class) - fun autocanonize(keyExpr: String): String { - return autocanonizeViaJNI(keyExpr) - } + internal external fun autocanonizeViaJNI(keyExpr: String): String @Throws(ZError::class) - fun intersects(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean = - intersectsViaJNI(ptrA, keyExprA, ptrB, keyExprB) + internal external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean @Throws(ZError::class) - fun includes(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean = - includesViaJNI(ptrA, keyExprA, ptrB, keyExprB) + internal external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean /** Returns SetIntersectionLevel ordinal as Int. Callers convert to SetIntersectionLevel. */ @Throws(ZError::class) - fun relationTo(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int { - return relationToViaJNI(ptrA, keyExprA, ptrB, keyExprB) - } - - @Throws(ZError::class) - fun join(ptrA: Long, keyExprA: String, other: String): String { - return joinViaJNI(ptrA, keyExprA, other) - } - - @Throws(ZError::class) - fun concat(ptrA: Long, keyExprA: String, other: String): String { - return concatViaJNI(ptrA, keyExprA, other) - } - - @Throws(ZError::class) - private external fun tryFromViaJNI(keyExpr: String): String - - @Throws(ZError::class) - private external fun autocanonizeViaJNI(keyExpr: String): String - - @Throws(ZError::class) - private external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean - - @Throws(ZError::class) - private external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean - - @Throws(ZError::class) - private external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int + internal external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int @Throws(ZError::class) - private external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String + internal external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String @Throws(ZError::class) - private external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String + internal external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String } fun close() { From c790cd48d7072c7b6773b7396e646723fe93cf29 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 18:40:59 +0200 Subject: [PATCH 19/52] made wrapped undeclare methods private --- .../src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNISession.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt index b9d8dbeb..9f847b9f 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt @@ -22,6 +22,6 @@ public class JNILivelinessToken(public val ptr: Long) { } companion object { - external fun undeclareViaJNI(ptr: Long) + private external fun undeclareViaJNI(ptr: Long) } } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt index 07baadeb..20db11f9 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt @@ -35,7 +35,7 @@ public class JNIQuerier(public val ptr: Long) { encodingSchema: String?, ) - external fun freePtrViaJNI(ptr: Long) + private external fun freePtrViaJNI(ptr: Long) fun close() { freePtrViaJNI(ptr) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt index 0f2db057..0fd624cd 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt @@ -39,7 +39,7 @@ public class JNIScout(private val ptr: Long) { configPtr: Long, ): Long - external fun freePtrViaJNI(ptr: Long) + private external fun freePtrViaJNI(ptr: Long) } fun close() { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 9b3ad49b..e0474649 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -41,7 +41,7 @@ public class JNISession(public val sessionPtr: Long) { } @Throws(ZError::class) - external fun closeSessionViaJNI(ptr: Long) + private external fun closeSessionViaJNI(ptr: Long) @Throws(ZError::class) external fun declarePublisherViaJNI( From 766950f29cd60cd2f5e2ffaaad2fd89d1fe3249c Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 18:47:36 +0200 Subject: [PATCH 20/52] get in querier --- .../commonMain/kotlin/io/zenoh/query/Querier.kt | 6 ++---- .../kotlin/io/zenoh/jni/JNIQuerier.kt | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 27cc6acb..d7e3704e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -171,8 +171,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v } callback.run(reply) } - jni.getViaJNI( - jni.ptr, + jni.get( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, options.parameters?.toString(), @@ -207,8 +206,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v } handler.handle(reply) } - jni.getViaJNI( - jni.ptr, + jni.get( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, options.parameters?.toString(), diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt index 20db11f9..988a2a8a 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt @@ -22,7 +22,22 @@ import io.zenoh.jni.callbacks.JNIOnCloseCallback public class JNIQuerier(public val ptr: Long) { @Throws(ZError::class) - external fun getViaJNI( + fun get( + keyExprPtr: Long, + keyExprString: String, + parameters: String?, + callback: JNIGetCallback, + onClose: JNIOnCloseCallback, + attachmentBytes: ByteArray?, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + ) { + getViaJNI(ptr, keyExprPtr, keyExprString, parameters, callback, onClose, attachmentBytes, payload, encodingId, encodingSchema) + } + + @Throws(ZError::class) + private external fun getViaJNI( querierPtr: Long, keyExprPtr: Long, keyExprString: String, From da92aa8171a3cc11090d42ab89330caa9531ddc5 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:09:53 +0200 Subject: [PATCH 21/52] declare jni method made private --- .../src/commonMain/kotlin/io/zenoh/Session.kt | 62 +++++++------------ .../kotlin/io/zenoh/liveliness/Liveliness.kt | 35 ++--------- .../kotlin/io/zenoh/jni/JNILiveliness.kt | 18 +++++- .../kotlin/io/zenoh/jni/JNISession.kt | 48 ++++++++++++-- 4 files changed, 87 insertions(+), 76 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 440d80b4..4a474240 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -597,21 +597,19 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) internal fun resolvePublisher(keyExpr: KeyExpr, options: PublisherOptions): Publisher { return jniSession?.run { - val publisherRawPtr = declarePublisherViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - options.congestionControl.value, - options.priority.value, - options.express, - options.reliability.ordinal - ) val publisher = Publisher( keyExpr, options.congestionControl, options.priority, options.encoding, - JNIPublisher(publisherRawPtr) + declarePublisher( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + options.congestionControl.value, + options.priority.value, + options.express, + options.reliability.ordinal + ) ) weakDeclarations.add(WeakReference(publisher)) publisher @@ -638,10 +636,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, subCallback, handler::onClose - ) - val subscriber = HandlerSubscriber(keyExpr, JNISubscriber(subscriberRawPtr), handler.receiver()) + val subscriber = HandlerSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, handler::onClose), handler.receiver()) strongDeclarations.add(subscriber) subscriber } ?: throw (sessionClosedException) @@ -667,10 +662,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, subCallback, fun() {} - ) - val subscriber = CallbackSubscriber(keyExpr, JNISubscriber(subscriberRawPtr)) + val subscriber = CallbackSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, fun() {})) strongDeclarations.add(subscriber) subscriber } ?: throw (sessionClosedException) @@ -698,10 +690,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, queryCallback, handler::onClose, options.complete - ) - val queryable = HandlerQueryable(keyExpr, JNIQueryable(queryableRawPtr), handler.receiver()) + val queryable = HandlerQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, queryCallback, handler::onClose, options.complete), handler.receiver()) strongDeclarations.add(queryable) queryable } ?: throw (sessionClosedException) @@ -729,10 +718,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, queryCallback, fun() {}, options.complete - ) - val queryable = CallbackQueryable(keyExpr, JNIQueryable(queryableRawPtr)) + val queryable = CallbackQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, queryCallback, fun() {}, options.complete)) strongDeclarations.add(queryable) queryable } ?: throw (sessionClosedException) @@ -744,22 +730,20 @@ class Session private constructor(private val config: Config) : AutoCloseable { options: QuerierOptions ): Querier { return jniSession?.run { - val querierRawPtr = declareQuerierViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - options.target.ordinal, - options.consolidationMode.ordinal, - options.congestionControl.value, - options.priority.value, - options.express, - options.timeout.toMillis(), - options.acceptReplies.ordinal - ) val querier = Querier( keyExpr, QoS(congestionControl = options.congestionControl, priority = options.priority, express = options.express), - JNIQuerier(querierRawPtr) + declareQuerier( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + options.target.ordinal, + options.consolidationMode.ordinal, + options.congestionControl.value, + options.priority.value, + options.express, + options.timeout.toMillis(), + options.acceptReplies.ordinal + ) ) weakDeclarations.add(WeakReference(querier)) querier diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index be811bba..f32c9247 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -24,8 +24,6 @@ import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler import io.zenoh.jni.JNILiveliness -import io.zenoh.jni.JNILivelinessToken -import io.zenoh.jni.JNISubscriber import io.zenoh.jni.callbacks.JNIGetCallback import io.zenoh.jni.callbacks.JNISubscriberCallback import io.zenoh.keyexpr.KeyExpr @@ -62,8 +60,7 @@ class Liveliness internal constructor(private val session: Session) { @Throws(ZError::class) fun declareToken(keyExpr: KeyExpr): LivelinessToken { val jniSession = session.jniSession ?: throw Session.sessionClosedException - val ptr = JNILiveliness.declareTokenViaJNI(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr) - return LivelinessToken(JNILivelinessToken(ptr)) + return LivelinessToken(JNILiveliness.declareToken(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr)) } /** @@ -177,15 +174,7 @@ class Liveliness internal constructor(private val session: Session) { val handler = BlockingQueueHandler(LinkedBlockingDeque()) val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(handler::handle) - val ptr = JNILiveliness.declareSubscriberViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - options.history, - handler::onClose - ) - return HandlerSubscriber(keyExpr, JNISubscriber(ptr), handler.receiver()) + return HandlerSubscriber(keyExpr, JNILiveliness.declareSubscriber(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) } /** @@ -204,15 +193,7 @@ class Liveliness internal constructor(private val session: Session) { ): CallbackSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(callback) - val ptr = JNILiveliness.declareSubscriberViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - options.history, - fun() {} - ) - return CallbackSubscriber(keyExpr, JNISubscriber(ptr)) + return CallbackSubscriber(keyExpr, JNILiveliness.declareSubscriber(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, fun() {})) } /** @@ -232,15 +213,7 @@ class Liveliness internal constructor(private val session: Session) { ): HandlerSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(handler::handle) - val ptr = JNILiveliness.declareSubscriberViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - options.history, - handler::onClose - ) - return HandlerSubscriber(keyExpr, JNISubscriber(ptr), handler.receiver()) + return HandlerSubscriber(keyExpr, JNILiveliness.declareSubscriber(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) } private fun buildSubscriberCallback(callback: Callback): JNISubscriberCallback = diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt index 580ffcc1..59f3b342 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt @@ -23,10 +23,24 @@ import io.zenoh.jni.callbacks.JNISubscriberCallback public object JNILiveliness { @Throws(ZError::class) - external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long + fun declareToken(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): JNILivelinessToken = + JNILivelinessToken(declareTokenViaJNI(sessionPtr, keyExprPtr, keyExprString)) @Throws(ZError::class) - external fun declareSubscriberViaJNI( + private external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long + + @Throws(ZError::class) + fun declareSubscriber( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNISubscriberCallback, + history: Boolean, + onClose: JNIOnCloseCallback, + ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, history, onClose)) + + @Throws(ZError::class) + private external fun declareSubscriberViaJNI( sessionPtr: Long, keyExprPtr: Long, keyExprString: String, diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index e0474649..4afdc3a0 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -44,7 +44,17 @@ public class JNISession(public val sessionPtr: Long) { private external fun closeSessionViaJNI(ptr: Long) @Throws(ZError::class) - external fun declarePublisherViaJNI( + fun declarePublisher( + keyExprPtr: Long, + keyExprString: String, + congestionControl: Int, + priority: Int, + express: Boolean, + reliability: Int + ): JNIPublisher = JNIPublisher(declarePublisherViaJNI(keyExprPtr, keyExprString, sessionPtr, congestionControl, priority, express, reliability)) + + @Throws(ZError::class) + private external fun declarePublisherViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, @@ -55,7 +65,15 @@ public class JNISession(public val sessionPtr: Long) { ): Long @Throws(ZError::class) - external fun declareSubscriberViaJNI( + fun declareSubscriber( + keyExprPtr: Long, + keyExprString: String, + callback: JNISubscriberCallback, + onClose: JNIOnCloseCallback, + ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(keyExprPtr, keyExprString, sessionPtr, callback, onClose)) + + @Throws(ZError::class) + private external fun declareSubscriberViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, @@ -64,7 +82,16 @@ public class JNISession(public val sessionPtr: Long) { ): Long @Throws(ZError::class) - external fun declareQueryableViaJNI( + fun declareQueryable( + keyExprPtr: Long, + keyExprString: String, + callback: JNIQueryableCallback, + onClose: JNIOnCloseCallback, + complete: Boolean + ): JNIQueryable = JNIQueryable(declareQueryableViaJNI(keyExprPtr, keyExprString, sessionPtr, callback, onClose, complete)) + + @Throws(ZError::class) + private external fun declareQueryableViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, @@ -74,7 +101,20 @@ public class JNISession(public val sessionPtr: Long) { ): Long @Throws(ZError::class) - external fun declareQuerierViaJNI( + fun declareQuerier( + keyExprPtr: Long, + keyExprString: String, + target: Int, + consolidation: Int, + congestionControl: Int, + priority: Int, + express: Boolean, + timeoutMs: Long, + acceptReplies: Int + ): JNIQuerier = JNIQuerier(declareQuerierViaJNI(keyExprPtr, keyExprString, sessionPtr, target, consolidation, congestionControl, priority, express, timeoutMs, acceptReplies)) + + @Throws(ZError::class) + private external fun declareQuerierViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, From 6ff42a54e8a71d7bfbc5b8b113e8b114489a72ee Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:17:43 +0200 Subject: [PATCH 22/52] declare/undeclare keyexpr wrapped --- zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt | 5 ++--- .../src/commonMain/kotlin/io/zenoh/jni/JNISession.kt | 10 ++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 4a474240..4f15b591 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -400,8 +400,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) fun declareKeyExpr(keyExpr: String): KeyExpr { return jniSession?.run { - val ptr = declareKeyExprViaJNI(sessionPtr, keyExpr) - val ke = KeyExpr(keyExpr, JNIKeyExpr(ptr)) + val ke = KeyExpr(keyExpr, declareKeyExpr(keyExpr)) strongDeclarations.add(ke) ke } ?: throw sessionClosedException @@ -420,7 +419,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { fun undeclare(keyExpr: KeyExpr) { jniSession?.run { keyExpr.jniKeyExpr?.run { - undeclareKeyExprViaJNI(sessionPtr, ptr) + undeclareKeyExpr(ptr) keyExpr.jniKeyExpr = null } ?: throw ZError("Attempting to undeclare a non declared key expression.") } ?: throw (sessionClosedException) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 4afdc3a0..342b6736 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -128,10 +128,16 @@ public class JNISession(public val sessionPtr: Long) { ): Long @Throws(ZError::class) - external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long + fun declareKeyExpr(keyExpr: String): JNIKeyExpr = JNIKeyExpr(declareKeyExprViaJNI(sessionPtr, keyExpr)) @Throws(ZError::class) - external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) + private external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long + + @Throws(ZError::class) + fun undeclareKeyExpr(keyExprPtr: Long) = undeclareKeyExprViaJNI(sessionPtr, keyExprPtr) + + @Throws(ZError::class) + private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) @Throws(ZError::class) external fun getViaJNI( From 7304cbafca8e3e87576712413644146fc50226b4 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:20:47 +0200 Subject: [PATCH 23/52] get/put wrapped --- .../src/commonMain/kotlin/io/zenoh/Session.kt | 12 ++--- .../kotlin/io/zenoh/jni/JNISession.kt | 51 +++++++++++++++++-- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 4f15b591..09972b92 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -777,11 +777,10 @@ class Session private constructor(private val config: Config) : AutoCloseable { handler.handle(reply) } val sel = selector.into() - getViaJNI( + get( sel.keyExpr.jniKeyExpr?.ptr ?: 0, sel.keyExpr.keyExpr, sel.parameters?.toString(), - sessionPtr, getCallback, handler::onClose, options.timeout.toMillis(), @@ -828,11 +827,10 @@ class Session private constructor(private val config: Config) : AutoCloseable { callback.run(reply) } val sel = selector.into() - getViaJNI( + get( sel.keyExpr.jniKeyExpr?.ptr ?: 0, sel.keyExpr.keyExpr, sel.parameters?.toString(), - sessionPtr, getCallback, fun() {}, options.timeout.toMillis(), @@ -854,10 +852,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) { jniSession?.run { val encoding = putOptions.encoding ?: Encoding.defaultEncoding() - putViaJNI( + put( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, - sessionPtr, payload.into().bytes, encoding.id, encoding.schema, @@ -873,10 +870,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) { jniSession?.run { - deleteViaJNI( + delete( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, - sessionPtr, deleteOptions.congestionControl.value, deleteOptions.priority.value, deleteOptions.express, diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 342b6736..72061f95 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -140,7 +140,27 @@ public class JNISession(public val sessionPtr: Long) { private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) @Throws(ZError::class) - external fun getViaJNI( + fun get( + keyExprPtr: Long, + keyExprString: String, + selectorParams: String?, + callback: JNIGetCallback, + onClose: JNIOnCloseCallback, + timeoutMs: Long, + target: Int, + consolidation: Int, + attachmentBytes: ByteArray?, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + congestionControl: Int, + priority: Int, + express: Boolean, + acceptReplies: Int, + ) = getViaJNI(keyExprPtr, keyExprString, selectorParams, sessionPtr, callback, onClose, timeoutMs, target, consolidation, attachmentBytes, payload, encodingId, encodingSchema, congestionControl, priority, express, acceptReplies) + + @Throws(ZError::class) + private external fun getViaJNI( keyExprPtr: Long, keyExprString: String, selectorParams: String?, @@ -161,7 +181,21 @@ public class JNISession(public val sessionPtr: Long) { ) @Throws(ZError::class) - external fun putViaJNI( + fun put( + keyExprPtr: Long, + keyExprString: String, + valuePayload: ByteArray, + valueEncoding: Int, + valueEncodingSchema: String?, + congestionControl: Int, + priority: Int, + express: Boolean, + attachmentBytes: ByteArray?, + reliability: Int + ) = putViaJNI(keyExprPtr, keyExprString, sessionPtr, valuePayload, valueEncoding, valueEncodingSchema, congestionControl, priority, express, attachmentBytes, reliability) + + @Throws(ZError::class) + private external fun putViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, @@ -176,7 +210,18 @@ public class JNISession(public val sessionPtr: Long) { ) @Throws(ZError::class) - external fun deleteViaJNI( + fun delete( + keyExprPtr: Long, + keyExprString: String, + congestionControl: Int, + priority: Int, + express: Boolean, + attachmentBytes: ByteArray?, + reliability: Int + ) = deleteViaJNI(keyExprPtr, keyExprString, sessionPtr, congestionControl, priority, express, attachmentBytes, reliability) + + @Throws(ZError::class) + private external fun deleteViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, From 63e9866988628d19f086831765124dc5d89635c8 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:23:50 +0200 Subject: [PATCH 24/52] session ptr is first parameter --- .../kotlin/io/zenoh/jni/JNISession.kt | 32 +++++++++---------- zenoh-jni/src/session.rs | 18 +++++------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 72061f95..bcc7a28d 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -51,13 +51,13 @@ public class JNISession(public val sessionPtr: Long) { priority: Int, express: Boolean, reliability: Int - ): JNIPublisher = JNIPublisher(declarePublisherViaJNI(keyExprPtr, keyExprString, sessionPtr, congestionControl, priority, express, reliability)) + ): JNIPublisher = JNIPublisher(declarePublisherViaJNI(sessionPtr, keyExprPtr, keyExprString, congestionControl, priority, express, reliability)) @Throws(ZError::class) private external fun declarePublisherViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, - sessionPtr: Long, congestionControl: Int, priority: Int, express: Boolean, @@ -70,13 +70,13 @@ public class JNISession(public val sessionPtr: Long) { keyExprString: String, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, - ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(keyExprPtr, keyExprString, sessionPtr, callback, onClose)) + ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, onClose)) @Throws(ZError::class) private external fun declareSubscriberViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, - sessionPtr: Long, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, ): Long @@ -88,13 +88,13 @@ public class JNISession(public val sessionPtr: Long) { callback: JNIQueryableCallback, onClose: JNIOnCloseCallback, complete: Boolean - ): JNIQueryable = JNIQueryable(declareQueryableViaJNI(keyExprPtr, keyExprString, sessionPtr, callback, onClose, complete)) + ): JNIQueryable = JNIQueryable(declareQueryableViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, onClose, complete)) @Throws(ZError::class) private external fun declareQueryableViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, - sessionPtr: Long, callback: JNIQueryableCallback, onClose: JNIOnCloseCallback, complete: Boolean @@ -111,13 +111,13 @@ public class JNISession(public val sessionPtr: Long) { express: Boolean, timeoutMs: Long, acceptReplies: Int - ): JNIQuerier = JNIQuerier(declareQuerierViaJNI(keyExprPtr, keyExprString, sessionPtr, target, consolidation, congestionControl, priority, express, timeoutMs, acceptReplies)) + ): JNIQuerier = JNIQuerier(declareQuerierViaJNI(sessionPtr, keyExprPtr, keyExprString, target, consolidation, congestionControl, priority, express, timeoutMs, acceptReplies)) @Throws(ZError::class) private external fun declareQuerierViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, - sessionPtr: Long, target: Int, consolidation: Int, congestionControl: Int, @@ -157,14 +157,14 @@ public class JNISession(public val sessionPtr: Long) { priority: Int, express: Boolean, acceptReplies: Int, - ) = getViaJNI(keyExprPtr, keyExprString, selectorParams, sessionPtr, callback, onClose, timeoutMs, target, consolidation, attachmentBytes, payload, encodingId, encodingSchema, congestionControl, priority, express, acceptReplies) + ) = getViaJNI(sessionPtr, keyExprPtr, keyExprString, selectorParams, callback, onClose, timeoutMs, target, consolidation, attachmentBytes, payload, encodingId, encodingSchema, congestionControl, priority, express, acceptReplies) @Throws(ZError::class) private external fun getViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, selectorParams: String?, - sessionPtr: Long, callback: JNIGetCallback, onClose: JNIOnCloseCallback, timeoutMs: Long, @@ -192,13 +192,13 @@ public class JNISession(public val sessionPtr: Long) { express: Boolean, attachmentBytes: ByteArray?, reliability: Int - ) = putViaJNI(keyExprPtr, keyExprString, sessionPtr, valuePayload, valueEncoding, valueEncodingSchema, congestionControl, priority, express, attachmentBytes, reliability) + ) = putViaJNI(sessionPtr, keyExprPtr, keyExprString, valuePayload, valueEncoding, valueEncodingSchema, congestionControl, priority, express, attachmentBytes, reliability) @Throws(ZError::class) private external fun putViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, - sessionPtr: Long, valuePayload: ByteArray, valueEncoding: Int, valueEncodingSchema: String?, @@ -218,13 +218,13 @@ public class JNISession(public val sessionPtr: Long) { express: Boolean, attachmentBytes: ByteArray?, reliability: Int - ) = deleteViaJNI(keyExprPtr, keyExprString, sessionPtr, congestionControl, priority, express, attachmentBytes, reliability) + ) = deleteViaJNI(sessionPtr, keyExprPtr, keyExprString, congestionControl, priority, express, attachmentBytes, reliability) @Throws(ZError::class) private external fun deleteViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprString: String, - sessionPtr: Long, congestionControl: Int, priority: Int, express: Boolean, @@ -243,9 +243,9 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) external fun declareAdvancedSubscriberViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprStr: String, - sessionPtr: Long, historyConfigEnabled: Boolean, historyDetectLatePublishers: Boolean, historyMaxSamples: Long, @@ -260,9 +260,9 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) external fun declareAdvancedPublisherViaJNI( + sessionPtr: Long, keyExprPtr: Long, keyExprStr: String, - sessionPtr: Long, congestionControl: Int, priority: Int, isExpress: Boolean, diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index cea91223..15a23a50 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -226,9 +226,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_closeSessionViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, congestion_control: jint, priority: jint, is_express: jboolean, @@ -289,9 +289,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, payload: JByteArray, encoding_id: jint, encoding_schema: JString, @@ -360,9 +360,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, congestion_control: jint, priority: jint, is_express: jboolean, @@ -426,9 +426,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, callback: JObject, on_close: JObject, ) -> *const Subscriber<()> { @@ -474,9 +474,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQuerierViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, target: jint, consolidation: jint, congestion_control: jint, @@ -550,9 +550,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQuerierViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, callback: JObject, on_close: JObject, complete: jboolean, @@ -814,10 +814,10 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, selector_params: /*nullable*/ JString, - session_ptr: *const Session, callback: JObject, on_close: JObject, timeout_ms: jlong, @@ -1156,9 +1156,9 @@ fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result< pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, // HistoryConfig history_config_enabled: jboolean, history_detect_late_publishers: jboolean, @@ -1269,9 +1269,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberV pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedPublisherViaJNI( mut env: JNIEnv, _class: JClass, + session_ptr: *const Session, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - session_ptr: *const Session, congestion_control: jint, priority: jint, is_express: jboolean, From b2c6e98feff23810a6d9cd287cbd6d9aa46ede72 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:27:48 +0200 Subject: [PATCH 25/52] ptr at the first place for publisher --- .../kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt | 8 ++++---- .../src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt | 8 ++++---- zenoh-jni/src/ext/advanced_publisher.rs | 4 ++-- zenoh-jni/src/publisher.rs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt index d27b0826..aa5d1aab 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt @@ -27,12 +27,12 @@ public class JNIAdvancedPublisher(public val ptr: Long) { @Throws(ZError::class) fun put(payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?) { - putViaJNI(payload, encodingId, encodingSchema, attachment, ptr) + putViaJNI(ptr, payload, encodingId, encodingSchema, attachment) } @Throws(ZError::class) fun delete(attachment: ByteArray?) { - deleteViaJNI(attachment, ptr) + deleteViaJNI(ptr, attachment) } @Throws(ZError::class) @@ -52,11 +52,11 @@ public class JNIAdvancedPublisher(public val ptr: Long) { @Throws(ZError::class) private external fun putViaJNI( - payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long + ptr: Long, payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray? ) @Throws(ZError::class) - private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) + private external fun deleteViaJNI(ptr: Long, attachment: ByteArray?) @Throws(ZError::class) private external fun declareMatchingListenerViaJNI( diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt index edfddc97..c3844d02 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt @@ -25,12 +25,12 @@ public class JNIPublisher(public val ptr: Long) { @Throws(ZError::class) fun put(payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?) { - putViaJNI(payload, encodingId, encodingSchema, attachment, ptr) + putViaJNI(ptr, payload, encodingId, encodingSchema, attachment) } @Throws(ZError::class) fun delete(attachment: ByteArray?) { - deleteViaJNI(attachment, ptr) + deleteViaJNI(ptr, attachment) } fun close() { @@ -39,11 +39,11 @@ public class JNIPublisher(public val ptr: Long) { @Throws(ZError::class) private external fun putViaJNI( - valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long + ptr: Long, valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray? ) @Throws(ZError::class) - private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) + private external fun deleteViaJNI(ptr: Long, attachment: ByteArray?) private external fun freePtrViaJNI(ptr: Long) } diff --git a/zenoh-jni/src/ext/advanced_publisher.rs b/zenoh-jni/src/ext/advanced_publisher.rs index c3d0c7c1..51500880 100644 --- a/zenoh-jni/src/ext/advanced_publisher.rs +++ b/zenoh-jni/src/ext/advanced_publisher.rs @@ -261,11 +261,11 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_getMatchingStatu pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_putViaJNI( mut env: JNIEnv, _class: JClass, + publisher_ptr: *const AdvancedPublisher<'static>, payload: JByteArray, encoding_id: jint, encoding_schema: /*nullable*/ JString, attachment: /*nullable*/ JByteArray, - publisher_ptr: *const AdvancedPublisher<'static>, ) { let publisher = OwnedObject::from_raw(publisher_ptr); let _ = || -> ZResult<()> { @@ -301,8 +301,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_putViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_deleteViaJNI( mut env: JNIEnv, _class: JClass, - attachment: /*nullable*/ JByteArray, publisher_ptr: *const AdvancedPublisher<'static>, + attachment: /*nullable*/ JByteArray, ) { let publisher = OwnedObject::from_raw(publisher_ptr); let _ = || -> ZResult<()> { diff --git a/zenoh-jni/src/publisher.rs b/zenoh-jni/src/publisher.rs index 80738de2..e2e76339 100644 --- a/zenoh-jni/src/publisher.rs +++ b/zenoh-jni/src/publisher.rs @@ -51,11 +51,11 @@ use crate::{ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( mut env: JNIEnv, _class: JClass, + publisher_ptr: *const Publisher<'static>, payload: JByteArray, encoding_id: jint, encoding_schema: /*nullable*/ JString, attachment: /*nullable*/ JByteArray, - publisher_ptr: *const Publisher<'static>, ) { let publisher = OwnedObject::from_raw(publisher_ptr); let _ = || -> ZResult<()> { @@ -91,8 +91,8 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_deleteViaJNI( mut env: JNIEnv, _class: JClass, - attachment: /*nullable*/ JByteArray, publisher_ptr: *const Publisher<'static>, + attachment: /*nullable*/ JByteArray, ) { let publisher = OwnedObject::from_raw(publisher_ptr); let _ = || -> ZResult<()> { From a1745b3ea99df809ec255b1b85ee0ca0ba969fdb Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:33:04 +0200 Subject: [PATCH 26/52] wrapped other session functiuons --- .../src/commonMain/kotlin/io/zenoh/Session.kt | 4 +- .../kotlin/io/zenoh/jni/JNISession.kt | 50 +++++++++++++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 09972b92..b1768a07 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -884,12 +884,12 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) internal fun zid(): ZenohId { - return jniSession?.run { ZenohId(getZidViaJNI(sessionPtr)) } ?: throw sessionClosedException + return jniSession?.run { ZenohId(getZid()) } ?: throw sessionClosedException } @Throws(ZError::class) internal fun getPeersId(): List { - return jniSession?.run { getPeersZidViaJNI(sessionPtr).map { ZenohId(it) } } ?: throw sessionClosedException + return jniSession?.run { getPeersZid().map { ZenohId(it) } } ?: throw sessionClosedException } @Throws(ZError::class) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index bcc7a28d..d934366c 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -233,16 +233,38 @@ public class JNISession(public val sessionPtr: Long) { ) @Throws(ZError::class) - external fun getZidViaJNI(ptr: Long): ByteArray + fun getZid(): ByteArray = getZidViaJNI(sessionPtr) @Throws(ZError::class) - external fun getPeersZidViaJNI(ptr: Long): List + private external fun getZidViaJNI(ptr: Long): ByteArray + + @Throws(ZError::class) + fun getPeersZid(): List = getPeersZidViaJNI(sessionPtr) + + @Throws(ZError::class) + private external fun getPeersZidViaJNI(ptr: Long): List @Throws(ZError::class) external fun getRoutersZidViaJNI(ptr: Long): List @Throws(ZError::class) - external fun declareAdvancedSubscriberViaJNI( + fun declareAdvancedSubscriber( + keyExprPtr: Long, + keyExprStr: String, + historyConfigEnabled: Boolean, + historyDetectLatePublishers: Boolean, + historyMaxSamples: Long, + historyMaxAgeSeconds: Double, + recoveryConfigEnabled: Boolean, + recoveryConfigIsHeartbeat: Boolean, + recoveryQueryPeriodMs: Long, + subscriberDetection: Boolean, + callback: JNISubscriberCallback, + onClose: JNIOnCloseCallback, + ): JNIAdvancedSubscriber = JNIAdvancedSubscriber(declareAdvancedSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprStr, historyConfigEnabled, historyDetectLatePublishers, historyMaxSamples, historyMaxAgeSeconds, recoveryConfigEnabled, recoveryConfigIsHeartbeat, recoveryQueryPeriodMs, subscriberDetection, callback, onClose)) + + @Throws(ZError::class) + private external fun declareAdvancedSubscriberViaJNI( sessionPtr: Long, keyExprPtr: Long, keyExprStr: String, @@ -259,7 +281,27 @@ public class JNISession(public val sessionPtr: Long) { ): Long @Throws(ZError::class) - external fun declareAdvancedPublisherViaJNI( + fun declareAdvancedPublisher( + keyExprPtr: Long, + keyExprStr: String, + congestionControl: Int, + priority: Int, + isExpress: Boolean, + reliability: Int, + cacheEnabled: Boolean, + cacheMaxSamples: Long, + cacheRepliesPriority: Int, + cacheRepliesCongestionControl: Int, + cacheRepliesIsExpress: Boolean, + sampleMissDetectionEnabled: Boolean, + sampleMissDetectionEnableHeartbeat: Boolean, + sampleMissDetectionHeartbeatMs: Long, + sampleMissDetectionHeartbeatIsSporadic: Boolean, + publisherDetection: Boolean, + ): JNIAdvancedPublisher = JNIAdvancedPublisher(declareAdvancedPublisherViaJNI(sessionPtr, keyExprPtr, keyExprStr, congestionControl, priority, isExpress, reliability, cacheEnabled, cacheMaxSamples, cacheRepliesPriority, cacheRepliesCongestionControl, cacheRepliesIsExpress, sampleMissDetectionEnabled, sampleMissDetectionEnableHeartbeat, sampleMissDetectionHeartbeatMs, sampleMissDetectionHeartbeatIsSporadic, publisherDetection)) + + @Throws(ZError::class) + private external fun declareAdvancedPublisherViaJNI( sessionPtr: Long, keyExprPtr: Long, keyExprStr: String, From d50b088666951592030ca4d9eb7e011bed0325d1 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 19:41:47 +0200 Subject: [PATCH 27/52] liveliness moved to session --- .../kotlin/io/zenoh/liveliness/Liveliness.kt | 18 +++--- .../kotlin/io/zenoh/jni/JNILiveliness.kt | 61 ------------------- .../kotlin/io/zenoh/jni/JNISession.kt | 45 ++++++++++++++ zenoh-jni/src/liveliness.rs | 6 +- 4 files changed, 55 insertions(+), 75 deletions(-) delete mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index f32c9247..23e8beef 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -23,7 +23,6 @@ import io.zenoh.exceptions.ZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler -import io.zenoh.jni.JNILiveliness import io.zenoh.jni.callbacks.JNIGetCallback import io.zenoh.jni.callbacks.JNISubscriberCallback import io.zenoh.keyexpr.KeyExpr @@ -60,7 +59,7 @@ class Liveliness internal constructor(private val session: Session) { @Throws(ZError::class) fun declareToken(keyExpr: KeyExpr): LivelinessToken { val jniSession = session.jniSession ?: throw Session.sessionClosedException - return LivelinessToken(JNILiveliness.declareToken(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr)) + return LivelinessToken(jniSession.declareLivelinessToken(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr)) } /** @@ -78,8 +77,7 @@ class Liveliness internal constructor(private val session: Session) { val jniSession = session.jniSession ?: throw Session.sessionClosedException val handler = BlockingQueueHandler(LinkedBlockingDeque()) val getCallback = buildGetCallback(handler::handle) - JNILiveliness.getViaJNI( - jniSession.sessionPtr, + jniSession.livelinessGet( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, getCallback, @@ -102,8 +100,7 @@ class Liveliness internal constructor(private val session: Session) { keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000) ) { val jniSession = session.jniSession ?: throw Session.sessionClosedException - JNILiveliness.getViaJNI( - jniSession.sessionPtr, + jniSession.livelinessGet( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, buildGetCallback(callback), @@ -126,8 +123,7 @@ class Liveliness internal constructor(private val session: Session) { keyExpr: KeyExpr, handler: Handler, timeout: Duration = Duration.ofMillis(10000) ): R { val jniSession = session.jniSession ?: throw Session.sessionClosedException - JNILiveliness.getViaJNI( - jniSession.sessionPtr, + jniSession.livelinessGet( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, buildGetCallback(handler::handle), @@ -174,7 +170,7 @@ class Liveliness internal constructor(private val session: Session) { val handler = BlockingQueueHandler(LinkedBlockingDeque()) val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(handler::handle) - return HandlerSubscriber(keyExpr, JNILiveliness.declareSubscriber(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) + return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) } /** @@ -193,7 +189,7 @@ class Liveliness internal constructor(private val session: Session) { ): CallbackSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(callback) - return CallbackSubscriber(keyExpr, JNILiveliness.declareSubscriber(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, fun() {})) + return CallbackSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, fun() {})) } /** @@ -213,7 +209,7 @@ class Liveliness internal constructor(private val session: Session) { ): HandlerSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(handler::handle) - return HandlerSubscriber(keyExpr, JNILiveliness.declareSubscriber(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) + return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) } private fun buildSubscriberCallback(callback: Callback): JNISubscriberCallback = diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt deleted file mode 100644 index 59f3b342..00000000 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2026 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.exceptions.ZError -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.jni.callbacks.JNISubscriberCallback - -/** Adapter object for interacting with Zenoh Liveliness through JNI. */ -public object JNILiveliness { - - @Throws(ZError::class) - fun declareToken(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): JNILivelinessToken = - JNILivelinessToken(declareTokenViaJNI(sessionPtr, keyExprPtr, keyExprString)) - - @Throws(ZError::class) - private external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long - - @Throws(ZError::class) - fun declareSubscriber( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNISubscriberCallback, - history: Boolean, - onClose: JNIOnCloseCallback, - ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, history, onClose)) - - @Throws(ZError::class) - private external fun declareSubscriberViaJNI( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNISubscriberCallback, - history: Boolean, - onClose: JNIOnCloseCallback, - ): Long - - @Throws(ZError::class) - external fun getViaJNI( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNIGetCallback, - timeoutMs: Long, - onClose: JNIOnCloseCallback, - ) -} diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index d934366c..644e0e2b 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -321,6 +321,51 @@ public class JNISession(public val sessionPtr: Long) { publisherDetection: Boolean, ): Long + @Throws(ZError::class) + fun declareLivelinessToken(keyExprPtr: Long, keyExprString: String): JNILivelinessToken = + JNILivelinessToken(declareLivelinessTokenViaJNI(sessionPtr, keyExprPtr, keyExprString)) + + @Throws(ZError::class) + private external fun declareLivelinessTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long + + @Throws(ZError::class) + fun declareLivelinessSubscriber( + keyExprPtr: Long, + keyExprString: String, + callback: JNISubscriberCallback, + history: Boolean, + onClose: JNIOnCloseCallback, + ): JNISubscriber = JNISubscriber(declareLivelinessSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, history, onClose)) + + @Throws(ZError::class) + private external fun declareLivelinessSubscriberViaJNI( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNISubscriberCallback, + history: Boolean, + onClose: JNIOnCloseCallback, + ): Long + + @Throws(ZError::class) + fun livelinessGet( + keyExprPtr: Long, + keyExprString: String, + callback: JNIGetCallback, + timeoutMs: Long, + onClose: JNIOnCloseCallback, + ) = livelinessGetViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, timeoutMs, onClose) + + @Throws(ZError::class) + private external fun livelinessGetViaJNI( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNIGetCallback, + timeoutMs: Long, + onClose: JNIOnCloseCallback, + ) + fun close() { closeSessionViaJNI(sessionPtr) } diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs index 3aaf1a98..37e0925b 100644 --- a/zenoh-jni/src/liveliness.rs +++ b/zenoh-jni/src/liveliness.rs @@ -38,7 +38,7 @@ use crate::{ #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( +pub extern "C" fn Java_io_zenoh_jni_JNISession_livelinessGetViaJNI( mut env: JNIEnv, _class: JClass, session_ptr: *const Session, @@ -101,7 +101,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareTokenViaJNI( +pub extern "C" fn Java_io_zenoh_jni_JNISession_declareLivelinessTokenViaJNI( mut env: JNIEnv, _class: JClass, session_ptr: *const Session, @@ -137,7 +137,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_00024Companion_undeclareV #[no_mangle] #[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareLivelinessSubscriberViaJNI( mut env: JNIEnv, _class: JClass, session_ptr: *const Session, From ca09547a6ca5a39b678b2a9d5f2a2a904451a865 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 21:11:32 +0200 Subject: [PATCH 28/52] config pointer wrapped --- .../src/commonMain/kotlin/io/zenoh/Config.kt | 12 +++---- .../src/commonMain/kotlin/io/zenoh/Session.kt | 2 +- .../kotlin/io/zenoh/jni/JNIConfig.kt | 31 +++++++++++++------ .../kotlin/io/zenoh/jni/JNISession.kt | 4 +-- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index e392c019..a2a334cc 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -46,7 +46,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic fun loadDefault(): Config { - return Config(JNIConfig(JNIConfig.loadDefaultConfigViaJNI())) + return Config(JNIConfig.loadDefault()) } /** @@ -59,7 +59,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromFile(file: File): Config { - return Config(JNIConfig(JNIConfig.loadConfigFileViaJNI(file.toString()))) + return Config(JNIConfig.loadFromFile(file.toString())) } /** @@ -72,7 +72,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromFile(path: Path): Config { - return Config(JNIConfig(JNIConfig.loadConfigFileViaJNI(path.toString()))) + return Config(JNIConfig.loadFromFile(path.toString())) } /** @@ -87,7 +87,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromJson(config: String): Config { - return Config(JNIConfig(JNIConfig.loadJsonConfigViaJNI(config))) + return Config(JNIConfig.loadFromJson(config)) } /** @@ -102,7 +102,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromJson5(config: String): Config { - return Config(JNIConfig(JNIConfig.loadJsonConfigViaJNI(config))) + return Config(JNIConfig.loadFromJson(config)) } /** @@ -117,7 +117,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { @JvmStatic @Throws(ZError::class) fun fromYaml(config: String): Config { - return Config(JNIConfig(JNIConfig.loadYamlConfigViaJNI(config))) + return Config(JNIConfig.loadFromYaml(config)) } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index b1768a07..3236ada0 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -900,7 +900,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { /** Launches the session through the jni session, returning the [Session] on success. */ @Throws(ZError::class) private fun launch(): Session { - this.jniSession = JNISession.open(config.jniConfig.ptr) + this.jniSession = JNISession.open(config.jniConfig) return this } } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt index a12fff4a..bf4978f2 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -17,7 +17,7 @@ package io.zenoh.jni import io.zenoh.ZenohLoad import io.zenoh.exceptions.ZError -/** Adapter for the native Zenoh config. Factory methods return raw pointers for use by facade classes. */ +/** Adapter for the native Zenoh config. */ public class JNIConfig(public val ptr: Long) { companion object { @@ -27,16 +27,28 @@ public class JNIConfig(public val ptr: Long) { } @Throws(ZError::class) - internal external fun loadDefaultConfigViaJNI(): Long + fun loadDefault(): JNIConfig = JNIConfig(loadDefaultConfigViaJNI()) @Throws(ZError::class) - internal external fun loadConfigFileViaJNI(path: String): Long + fun loadFromFile(path: String): JNIConfig = JNIConfig(loadConfigFileViaJNI(path)) @Throws(ZError::class) - internal external fun loadJsonConfigViaJNI(rawConfig: String): Long + fun loadFromJson(rawConfig: String): JNIConfig = JNIConfig(loadJsonConfigViaJNI(rawConfig)) @Throws(ZError::class) - internal external fun loadYamlConfigViaJNI(rawConfig: String): Long + fun loadFromYaml(rawConfig: String): JNIConfig = JNIConfig(loadYamlConfigViaJNI(rawConfig)) + + @Throws(ZError::class) + private external fun loadDefaultConfigViaJNI(): Long + + @Throws(ZError::class) + private external fun loadConfigFileViaJNI(path: String): Long + + @Throws(ZError::class) + private external fun loadJsonConfigViaJNI(rawConfig: String): Long + + @Throws(ZError::class) + private external fun loadYamlConfigViaJNI(rawConfig: String): Long @Throws(ZError::class) private external fun getIdViaJNI(ptr: Long): ByteArray @@ -55,12 +67,13 @@ public class JNIConfig(public val ptr: Long) { } @Throws(ZError::class) - fun getJson(key: String): String { - return getJsonViaJNI(ptr, key) - } + fun getId(): ByteArray = getIdViaJNI(ptr) + + @Throws(ZError::class) + fun getJson(key: String): String = getJsonViaJNI(ptr, key) @Throws(ZError::class) fun insertJson5(key: String, value: String) { - insertJson5ViaJNI(this.ptr, key, value) + insertJson5ViaJNI(ptr, key, value) } } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 644e0e2b..2492a610 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -30,8 +30,8 @@ public class JNISession(public val sessionPtr: Long) { } @Throws(ZError::class) - fun open(configPtr: Long): JNISession { - val sessionPtr = openSessionViaJNI(configPtr) + fun open(config: JNIConfig): JNISession { + val sessionPtr = openSessionViaJNI(config.ptr) return JNISession(sessionPtr) } From 3b05665eb099715c81515bfb8f9147466a91fa2b Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Apr 2026 21:43:53 +0200 Subject: [PATCH 29/52] ptr made private/internal --- zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt | 9 +++------ .../kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt | 2 +- .../kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt | 2 +- .../kotlin/io/zenoh/jni/JNILivelinessToken.kt | 2 +- .../kotlin/io/zenoh/jni/JNIMatchingListener.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt | 2 +- .../kotlin/io/zenoh/jni/JNISampleMissListener.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt | 10 +++++++++- .../commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt | 2 +- 12 files changed, 22 insertions(+), 17 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt index 7a5500d6..e0c60c6c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -60,8 +60,7 @@ object Zenoh { handler.handle(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators)) } val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it } - val ptr = JNIScout.scoutViaJNI(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig?.ptr ?: 0) - return HandlerScout(JNIScout(ptr), handler.receiver()) + return HandlerScout(JNIScout.scout(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig), handler.receiver()) } /** @@ -83,8 +82,7 @@ object Zenoh { handler.handle(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators)) } val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it } - val ptr = JNIScout.scoutViaJNI(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig?.ptr ?: 0) - return HandlerScout(JNIScout(ptr), handler.receiver()) + return HandlerScout(JNIScout.scout(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig), handler.receiver()) } /** @@ -105,8 +103,7 @@ object Zenoh { callback.run(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators)) } val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it } - val ptr = JNIScout.scoutViaJNI(binaryWhatAmI, scoutCallback, fun() {}, scoutOptions.config?.jniConfig?.ptr ?: 0) - return CallbackScout(JNIScout(ptr)) + return CallbackScout(JNIScout.scout(binaryWhatAmI, scoutCallback, fun() {}, scoutOptions.config?.jniConfig)) } /** diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt index aa5d1aab..eee392b6 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt @@ -23,7 +23,7 @@ import io.zenoh.jni.callbacks.JNIOnCloseCallback * * @property ptr Raw pointer to the underlying native AdvancedPublisher. */ -public class JNIAdvancedPublisher(public val ptr: Long) { +public class JNIAdvancedPublisher(private val ptr: Long) { @Throws(ZError::class) fun put(payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?) { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt index c76d1dbb..13df29a8 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt @@ -24,7 +24,7 @@ import io.zenoh.jni.callbacks.JNISubscriberCallback * * @property ptr Raw pointer to the underlying native AdvancedSubscriber. */ -public class JNIAdvancedSubscriber(public val ptr: Long) { +public class JNIAdvancedSubscriber(private val ptr: Long) { @Throws(ZError::class) fun declareDetectPublishersSubscriber( diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt index bf4978f2..9cdd403c 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -18,7 +18,7 @@ import io.zenoh.ZenohLoad import io.zenoh.exceptions.ZError /** Adapter for the native Zenoh config. */ -public class JNIConfig(public val ptr: Long) { +public class JNIConfig(internal val ptr: Long) { companion object { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt index 9f847b9f..dcf14b6b 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** Adapter class for a native Zenoh LivelinessToken. */ -public class JNILivelinessToken(public val ptr: Long) { +public class JNILivelinessToken(private val ptr: Long) { fun undeclare() { undeclareViaJNI(ptr) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt index d41814ac..7c58edee 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** Adapter class for a native Zenoh MatchingListener. */ -public class JNIMatchingListener(public val ptr: Long) { +public class JNIMatchingListener(private val ptr: Long) { fun close() { freePtrViaJNI(ptr) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt index c3844d02..7f800597 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt @@ -21,7 +21,7 @@ import io.zenoh.exceptions.ZError * * @property ptr Raw pointer to the underlying native Publisher. */ -public class JNIPublisher(public val ptr: Long) { +public class JNIPublisher(private val ptr: Long) { @Throws(ZError::class) fun put(payload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?) { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt index 988a2a8a..a79a293b 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt @@ -19,7 +19,7 @@ import io.zenoh.jni.callbacks.JNIGetCallback import io.zenoh.jni.callbacks.JNIOnCloseCallback /** Adapter class for a native Zenoh querier. */ -public class JNIQuerier(public val ptr: Long) { +public class JNIQuerier(private val ptr: Long) { @Throws(ZError::class) fun get( diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt index 7299b67e..ba64f7e9 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt @@ -19,7 +19,7 @@ package io.zenoh.jni * * @property ptr: raw pointer to the underlying native Queryable. */ -public class JNIQueryable(public val ptr: Long) { +public class JNIQueryable(private val ptr: Long) { fun close() { freePtrViaJNI(ptr) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt index 08edf3d3..dea27edf 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** Adapter class for a native Zenoh SampleMissListener. */ -public class JNISampleMissListener(public val ptr: Long) { +public class JNISampleMissListener(private val ptr: Long) { fun close() { freePtrViaJNI(ptr) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt index 0fd624cd..1b3d1bd0 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt @@ -32,7 +32,15 @@ public class JNIScout(private val ptr: Long) { } @Throws(ZError::class) - external fun scoutViaJNI( + fun scout( + whatAmI: Int, + callback: JNIScoutCallback, + onClose: JNIOnCloseCallback, + config: JNIConfig?, + ): JNIScout = JNIScout(scoutViaJNI(whatAmI, callback, onClose, config?.ptr ?: 0)) + + @Throws(ZError::class) + private external fun scoutViaJNI( whatAmI: Int, callback: JNIScoutCallback, onClose: JNIOnCloseCallback, diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt index 7999854e..b9877d70 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt @@ -19,7 +19,7 @@ package io.zenoh.jni * * @property ptr: raw pointer to the underlying native Subscriber. */ -public class JNISubscriber(public val ptr: Long) { +public class JNISubscriber(private val ptr: Long) { fun close() { freePtrViaJNI(ptr) From ca7d96459fc41b12a11608cba5ff76460996a10b Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 10:19:38 +0200 Subject: [PATCH 30/52] made ptr in keyexpr internal --- .../src/commonMain/kotlin/io/zenoh/Session.kt | 22 ++++---- .../kotlin/io/zenoh/keyexpr/KeyExpr.kt | 10 ++-- .../kotlin/io/zenoh/liveliness/Liveliness.kt | 14 +++--- .../kotlin/io/zenoh/query/Querier.kt | 4 +- .../commonMain/kotlin/io/zenoh/query/Query.kt | 4 +- .../kotlin/io/zenoh/jni/JNIKeyExpr.kt | 34 ++++++++++--- .../kotlin/io/zenoh/jni/JNIQuerier.kt | 4 +- .../kotlin/io/zenoh/jni/JNIQuery.kt | 8 +-- .../kotlin/io/zenoh/jni/JNISession.kt | 50 +++++++++---------- 9 files changed, 85 insertions(+), 65 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 3236ada0..92f6e22b 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -419,7 +419,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { fun undeclare(keyExpr: KeyExpr) { jniSession?.run { keyExpr.jniKeyExpr?.run { - undeclareKeyExpr(ptr) + undeclareKeyExpr(this) keyExpr.jniKeyExpr = null } ?: throw ZError("Attempting to undeclare a non declared key expression.") } ?: throw (sessionClosedException) @@ -602,7 +602,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { options.priority, options.encoding, declarePublisher( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, options.congestionControl.value, options.priority.value, @@ -635,7 +635,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val subscriber = HandlerSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, handler::onClose), handler.receiver()) + val subscriber = HandlerSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, handler::onClose), handler.receiver()) strongDeclarations.add(subscriber) subscriber } ?: throw (sessionClosedException) @@ -661,7 +661,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val subscriber = CallbackSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, fun() {})) + val subscriber = CallbackSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, fun() {})) strongDeclarations.add(subscriber) subscriber } ?: throw (sessionClosedException) @@ -689,7 +689,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val queryable = HandlerQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, queryCallback, handler::onClose, options.complete), handler.receiver()) + val queryable = HandlerQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr, keyExpr.keyExpr, queryCallback, handler::onClose, options.complete), handler.receiver()) strongDeclarations.add(queryable) queryable } ?: throw (sessionClosedException) @@ -717,7 +717,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { ) ) } - val queryable = CallbackQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, queryCallback, fun() {}, options.complete)) + val queryable = CallbackQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr, keyExpr.keyExpr, queryCallback, fun() {}, options.complete)) strongDeclarations.add(queryable) queryable } ?: throw (sessionClosedException) @@ -733,7 +733,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { keyExpr, QoS(congestionControl = options.congestionControl, priority = options.priority, express = options.express), declareQuerier( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, options.target.ordinal, options.consolidationMode.ordinal, @@ -778,7 +778,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { } val sel = selector.into() get( - sel.keyExpr.jniKeyExpr?.ptr ?: 0, + sel.keyExpr.jniKeyExpr, sel.keyExpr.keyExpr, sel.parameters?.toString(), getCallback, @@ -828,7 +828,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { } val sel = selector.into() get( - sel.keyExpr.jniKeyExpr?.ptr ?: 0, + sel.keyExpr.jniKeyExpr, sel.keyExpr.keyExpr, sel.parameters?.toString(), getCallback, @@ -853,7 +853,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { jniSession?.run { val encoding = putOptions.encoding ?: Encoding.defaultEncoding() put( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, payload.into().bytes, encoding.id, @@ -871,7 +871,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) { jniSession?.run { delete( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, deleteOptions.congestionControl.value, deleteOptions.priority.value, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index 3e6ff24a..427f643c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -106,7 +106,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun intersects(other: KeyExpr): Boolean { - return JNIKeyExpr.intersectsViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) + return JNIKeyExpr.intersects(jniKeyExpr, keyExpr, other.jniKeyExpr, other.keyExpr) } /** @@ -116,7 +116,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun includes(other: KeyExpr): Boolean { - return JNIKeyExpr.includesViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr) + return JNIKeyExpr.includes(jniKeyExpr, keyExpr, other.jniKeyExpr, other.keyExpr) } /** @@ -126,7 +126,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun relationTo(other: KeyExpr): SetIntersectionLevel { - return SetIntersectionLevel.fromInt(JNIKeyExpr.relationToViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other.jniKeyExpr?.ptr ?: 0, other.keyExpr)) + return SetIntersectionLevel.fromInt(JNIKeyExpr.relationTo(jniKeyExpr, keyExpr, other.jniKeyExpr, other.keyExpr)) } /** @@ -135,7 +135,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun join(other: String): KeyExpr { - return KeyExpr(JNIKeyExpr.joinViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other)) + return KeyExpr(JNIKeyExpr.join(jniKeyExpr, keyExpr, other)) } /** @@ -144,7 +144,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @Throws(ZError::class) fun concat(other: String): KeyExpr { - return KeyExpr(JNIKeyExpr.concatViaJNI(jniKeyExpr?.ptr ?: 0, keyExpr, other)) + return KeyExpr(JNIKeyExpr.concat(jniKeyExpr, keyExpr, other)) } override fun toString(): String { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index 23e8beef..62aff6c5 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -59,7 +59,7 @@ class Liveliness internal constructor(private val session: Session) { @Throws(ZError::class) fun declareToken(keyExpr: KeyExpr): LivelinessToken { val jniSession = session.jniSession ?: throw Session.sessionClosedException - return LivelinessToken(jniSession.declareLivelinessToken(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr)) + return LivelinessToken(jniSession.declareLivelinessToken(keyExpr.jniKeyExpr, keyExpr.keyExpr)) } /** @@ -78,7 +78,7 @@ class Liveliness internal constructor(private val session: Session) { val handler = BlockingQueueHandler(LinkedBlockingDeque()) val getCallback = buildGetCallback(handler::handle) jniSession.livelinessGet( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, getCallback, timeout.toMillis(), @@ -101,7 +101,7 @@ class Liveliness internal constructor(private val session: Session) { ) { val jniSession = session.jniSession ?: throw Session.sessionClosedException jniSession.livelinessGet( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, buildGetCallback(callback), timeout.toMillis(), @@ -124,7 +124,7 @@ class Liveliness internal constructor(private val session: Session) { ): R { val jniSession = session.jniSession ?: throw Session.sessionClosedException jniSession.livelinessGet( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, buildGetCallback(handler::handle), timeout.toMillis(), @@ -170,7 +170,7 @@ class Liveliness internal constructor(private val session: Session) { val handler = BlockingQueueHandler(LinkedBlockingDeque()) val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(handler::handle) - return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) + return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) } /** @@ -189,7 +189,7 @@ class Liveliness internal constructor(private val session: Session) { ): CallbackSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(callback) - return CallbackSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, fun() {})) + return CallbackSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, options.history, fun() {})) } /** @@ -209,7 +209,7 @@ class Liveliness internal constructor(private val session: Session) { ): HandlerSubscriber { val jniSession = session.jniSession ?: throw Session.sessionClosedException val subCallback = buildSubscriberCallback(handler::handle) - return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) + return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver()) } private fun buildSubscriberCallback(callback: Callback): JNISubscriberCallback = diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index d7e3704e..faca37a8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -172,7 +172,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v callback.run(reply) } jni.get( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, options.parameters?.toString(), getCallback, @@ -207,7 +207,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v handler.handle(reply) } jni.get( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, options.parameters?.toString(), getCallback, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index 9731983d..c2128efa 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -68,7 +68,7 @@ class Query internal constructor( val timestampEnabled = timestamp != null jniQuery?.apply { replySuccess( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, zbytes.bytes, encoding?.id ?: Encoding.defaultEncoding().id, @@ -108,7 +108,7 @@ class Query internal constructor( val timestampEnabled = timestamp != null jniQuery?.apply { replyDelete( - keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.jniKeyExpr, keyExpr.keyExpr, timestampEnabled, if (timestampEnabled) timestamp!!.ntpValue() else 0, diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index 2ccaa4ef..831e92c6 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -17,8 +17,8 @@ package io.zenoh.jni import io.zenoh.ZenohLoad import io.zenoh.exceptions.ZError -/** Adapter for native Zenoh key expressions. Factory methods return raw primitives. */ -public class JNIKeyExpr(public val ptr: Long) { +/** Adapter for native Zenoh key expressions. */ +public class JNIKeyExpr(internal val ptr: Long) { companion object { init { @@ -32,20 +32,40 @@ public class JNIKeyExpr(public val ptr: Long) { internal external fun autocanonizeViaJNI(keyExpr: String): String @Throws(ZError::class) - internal external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean + fun intersects(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Boolean = + intersectsViaJNI(a?.ptr ?: 0, aStr, b?.ptr ?: 0, bStr) @Throws(ZError::class) - internal external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean + fun includes(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Boolean = + includesViaJNI(a?.ptr ?: 0, aStr, b?.ptr ?: 0, bStr) /** Returns SetIntersectionLevel ordinal as Int. Callers convert to SetIntersectionLevel. */ @Throws(ZError::class) - internal external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int + fun relationTo(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Int = + relationToViaJNI(a?.ptr ?: 0, aStr, b?.ptr ?: 0, bStr) @Throws(ZError::class) - internal external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String + fun join(a: JNIKeyExpr?, aStr: String, other: String): String = + joinViaJNI(a?.ptr ?: 0, aStr, other) @Throws(ZError::class) - internal external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String + fun concat(a: JNIKeyExpr?, aStr: String, other: String): String = + concatViaJNI(a?.ptr ?: 0, aStr, other) + + @Throws(ZError::class) + private external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean + + @Throws(ZError::class) + private external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean + + @Throws(ZError::class) + private external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int + + @Throws(ZError::class) + private external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String + + @Throws(ZError::class) + private external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String } fun close() { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt index a79a293b..7757c086 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt @@ -23,7 +23,7 @@ public class JNIQuerier(private val ptr: Long) { @Throws(ZError::class) fun get( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, parameters: String?, callback: JNIGetCallback, @@ -33,7 +33,7 @@ public class JNIQuerier(private val ptr: Long) { encodingId: Int, encodingSchema: String?, ) { - getViaJNI(ptr, keyExprPtr, keyExprString, parameters, callback, onClose, attachmentBytes, payload, encodingId, encodingSchema) + getViaJNI(ptr, jniKeyExpr?.ptr ?: 0, keyExprString, parameters, callback, onClose, attachmentBytes, payload, encodingId, encodingSchema) } @Throws(ZError::class) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt index d2711638..78bd5dc3 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt @@ -25,7 +25,7 @@ public class JNIQuery(private val ptr: Long) { @Throws(ZError::class) fun replySuccess( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, payload: ByteArray, encodingId: Int, @@ -35,7 +35,7 @@ public class JNIQuery(private val ptr: Long) { attachment: ByteArray?, qosExpress: Boolean, ) { - replySuccessViaJNI(ptr, keyExprPtr, keyExprString, payload, encodingId, encodingSchema, timestampEnabled, timestampNtp64, attachment, qosExpress) + replySuccessViaJNI(ptr, jniKeyExpr?.ptr ?: 0, keyExprString, payload, encodingId, encodingSchema, timestampEnabled, timestampNtp64, attachment, qosExpress) } @Throws(ZError::class) @@ -45,14 +45,14 @@ public class JNIQuery(private val ptr: Long) { @Throws(ZError::class) fun replyDelete( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, timestampEnabled: Boolean, timestampNtp64: Long, attachment: ByteArray?, qosExpress: Boolean, ) { - replyDeleteViaJNI(ptr, keyExprPtr, keyExprString, timestampEnabled, timestampNtp64, attachment, qosExpress) + replyDeleteViaJNI(ptr, jniKeyExpr?.ptr ?: 0, keyExprString, timestampEnabled, timestampNtp64, attachment, qosExpress) } fun close() { diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 2492a610..cebe1b97 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -45,13 +45,13 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun declarePublisher( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, congestionControl: Int, priority: Int, express: Boolean, reliability: Int - ): JNIPublisher = JNIPublisher(declarePublisherViaJNI(sessionPtr, keyExprPtr, keyExprString, congestionControl, priority, express, reliability)) + ): JNIPublisher = JNIPublisher(declarePublisherViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, congestionControl, priority, express, reliability)) @Throws(ZError::class) private external fun declarePublisherViaJNI( @@ -66,11 +66,11 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun declareSubscriber( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, - ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, onClose)) + ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, callback, onClose)) @Throws(ZError::class) private external fun declareSubscriberViaJNI( @@ -83,12 +83,12 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun declareQueryable( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, callback: JNIQueryableCallback, onClose: JNIOnCloseCallback, complete: Boolean - ): JNIQueryable = JNIQueryable(declareQueryableViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, onClose, complete)) + ): JNIQueryable = JNIQueryable(declareQueryableViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, callback, onClose, complete)) @Throws(ZError::class) private external fun declareQueryableViaJNI( @@ -102,7 +102,7 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun declareQuerier( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, target: Int, consolidation: Int, @@ -111,7 +111,7 @@ public class JNISession(public val sessionPtr: Long) { express: Boolean, timeoutMs: Long, acceptReplies: Int - ): JNIQuerier = JNIQuerier(declareQuerierViaJNI(sessionPtr, keyExprPtr, keyExprString, target, consolidation, congestionControl, priority, express, timeoutMs, acceptReplies)) + ): JNIQuerier = JNIQuerier(declareQuerierViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, target, consolidation, congestionControl, priority, express, timeoutMs, acceptReplies)) @Throws(ZError::class) private external fun declareQuerierViaJNI( @@ -134,14 +134,14 @@ public class JNISession(public val sessionPtr: Long) { private external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long @Throws(ZError::class) - fun undeclareKeyExpr(keyExprPtr: Long) = undeclareKeyExprViaJNI(sessionPtr, keyExprPtr) + fun undeclareKeyExpr(jniKeyExpr: JNIKeyExpr) = undeclareKeyExprViaJNI(sessionPtr, jniKeyExpr.ptr) @Throws(ZError::class) private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) @Throws(ZError::class) fun get( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, selectorParams: String?, callback: JNIGetCallback, @@ -157,7 +157,7 @@ public class JNISession(public val sessionPtr: Long) { priority: Int, express: Boolean, acceptReplies: Int, - ) = getViaJNI(sessionPtr, keyExprPtr, keyExprString, selectorParams, callback, onClose, timeoutMs, target, consolidation, attachmentBytes, payload, encodingId, encodingSchema, congestionControl, priority, express, acceptReplies) + ) = getViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, selectorParams, callback, onClose, timeoutMs, target, consolidation, attachmentBytes, payload, encodingId, encodingSchema, congestionControl, priority, express, acceptReplies) @Throws(ZError::class) private external fun getViaJNI( @@ -182,7 +182,7 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun put( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, valuePayload: ByteArray, valueEncoding: Int, @@ -192,7 +192,7 @@ public class JNISession(public val sessionPtr: Long) { express: Boolean, attachmentBytes: ByteArray?, reliability: Int - ) = putViaJNI(sessionPtr, keyExprPtr, keyExprString, valuePayload, valueEncoding, valueEncodingSchema, congestionControl, priority, express, attachmentBytes, reliability) + ) = putViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, valuePayload, valueEncoding, valueEncodingSchema, congestionControl, priority, express, attachmentBytes, reliability) @Throws(ZError::class) private external fun putViaJNI( @@ -211,14 +211,14 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun delete( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, congestionControl: Int, priority: Int, express: Boolean, attachmentBytes: ByteArray?, reliability: Int - ) = deleteViaJNI(sessionPtr, keyExprPtr, keyExprString, congestionControl, priority, express, attachmentBytes, reliability) + ) = deleteViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, congestionControl, priority, express, attachmentBytes, reliability) @Throws(ZError::class) private external fun deleteViaJNI( @@ -249,7 +249,7 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun declareAdvancedSubscriber( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprStr: String, historyConfigEnabled: Boolean, historyDetectLatePublishers: Boolean, @@ -261,7 +261,7 @@ public class JNISession(public val sessionPtr: Long) { subscriberDetection: Boolean, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, - ): JNIAdvancedSubscriber = JNIAdvancedSubscriber(declareAdvancedSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprStr, historyConfigEnabled, historyDetectLatePublishers, historyMaxSamples, historyMaxAgeSeconds, recoveryConfigEnabled, recoveryConfigIsHeartbeat, recoveryQueryPeriodMs, subscriberDetection, callback, onClose)) + ): JNIAdvancedSubscriber = JNIAdvancedSubscriber(declareAdvancedSubscriberViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprStr, historyConfigEnabled, historyDetectLatePublishers, historyMaxSamples, historyMaxAgeSeconds, recoveryConfigEnabled, recoveryConfigIsHeartbeat, recoveryQueryPeriodMs, subscriberDetection, callback, onClose)) @Throws(ZError::class) private external fun declareAdvancedSubscriberViaJNI( @@ -282,7 +282,7 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun declareAdvancedPublisher( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprStr: String, congestionControl: Int, priority: Int, @@ -298,7 +298,7 @@ public class JNISession(public val sessionPtr: Long) { sampleMissDetectionHeartbeatMs: Long, sampleMissDetectionHeartbeatIsSporadic: Boolean, publisherDetection: Boolean, - ): JNIAdvancedPublisher = JNIAdvancedPublisher(declareAdvancedPublisherViaJNI(sessionPtr, keyExprPtr, keyExprStr, congestionControl, priority, isExpress, reliability, cacheEnabled, cacheMaxSamples, cacheRepliesPriority, cacheRepliesCongestionControl, cacheRepliesIsExpress, sampleMissDetectionEnabled, sampleMissDetectionEnableHeartbeat, sampleMissDetectionHeartbeatMs, sampleMissDetectionHeartbeatIsSporadic, publisherDetection)) + ): JNIAdvancedPublisher = JNIAdvancedPublisher(declareAdvancedPublisherViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprStr, congestionControl, priority, isExpress, reliability, cacheEnabled, cacheMaxSamples, cacheRepliesPriority, cacheRepliesCongestionControl, cacheRepliesIsExpress, sampleMissDetectionEnabled, sampleMissDetectionEnableHeartbeat, sampleMissDetectionHeartbeatMs, sampleMissDetectionHeartbeatIsSporadic, publisherDetection)) @Throws(ZError::class) private external fun declareAdvancedPublisherViaJNI( @@ -322,20 +322,20 @@ public class JNISession(public val sessionPtr: Long) { ): Long @Throws(ZError::class) - fun declareLivelinessToken(keyExprPtr: Long, keyExprString: String): JNILivelinessToken = - JNILivelinessToken(declareLivelinessTokenViaJNI(sessionPtr, keyExprPtr, keyExprString)) + fun declareLivelinessToken(jniKeyExpr: JNIKeyExpr?, keyExprString: String): JNILivelinessToken = + JNILivelinessToken(declareLivelinessTokenViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString)) @Throws(ZError::class) private external fun declareLivelinessTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long @Throws(ZError::class) fun declareLivelinessSubscriber( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, callback: JNISubscriberCallback, history: Boolean, onClose: JNIOnCloseCallback, - ): JNISubscriber = JNISubscriber(declareLivelinessSubscriberViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, history, onClose)) + ): JNISubscriber = JNISubscriber(declareLivelinessSubscriberViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, callback, history, onClose)) @Throws(ZError::class) private external fun declareLivelinessSubscriberViaJNI( @@ -349,12 +349,12 @@ public class JNISession(public val sessionPtr: Long) { @Throws(ZError::class) fun livelinessGet( - keyExprPtr: Long, + jniKeyExpr: JNIKeyExpr?, keyExprString: String, callback: JNIGetCallback, timeoutMs: Long, onClose: JNIOnCloseCallback, - ) = livelinessGetViaJNI(sessionPtr, keyExprPtr, keyExprString, callback, timeoutMs, onClose) + ) = livelinessGetViaJNI(sessionPtr, jniKeyExpr?.ptr ?: 0, keyExprString, callback, timeoutMs, onClose) @Throws(ZError::class) private external fun livelinessGetViaJNI( From 7f89a4681fdc12ba343d7e72e90ab72bae19987e Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 10:26:49 +0200 Subject: [PATCH 31/52] logger in zenoh-jni-runtime --- .../src/commonMain/kotlin/io/zenoh/Logger.kt | 9 +---- .../kotlin/io/zenoh/jni/JNILogger.kt | 37 +++++++++++++++++++ zenoh-jni/src/logger.rs | 2 +- 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt index 53a6caef..1b6101bf 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt @@ -15,6 +15,7 @@ package io.zenoh import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNILogger /** Logger class to redirect the Rust logs from Zenoh to the kotlin environment. */ internal class Logger { @@ -23,12 +24,6 @@ internal class Logger { internal const val LOG_ENV: String = "RUST_LOG" - @Throws(ZError::class) - fun start(filter: String) { - ZenohLoad - startLogsViaJNI(filter) - } - /** * Redirects the rust logs either to logcat for Android systems or to the standard output (for non-android * systems). @@ -36,6 +31,6 @@ internal class Logger { * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. */ @Throws(ZError::class) - private external fun startLogsViaJNI(filter: String) + fun start(filter: String) = JNILogger.startLogs(filter) } } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt new file mode 100644 index 00000000..47c0de53 --- /dev/null +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt @@ -0,0 +1,37 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError + +/** Adapter for initializing Rust logging through JNI. */ +public object JNILogger { + + init { + ZenohLoad + } + + /** + * Redirects Rust logs either to logcat (Android) or standard output. + * + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. + */ + @Throws(ZError::class) + fun startLogs(filter: String) = startLogsViaJNI(filter) + + @Throws(ZError::class) + private external fun startLogsViaJNI(filter: String) +} diff --git a/zenoh-jni/src/logger.rs b/zenoh-jni/src/logger.rs index 785a6cd1..dd64548f 100644 --- a/zenoh-jni/src/logger.rs +++ b/zenoh-jni/src/logger.rs @@ -37,7 +37,7 @@ use crate::{errors::ZResult, throw_exception, zerror}; /// #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_Logger_00024Companion_startLogsViaJNI( +pub extern "C" fn Java_io_zenoh_jni_JNILogger_startLogsViaJNI( mut env: JNIEnv, _class: JClass, filter: JString, From 8f2bf56e54ada8e8adc70641355fb49d5ade1d3a Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 10:31:58 +0200 Subject: [PATCH 32/52] getRoutersZid wrapped --- zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNISession.kt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 92f6e22b..e60e6b4f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -894,7 +894,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) internal fun getRoutersId(): List { - return jniSession?.run { getRoutersZidViaJNI(sessionPtr).map { ZenohId(it) } } ?: throw sessionClosedException + return jniSession?.run { getRoutersZid().map { ZenohId(it) } } ?: throw sessionClosedException } /** Launches the session through the jni session, returning the [Session] on success. */ diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index cebe1b97..841f6306 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -245,7 +245,10 @@ public class JNISession(public val sessionPtr: Long) { private external fun getPeersZidViaJNI(ptr: Long): List @Throws(ZError::class) - external fun getRoutersZidViaJNI(ptr: Long): List + fun getRoutersZid(): List = getRoutersZidViaJNI(sessionPtr) + + @Throws(ZError::class) + private external fun getRoutersZidViaJNI(ptr: Long): List @Throws(ZError::class) fun declareAdvancedSubscriber( From 7ad83e8fa7afea3b91d66fadf53396a552b02e8b Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 10:37:04 +0200 Subject: [PATCH 33/52] wrapped remaining ViaJNI functions --- .../src/commonMain/kotlin/io/zenoh/config/ZenohId.kt | 2 +- .../commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt | 2 +- .../src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt | 8 ++++++-- .../src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt | 4 ++-- .../src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt | 10 ++++++++-- .../src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt | 4 +++- 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt index 9787fced..53304190 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt @@ -23,7 +23,7 @@ import kotlin.math.absoluteValue data class ZenohId internal constructor(internal val bytes: ByteArray) { override fun toString(): String { - return JNIZenohId.toStringViaJNI(bytes) + return JNIZenohId.toString(bytes) } override fun equals(other: Any?): Boolean { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt index 1d039cbe..45e91db9 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt @@ -106,6 +106,6 @@ abstract class ZDeserializer: TypeToken() { */ fun deserialize(zbytes: IntoZBytes): T { @Suppress("UNCHECKED_CAST") - return JNIZBytes.deserializeViaJNI(zbytes.into(), this.type) as T + return JNIZBytes.deserialize(zbytes.into(), this.type) as T } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt index 8ee4a1b7..997814ff 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt @@ -104,6 +104,6 @@ abstract class ZSerializer: TypeToken() { * Serialize [t] into a [ZBytes]. */ fun serialize(t: T): ZBytes { - return JNIZBytes.serializeViaJNI(t as Any, this.type) + return JNIZBytes.serialize(t as Any, this.type) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt index 799b9cac..e9c61964 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt @@ -25,9 +25,13 @@ internal object JNIZBytes { ZenohLoad } + fun serialize(any: Any, type: Type): ZBytes = serializeViaJNI(any, type) + + fun deserialize(zBytes: ZBytes, type: Type): Any = deserializeViaJNI(zBytes, type) + @JvmStatic - external fun serializeViaJNI(any: Any, type: Type): ZBytes + private external fun serializeViaJNI(any: Any, type: Type): ZBytes @JvmStatic - external fun deserializeViaJNI(zBytes: ZBytes, type: Type): Any + private external fun deserializeViaJNI(zBytes: ZBytes, type: Type): Any } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index 427f643c..bf0461a2 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -79,7 +79,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn @JvmStatic @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { - return KeyExpr(JNIKeyExpr.tryFromViaJNI(keyExpr)) + return KeyExpr(JNIKeyExpr.tryFrom(keyExpr)) } /** @@ -95,7 +95,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn @JvmStatic @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { - return KeyExpr(JNIKeyExpr.autocanonizeViaJNI(keyExpr)) + return KeyExpr(JNIKeyExpr.autocanonize(keyExpr)) } } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index 831e92c6..c13395c2 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -26,10 +26,16 @@ public class JNIKeyExpr(internal val ptr: Long) { } @Throws(ZError::class) - internal external fun tryFromViaJNI(keyExpr: String): String + fun tryFrom(keyExpr: String): String = tryFromViaJNI(keyExpr) @Throws(ZError::class) - internal external fun autocanonizeViaJNI(keyExpr: String): String + fun autocanonize(keyExpr: String): String = autocanonizeViaJNI(keyExpr) + + @Throws(ZError::class) + private external fun tryFromViaJNI(keyExpr: String): String + + @Throws(ZError::class) + private external fun autocanonizeViaJNI(keyExpr: String): String @Throws(ZError::class) fun intersects(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Boolean = diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt index 9bb02cb3..0eb1f394 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt @@ -23,5 +23,7 @@ public object JNIZenohId { ZenohLoad } - external fun toStringViaJNI(bytes: ByteArray): String + fun toString(bytes: ByteArray): String = toStringViaJNI(bytes) + + private external fun toStringViaJNI(bytes: ByteArray): String } From 44dfafe42e456a628562533fdb7bdb13147fe7f7 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:13:49 +0200 Subject: [PATCH 34/52] ZBytes only on zenoh-java level --- .../kotlin/io/zenoh/ext/ZDeserializer.kt | 2 +- .../kotlin/io/zenoh/ext/ZSerializer.kt | 2 +- .../kotlin/io/zenoh/jni/JNIZBytes.kt | 9 ++++----- zenoh-jni/src/zbytes.rs | 20 +++---------------- 4 files changed, 9 insertions(+), 24 deletions(-) rename {zenoh-java => zenoh-jni-runtime}/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt (72%) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt index 45e91db9..fadbac89 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt @@ -106,6 +106,6 @@ abstract class ZDeserializer: TypeToken() { */ fun deserialize(zbytes: IntoZBytes): T { @Suppress("UNCHECKED_CAST") - return JNIZBytes.deserialize(zbytes.into(), this.type) as T + return JNIZBytes.deserialize(zbytes.into().bytes, this.type) as T } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt index 997814ff..5f47b7eb 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt @@ -104,6 +104,6 @@ abstract class ZSerializer: TypeToken() { * Serialize [t] into a [ZBytes]. */ fun serialize(t: T): ZBytes { - return JNIZBytes.serialize(t as Any, this.type) + return ZBytes(JNIZBytes.serialize(t as Any, this.type)) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt similarity index 72% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt index e9c61964..224a0711 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt @@ -15,7 +15,6 @@ package io.zenoh.jni import io.zenoh.ZenohLoad -import io.zenoh.bytes.ZBytes import java.lang.reflect.Type @PublishedApi @@ -25,13 +24,13 @@ internal object JNIZBytes { ZenohLoad } - fun serialize(any: Any, type: Type): ZBytes = serializeViaJNI(any, type) + fun serialize(any: Any, type: Type): ByteArray = serializeViaJNI(any, type) - fun deserialize(zBytes: ZBytes, type: Type): Any = deserializeViaJNI(zBytes, type) + fun deserialize(bytes: ByteArray, type: Type): Any = deserializeViaJNI(bytes, type) @JvmStatic - private external fun serializeViaJNI(any: Any, type: Type): ZBytes + private external fun serializeViaJNI(any: Any, type: Type): ByteArray @JvmStatic - private external fun deserializeViaJNI(zBytes: ZBytes, type: Type): Any + private external fun deserializeViaJNI(bytes: ByteArray, type: Type): Any } diff --git a/zenoh-jni/src/zbytes.rs b/zenoh-jni/src/zbytes.rs index 627bb8b0..ae4ca8b8 100644 --- a/zenoh-jni/src/zbytes.rs +++ b/zenoh-jni/src/zbytes.rs @@ -171,15 +171,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_serializeViaJNI( let zbytes = serializer.finish(); let byte_array = bytes_to_java_array(&env, &zbytes).map_err(|err| zerror!(err))?; - let zbytes_obj = env - .new_object( - "io/zenoh/bytes/ZBytes", - "([B)V", - &[JValue::Object(&JObject::from(byte_array))], - ) - .map_err(|err| zerror!(err))?; - - Ok(zbytes_obj.as_raw()) + Ok(byte_array.as_raw()) }() .unwrap_or_else(|err| { throw_exception!(env, err); @@ -296,17 +288,11 @@ fn serialize( pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_deserializeViaJNI( mut env: JNIEnv, _class: JClass, - zbytes: JObject, + bytes: JByteArray, jtype: JObject, ) -> jobject { || -> ZResult { - let payload = env - .get_field(zbytes, "bytes", "[B") - .map_err(|err| zerror!(err))?; - let decoded_bytes: Vec = decode_byte_array( - &env, - JByteArray::from(payload.l().map_err(|err| zerror!(err))?), - )?; + let decoded_bytes: Vec = decode_byte_array(&env, bytes)?; let zbytes = ZBytes::from(decoded_bytes); let mut deserializer = ZDeserializer::new(&zbytes); let jtype = decode_token_type(&mut env, jtype)?; From 35f6a053bb98528fee62032d00139e2759ee401b Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:32:14 +0200 Subject: [PATCH 35/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 8e4fcdd3..9a85452c 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -1,5 +1,5 @@ // -// Copyright (c) 2026 ZettaScale Technology +// Copyright (c) 2023 ZettaScale Technology // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License 2.0 which is available at From 52cd138cd14a214b4bb7d20103530f25fec66a77 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:32:28 +0200 Subject: [PATCH 36/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 9a85452c..19274167 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -21,7 +21,8 @@ import java.io.InputStream import java.util.zip.ZipInputStream /** - * Static singleton class to load the Zenoh native library once and only once. + * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the + * log level configuration. */ public actual object ZenohLoad { private const val ZENOH_LIB_NAME = "zenoh_jni" From 9b0d18298d942fd1a2e60e06ba06ff958bae5d9b Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:32:38 +0200 Subject: [PATCH 37/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- .../src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 19274167..45d7ac8a 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -35,7 +35,13 @@ public actual object ZenohLoad { tryLoadingLibraryFromJarPackage(target).getOrThrow() } } - + /** + * Determine target + * + * Determines the [Target] corresponding to the machine on top of which the native code will run. + * + * @return A result with the target. + */ private fun determineTarget(): Result = runCatching { val osName = System.getProperty("os.name").lowercase() val osArch = System.getProperty("os.arch").lowercase() From f492c2487e75975d5ec7da0c09622db9511b8ab4 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:35:45 +0200 Subject: [PATCH 38/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- .../src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 45d7ac8a..fb0e2341 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -82,6 +82,18 @@ public actual object ZenohLoad { return Result.success(target) } + /** + * Unzip library. + * + * The Zenoh libraries are stored within the JAR as compressed ZIP files. + * The location of the zipped files is expected to be under target/target.zip. + * It is expected that the zip file only contains the compressed library. + * + * The uncompressed library will be stored temporarily and deleted on exit. + * + * @param compressedLib Input stream pointing to the compressed library. + * @return A result with the uncompressed library file. + */ private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { val zipInputStream = ZipInputStream(compressedLib) val buffer = ByteArray(1024) From edf32b6cd37ca6aab8a1366eab04d804e004f8b4 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:35:52 +0200 Subject: [PATCH 39/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index fb0e2341..8ca14d30 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -35,6 +35,7 @@ public actual object ZenohLoad { tryLoadingLibraryFromJarPackage(target).getOrThrow() } } + /** * Determine target * From dd7a570a9bc9ea3fa03fc33c0be6a0017b95a68d Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:35:59 +0200 Subject: [PATCH 40/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 8ca14d30..29afdb80 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -140,6 +140,13 @@ public actual object ZenohLoad { System.load(tempLib.absolutePath) } + /** + * Load library from jar package. + * + * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. + * + * @param target + */ private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { val lib: Result = loadLibraryAsInputStream(target) lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } From f2780ac84bf526cefe8debe83539f099cb67374e Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:36:08 +0200 Subject: [PATCH 41/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 29afdb80..c92d686e 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -152,6 +152,13 @@ public actual object ZenohLoad { lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } } + /** + * Load library from jar package. + * + * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. + * + * @param target + */ private fun tryLoadingLocalLibrary(): Result = runCatching { val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) ?: javaClass.classLoader.findLibraryStream( From 51726c8c7f4063c706672ffd7c8497bb69f0aa33 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:39:25 +0200 Subject: [PATCH 42/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index c92d686e..1023b76a 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -153,9 +153,7 @@ public actual object ZenohLoad { } /** - * Load library from jar package. - * - * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. + * Try loading local library. * * @param target */ From c4ec1d89c246a76edd03128593fd34f6641c405d Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 11:39:32 +0200 Subject: [PATCH 43/52] Update zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt index 1023b76a..aa542844 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt @@ -155,7 +155,8 @@ public actual object ZenohLoad { /** * Try loading local library. * - * @param target + * This function aims to load the default library that is usually included when building the zenoh kotlin library + * locally. */ private fun tryLoadingLocalLibrary(): Result = runCatching { val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) From 6433a7d77c7f4c7651dbb2c9e4842c901e669de2 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 20:54:24 +0200 Subject: [PATCH 44/52] zenoh-jni-runtime api corrected --- .../commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt | 4 ++-- .../commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt | 4 ++-- .../src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt | 4 +--- .../src/commonMain/kotlin/io/zenoh/jni/JNISession.kt | 2 +- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt | 2 +- .../{commonMain => jvmMain}/kotlin/io/zenoh/jni/JNIZBytes.kt | 0 zenoh-jni/src/liveliness.rs | 4 ++-- 7 files changed, 9 insertions(+), 11 deletions(-) rename zenoh-jni-runtime/src/{commonMain => jvmMain}/kotlin/io/zenoh/jni/JNIZBytes.kt (100%) diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt index eee392b6..62d822f6 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt @@ -36,8 +36,8 @@ public class JNIAdvancedPublisher(private val ptr: Long) { } @Throws(ZError::class) - fun declareMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback): Long = - declareMatchingListenerViaJNI(ptr, callback, onClose) + fun declareMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback): JNIMatchingListener = + JNIMatchingListener(declareMatchingListenerViaJNI(ptr, callback, onClose)) @Throws(ZError::class) fun declareBackgroundMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback) = diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt index 13df29a8..084b00f9 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt @@ -31,7 +31,7 @@ public class JNIAdvancedSubscriber(private val ptr: Long) { history: Boolean, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, - ): Long = declareDetectPublishersSubscriberViaJNI(ptr, history, callback, onClose) + ): JNISubscriber = JNISubscriber(declareDetectPublishersSubscriberViaJNI(ptr, history, callback, onClose)) @Throws(ZError::class) fun declareBackgroundDetectPublishersSubscriber( @@ -44,7 +44,7 @@ public class JNIAdvancedSubscriber(private val ptr: Long) { fun declareSampleMissListener( callback: JNISampleMissListenerCallback, onClose: JNIOnCloseCallback, - ): Long = declareSampleMissListenerViaJNI(ptr, callback, onClose) + ): JNISampleMissListener = JNISampleMissListener(declareSampleMissListenerViaJNI(ptr, callback, onClose)) @Throws(ZError::class) fun declareBackgroundSampleMissListener( diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt index dcf14b6b..1f88d3ad 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt @@ -21,7 +21,5 @@ public class JNILivelinessToken(private val ptr: Long) { undeclareViaJNI(ptr) } - companion object { - private external fun undeclareViaJNI(ptr: Long) - } + private external fun undeclareViaJNI(ptr: Long) } diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 841f6306..688b3eb0 100644 --- a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -22,7 +22,7 @@ import io.zenoh.jni.callbacks.JNIQueryableCallback import io.zenoh.jni.callbacks.JNISubscriberCallback /** Adapter class to handle communication with the Zenoh JNI code for a Session. */ -public class JNISession(public val sessionPtr: Long) { +public class JNISession(internal val sessionPtr: Long) { companion object { init { diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt index ad35d536..6460f7ce 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt @@ -14,7 +14,7 @@ package io.zenoh -public enum class Target { +internal enum class Target { WINDOWS_X86_64_MSVC, WINDOWS_AARCH64_MSVC, LINUX_X86_64, diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt similarity index 100% rename from zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt rename to zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs index 37e0925b..8acdc551 100644 --- a/zenoh-jni/src/liveliness.rs +++ b/zenoh-jni/src/liveliness.rs @@ -127,9 +127,9 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_declareLivelinessTokenViaJNI( #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_00024Companion_undeclareViaJNI( +pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_undeclareViaJNI( _env: JNIEnv, - _: JClass, + _: JObject, token_ptr: *const LivelinessToken, ) { unsafe { Arc::from_raw(token_ptr) }; From 0ba4a69c295714324140e3c9ace262d761b78b3b Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Apr 2026 21:25:16 +0200 Subject: [PATCH 45/52] JNIZBytes made public --- zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt index 224a0711..f9690eec 100644 --- a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt +++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt @@ -17,8 +17,7 @@ package io.zenoh.jni import io.zenoh.ZenohLoad import java.lang.reflect.Type -@PublishedApi -internal object JNIZBytes { +object JNIZBytes { init { ZenohLoad From 8bbfaa559d5ddd34a9f87aa12e77f318e9345dd2 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Apr 2026 02:58:14 +0200 Subject: [PATCH 46/52] moved serializaion out of commonMain for correctness --- zenoh-java/build.gradle.kts | 9 ++++++++- .../kotlin/io/zenoh/ext/ZDeserializer.kt | 0 .../kotlin/io/zenoh/ext/ZSerializer.kt | 0 zenoh-jni-runtime/build.gradle.kts | 7 +++++++ .../kotlin/io/zenoh/jni/JNIZBytes.kt | 0 5 files changed, 15 insertions(+), 1 deletion(-) rename zenoh-java/src/{commonMain => javaMain}/kotlin/io/zenoh/ext/ZDeserializer.kt (100%) rename zenoh-java/src/{commonMain => javaMain}/kotlin/io/zenoh/ext/ZSerializer.kt (100%) rename zenoh-jni-runtime/src/{jvmMain => javaMain}/kotlin/io/zenoh/jni/JNIZBytes.kt (100%) diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index 81ea2335..fe36083a 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -67,19 +67,26 @@ kotlin { implementation("com.google.guava:guava:33.3.1-jre") } } + // javaMain is an intermediate source set between commonMain and both jvmMain/androidMain. + // ZSerializer and ZDeserializer use java.lang.reflect.Type (via Guava's TypeToken) — + // available on JVM and Android (ART), but absent on Kotlin/Native and Kotlin/JS targets. + // Placing them here (rather than commonMain) ensures those targets can be added in the + // future without compilation errors. + val javaMain by creating { dependsOn(commonMain) } val commonTest by getting { dependencies { implementation(kotlin("test")) } } if (androidEnabled) { + val androidMain by getting { dependsOn(javaMain) } val androidUnitTest by getting { dependencies { implementation(kotlin("test-junit")) } } } - val jvmMain by getting {} + val jvmMain by getting { dependsOn(javaMain) } val jvmTest by getting {} } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZDeserializer.kt similarity index 100% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt rename to zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZDeserializer.kt diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt b/zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZSerializer.kt similarity index 100% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt rename to zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZSerializer.kt diff --git a/zenoh-jni-runtime/build.gradle.kts b/zenoh-jni-runtime/build.gradle.kts index dc997ce5..3fd84190 100644 --- a/zenoh-jni-runtime/build.gradle.kts +++ b/zenoh-jni-runtime/build.gradle.kts @@ -60,12 +60,18 @@ kotlin { @Suppress("Unused") sourceSets { val commonMain by getting {} + // javaMain is an intermediate source set between commonMain and both jvmMain/androidMain. + // It holds code that uses java.lang.reflect.Type — available on JVM and Android (ART), + // but absent on Kotlin/Native and Kotlin/JS targets. Placing such code here (rather than + // commonMain) ensures those targets can be added in the future without compilation errors. + val javaMain by creating { dependsOn(commonMain) } val commonTest by getting { dependencies { implementation(kotlin("test")) } } if (androidEnabled) { + val androidMain by getting { dependsOn(javaMain) } val androidUnitTest by getting { dependencies { implementation(kotlin("test-junit")) @@ -73,6 +79,7 @@ kotlin { } } val jvmMain by getting { + dependsOn(javaMain) if (isRemotePublication) { resources.srcDir("../jni-libs").include("*/**") } else { diff --git a/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-jni-runtime/src/javaMain/kotlin/io/zenoh/jni/JNIZBytes.kt similarity index 100% rename from zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/jni/JNIZBytes.kt rename to zenoh-jni-runtime/src/javaMain/kotlin/io/zenoh/jni/JNIZBytes.kt From 08cbd450f7d53d25192f5f6f4d3f536c320b93b9 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Apr 2026 03:25:55 +0200 Subject: [PATCH 47/52] gradlew added --- README.md | 7 +- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 8 + gradlew | 251 +++++++++++++++++++++++ gradlew.bat | 94 +++++++++ 5 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat diff --git a/README.md b/README.md index affbaa42..1c214a6d 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,17 @@ Basically: - Rust ([Installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html)) - Kotlin ([Installation guide](https://kotlinlang.org/docs/getting-started.html#backend)) -- Gradle ([Installation guide](https://gradle.org/install/)) and in case of targeting Android you'll also need: - Android SDK ([Installation guide](https://developer.android.com/about/versions/11/setup-sdk)) +## Gradle wrapper + +This repository ships a [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) (`./gradlew` / `gradlew.bat`), so no system-wide Gradle installation is required. The wrapper pins the build to **Gradle 8.12.1** and verifies the distribution checksum before use, ensuring a reproducible and tamper-evident build environment. + +Use `./gradlew` in place of `gradle` for all commands listed below. + ## JVM JVM To publish a library for a JVM project into Maven local, run diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..63532c5e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..057afac5 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..640d6868 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 743e696fb5f2da34c0017d511ffc4f06e637e093 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Apr 2026 03:29:32 +0200 Subject: [PATCH 48/52] use gradlew in ci --- .github/workflows/ci.yml | 4 +--- .github/workflows/publish-android.yml | 6 ------ .github/workflows/publish-dokka.yml | 8 +------- .github/workflows/publish-jvm.yml | 6 ------ 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39e3832b..dd30cfe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,11 +55,9 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - name: Gradle Test - run: gradle jvmTest --info + run: ./gradlew jvmTest --info markdown_lint: runs-on: ubuntu-latest diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 77f9f611..380e5444 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -58,12 +58,6 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - - - name: Gradle Wrapper - run: | - gradle wrapper - name: Set pub mode env var # Note: This step is intended to allow publishing snapshot packages. diff --git a/.github/workflows/publish-dokka.yml b/.github/workflows/publish-dokka.yml index b5fe106d..4964557b 100644 --- a/.github/workflows/publish-dokka.yml +++ b/.github/workflows/publish-dokka.yml @@ -27,15 +27,9 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - - - name: Gradle Wrapper - run: | - gradle wrapper - name: Build doc - run: gradle dokkaGenerate + run: ./gradlew dokkaGenerate - name: Deploy doc if: ${{ inputs.live-run || false }} diff --git a/.github/workflows/publish-jvm.yml b/.github/workflows/publish-jvm.yml index 7caf1b48..ce0ba5e6 100644 --- a/.github/workflows/publish-jvm.yml +++ b/.github/workflows/publish-jvm.yml @@ -163,12 +163,6 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - - - name: Gradle Wrapper - run: | - gradle wrapper - name: Set pub mode env var # Note: This step is intended to allow publishing snapshot packages. From 4596466bf86301e7136880aebb4c16cae94c3a00 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Apr 2026 14:59:27 +0200 Subject: [PATCH 49/52] build naming conflict fix --- zenoh-java/build.gradle.kts | 10 ++++++---- .../kotlin/io/zenoh/ext/ZDeserializer.kt | 0 .../kotlin/io/zenoh/ext/ZSerializer.kt | 0 zenoh-jni-runtime/build.gradle.kts | 10 ++++++---- .../kotlin/io/zenoh/jni/JNIZBytes.kt | 0 5 files changed, 12 insertions(+), 8 deletions(-) rename zenoh-java/src/{javaMain => jvmAndAndroidMain}/kotlin/io/zenoh/ext/ZDeserializer.kt (100%) rename zenoh-java/src/{javaMain => jvmAndAndroidMain}/kotlin/io/zenoh/ext/ZSerializer.kt (100%) rename zenoh-jni-runtime/src/{javaMain => jvmAndAndroidMain}/kotlin/io/zenoh/jni/JNIZBytes.kt (100%) diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index fe36083a..dfb36284 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -67,26 +67,28 @@ kotlin { implementation("com.google.guava:guava:33.3.1-jre") } } - // javaMain is an intermediate source set between commonMain and both jvmMain/androidMain. + // jvmAndAndroidMain is an intermediate source set between commonMain and both jvmMain/androidMain. // ZSerializer and ZDeserializer use java.lang.reflect.Type (via Guava's TypeToken) — // available on JVM and Android (ART), but absent on Kotlin/Native and Kotlin/JS targets. // Placing them here (rather than commonMain) ensures those targets can be added in the // future without compilation errors. - val javaMain by creating { dependsOn(commonMain) } + // Note: named jvmAndAndroidMain (not javaMain) to avoid conflicting with the DokkaSourceSetSpec + // that Dokka auto-registers for the Java compilation created by withJava(). + val jvmAndAndroidMain by creating { dependsOn(commonMain) } val commonTest by getting { dependencies { implementation(kotlin("test")) } } if (androidEnabled) { - val androidMain by getting { dependsOn(javaMain) } + val androidMain by getting { dependsOn(jvmAndAndroidMain) } val androidUnitTest by getting { dependencies { implementation(kotlin("test-junit")) } } } - val jvmMain by getting { dependsOn(javaMain) } + val jvmMain by getting { dependsOn(jvmAndAndroidMain) } val jvmTest by getting {} } diff --git a/zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserializer.kt similarity index 100% rename from zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZDeserializer.kt rename to zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserializer.kt diff --git a/zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZSerializer.kt b/zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerializer.kt similarity index 100% rename from zenoh-java/src/javaMain/kotlin/io/zenoh/ext/ZSerializer.kt rename to zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerializer.kt diff --git a/zenoh-jni-runtime/build.gradle.kts b/zenoh-jni-runtime/build.gradle.kts index 3fd84190..7a4c7b15 100644 --- a/zenoh-jni-runtime/build.gradle.kts +++ b/zenoh-jni-runtime/build.gradle.kts @@ -60,18 +60,20 @@ kotlin { @Suppress("Unused") sourceSets { val commonMain by getting {} - // javaMain is an intermediate source set between commonMain and both jvmMain/androidMain. + // jvmAndAndroidMain is an intermediate source set between commonMain and both jvmMain/androidMain. // It holds code that uses java.lang.reflect.Type — available on JVM and Android (ART), // but absent on Kotlin/Native and Kotlin/JS targets. Placing such code here (rather than // commonMain) ensures those targets can be added in the future without compilation errors. - val javaMain by creating { dependsOn(commonMain) } + // Note: named jvmAndAndroidMain (not javaMain) to avoid conflicting with the DokkaSourceSetSpec + // that Dokka auto-registers for the Java compilation created by withJava(). + val jvmAndAndroidMain by creating { dependsOn(commonMain) } val commonTest by getting { dependencies { implementation(kotlin("test")) } } if (androidEnabled) { - val androidMain by getting { dependsOn(javaMain) } + val androidMain by getting { dependsOn(jvmAndAndroidMain) } val androidUnitTest by getting { dependencies { implementation(kotlin("test-junit")) @@ -79,7 +81,7 @@ kotlin { } } val jvmMain by getting { - dependsOn(javaMain) + dependsOn(jvmAndAndroidMain) if (isRemotePublication) { resources.srcDir("../jni-libs").include("*/**") } else { diff --git a/zenoh-jni-runtime/src/javaMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytes.kt similarity index 100% rename from zenoh-jni-runtime/src/javaMain/kotlin/io/zenoh/jni/JNIZBytes.kt rename to zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytes.kt From 0000b7aac4fdca6a1ae849984f263bb1592ad790 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Apr 2026 18:31:35 +0200 Subject: [PATCH 50/52] kotlin serialization --- .../kotlin/io/zenoh/jni/JNIZBytesKotlin.kt | 49 ++ zenoh-jni/src/lib.rs | 2 + zenoh-jni/src/zbytes_kotlin.rs | 540 ++++++++++++++++++ 3 files changed, 591 insertions(+) create mode 100644 zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt create mode 100644 zenoh-jni/src/zbytes_kotlin.rs diff --git a/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt new file mode 100644 index 00000000..3570b9e6 --- /dev/null +++ b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt @@ -0,0 +1,49 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.ZenohLoad +import kotlin.reflect.KType + +/** + * JNI bridge for Kotlin-type-aware serialization/deserialization. + * + * Uses [KType] (obtained via `typeOf()` with reified generics) instead of + * [java.lang.reflect.Type], making this bridge usable from commonMain Kotlin + * code and compatible with Kotlin-specific types (unsigned integers, Pair, Triple). + * + * Supported types: + * - Primitives: Boolean, Byte, Short, Int, Long, Float, Double + * - Unsigned (Kotlin-only): UByte, UShort, UInt, ULong + * - Text/Binary: String, ByteArray + * - Collections: List, Map (recursive) + * - Tuples: Pair, Triple + */ +object JNIZBytesKotlin { + + init { + ZenohLoad + } + + fun serialize(any: Any, kType: KType): ByteArray = serializeViaJNI(any, kType) + + fun deserialize(bytes: ByteArray, kType: KType): Any = deserializeViaJNI(bytes, kType) + + @JvmStatic + private external fun serializeViaJNI(any: Any, kType: KType): ByteArray + + @JvmStatic + private external fun deserializeViaJNI(bytes: ByteArray, kType: KType): Any +} diff --git a/zenoh-jni/src/lib.rs b/zenoh-jni/src/lib.rs index 93765edb..f8603617 100644 --- a/zenoh-jni/src/lib.rs +++ b/zenoh-jni/src/lib.rs @@ -31,6 +31,8 @@ mod subscriber; mod utils; #[cfg(feature = "zenoh-ext")] mod zbytes; +#[cfg(feature = "zenoh-ext")] +mod zbytes_kotlin; mod zenoh_id; // Test should be runned with `cargo test --no-default-features` diff --git a/zenoh-jni/src/zbytes_kotlin.rs b/zenoh-jni/src/zbytes_kotlin.rs new file mode 100644 index 00000000..6d1cc44c --- /dev/null +++ b/zenoh-jni/src/zbytes_kotlin.rs @@ -0,0 +1,540 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! JNI bridge for Kotlin-type-aware (de)serialization. +//! +//! Mirrors [zbytes.rs] but uses `kotlin.reflect.KType` instead of +//! `java.lang.reflect.Type`, enabling support for Kotlin-specific types: +//! unsigned integers (UByte, UShort, UInt, ULong) and tuples (Pair, Triple). +//! +//! Entry points are named for class `io.zenoh.jni.JNIZBytesKotlin` to avoid +//! JNI symbol conflicts with the existing `JNIZBytes` class (same method name, +//! different receiver type — JNI cannot overload on parameter type alone). + +use jni::{ + objects::{JByteArray, JClass, JList, JMap, JObject, JString, JValue}, + sys::jobject, + JNIEnv, +}; +use zenoh::bytes::ZBytes; +use zenoh_ext::{VarInt, ZDeserializeError, ZDeserializer, ZSerializer}; + +use crate::{ + errors::ZResult, + throw_exception, + utils::{bytes_to_java_array, decode_byte_array}, + zerror, +}; + +enum KotlinType { + Boolean, + String, + ByteArray, + Byte, + Short, + Int, + Long, + Float, + Double, + UByte, + UShort, + UInt, + ULong, + List(Box), + Map(Box, Box), + Pair(Box, Box), + Triple(Box, Box, Box), +} + +/// Extracts the KType of the Nth type argument from a generic KType. +/// +/// `ktype.getArguments()` → `List`. +/// `projection.getType()` → `KType?`. +/// Extracts the KType at `index` from the type arguments of `ktype` and +/// immediately decodes it to [KotlinType]. Returning [KotlinType] (a pure Rust +/// type) avoids the JObject lifetime chain that arises when returning JObject. +fn decode_ktype_arg(env: &mut JNIEnv, ktype: &JObject, index: i32) -> ZResult { + let args = env + .call_method(ktype, "getArguments", "()Ljava/util/List;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + + let projection = env + .call_method(&args, "get", "(I)Ljava/lang/Object;", &[JValue::Int(index)]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + + let arg_type = env + .call_method(&projection, "getType", "()Lkotlin/reflect/KType;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + + if arg_type.is_null() { + return Err(zerror!("KTypeProjection.type is null (star projection not supported)")); + } + decode_ktype(env, arg_type) +} + +/// Decodes a `kotlin.reflect.KType` JVM object into a [KotlinType] enum. +fn decode_ktype(env: &mut JNIEnv, ktype: JObject) -> ZResult { + let classifier = env + .call_method(&ktype, "getClassifier", "()Lkotlin/reflect/KClassifier;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + + if classifier.is_null() { + return Err(zerror!("KType has no classifier (star projection not supported)")); + } + + let name_obj = env + .call_method(&classifier, "getQualifiedName", "()Ljava/lang/String;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + + if name_obj.is_null() { + return Err(zerror!("KClass has no qualified name (anonymous/local class not supported)")); + } + + let qualified_name: std::string::String = env + .get_string(&JString::from(name_obj)) + .map_err(|err| zerror!(err))? + .into(); + + match qualified_name.as_str() { + "kotlin.Boolean" => Ok(KotlinType::Boolean), + "kotlin.String" => Ok(KotlinType::String), + "kotlin.ByteArray" => Ok(KotlinType::ByteArray), + "kotlin.Byte" => Ok(KotlinType::Byte), + "kotlin.Short" => Ok(KotlinType::Short), + "kotlin.Int" => Ok(KotlinType::Int), + "kotlin.Long" => Ok(KotlinType::Long), + "kotlin.Float" => Ok(KotlinType::Float), + "kotlin.Double" => Ok(KotlinType::Double), + "kotlin.UByte" => Ok(KotlinType::UByte), + "kotlin.UShort" => Ok(KotlinType::UShort), + "kotlin.UInt" => Ok(KotlinType::UInt), + "kotlin.ULong" => Ok(KotlinType::ULong), + "kotlin.collections.List" => { + Ok(KotlinType::List(Box::new(decode_ktype_arg(env, &ktype, 0)?))) + } + "kotlin.collections.Map" => { + let key = decode_ktype_arg(env, &ktype, 0)?; + let val = decode_ktype_arg(env, &ktype, 1)?; + Ok(KotlinType::Map(Box::new(key), Box::new(val))) + } + "kotlin.Pair" => { + let first = decode_ktype_arg(env, &ktype, 0)?; + let second = decode_ktype_arg(env, &ktype, 1)?; + Ok(KotlinType::Pair(Box::new(first), Box::new(second))) + } + "kotlin.Triple" => { + let first = decode_ktype_arg(env, &ktype, 0)?; + let second = decode_ktype_arg(env, &ktype, 1)?; + let third = decode_ktype_arg(env, &ktype, 2)?; + Ok(KotlinType::Triple(Box::new(first), Box::new(second), Box::new(third))) + } + _ => Err(zerror!("Unsupported Kotlin type: {}", qualified_name)), + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIZBytesKotlin_serializeViaJNI( + mut env: JNIEnv, + _class: JClass, + any: JObject, + ktype: JObject, +) -> jobject { + || -> ZResult { + let kotlin_type = decode_ktype(&mut env, ktype)?; + let mut serializer = ZSerializer::new(); + serialize(&mut env, &mut serializer, any, &kotlin_type)?; + let zbytes = serializer.finish(); + let byte_array = bytes_to_java_array(&env, &zbytes).map_err(|err| zerror!(err))?; + Ok(byte_array.as_raw()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }) +} + +fn serialize( + env: &mut JNIEnv, + serializer: &mut ZSerializer, + any: JObject, + ktype: &KotlinType, +) -> ZResult<()> { + match ktype { + KotlinType::Boolean => { + let v = env + .call_method(&any, "booleanValue", "()Z", &[]) + .map_err(|err| zerror!(err))? + .z() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::Byte => { + let v = env + .call_method(&any, "byteValue", "()B", &[]) + .map_err(|err| zerror!(err))? + .b() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::Short => { + let v = env + .call_method(&any, "shortValue", "()S", &[]) + .map_err(|err| zerror!(err))? + .s() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::Int => { + let v = env + .call_method(&any, "intValue", "()I", &[]) + .map_err(|err| zerror!(err))? + .i() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::Long => { + let v = env + .call_method(&any, "longValue", "()J", &[]) + .map_err(|err| zerror!(err))? + .j() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::Float => { + let v = env + .call_method(&any, "floatValue", "()F", &[]) + .map_err(|err| zerror!(err))? + .f() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::Double => { + let v = env + .call_method(&any, "doubleValue", "()D", &[]) + .map_err(|err| zerror!(err))? + .d() + .map_err(|err| zerror!(err))?; + serializer.serialize(v); + } + KotlinType::String => { + let s: std::string::String = env + .get_string(&JString::from(any)) + .map_err(|err| zerror!(err))? + .into(); + serializer.serialize(s); + } + KotlinType::ByteArray => { + let bytes = decode_byte_array(env, JByteArray::from(any)).map_err(|err| zerror!(err))?; + serializer.serialize(bytes); + } + // Unsigned types: `toByte/Short/Int/Long()` returns the raw signed bits; + // we reinterpret as unsigned — wire encoding is identical (LE fixed-width). + KotlinType::UByte => { + let v = env + .call_method(&any, "toByte", "()B", &[]) + .map_err(|err| zerror!(err))? + .b() + .map_err(|err| zerror!(err))?; + serializer.serialize(v as u8); + } + KotlinType::UShort => { + let v = env + .call_method(&any, "toShort", "()S", &[]) + .map_err(|err| zerror!(err))? + .s() + .map_err(|err| zerror!(err))?; + serializer.serialize(v as u16); + } + KotlinType::UInt => { + let v = env + .call_method(&any, "toInt", "()I", &[]) + .map_err(|err| zerror!(err))? + .i() + .map_err(|err| zerror!(err))?; + serializer.serialize(v as u32); + } + KotlinType::ULong => { + let v = env + .call_method(&any, "toLong", "()J", &[]) + .map_err(|err| zerror!(err))? + .j() + .map_err(|err| zerror!(err))?; + serializer.serialize(v as u64); + } + KotlinType::List(inner) => { + let jlist = JList::from_env(env, &any).map_err(|err| zerror!(err))?; + let size = jlist.size(env).map_err(|err| zerror!(err))?; + serializer.serialize(VarInt(size as usize)); + let mut iter = jlist.iter(env).map_err(|err| zerror!(err))?; + while let Some(item) = iter.next(env).map_err(|err| zerror!(err))? { + serialize(env, serializer, item, inner)?; + } + } + KotlinType::Map(key_type, val_type) => { + let jmap = JMap::from_env(env, &any).map_err(|err| zerror!(err))?; + let size = env + .call_method(&jmap, "size", "()I", &[]) + .map_err(|err| zerror!(err))? + .i() + .map_err(|err| zerror!(err))?; + serializer.serialize(VarInt(size as usize)); + let mut iter = jmap.iter(env).map_err(|err| zerror!(err))?; + while let Some((k, v)) = iter.next(env).map_err(|err| zerror!(err))? { + serialize(env, serializer, k, key_type)?; + serialize(env, serializer, v, val_type)?; + } + } + KotlinType::Pair(first_type, second_type) => { + let first = env + .call_method(&any, "getFirst", "()Ljava/lang/Object;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + let second = env + .call_method(&any, "getSecond", "()Ljava/lang/Object;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + serialize(env, serializer, first, first_type)?; + serialize(env, serializer, second, second_type)?; + } + KotlinType::Triple(first_type, second_type, third_type) => { + let first = env + .call_method(&any, "getFirst", "()Ljava/lang/Object;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + let second = env + .call_method(&any, "getSecond", "()Ljava/lang/Object;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + let third = env + .call_method(&any, "getThird", "()Ljava/lang/Object;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + serialize(env, serializer, first, first_type)?; + serialize(env, serializer, second, second_type)?; + serialize(env, serializer, third, third_type)?; + } + } + Ok(()) +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIZBytesKotlin_deserializeViaJNI( + mut env: JNIEnv, + _class: JClass, + bytes: JByteArray, + ktype: JObject, +) -> jobject { + || -> ZResult { + let raw = decode_byte_array(&env, bytes)?; + let zbytes = ZBytes::from(raw); + let mut deserializer = ZDeserializer::new(&zbytes); + let kotlin_type = decode_ktype(&mut env, ktype)?; + let obj = deserialize(&mut env, &mut deserializer, &kotlin_type)?; + if !deserializer.done() { + return Err(zerror!(ZDeserializeError)); + } + Ok(obj) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }) +} + +fn deserialize( + env: &mut JNIEnv, + deserializer: &mut ZDeserializer, + ktype: &KotlinType, +) -> ZResult { + match ktype { + KotlinType::Boolean => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Boolean", "(Z)V", &[JValue::Bool(v as u8)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::Byte => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Byte", "(B)V", &[JValue::Byte(v)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::Short => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Short", "(S)V", &[JValue::Short(v)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::Int => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Integer", "(I)V", &[JValue::Int(v)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::Long => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Long", "(J)V", &[JValue::Long(v)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::Float => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Float", "(F)V", &[JValue::Float(v)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::Double => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("java/lang/Double", "(D)V", &[JValue::Double(v)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::String => { + let s = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let jstr = env.new_string(&s).map_err(|err| zerror!(err))?; + Ok(jstr.into_raw()) + } + KotlinType::ByteArray => { + let bytes = deserializer + .deserialize::>() + .map_err(|err| zerror!(err))?; + let jbytes = env + .byte_array_from_slice(bytes.as_slice()) + .map_err(|err| zerror!(err))?; + Ok(jbytes.into_raw()) + } + KotlinType::UByte => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("kotlin/UByte", "(B)V", &[JValue::Byte(v as i8)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::UShort => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("kotlin/UShort", "(S)V", &[JValue::Short(v as i16)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::UInt => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("kotlin/UInt", "(I)V", &[JValue::Int(v as i32)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::ULong => { + let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let obj = env + .new_object("kotlin/ULong", "(J)V", &[JValue::Long(v as i64)]) + .map_err(|err| zerror!(err))?; + Ok(obj.as_raw()) + } + KotlinType::List(inner) => { + let size = deserializer + .deserialize::>() + .map_err(|err| zerror!(err))? + .0; + let array_list = env + .new_object("java/util/ArrayList", "()V", &[]) + .map_err(|err| zerror!(err))?; + let jlist = JList::from_env(env, &array_list).map_err(|err| zerror!(err))?; + for _ in 0..size { + let item = deserialize(env, deserializer, inner)?; + let item_obj = unsafe { JObject::from_raw(item) }; + jlist.add(env, &item_obj).map_err(|err| zerror!(err))?; + } + Ok(array_list.as_raw()) + } + KotlinType::Map(key_type, val_type) => { + let size = deserializer + .deserialize::>() + .map_err(|err| zerror!(err))? + .0; + let hash_map = env + .new_object("java/util/HashMap", "()V", &[]) + .map_err(|err| zerror!(err))?; + let jmap = JMap::from_env(env, &hash_map).map_err(|err| zerror!(err))?; + for _ in 0..size { + let k = deserialize(env, deserializer, key_type)?; + let k_obj = unsafe { JObject::from_raw(k) }; + let v = deserialize(env, deserializer, val_type)?; + let v_obj = unsafe { JObject::from_raw(v) }; + jmap.put(env, &k_obj, &v_obj).map_err(|err| zerror!(err))?; + } + Ok(hash_map.as_raw()) + } + KotlinType::Pair(first_type, second_type) => { + let first = deserialize(env, deserializer, first_type)?; + let second = deserialize(env, deserializer, second_type)?; + let first_obj = unsafe { JObject::from_raw(first) }; + let second_obj = unsafe { JObject::from_raw(second) }; + let pair = env + .new_object( + "kotlin/Pair", + "(Ljava/lang/Object;Ljava/lang/Object;)V", + &[JValue::Object(&first_obj), JValue::Object(&second_obj)], + ) + .map_err(|err| zerror!(err))?; + Ok(pair.as_raw()) + } + KotlinType::Triple(first_type, second_type, third_type) => { + let first = deserialize(env, deserializer, first_type)?; + let second = deserialize(env, deserializer, second_type)?; + let third = deserialize(env, deserializer, third_type)?; + let first_obj = unsafe { JObject::from_raw(first) }; + let second_obj = unsafe { JObject::from_raw(second) }; + let third_obj = unsafe { JObject::from_raw(third) }; + let triple = env + .new_object( + "kotlin/Triple", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + &[ + JValue::Object(&first_obj), + JValue::Object(&second_obj), + JValue::Object(&third_obj), + ], + ) + .map_err(|err| zerror!(err))?; + Ok(triple.as_raw()) + } + } +} From 969166ab357ff382ba32198f14a7dbfa5b69933e Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Apr 2026 21:58:52 +0200 Subject: [PATCH 51/52] serialiser iteroperability test --- zenoh-jni-runtime/build.gradle.kts | 3 + .../kotlin/io/zenoh/ZBytesInteropTests.kt | 308 ++++++++++++++++++ zenoh-jni/src/zbytes_kotlin.rs | 10 +- 3 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt diff --git a/zenoh-jni-runtime/build.gradle.kts b/zenoh-jni-runtime/build.gradle.kts index 7a4c7b15..5e6d4def 100644 --- a/zenoh-jni-runtime/build.gradle.kts +++ b/zenoh-jni-runtime/build.gradle.kts @@ -91,6 +91,9 @@ kotlin { val jvmTest by getting { resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) + dependencies { + implementation(kotlin("test-junit")) + } } } diff --git a/zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt b/zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt new file mode 100644 index 00000000..5e8f6bd5 --- /dev/null +++ b/zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt @@ -0,0 +1,308 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +import io.zenoh.jni.JNIZBytes +import io.zenoh.jni.JNIZBytesKotlin +import kotlin.reflect.typeOf +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +/** + * Tests for Java/Kotlin serialization interoperability at the JNI bridge layer. + * + * Two groups: + * 1. Kotlin round-trips — serialize and deserialize via [JNIZBytesKotlin] (KType path), covering + * all supported KotlinType variants including unsigned integers and Pair/Triple. + * 2. Cross-path interop — verify that [JNIZBytes] (Java Type path) and [JNIZBytesKotlin] + * (KType path) produce identical wire bytes for the common types they share. + */ +class ZBytesInteropTests { + + // ------------------------------------------------------------------------- + // Group 1: Kotlin round-trips via JNIZBytesKotlin + // ------------------------------------------------------------------------- + + @Test + fun testBooleanKotlinRoundTrip() { + val input = true + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Boolean) + } + + @Test + fun testByteKotlinRoundTrip() { + val input: Byte = 42 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Byte) + } + + @Test + fun testShortKotlinRoundTrip() { + val input: Short = 1234 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Short) + } + + @Test + fun testIntKotlinRoundTrip() { + val input = 1234567 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Int) + } + + @Test + fun testLongKotlinRoundTrip() { + val input = 123456789012345L + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Long) + } + + @Test + fun testFloatKotlinRoundTrip() { + val input = 3.1415f + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Float, 0.0001f) + } + + @Test + fun testDoubleKotlinRoundTrip() { + val input = 2.718281828 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Double, 0.000000001) + } + + @Test + fun testStringKotlinRoundTrip() { + val input = "hello zenoh" + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as String) + } + + @Test + fun testByteArrayKotlinRoundTrip() { + val input = byteArrayOf(1, 2, 3, 4, 5) + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertContentEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as ByteArray) + } + + // Kotlin-only unsigned types + + @Test + fun testUByteKotlinRoundTrip() { + val input: UByte = 200u + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as UByte) + } + + @Test + fun testUShortKotlinRoundTrip() { + val input: UShort = 60000u + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as UShort) + } + + @Test + fun testUIntKotlinRoundTrip() { + val input: UInt = 3000000000u + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as UInt) + } + + @Test + fun testULongKotlinRoundTrip() { + val input: ULong = 10000000000000000000u + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as ULong) + } + + // Collections + + @Test + fun testListOfIntKotlinRoundTrip() { + val input = listOf(1, 2, 3, 4, 5) + val bytes = JNIZBytesKotlin.serialize(input, typeOf>()) + @Suppress("UNCHECKED_CAST") + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf>()) as List) + } + + @Test + fun testListOfStringKotlinRoundTrip() { + val input = listOf("a", "b", "c") + val bytes = JNIZBytesKotlin.serialize(input, typeOf>()) + @Suppress("UNCHECKED_CAST") + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf>()) as List) + } + + @Test + fun testMapOfStringToIntKotlinRoundTrip() { + val input = mapOf("one" to 1, "two" to 2, "three" to 3) + val bytes = JNIZBytesKotlin.serialize(input, typeOf>()) + @Suppress("UNCHECKED_CAST") + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf>()) as Map) + } + + // Kotlin-only compound types + + @Test + fun testPairKotlinRoundTrip() { + val input = Pair(42, "hello") + val bytes = JNIZBytesKotlin.serialize(input, typeOf>()) + @Suppress("UNCHECKED_CAST") + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf>()) as Pair) + } + + @Test + fun testTripleKotlinRoundTrip() { + val input = Triple("zenoh", 99, true) + val bytes = JNIZBytesKotlin.serialize(input, typeOf>()) + @Suppress("UNCHECKED_CAST") + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf>()) as Triple) + } + + // ------------------------------------------------------------------------- + // Group 2: Cross-path interop — Java Type path ↔ KType path + // + // For the Java path, java.lang.Class implements java.lang.reflect.Type. + // Boxed types give the qualified names zbytes.rs expects ("java.lang.Integer", etc.). + // ------------------------------------------------------------------------- + + @Test + fun testBooleanJavaToKotlinInterop() { + val input = true + val bytes = JNIZBytes.serialize(input, java.lang.Boolean::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Boolean) + } + + @Test + fun testBooleanKotlinToJavaInterop() { + val input = true + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Boolean::class.java) as Boolean) + } + + @Test + fun testByteJavaToKotlinInterop() { + val input: Byte = 42 + val bytes = JNIZBytes.serialize(input, java.lang.Byte::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Byte) + } + + @Test + fun testByteKotlinToJavaInterop() { + val input: Byte = 42 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Byte::class.java) as Byte) + } + + @Test + fun testShortJavaToKotlinInterop() { + val input: Short = 1234 + val bytes = JNIZBytes.serialize(input, java.lang.Short::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Short) + } + + @Test + fun testShortKotlinToJavaInterop() { + val input: Short = 1234 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Short::class.java) as Short) + } + + @Test + fun testIntJavaToKotlinInterop() { + val input = 42 + val bytes = JNIZBytes.serialize(input, java.lang.Integer::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Int) + } + + @Test + fun testIntKotlinToJavaInterop() { + val input = 42 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Integer::class.java) as Int) + } + + @Test + fun testLongJavaToKotlinInterop() { + val input = 123456789012345L + val bytes = JNIZBytes.serialize(input, java.lang.Long::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Long) + } + + @Test + fun testLongKotlinToJavaInterop() { + val input = 123456789012345L + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Long::class.java) as Long) + } + + @Test + fun testFloatJavaToKotlinInterop() { + val input = 3.1415f + val bytes = JNIZBytes.serialize(input, java.lang.Float::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Float, 0.0001f) + } + + @Test + fun testFloatKotlinToJavaInterop() { + val input = 3.1415f + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Float::class.java) as Float, 0.0001f) + } + + @Test + fun testDoubleJavaToKotlinInterop() { + val input = 2.718281828 + val bytes = JNIZBytes.serialize(input, java.lang.Double::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Double, 0.000000001) + } + + @Test + fun testDoubleKotlinToJavaInterop() { + val input = 2.718281828 + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.Double::class.java) as Double, 0.000000001) + } + + @Test + fun testStringJavaToKotlinInterop() { + val input = "hello zenoh" + val bytes = JNIZBytes.serialize(input, java.lang.String::class.java) + assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as String) + } + + @Test + fun testStringKotlinToJavaInterop() { + val input = "hello zenoh" + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertEquals(input, JNIZBytes.deserialize(bytes, java.lang.String::class.java) as String) + } + + @Test + fun testByteArrayJavaToKotlinInterop() { + val input = byteArrayOf(10, 20, 30, 40, 50) + val bytes = JNIZBytes.serialize(input, ByteArray::class.java) + assertContentEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as ByteArray) + } + + @Test + fun testByteArrayKotlinToJavaInterop() { + val input = byteArrayOf(10, 20, 30, 40, 50) + val bytes = JNIZBytesKotlin.serialize(input, typeOf()) + assertContentEquals(input, JNIZBytes.deserialize(bytes, ByteArray::class.java) as ByteArray) + } +} diff --git a/zenoh-jni/src/zbytes_kotlin.rs b/zenoh-jni/src/zbytes_kotlin.rs index 6d1cc44c..c2fd84ef 100644 --- a/zenoh-jni/src/zbytes_kotlin.rs +++ b/zenoh-jni/src/zbytes_kotlin.rs @@ -249,11 +249,9 @@ fn serialize( let bytes = decode_byte_array(env, JByteArray::from(any)).map_err(|err| zerror!(err))?; serializer.serialize(bytes); } - // Unsigned types: `toByte/Short/Int/Long()` returns the raw signed bits; - // we reinterpret as unsigned — wire encoding is identical (LE fixed-width). KotlinType::UByte => { let v = env - .call_method(&any, "toByte", "()B", &[]) + .get_field(&any, "data", "B") .map_err(|err| zerror!(err))? .b() .map_err(|err| zerror!(err))?; @@ -261,7 +259,7 @@ fn serialize( } KotlinType::UShort => { let v = env - .call_method(&any, "toShort", "()S", &[]) + .get_field(&any, "data", "S") .map_err(|err| zerror!(err))? .s() .map_err(|err| zerror!(err))?; @@ -269,7 +267,7 @@ fn serialize( } KotlinType::UInt => { let v = env - .call_method(&any, "toInt", "()I", &[]) + .get_field(&any, "data", "I") .map_err(|err| zerror!(err))? .i() .map_err(|err| zerror!(err))?; @@ -277,7 +275,7 @@ fn serialize( } KotlinType::ULong => { let v = env - .call_method(&any, "toLong", "()J", &[]) + .get_field(&any, "data", "J") .map_err(|err| zerror!(err))? .j() .map_err(|err| zerror!(err))?; From fc47b41f40974af89af6a3885821228ca9c66427 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sun, 19 Apr 2026 16:28:25 +0200 Subject: [PATCH 52/52] cargo fmt --- zenoh-jni/src/zbytes_kotlin.rs | 78 +++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/zenoh-jni/src/zbytes_kotlin.rs b/zenoh-jni/src/zbytes_kotlin.rs index c2fd84ef..997bd51b 100644 --- a/zenoh-jni/src/zbytes_kotlin.rs +++ b/zenoh-jni/src/zbytes_kotlin.rs @@ -84,7 +84,9 @@ fn decode_ktype_arg(env: &mut JNIEnv, ktype: &JObject, index: i32) -> ZResult ZResult ZResult { let classifier = env - .call_method(&ktype, "getClassifier", "()Lkotlin/reflect/KClassifier;", &[]) + .call_method( + &ktype, + "getClassifier", + "()Lkotlin/reflect/KClassifier;", + &[], + ) .map_err(|err| zerror!(err))? .l() .map_err(|err| zerror!(err))?; if classifier.is_null() { - return Err(zerror!("KType has no classifier (star projection not supported)")); + return Err(zerror!( + "KType has no classifier (star projection not supported)" + )); } let name_obj = env @@ -108,7 +117,9 @@ fn decode_ktype(env: &mut JNIEnv, ktype: JObject) -> ZResult { .map_err(|err| zerror!(err))?; if name_obj.is_null() { - return Err(zerror!("KClass has no qualified name (anonymous/local class not supported)")); + return Err(zerror!( + "KClass has no qualified name (anonymous/local class not supported)" + )); } let qualified_name: std::string::String = env @@ -130,9 +141,9 @@ fn decode_ktype(env: &mut JNIEnv, ktype: JObject) -> ZResult { "kotlin.UShort" => Ok(KotlinType::UShort), "kotlin.UInt" => Ok(KotlinType::UInt), "kotlin.ULong" => Ok(KotlinType::ULong), - "kotlin.collections.List" => { - Ok(KotlinType::List(Box::new(decode_ktype_arg(env, &ktype, 0)?))) - } + "kotlin.collections.List" => Ok(KotlinType::List(Box::new(decode_ktype_arg( + env, &ktype, 0, + )?))), "kotlin.collections.Map" => { let key = decode_ktype_arg(env, &ktype, 0)?; let val = decode_ktype_arg(env, &ktype, 1)?; @@ -147,7 +158,11 @@ fn decode_ktype(env: &mut JNIEnv, ktype: JObject) -> ZResult { let first = decode_ktype_arg(env, &ktype, 0)?; let second = decode_ktype_arg(env, &ktype, 1)?; let third = decode_ktype_arg(env, &ktype, 2)?; - Ok(KotlinType::Triple(Box::new(first), Box::new(second), Box::new(third))) + Ok(KotlinType::Triple( + Box::new(first), + Box::new(second), + Box::new(third), + )) } _ => Err(zerror!("Unsupported Kotlin type: {}", qualified_name)), } @@ -246,7 +261,8 @@ fn serialize( serializer.serialize(s); } KotlinType::ByteArray => { - let bytes = decode_byte_array(env, JByteArray::from(any)).map_err(|err| zerror!(err))?; + let bytes = + decode_byte_array(env, JByteArray::from(any)).map_err(|err| zerror!(err))?; serializer.serialize(bytes); } KotlinType::UByte => { @@ -374,49 +390,63 @@ fn deserialize( ) -> ZResult { match ktype { KotlinType::Boolean => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Boolean", "(Z)V", &[JValue::Bool(v as u8)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::Byte => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Byte", "(B)V", &[JValue::Byte(v)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::Short => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Short", "(S)V", &[JValue::Short(v)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::Int => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Integer", "(I)V", &[JValue::Int(v)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::Long => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Long", "(J)V", &[JValue::Long(v)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::Float => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Float", "(F)V", &[JValue::Float(v)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::Double => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("java/lang/Double", "(D)V", &[JValue::Double(v)]) .map_err(|err| zerror!(err))?; @@ -439,28 +469,36 @@ fn deserialize( Ok(jbytes.into_raw()) } KotlinType::UByte => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("kotlin/UByte", "(B)V", &[JValue::Byte(v as i8)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::UShort => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("kotlin/UShort", "(S)V", &[JValue::Short(v as i16)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::UInt => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("kotlin/UInt", "(I)V", &[JValue::Int(v as i32)]) .map_err(|err| zerror!(err))?; Ok(obj.as_raw()) } KotlinType::ULong => { - let v = deserializer.deserialize::().map_err(|err| zerror!(err))?; + let v = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; let obj = env .new_object("kotlin/ULong", "(J)V", &[JValue::Long(v as i64)]) .map_err(|err| zerror!(err))?;