feat(java): expose hash function, use OptionalLong in TextChange

This commit is contained in:
zaaarf 2024-08-16 01:21:21 +02:00
parent 8b704fa668
commit 0d3af40eb0
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
5 changed files with 42 additions and 62 deletions

10
dist/java/src/mp/code/Utils.java vendored Normal file
View file

@ -0,0 +1,10 @@
package mp.code;
public class Utils {
/**
* Hashes the given {@link String} using CodeMP's hashing algorithm (xxh3).
* @param input the string to hash
* @return the hash
*/
public static native long hash(String input);
}

View file

@ -1,21 +1,17 @@
package mp.code.data; package mp.code.data;
import java.util.OptionalLong;
public class TextChange { public class TextChange {
public final long start; public final long start;
public final long end; public final long end;
public final String content; public final String content;
private final long hash; // xxh3 hash public final OptionalLong hash; // xxh3 hash
public TextChange(long start, long end, String content, long hash) { public TextChange(long start, long end, String content, OptionalLong hash) {
this.start = start; this.start = start;
this.end = end; this.end = end;
this.content = content; this.content = content;
this.hash = hash; this.hash = hash;
} }
private static native long hash(String content);
public boolean hashMatches(String content) {
// 0 is Rust default value and a very unlikely hash
return hash == 0L || this.hash == hash(content);
}
} }

View file

@ -63,71 +63,27 @@ fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject
None => JObject::default(), None => JObject::default(),
Some(event) => { Some(event) => {
let content = env.new_string(event.content).jexcept(env); let content = env.new_string(event.content).jexcept(env);
let hash = env.find_class("java/util/OptionalLong").and_then(|class| {
if let Some(h) = event.hash {
env.call_static_method(class, "of", "(J)Ljava/util/OptionalLong;", &[JValueGen::Long(h)])
} else {
env.call_static_method(class, "empty", "()Ljava/util/OptionalLong;", &[])
}
}).and_then(|o| o.l()).jexcept(env);
env.find_class("mp/code/data/TextChange") env.find_class("mp/code/data/TextChange")
.and_then(|class| { .and_then(|class| {
env.new_object( env.new_object(
class, class,
"(JJLjava/lang/String;J)V", "(JJLjava/lang/String;Ljava/util/OptionalLong;)V",
&[ &[
JValueGen::Long(jlong::from(event.start)), JValueGen::Long(jlong::from(event.start)),
JValueGen::Long(jlong::from(event.end)), JValueGen::Long(jlong::from(event.end)),
JValueGen::Object(&content), JValueGen::Object(&content),
JValueGen::Long(event.hash.unwrap_or_default()) JValueGen::Object(&hash)
] ]
) )
}).jexcept(env) }).jexcept(env)
} }
}.as_raw() }.as_raw()
} }
/// Receives from Java, converts and sends a [crate::api::TextChange].
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_send<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
input: JObject<'local>
) {
let start = env.get_field(&input, "start", "J").and_then(|s| s.j()).jexcept(&mut env);
let end = env.get_field(&input, "end", "J").and_then(|e| e.j()).jexcept(&mut env);
let content = env.get_field(&input, "content", "Ljava/lang/String;")
.and_then(|c| c.l())
.map(|c| c.into())
.jexcept(&mut env);
let content = unsafe { env.get_string_unchecked(&content) }
.map(|c| c.to_string_lossy().to_string())
.jexcept(&mut env);
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
RT.block_on(controller.send(crate::api::TextChange {
start: start as u32,
end: end as u32,
content,
hash: None
})).jexcept(&mut env);
}
/// Called by the Java GC to drop a [crate::buffer::Controller].
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_free(
_env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) {
let _ = unsafe { Box::from_raw(self_ptr as *mut crate::cursor::Controller) };
}
/// Calculates the XXH3 hash for a given String.
#[no_mangle]
pub extern "system" fn Java_mp_code_data_TextChange_hash<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
content: JString<'local>,
) -> jlong {
let content: String = env.get_string(&content)
.map(|s| s.into())
.jexcept(&mut env);
let hash = crate::ext::hash(content.as_bytes());
i64::from_ne_bytes(hash.to_ne_bytes())
}

View file

@ -2,6 +2,7 @@ pub mod client;
pub mod workspace; pub mod workspace;
pub mod cursor; pub mod cursor;
pub mod buffer; pub mod buffer;
pub mod utils;
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub(crate) static ref RT: tokio::runtime::Runtime = tokio::runtime::Runtime::new().expect("could not create tokio runtime"); pub(crate) static ref RT: tokio::runtime::Runtime = tokio::runtime::Runtime::new().expect("could not create tokio runtime");

17
src/ffi/java/utils.rs Normal file
View file

@ -0,0 +1,17 @@
use jni::{objects::{JClass, JString}, sys::jlong, JNIEnv};
use super::JExceptable;
/// Calculates the XXH3 hash for a given String.
#[no_mangle]
pub extern "system" fn Java_mp_code_Utils_hash<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
content: JString<'local>,
) -> jlong {
let content: String = env.get_string(&content)
.map(|s| s.into())
.jexcept(&mut env);
let hash = crate::ext::hash(content.as_bytes());
i64::from_ne_bytes(hash.to_ne_bytes())
}