feat(java): poll, stop, activeBuffers, general fixes and improvements

This commit is contained in:
zaaarf 2024-09-17 02:40:03 +02:00
parent aaf45e3f8c
commit d87b0923d9
No known key found for this signature in database
GPG key ID: C91CFF9E2262BBA1
12 changed files with 406 additions and 270 deletions

View file

@ -44,9 +44,28 @@ public class BufferController {
callback(this.ptr, cb); callback(this.ptr, cb);
} }
private static native void clear_callback(long self);
public void clearCallback() {
clear_callback(this.ptr);
}
private static native void poll(long self);
public void poll() {
poll(this.ptr);
}
private static native boolean stop(long self);
public boolean stop() {
return stop(this.ptr);
}
private static native void free(long self); private static native void free(long self);
@Override @Override
protected void finalize() { protected void finalize() {
free(this.ptr); free(this.ptr);
} }
static {
Extensions.loadLibraryIfNotPresent();
}
} }

View file

@ -1,11 +1,9 @@
package mp.code; package mp.code;
import cz.adamh.utils.NativeUtils;
import mp.code.data.User; import mp.code.data.User;
import mp.code.exceptions.ConnectionException; import mp.code.exceptions.ConnectionException;
import mp.code.exceptions.ConnectionRemoteException; import mp.code.exceptions.ConnectionRemoteException;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
public class Client { public class Client {
@ -74,16 +72,8 @@ public class Client {
free(this.ptr); free(this.ptr);
} }
private static native void setup_tracing(String path);
static { static {
try { Extensions.loadLibraryIfNotPresent();
if(System.getProperty("os.name").startsWith("Windows"))
NativeUtils.loadLibraryFromJar("/natives/codemp.dll");
else NativeUtils.loadLibraryFromJar("/natives/libcodemp.so");
setup_tracing(System.getenv().get("CODEMP_TRACING_LOG"));
} catch(IOException e) {
throw new RuntimeException(e);
}
} }
} }

View file

@ -33,9 +33,28 @@ public class CursorController {
callback(this.ptr, cb); callback(this.ptr, cb);
} }
private static native void clear_callback(long self);
public void clearCallback() {
clear_callback(this.ptr);
}
private static native void poll(long self);
public void poll() {
poll(this.ptr);
}
private static native boolean stop(long self);
public boolean stop() {
return stop(this.ptr);
}
private static native void free(long self); private static native void free(long self);
@Override @Override
protected void finalize() { protected void finalize() {
free(this.ptr); free(this.ptr);
} }
static {
Extensions.loadLibraryIfNotPresent();
}
} }

View file

@ -1,6 +1,22 @@
package mp.code; package mp.code;
import java.io.IOException;
public class Extensions { public class Extensions {
private static boolean loaded = false;
static synchronized void loadLibraryIfNotPresent() {
if(loaded) return;
try {
String filename = System.getProperty("os.name").startsWith("Windows")
? "/natives/codemp.dll"
: "/natives/libcodemp.so";
cz.adamh.utils.NativeUtils.loadLibraryFromJar(filename);
loaded = true;
} catch(IOException e) {
throw new RuntimeException(e);
}
}
/** /**
* Hashes the given {@link String} using CodeMP's hashing algorithm (xxh3). * Hashes the given {@link String} using CodeMP's hashing algorithm (xxh3).
* @param input the string to hash * @param input the string to hash
@ -14,4 +30,15 @@ public class Extensions {
* spawn a separate one * spawn a separate one
*/ */
public static native void drive(boolean block); public static native void drive(boolean block);
/**
* Configures the tracing subscriber for the native logs.
* @param path where to output this, null to use stdout
* @param debug whether to run it in debug mode
*/
public static native void setupTracing(String path, boolean debug);
static {
Extensions.loadLibraryIfNotPresent();
}
} }

View file

