Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KeyExpr alignment #211

Merged
merged 4 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 120 additions & 6 deletions zenoh-jni/src/key_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ use std::ops::Deref;
use std::sync::Arc;

use jni::objects::JClass;
use jni::sys::{jboolean, jstring};
use jni::sys::{jboolean, jint, jstring};
use jni::{objects::JString, JNIEnv};
use zenoh::key_expr::KeyExpr;

use crate::errors::Error;
use crate::errors::Result;
use crate::utils::decode_string;
use crate::{jni_error, key_expr_error, throw_exception};
use crate::{jni_error, key_expr_error, session_error, throw_exception};

/// Validates the provided `key_expr` to be a valid key expression, returning it back
/// in case of success or throwing an exception in case of failure.
Expand Down Expand Up @@ -79,9 +79,9 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_autocanonizeViaJNI
///
/// # Params:
/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr.
/// - `key_expr_ptr_1`: String representation of the key expression 1.
/// - `key_expr_str_1`: String representation of the key expression 1.
/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr.
/// - `key_expr_ptr_2`: String representation of the key expression 2.
/// - `key_expr_str_2`: String representation of the key expression 2.
///
/// # Safety
/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing
Expand Down Expand Up @@ -114,9 +114,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_intersectsV
///
/// # Params:
/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr.
/// - `key_expr_ptr_1`: String representation of the key expression 1.
/// - `key_expr_str_1`: String representation of the key expression 1.
/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr.
/// - `key_expr_ptr_2`: String representation of the key expression 2.
/// - `key_expr_str_2`: String representation of the key expression 2.
///
/// # Safety
/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing
Expand Down Expand Up @@ -145,6 +145,120 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_includesVia
})
}

/// Returns the integer representation of the intersection level of the key expression 1 and key expression 2,
/// from the perspective of key expression 1.
///
/// # Params:
/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr.
/// - `key_expr_str_1`: String representation of the key expression 1.
/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr.
/// - `key_expr_str_2`: String representation of the key expression 2.
///
/// # Safety
/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing
/// key expressions that were declared from a session (in that case the key expression has a pointer associated).
/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers
/// remain valid after the call to this function.
///
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_relationToViaJNI(
mut env: JNIEnv,
_: JClass,
key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>,
key_expr_str_1: JString,
key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>,
key_expr_str_2: JString,
) -> jint {
|| -> Result<jint> {
let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?;
let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?;
Ok(key_expr_1.relation_to(&key_expr_2) as jint)
}()
.unwrap_or_else(|err| {
throw_exception!(env, err);
-1 as jint
})
}

/// Joins key expression 1 with key expression 2, where key_expr_2 is a string. Returns the string representation
/// of the result, or throws an exception in case of failure.
///
/// # Params:
/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr.
/// - `key_expr_ptr_1`: String representation of the key expression 1.
/// - `key_expr_2`: String representation of the key expression 2.
///
/// # Safety
/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing
/// key expressions that were declared from a session (in that case the key expression has a pointer associated).
/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers
/// remain valid after the call to this function.
///
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_joinViaJNI(
mut env: JNIEnv,
_class: JClass,
key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>,
key_expr_str_1: JString,
key_expr_2: JString,
) -> jstring {
|| -> Result<jstring> {
let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?;
let key_expr_2_str = decode_string(&mut env, &key_expr_2)?;
let result = key_expr_1
.join(key_expr_2_str.as_str())
.map_err(|err| session_error!(err))?;
env.new_string(result.to_string())
.map(|kexp| kexp.as_raw())
.map_err(|err| jni_error!(err))
}()
.unwrap_or_else(|err| {
throw_exception!(env, err);
JString::default().as_raw()
})
}

/// Concats key_expr_1 with key_expr_2, where key_expr_2 is a string. Returns the string representation
/// of the result, or throws an exception in case of failure.
///
/// # Params:
/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr.
/// - `key_expr_ptr_1`: String representation of the key expression 1.
/// - `key_expr_2`: String representation of the key expression 2.
///
/// # Safety
/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing
/// key expressions that were declared from a session (in that case the key expression has a pointer associated).
/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers
/// remain valid after the call to this function.
///
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_concatViaJNI(
mut env: JNIEnv,
_class: JClass,
key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>,
key_expr_str_1: JString,
key_expr_2: JString,
) -> jstring {
|| -> Result<jstring> {
let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?;
let key_expr_2_str = decode_string(&mut env, &key_expr_2)?;
let result = key_expr_1
.concat(key_expr_2_str.as_str())
.map_err(|err| session_error!(err))?;
env.new_string(result.to_string())
.map(|kexp| kexp.as_raw())
.map_err(|err| jni_error!(err))
}()
.unwrap_or_else(|err| {
throw_exception!(env, err);
JString::default().as_raw()
})
}

/// Frees a declared key expression.
///
/// # Parameters
Expand Down
28 changes: 28 additions & 0 deletions zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package io.zenoh.jni

import io.zenoh.ZenohLoad
import io.zenoh.keyexpr.KeyExpr
import io.zenoh.keyexpr.SetIntersectionLevel

internal class JNIKeyExpr(internal val ptr: Long) {

Expand Down Expand Up @@ -46,6 +47,24 @@ internal class JNIKeyExpr(internal val ptr: Long) {
keyExprB.keyExpr
)

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 joinViaJNI(keyExpr: KeyExpr, other: String): Result<KeyExpr> = runCatching {
KeyExpr(joinViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other))
}

fun concatViaJNI(keyExpr: KeyExpr, other: String): Result<KeyExpr> = runCatching {
KeyExpr(concatViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other))
}