@ -35,9 +35,14 @@ public class Workspace {
return get_file_tree(this.ptr, filter.orElse(null), strict); return get_file_tree(this.ptr, filter.orElse(null), strict);
} }
private static native void create_buffer(String path) throws ConnectionRemoteException; private static native String[] active_buffers(long self);
public String[] activeBuffers() {
return active_buffers(this.ptr);
}
private static native void create_buffer(long self, String path) throws ConnectionRemoteException;
public void createBuffer(String path) throws ConnectionRemoteException { public void createBuffer(String path) throws ConnectionRemoteException {
create_buffer(path); create_buffer(this.ptr, path);
} }
private static native BufferController attach_to_buffer(long self, String path) throws ConnectionException; private static native BufferController attach_to_buffer(long self, String path) throws ConnectionException;
@ -75,17 +80,16 @@ public class Workspace {
return event(this.ptr); return event(this.ptr);
} }
private static native BufferController select_buffer(long self, long timeout) throws ControllerException;
public Optional<BufferController> selectBuffer(long timeout) throws ControllerException {
return Optional.ofNullable(select_buffer(this.ptr, timeout));
}
private static native void free(long self); private static native void free(long self);
@Override @Override
protected void finalize() { protected void finalize() {
free(this.ptr); free(this.ptr);
} }
static {
Extensions.loadLibraryIfNotPresent();
}
public static class Event { public static class Event {
private final Type type; private final Type type;
private final String argument; private final String argument;
@ -113,7 +117,7 @@ public class Workspace {
} else return Optional.empty(); } else return Optional.empty();
} }
private enum Type { enum Type {
USER_JOIN, USER_JOIN,
USER_LEAVE, USER_LEAVE,
FILE_TREE_UPDATED FILE_TREE_UPDATED

View file

@ -14,4 +14,18 @@ public class TextChange {
this.content = content; this.content = content;
this.hash = hash; this.hash = hash;
} }
public boolean isDelete() {
return this.start != this.end;
}
public boolean isInsert() {
return !this.content.isEmpty();
}
public boolean isEmpty() {
return !this.isDelete() && !this.isInsert();
}
//TODO: apply()
} }

View file

@ -1,8 +1,8 @@
use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, JNIEnv}; use jni::{objects::{JClass, JObject}, sys::{jboolean, jlong, jobject, jstring}, JNIEnv};
use crate::api::Controller; use crate::api::Controller;
use super::JExceptable; use super::{JExceptable, JObjectify};
/// Gets the name of the buffer. /// Gets the name of the buffer.
#[no_mangle] #[no_mangle]
@ -41,8 +41,10 @@ pub extern "system" fn Java_mp_code_BufferController_try_1recv(
self_ptr: jlong, self_ptr: jlong,
) -> jobject { ) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
let change = super::tokio().block_on(controller.try_recv()).jexcept(&mut env); super::tokio().block_on(controller.try_recv())
recv_jni(&mut env, change) .jexcept(&mut env)
.map(|change| change.jobjectify(&mut env).jexcept(&mut env).as_raw())
.unwrap_or_else(std::ptr::null_mut)
} }
/// Blocks until it receives a [crate::api::TextChange]. /// Blocks until it receives a [crate::api::TextChange].
@ -53,50 +55,66 @@ pub extern "system" fn Java_mp_code_BufferController_recv(
self_ptr: jlong, self_ptr: jlong,
) -> jobject { ) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
let change = super::tokio().block_on(controller.recv()).map(Some).jexcept(&mut env); super::tokio().block_on(controller.recv())
recv_jni(&mut env, change) .jexcept(&mut env)
.jobjectify(&mut env)
.jexcept(&mut env)
.as_raw()
} }
/// Utility method to convert a [crate::api::TextChange] to its Java equivalent. /// Receive from Java, converts and sends a [crate::api::TextChange].
fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject {
match change {
None => JObject::default(),
Some(event) => {
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")
.and_then(|class| {
env.new_object(
class,
"(JJLjava/lang/String;Ljava/util/OptionalLong;)V",
&[
JValueGen::Long(jlong::from(event.start)),
JValueGen::Long(jlong::from(event.end)),
JValueGen::Object(&content),
JValueGen::Object(&hash)
]
)
}).jexcept(env)
}
}.as_raw()
}
/// Clears the callback for buffer changes.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_clear_1callback( pub extern "system" fn Java_mp_code_BufferController_send<'local>(
_env: JNIEnv, mut env: JNIEnv,
_class: JClass, _class: JClass<'local>,
self_ptr: jlong, self_ptr: jlong,
input: JObject<'local>,
) { ) {
unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) } let Ok(start) = env.get_field(&input, "start", "J")
.clear_callback(); .and_then(|sr| sr.j())
.jexcept(&mut env)
.try_into()
else {
return env.throw_new("java/lang/IllegalArgumentException", "Start index cannot be negative!")
.expect("Failed to throw exception!");
};
let Ok(end) = env.get_field(&input, "end", "J")
.and_then(|er| er.j())
.jexcept(&mut env)
.try_into()
else {
return env.throw_new("java/lang/IllegalArgumentException", "End index cannot be negative!")
.expect("Failed to throw exception!");
};
let content = env.get_field(&input, "content", "Ljava/lang/String;")
.and_then(|b| b.l())
.map(|b| b.into())
.jexcept(&mut env);
let content = env.get_string(&content)
.map(|b| b.into())
.jexcept(&mut env);
let hash = env.get_field(&input, "hash", "Ljava/util/OptionalLong;")
.and_then(|hash| hash.l())
.and_then(|hash| {
if env.call_method(&hash, "isPresent", "()Z", &[]).and_then(|r| r.z()).jexcept(&mut env) {
env.call_method(&hash, "getAsLong", "()J", &[])
.and_then(|r| r.j())
.map(Some)
} else {
Ok(None)
}
}).jexcept(&mut env);
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
super::tokio().block_on(controller.send(crate::api::TextChange {
start,
end,
content,
hash,
})).jexcept(&mut env);
} }
/// Registers a callback for buffer changes. /// Registers a callback for buffer changes.
@ -139,59 +157,46 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>(
}); });
} }
/// Receive from Java, converts and sends a [crate::api::TextChange]. /// Clears the callback for buffer changes.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_send<'local>( pub extern "system" fn Java_mp_code_BufferController_clear_1callback(
mut env: JNIEnv, _env: JNIEnv,
_class: JClass<'local>, _class: JClass,
self_ptr: jlong, self_ptr: jlong,
input: JObject<'local>,
) { ) {
let Ok(start) = env.get_field(&input, "start", "J") unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }
.and_then(|sr| sr.j()) .clear_callback();
.jexcept(&mut env)
.try_into()
else {
env.throw_new("java/lang/IllegalArgumentException", "Start index cannot be negative!")
.expect("Failed to throw exception!");
return;
};
let Ok(end) = env.get_field(&input, "end", "J")
.and_then(|er| er.j())
.jexcept(&mut env)
.try_into()
else {
env.throw_new("java/lang/IllegalArgumentException", "End index cannot be negative!")
.expect("Failed to throw exception!");
return;
};
let content = env.get_field(&input, "content", "Ljava/lang/String;")
.and_then(|b| b.l())
.map(|b| b.into())
.jexcept(&mut env);
let content = env.get_string(&content)
.map(|b| b.into())
.jexcept(&mut env);
let hash = env.get_field(&input, "hash", "Ljava/util/OptionalLong;")
.and_then(|hash| hash.l())
.and_then(|hash| {
if env.call_method(&hash, "isPresent", "()Z", &[]).and_then(|r| r.z()).jexcept(&mut env) {
env.call_method(&hash, "getAsLong", "()J", &[])
.and_then(|r| r.j())
.map(Some)
} else {
Ok(None)
} }
}).jexcept(&mut env);
/// Blocks until there is a new value available.
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_poll(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
super::tokio().block_on(controller.send(crate::api::TextChange { super::tokio().block_on(controller.poll())
start, .jexcept(&mut env);
end, }
content,
hash, /// Stops the controller.
})).jexcept(&mut env); #[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_stop(
_env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) -> jboolean {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
controller.stop() as jboolean
}
/// 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::buffer::Controller) };
} }