@Throws(Exception::class)
private external fun tryFromViaJNI(keyExpr: String): String

Expand All @@ -57,6 +76,15 @@ internal class JNIKeyExpr(internal val ptr: Long) {

@Throws(Exception::class)
private external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean

@Throws(Exception::class)
private external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int

@Throws(Exception::class)
private external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String

@Throws(Exception::class)
private external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String
}

fun close() {
Expand Down
42 changes: 26 additions & 16 deletions zenoh-kotlin/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package io.zenoh.keyexpr
import io.zenoh.Session
import io.zenoh.SessionDeclaration
import io.zenoh.jni.JNIKeyExpr
import io.zenoh.selector.Selector

/**
* # Address space
Expand Down Expand Up @@ -112,40 +111,51 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn
}

/**
* Undeclare the key expression if it was previously declared on the specified [session].
*
* @param session The session from which the key expression was previously declared.
* @return A [Result] with the operation status.
* Returns the relation between 'this' and other from 'this''s point of view (SetIntersectionLevel::Includes
* signifies that self includes other). Note that this is slower than [intersects] and [includes],
* so you should favor these methods for most applications.
*/
fun undeclare(session: Session): Result<Unit> {
return session.undeclare(this)
fun relationTo(other: KeyExpr): SetIntersectionLevel {
return JNIKeyExpr.relationTo(this, other)
}

/**
* Returns true if the [KeyExpr] has still associated a native key expression allowing it to perform operations.
* Joins both sides, inserting a / in between them.
* This should be your preferred method when concatenating path segments.
*/
fun isValid(): Boolean {
return jniKeyExpr != null
fun join(other: String): Result<KeyExpr> {
return JNIKeyExpr.joinViaJNI(this, other)
}

fun intoSelector(): Selector {
return Selector(this)
/**
* Performs string concatenation and returns the result as a KeyExpr if possible.
* You should probably prefer [join] as Zenoh may then take advantage of the hierarchical separation it inserts.
*/
fun concat(other: String): Result<KeyExpr> {
return JNIKeyExpr.concatViaJNI(this, other)
}

override fun toString(): String {
return keyExpr
}

/**
* Closes the key expression. Operations performed on this key expression won't be valid anymore after this call.
* Equivalent to [undeclare]. This function is automatically called when using try with resources.
*
* @see undeclare
*/
override fun close() {
jniKeyExpr?.close()
jniKeyExpr = null
undeclare()
}

/**
* If the key expression was declared from a [Session], then [undeclare] frees the native key expression associated
* to this instance. The KeyExpr instance is downgraded into a normal KeyExpr, which still allows performing
* operations on it, but without the inner optimizations.
*/
override fun undeclare() {
close()
jniKeyExpr?.close()
jniKeyExpr = null
}

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// 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, <[email protected]>
//

package io.zenoh.keyexpr

enum class SetIntersectionLevel(internal val value: Int) {
DISJOINT(0),
INTERSECTS(1),
INCLUDES(2),
EQUALS(3);

companion object {
internal fun fromInt(value: Int) = SetIntersectionLevel.entries.first { it.value == value }
}
}
51 changes: 51 additions & 0 deletions zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package io.zenoh

import io.zenoh.exceptions.SessionException
import io.zenoh.keyexpr.KeyExpr
import io.zenoh.keyexpr.SetIntersectionLevel
import io.zenoh.keyexpr.intoKeyExpr
import kotlin.test.*

Expand Down Expand Up @@ -127,4 +128,54 @@ class KeyExprTest {
keyExpr.close()
keyExpr2.close()
}

@Test
fun `relationTo returns includes test`() {
val keyExprA = KeyExpr.tryFrom("A/**").getOrThrow()
val keyExprB = KeyExpr.tryFrom("A/B/C").getOrThrow()

assertEquals(SetIntersectionLevel.INCLUDES, keyExprA.relationTo(keyExprB))
}

@Test
fun `relationTo returns intersect test`() {
val keyExprA = KeyExpr.tryFrom("A/*/C/D").getOrThrow()
val keyExprB = KeyExpr.tryFrom("A/B/C/*").getOrThrow()

assertEquals(SetIntersectionLevel.INTERSECTS, keyExprA.relationTo(keyExprB))
}

@Test
fun `relationTo returns equals test`() {
val keyExprA = KeyExpr.tryFrom("A/B/C").getOrThrow()
val keyExprB = KeyExpr.tryFrom("A/B/C").getOrThrow()

assertEquals(SetIntersectionLevel.EQUALS, keyExprA.relationTo(keyExprB))
}

@Test
fun `relationTo returns disjoint test`() {
val keyExprA = KeyExpr.tryFrom("A/B/C").getOrThrow()
val keyExprB = KeyExpr.tryFrom("D/E/F").getOrThrow()

assertEquals(SetIntersectionLevel.DISJOINT, keyExprA.relationTo(keyExprB))
}

@Test
fun `join test`() {
val keyExprA = KeyExpr.tryFrom("A/B").getOrThrow()
val keyExprExpected = KeyExpr.tryFrom("A/B/C/D").getOrThrow()

val keyExprJoined = keyExprA.join("C/D").getOrThrow()
assertEquals(keyExprExpected, keyExprJoined)
}

@Test
fun `concat test`() {
val keyExprA = KeyExpr.tryFrom("A/B").getOrThrow()
val keyExprExpected = KeyExpr.tryFrom("A/B/C/D").getOrThrow()

val keyExprConcat = keyExprA.concat("/C/D").getOrThrow()
assertEquals(keyExprExpected, keyExprConcat)
}
}
Loading
Loading