View file

@ -268,22 +268,6 @@ pub extern "system" fn Java_mp_code_Client_refresh<'local>(
.jexcept(&mut env); .jexcept(&mut env);
} }
/// Set up the tracing subscriber.
#[no_mangle]
pub extern "system" fn Java_mp_code_Client_setup_1tracing<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
path: JString<'local>
) {
super::setup_logger(
true,
Some(path)
.filter(|p| !p.is_null())
.map(|p| env.get_string(&p).map(|s| s.into())
.jexcept(&mut env))
);
}
/// Called by the Java GC to drop a [Client]. /// Called by the Java GC to drop a [Client].
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, input: jlong) { pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, input: jlong) {

View file

@ -1,7 +1,7 @@
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv}; use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject}, JNIEnv};
use crate::api::Controller; use crate::api::Controller;
use super::JExceptable; use super::{JExceptable, JObjectify};
/// Try to fetch a [crate::api::Cursor], or returns null if there's nothing. /// Try to fetch a [crate::api::Cursor], or returns null if there's nothing.
#[no_mangle] #[no_mangle]
@ -11,8 +11,10 @@ pub extern "system" fn Java_mp_code_CursorController_try_1recv(
self_ptr: jlong, self_ptr: jlong,
) -> jobject { ) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
let cursor = super::tokio().block_on(controller.try_recv()).jexcept(&mut env); super::tokio().block_on(controller.try_recv())
jni_recv(&mut env, cursor) .jexcept(&mut env)
.map(|change| change.jobjectify(&mut env).jexcept(&mut env).as_raw())
.unwrap_or_else(std::ptr::null_mut)
} }
/// Block until it receives a [crate::api::Cursor]. /// Block until it receives a [crate::api::Cursor].
@ -23,88 +25,11 @@ pub extern "system" fn Java_mp_code_CursorController_recv(
self_ptr: jlong, self_ptr: jlong,
) -> jobject { ) -> jobject {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
let cursor = super::tokio().block_on(controller.recv()).map(Some).jexcept(&mut env); super::tokio().block_on(controller.recv())
jni_recv(&mut env, cursor) .jexcept(&mut env)
} .jobjectify(&mut env)
.jexcept(&mut env)
/// Utility method to convert a [crate::api::Cursor] to its Java equivalent. .as_raw()
fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
match cursor {
None => JObject::default(),
Some(event) => {
env.find_class("mp/code/data/Cursor")
.and_then(|class| {
let buffer = env.new_string(&event.buffer).jexcept(env);
let user = event.user
.map(|uuid| uuid.to_string())
.map(|user| env.new_string(user).jexcept(env))
.unwrap_or_default();
env.new_object(
class,
"(IIIILjava/lang/String;Ljava/lang/String;)V",
&[
JValueGen::Int(event.start.0),
JValueGen::Int(event.start.1),
JValueGen::Int(event.end.0),
JValueGen::Int(event.end.1),
JValueGen::Object(&buffer),
JValueGen::Object(&user)
]
)
}).jexcept(env)
}
}.as_raw()
}
/// Clears the callback for cursor changes.
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_clear_1callback(
_env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) {
unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }
.clear_callback();
}
/// Registers a callback for cursor changes.
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
cb: JObject<'local>,
) {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
let Ok(cb_ref) = env.new_global_ref(cb) else {
env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
.expect("Failed to throw exception!");
return;
};
controller.callback(move |controller: crate::cursor::Controller| {
let jvm = super::jvm();
let mut env = jvm.attach_current_thread_permanently()
.expect("failed attaching to main JVM thread");
if let Err(e) = env.with_local_frame(5, |env| {
use crate::ffi::java::JObjectify;
let jcontroller = controller.jobjectify(env)?;
let sig = format!("(L{};)V", "java/lang/Object");
if let Err(e) = env.call_method(
&cb_ref,
"invoke",
&sig,
&[jni::objects::JValueGen::Object(&jcontroller)]
) {
tracing::error!("error invoking callback: {e:?}");
};
Ok::<(), jni::errors::Error>(())
}) {
tracing::error!("error invoking callback: {e}");
let _ = env.exception_describe();
}
});
} }
/// Receive from Java, converts and sends a [crate::api::Cursor]. /// Receive from Java, converts and sends a [crate::api::Cursor].
@ -158,6 +83,80 @@ pub extern "system" fn Java_mp_code_CursorController_send<'local>(
})).jexcept(&mut env); })).jexcept(&mut env);
} }
/// Registers a callback for cursor changes.
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
cb: JObject<'local>,
) {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
let Ok(cb_ref) = env.new_global_ref(cb) else {
env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
.expect("Failed to throw exception!");
return;
};
controller.callback(move |controller: crate::cursor::Controller| {
let jvm = super::jvm();
let mut env = jvm.attach_current_thread_permanently()
.expect("failed attaching to main JVM thread");
if let Err(e) = env.with_local_frame(5, |env| {
use crate::ffi::java::JObjectify;
let jcontroller = controller.jobjectify(env)?;
let sig = format!("(L{};)V", "java/lang/Object");
if let Err(e) = env.call_method(
&cb_ref,
"invoke",
&sig,
&[jni::objects::JValueGen::Object(&jcontroller)]
) {
tracing::error!("error invoking callback: {e:?}");
};
Ok::<(), jni::errors::Error>(())
}) {
tracing::error!("error invoking callback: {e}");
let _ = env.exception_describe();
}
});
}
/// Clears the callback for cursor changes.
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_clear_1callback(
_env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) {
unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }
.clear_callback();
}
/// Blocks until there is a new value available.
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_poll(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
super::tokio().block_on(controller.poll())
.jexcept(&mut env);
}
/// Stops the controller.
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_stop(
_env: JNIEnv,
_class: JClass,
self_ptr: jlong,
) -> jboolean {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
controller.stop() as jboolean
}
/// Called by the Java GC to drop a [crate::cursor::Controller]. /// Called by the Java GC to drop a [crate::cursor::Controller].
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_free( pub extern "system" fn Java_mp_code_CursorController_free(

View file

@ -32,3 +32,20 @@ pub extern "system" fn Java_mp_code_Extensions_drive(
} }
} }
/// Set up the tracing subscriber.
#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_mp_code_Extensions_setupTracing<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
path: JString<'local>,
debug: jboolean
) {
super::setup_logger(
debug != 0,
Some(path)
.filter(|p| !p.is_null())
.map(|p| env.get_string(&p).map(|s| s.into())
.jexcept(&mut env))
);
}

View file

@ -87,7 +87,7 @@ impl<T> JExceptable<T> for crate::errors::RemoteResult<T> where T: Default {
fn jexcept(self, env: &mut jni::JNIEnv) -> T { fn jexcept(self, env: &mut jni::JNIEnv) -> T {
if let Err(err) = &self { if let Err(err) = &self {
let msg = format!("{err}"); let msg = format!("{err}");
env.throw_new("mp/code/exceptions/connection/RemoteException", msg).jexcept(env); env.throw_new("mp/code/exceptions/ConnectionRemoteException", msg).jexcept(env);
} }
self.unwrap_or_default() self.unwrap_or_default()
} }
@ -204,3 +204,59 @@ impl<'local> JObjectify<'local> for crate::buffer::Controller {
) )
} }
} }
impl<'local> JObjectify<'local> for crate::api::TextChange {
type Error = jni::errors::Error;
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
let content = env.new_string(self.content)?;
let hash = env.find_class("java/util/OptionalLong").and_then(|class| {
if let Some(h) = self.hash {
env.call_static_method(class, "of", "(J)Ljava/util/OptionalLong;", &[jni::objects::JValueGen::Long(h)])
} else {
env.call_static_method(class, "empty", "()Ljava/util/OptionalLong;", &[])
}
}).and_then(|o| o.l())?;
env.find_class("mp/code/data/TextChange").and_then(|class| {
env.new_object(
class,
"(JJLjava/lang/String;Ljava/util/OptionalLong;)V",
&[
jni::objects::JValueGen::Long(jni::sys::jlong::from(self.start)),
jni::objects::JValueGen::Long(jni::sys::jlong::from(self.end)),
jni::objects::JValueGen::Object(&content),
jni::objects::JValueGen::Object(&hash)
]
)
})
}
}
impl<'local> JObjectify<'local> for crate::api::Cursor {
type Error = jni::errors::Error;
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
env.find_class("mp/code/data/Cursor").and_then(|class| {
let buffer = env.new_string(&self.buffer)?;
let user = if let Some(user) = self.user {
env.new_string(user)?.into()
} else {
jni::objects::JObject::null()
};
env.new_object(
class,
"(IIIILjava/lang/String;Ljava/lang/String;)V",
&[
jni::objects::JValueGen::Int(self.start.0),
jni::objects::JValueGen::Int(self.start.1),
jni::objects::JValueGen::Int(self.end.0),
jni::objects::JValueGen::Int(self.end.1),
jni::objects::JValueGen::Object(&buffer),
jni::objects::JValueGen::Object(&user)
]
)
})
}
}

View file

@ -1,4 +1,4 @@
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv}; use jni::{objects::{JClass, JObject, JObjectArray, JString, JValueGen}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv};
use crate::Workspace; use crate::Workspace;
use super::{JExceptable, JObjectify}; use super::{JExceptable, JObjectify};
@ -47,22 +47,6 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>(
.as_raw() .as_raw()
} }
/// Create a new buffer.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) {
let ws = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let path = unsafe { env.get_string_unchecked(&input) }
.map(|path| path.to_string_lossy().to_string())
.jexcept(&mut env);
super::tokio().block_on(ws.create(&path))
.jexcept(&mut env);
}
/// Get the filetree. /// Get the filetree.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree( pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree(
@ -95,6 +79,47 @@ pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree(
}).jexcept(&mut env).as_raw() }).jexcept(&mut env).as_raw()
} }
/// Gets a list of the active buffers.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_active_1buffers(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong
) -> jobjectArray {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let active_buffer_list = workspace.buffer_list();
env.find_class("java/lang/String")
.and_then(|class| env.new_object_array(active_buffer_list.len() as i32, class, JObject::null()))
.inspect(|arr| {
for (idx, path) in active_buffer_list.iter().enumerate() {
env.new_string(path)
.and_then(|path| env.set_object_array_element(arr, idx as i32, path))
.jexcept(&mut env)
}
}).jexcept(&mut env).as_raw()
}
/// Create a new buffer.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
input: JString<'local>
) {
if input.is_null() {
return env.throw_new("java/lang/NullPointerException", "Buffer name cannot be null!")
.expect("Failed to throw exception!");
}
let ws = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let path = unsafe { env.get_string_unchecked(&input) }
.map(|path| path.to_string_lossy().to_string())
.jexcept(&mut env);
super::tokio().block_on(ws.create(&path))
.jexcept(&mut env);
}
/// Attach to a buffer and return a pointer to its [crate::buffer::Controller]. /// Attach to a buffer and return a pointer to its [crate::buffer::Controller].
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>( pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>(
@ -175,6 +200,10 @@ pub extern "system" fn Java_mp_code_Workspace_list_1buffer_1users<'local>(
let users = super::tokio().block_on(workspace.list_buffer_users(&buffer)) let users = super::tokio().block_on(workspace.list_buffer_users(&buffer))
.jexcept(&mut env); .jexcept(&mut env);
if env.exception_check().unwrap_or(false) { // prevent illegal state
return std::ptr::null_mut();
}
env.find_class("java/util/UUID") env.find_class("java/util/UUID")
.and_then(|class| env.new_object_array(users.len() as i32, &class, JObject::null())) .and_then(|class| env.new_object_array(users.len() as i32, &class, JObject::null()))
.inspect(|arr| { .inspect(|arr| {
@ -212,19 +241,21 @@ pub extern "system" fn Java_mp_code_Workspace_event(
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
super::tokio().block_on(workspace.event()) super::tokio().block_on(workspace.event())
.map(|event| { .map(|event| {
let (name, arg) = match event { let (ordinal, arg) = match event {
crate::api::Event::FileTreeUpdated(arg) => ("FILE_TREE_UPDATED", env.new_string(arg).unwrap_or_default()), crate::api::Event::UserJoin(arg) => (0, env.new_string(arg).unwrap_or_default()),
crate::api::Event::UserJoin(arg) => ("USER_JOIN", env.new_string(arg).unwrap_or_default()), crate::api::Event::UserLeave(arg) => (1, env.new_string(arg).unwrap_or_default()),
crate::api::Event::UserLeave(arg) => ("USER_LEAVE", env.new_string(arg).unwrap_or_default()), crate::api::Event::FileTreeUpdated(arg) => (2, env.new_string(arg).unwrap_or_default()),
}; };
let event_type = env.find_class("mp/code/Workspace$Event$Type") let event_type = env.find_class("mp/code/Workspace$Event$Type")
.and_then(|class| env.get_static_field(class, name, "Lmp/code/Workspace/Event/Type;")) .and_then(|class| env.call_method(class, "getEnumConstants", "()[Ljava/lang/Object;", &[]))
.and_then(|f| f.l()) .and_then(|enums| enums.l().map(|e| e.into()))
.and_then(|enums: JObjectArray| env.get_object_array_element(enums, ordinal))
.jexcept(&mut env); .jexcept(&mut env);
env.find_class("mp/code/Workspace$Event").and_then(|class| env.find_class("mp/code/Workspace$Event").and_then(|class|
env.new_object( env.new_object(
class, class,
"(Lmp/code/Workspace/Event/Type;Ljava/lang/String;)V", "(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
&[ &[
JValueGen::Object(&event_type), JValueGen::Object(&event_type),
JValueGen::Object(&arg) JValueGen::Object(&arg)
@ -234,35 +265,6 @@ pub extern "system" fn Java_mp_code_Workspace_event(
}).jexcept(&mut env).as_raw() }).jexcept(&mut env).as_raw()
} }
/// Poll a list of buffers, returning the first ready one.
#[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_select_1buffer(
mut env: JNIEnv,
_class: JClass,
self_ptr: jlong,
timeout: jlong
) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let buffers = workspace.buffer_list();
let mut controllers = Vec::default();
for buffer in buffers {
if let Some(controller) = workspace.buffer_by_name(&buffer) {
controllers.push(controller);
}
}
super::tokio().block_on(crate::ext::select_buffer(
&controllers,
Some(std::time::Duration::from_millis(timeout as u64)),
super::tokio(),
)).jexcept(&mut env)
.map(|buf| {
env.find_class("mp/code/BufferController").and_then(|class|
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)])
).jexcept(&mut env)
}).unwrap_or_default().as_raw()
}
/// Called by the Java GC to drop a [Workspace]. /// Called by the Java GC to drop a [Workspace].
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_free(_env: JNIEnv, _class: JClass, input: jlong) { pub extern "system" fn Java_mp_code_Workspace_free(_env: JNIEnv, _class: JClass, input: jlong) {