mirror of
https://github.com/hexedtech/codemp.git
synced 2024-12-26 06:44:54 +01:00
feat(java): general cleanup and overhaul of glue code
This commit is contained in:
parent
cd11b64a96
commit
6e63468e48
15 changed files with 463 additions and 404 deletions
2
dist/java/build.gradle
vendored
2
dist/java/build.gradle
vendored
|
@ -23,6 +23,8 @@ sourceSets {
|
|||
|
||||
dependencies {
|
||||
implementation 'com.github.adamheinrich:native-utils:master-SNAPSHOT'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
|
5
dist/java/src/mp/code/BufferController.java
vendored
5
dist/java/src/mp/code/BufferController.java
vendored
|
@ -5,6 +5,7 @@ import mp.code.data.Cursor;
|
|||
import mp.code.data.TextChange;
|
||||
import mp.code.exceptions.ControllerException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BufferController {
|
||||
|
@ -36,12 +37,12 @@ public class BufferController {
|
|||
|
||||
private static native void send(long self, TextChange change) throws ControllerException;
|
||||
public void send(TextChange change) throws ControllerException {
|
||||
send(this.ptr, change);
|
||||
send(this.ptr, Objects.requireNonNull(change));
|
||||
}
|
||||
|
||||
private static native void callback(long self, Callback<BufferController> cb);
|
||||
public void callback(Callback<BufferController> cb) {
|
||||
callback(this.ptr, cb);
|
||||
callback(this.ptr, Objects.requireNonNull(cb));
|
||||
}
|
||||
|
||||
private static native void clear_callback(long self);
|
||||
|
|
21
dist/java/src/mp/code/Client.java
vendored
21
dist/java/src/mp/code/Client.java
vendored
|
@ -1,16 +1,17 @@
|
|||
package mp.code;
|
||||
|
||||
import mp.code.data.Config;
|
||||
import mp.code.data.User;
|
||||
import mp.code.exceptions.ConnectionException;
|
||||
import mp.code.exceptions.ConnectionRemoteException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Client {
|
||||
private final long ptr;
|
||||
|
||||
public static native Client connect(String username, String password) throws ConnectionException;
|
||||
public static native Client connectToServer(String username, String password, String host, int port, boolean tls) throws ConnectionException;
|
||||
public static native Client connect(Config config) throws ConnectionException;
|
||||
|
||||
Client(long ptr) {
|
||||
this.ptr = ptr;
|
||||
|
@ -23,22 +24,22 @@ public class Client {
|
|||
|
||||
private static native Workspace join_workspace(long self, String id) throws ConnectionException;
|
||||
public Workspace joinWorkspace(String id) throws ConnectionException {
|
||||
return join_workspace(this.ptr, id);
|
||||
return join_workspace(this.ptr, Objects.requireNonNull(id));
|
||||
}
|
||||
|
||||
private static native void create_workspace(long self, String id) throws ConnectionRemoteException;
|
||||
public void createWorkspace(String id) throws ConnectionRemoteException {
|
||||
create_workspace(this.ptr, id);
|
||||
create_workspace(this.ptr, Objects.requireNonNull(id));
|
||||
}
|
||||
|
||||
private static native void delete_workspace(long self, String id) throws ConnectionRemoteException;
|
||||
public void deleteWorkspace(String id) throws ConnectionRemoteException {
|
||||
delete_workspace(this.ptr, id);
|
||||
delete_workspace(this.ptr, Objects.requireNonNull(id));
|
||||
}
|
||||
|
||||
private static native void invite_to_workspace(long self, String ws, String usr) throws ConnectionRemoteException;
|
||||
public void inviteToWorkspace(String ws, String usr) throws ConnectionRemoteException {
|
||||
invite_to_workspace(this.ptr, ws, usr);
|
||||
private static native void invite_to_workspace(long self, String workspaceId, String user) throws ConnectionRemoteException;
|
||||
public void inviteToWorkspace(String workspaceId, String user) throws ConnectionRemoteException {
|
||||
invite_to_workspace(this.ptr, Objects.requireNonNull(workspaceId), Objects.requireNonNull(user));
|
||||
}
|
||||
|
||||
private static native String[] list_workspaces(long self, boolean owned, boolean invited) throws ConnectionRemoteException;
|
||||
|
@ -53,12 +54,12 @@ public class Client {
|
|||
|
||||
private static native boolean leave_workspace(long self, String id);
|
||||
public boolean leaveWorkspace(String id) {
|
||||
return leave_workspace(this.ptr, id);
|
||||
return leave_workspace(this.ptr, Objects.requireNonNull(id));
|
||||
}
|
||||
|
||||
private static native Workspace get_workspace(long self, String workspace);
|
||||
public Optional<Workspace> getWorkspace(String workspace) {
|
||||
return Optional.ofNullable(get_workspace(this.ptr, workspace));
|
||||
return Optional.ofNullable(get_workspace(this.ptr, Objects.requireNonNull(workspace)));
|
||||
}
|
||||
|
||||
private static native void refresh(long self) throws ConnectionRemoteException;
|
||||
|
|
5
dist/java/src/mp/code/CursorController.java
vendored
5
dist/java/src/mp/code/CursorController.java
vendored
|
@ -4,6 +4,7 @@ import mp.code.data.Callback;
|
|||
import mp.code.data.Cursor;
|
||||
import mp.code.exceptions.ControllerException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CursorController {
|
||||
|
@ -25,12 +26,12 @@ public class CursorController {
|
|||
|
||||
private static native void send(long self, Cursor cursor) throws ControllerException;
|
||||
public void send(Cursor cursor) throws ControllerException {
|
||||
send(this.ptr, cursor);
|
||||
send(this.ptr, Objects.requireNonNull(cursor));
|
||||
}
|
||||
|
||||
private static native void callback(long self, Callback<CursorController> cb);
|
||||
public void callback(Callback<CursorController> cb) {
|
||||
callback(this.ptr, cb);
|
||||
callback(this.ptr, Objects.requireNonNull(cb));
|
||||
}
|
||||
|
||||
private static native void clear_callback(long self);
|
||||
|
|
13
dist/java/src/mp/code/Workspace.java
vendored
13
dist/java/src/mp/code/Workspace.java
vendored
|
@ -1,5 +1,6 @@
|
|||
package mp.code;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -27,7 +28,7 @@ public class Workspace {
|
|||
|
||||
private static native BufferController get_buffer(long self, String path);
|
||||
public Optional<BufferController> getBuffer(String path) {
|
||||
return Optional.ofNullable(get_buffer(this.ptr, path));
|
||||
return Optional.ofNullable(get_buffer(this.ptr, Objects.requireNonNull(path)));
|
||||
}
|
||||
|
||||
private static native String[] get_file_tree(long self, String filter, boolean strict);
|
||||
|
@ -42,17 +43,17 @@ public class Workspace {
|
|||
|
||||
private static native void create_buffer(long self, String path) throws ConnectionRemoteException;
|
||||
public void createBuffer(String path) throws ConnectionRemoteException {
|
||||
create_buffer(this.ptr, path);
|
||||
create_buffer(this.ptr, Objects.requireNonNull(path));
|
||||
}
|
||||
|
||||
private static native BufferController attach_to_buffer(long self, String path) throws ConnectionException;
|
||||
public BufferController attachToBuffer(String path) throws ConnectionException {
|
||||
return attach_to_buffer(ptr, path);
|
||||
return attach_to_buffer(ptr, Objects.requireNonNull(path));
|
||||
}
|
||||
|
||||
private static native DetachResult detach_from_buffer(long self, String path);
|
||||
public DetachResult detachFromBuffer(String path) {
|
||||
return detach_from_buffer(this.ptr, path);
|
||||
return detach_from_buffer(this.ptr, Objects.requireNonNull(path));
|
||||
}
|
||||
|
||||
private static native void fetch_buffers(long self) throws ConnectionRemoteException;
|
||||
|
@ -67,12 +68,12 @@ public class Workspace {
|
|||
|
||||
private static native UUID[] list_buffer_users(long self, String path) throws ConnectionRemoteException;
|
||||
public UUID[] listBufferUsers(String path) throws ConnectionRemoteException {
|
||||
return list_buffer_users(this.ptr, path);
|
||||
return list_buffer_users(this.ptr, Objects.requireNonNull(path));
|
||||
}
|
||||
|
||||
private static native void delete_buffer(long self, String path) throws ConnectionRemoteException;
|
||||
public void deleteBuffer(String path) throws ConnectionRemoteException {
|
||||
delete_buffer(this.ptr, path);
|
||||
delete_buffer(this.ptr, Objects.requireNonNull(path));
|
||||
}
|
||||
|
||||
private static native Event event(long self) throws ControllerException;
|
||||
|
|
47
dist/java/src/mp/code/data/Config.java
vendored
Normal file
47
dist/java/src/mp/code/data/Config.java
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package mp.code.data;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class Config {
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final Optional<String> host;
|
||||
private final OptionalInt port;
|
||||
private final Optional<Boolean> tls;
|
||||
|
||||
public Config(String username, String password) {
|
||||
this(
|
||||
username,
|
||||
password,
|
||||
Optional.empty(),
|
||||
OptionalInt.empty(),
|
||||
Optional.empty()
|
||||
);
|
||||
}
|
||||
|
||||
public Config(String username, String password, String host, int port, boolean tls) {
|
||||
this(
|
||||
username,
|
||||
password,
|
||||
Optional.of(host),
|
||||
OptionalInt.of(checkPort(port)),
|
||||
Optional.of(tls)
|
||||
);
|
||||
}
|
||||
|
||||
private static int checkPort(int port) {
|
||||
if(port < 0 || port > 65535)
|
||||
throw new IllegalArgumentException("Port value must be between 0 and 65535!");
|
||||
return port;
|
||||
}
|
||||
}
|
16
dist/java/src/mp/code/data/Cursor.java
vendored
16
dist/java/src/mp/code/data/Cursor.java
vendored
|
@ -1,16 +1,14 @@
|
|||
package mp.code.data;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class Cursor {
|
||||
public final int startRow, startCol, endRow, endCol;
|
||||
public final String buffer;
|
||||
public final String user;
|
||||
|
||||
public Cursor(int startRow, int startCol, int endRow, int endCol, String buffer, String user) {
|
||||
this.startRow = startRow;
|
||||
this.startCol = startCol;
|
||||
this.endRow = endRow;
|
||||
this.endCol = endCol;
|
||||
this.buffer = buffer;
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
|
|
19
dist/java/src/mp/code/data/TextChange.java
vendored
19
dist/java/src/mp/code/data/TextChange.java
vendored
|
@ -1,20 +1,21 @@
|
|||
package mp.code.data;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class TextChange {
|
||||
public final long start;
|
||||
public final long end;
|
||||
public final int start;
|
||||
public final int end;
|
||||
public final String content;
|
||||
public final OptionalLong hash; // xxh3 hash
|
||||
|
||||
public TextChange(long start, long end, String content, OptionalLong hash) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.content = content;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public boolean isDelete() {
|
||||
return this.start != this.end;
|
||||
}
|
||||
|
|
12
dist/java/src/mp/code/data/User.java
vendored
12
dist/java/src/mp/code/data/User.java
vendored
|
@ -1,13 +1,15 @@
|
|||
package mp.code.data;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class User {
|
||||
public final UUID id;
|
||||
public final String name;
|
||||
|
||||
public User(UUID id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use jni::{objects::{JClass, JObject}, sys::{jboolean, jlong, jobject, jstring}, JNIEnv};
|
||||
|
||||
use crate::api::Controller;
|
||||
use crate::api::{Controller, TextChange};
|
||||
|
||||
use super::{JExceptable, JObjectify};
|
||||
use super::{handle_error, null_check, tokio, Deobjectify, JExceptable, JObjectify};
|
||||
|
||||
/// Gets the name of the buffer.
|
||||
#[no_mangle]
|
||||
|
@ -26,14 +26,14 @@ pub extern "system" fn Java_mp_code_BufferController_get_1content(
|
|||
self_ptr: jlong,
|
||||
) -> jstring {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
|
||||
let content = super::tokio().block_on(controller.content())
|
||||
let content = tokio().block_on(controller.content())
|
||||
.jexcept(&mut env);
|
||||
env.new_string(content)
|
||||
.jexcept(&mut env)
|
||||
.as_raw()
|
||||
}
|
||||
|
||||
/// Tries to fetch a [crate::api::TextChange], or returns null if there's nothing.
|
||||
/// Tries to fetch a [TextChange], or returns null if there's nothing.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_BufferController_try_1recv(
|
||||
mut env: JNIEnv,
|
||||
|
@ -41,13 +41,13 @@ pub extern "system" fn Java_mp_code_BufferController_try_1recv(
|
|||
self_ptr: jlong,
|
||||
) -> jobject {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
|
||||
super::tokio().block_on(controller.try_recv())
|
||||
tokio().block_on(controller.try_recv())
|
||||
.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 [TextChange].
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_BufferController_recv(
|
||||
mut env: JNIEnv,
|
||||
|
@ -55,66 +55,29 @@ pub extern "system" fn Java_mp_code_BufferController_recv(
|
|||
self_ptr: jlong,
|
||||
) -> jobject {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
|
||||
super::tokio().block_on(controller.recv())
|
||||
tokio().block_on(controller.recv())
|
||||
.jexcept(&mut env)
|
||||
.jobjectify(&mut env)
|
||||
.jexcept(&mut env)
|
||||
.as_raw()
|
||||
}
|
||||
|
||||
/// Receive from Java, converts and sends a [crate::api::TextChange].
|
||||
/// Receive from Java, converts and sends a [TextChange].
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_BufferController_send<'local>(
|
||||
mut env: JNIEnv,
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JObject<'local>,
|
||||
change: JObject<'local>,
|
||||
) {
|
||||
let Ok(start) = env.get_field(&input, "start", "J")
|
||||
.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);
|
||||
|
||||
null_check!(env, change, {});
|
||||
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);
|
||||
let change = TextChange::deobjectify(&mut env, change);
|
||||
if let Ok(change) = change {
|
||||
tokio().block_on(controller.send(change)).jexcept(&mut env)
|
||||
} else {
|
||||
handle_error!(&mut env, change, {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a callback for buffer changes.
|
||||
|
@ -125,8 +88,8 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>(
|
|||
self_ptr: jlong,
|
||||
cb: JObject<'local>,
|
||||
) {
|
||||
null_check!(env, cb, {});
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::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!");
|
||||
|
@ -176,7 +139,7 @@ pub extern "system" fn Java_mp_code_BufferController_poll(
|
|||
self_ptr: jlong,
|
||||
) {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
|
||||
super::tokio().block_on(controller.poll())
|
||||
tokio().block_on(controller.poll())
|
||||
.jexcept(&mut env);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,75 +1,27 @@
|
|||
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jboolean, jint, jlong, jobject, jobjectArray}, JNIEnv};
|
||||
use crate::{api::Config, client::Client, Workspace};
|
||||
use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray}, JNIEnv};
|
||||
use crate::{api::Config, client::Client, ffi::java::{handle_error, null_check}, Workspace};
|
||||
|
||||
use super::{JExceptable, JObjectify};
|
||||
use super::{Deobjectify, JExceptable, JObjectify, tokio};
|
||||
|
||||
/// Connect using the given credentials to the default server, and return a [Client] to interact with it.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_Client_connect<'local>(
|
||||
mut env: JNIEnv,
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
user: JString<'local>,
|
||||
pwd: JString<'local>
|
||||
config: JObject<'local>
|
||||
) -> jobject {
|
||||
let username: String = env.get_string(&user)
|
||||
.map(|s| s.into())
|
||||
.jexcept(&mut env);
|
||||
let password: String = env.get_string(&pwd)
|
||||
.map(|s| s.into())
|
||||
.jexcept(&mut env);
|
||||
connect_internal(env, Config {
|
||||
username,
|
||||
password,
|
||||
host: None,
|
||||
port: None,
|
||||
tls: None
|
||||
})
|
||||
}
|
||||
|
||||
/// Connect to a given URL and return a [Client] to interact with that server.
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub extern "system" fn Java_mp_code_Client_connectToServer<'local>(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass<'local>,
|
||||
user: JString<'local>,
|
||||
pwd: JString<'local>,
|
||||
host: JString<'local>,
|
||||
port: jint,
|
||||
tls: jboolean
|
||||
) -> jobject {
|
||||
let username: String = env.get_string(&user)
|
||||
.map(|s| s.into())
|
||||
.jexcept(&mut env);
|
||||
let password: String = env.get_string(&pwd)
|
||||
.map(|s| s.into())
|
||||
.jexcept(&mut env);
|
||||
let host: String = env.get_string(&host)
|
||||
.map(|s| s.into())
|
||||
.jexcept(&mut env);
|
||||
|
||||
if port < 0 {
|
||||
env.throw_new("mp/code/exceptions/JNIException", "Negative port number!")
|
||||
.jexcept(&mut env);
|
||||
null_check!(env, config, std::ptr::null_mut());
|
||||
let config = Config::deobjectify(&mut env, config);
|
||||
if config.is_err() {
|
||||
handle_error!(&mut env, config, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
connect_internal(env, Config {
|
||||
username,
|
||||
password,
|
||||
host: Some(host),
|
||||
port: Some(port as u16),
|
||||
tls: Some(tls != 0),
|
||||
})
|
||||
}
|
||||
|
||||
fn connect_internal(mut env: JNIEnv, config: Config) -> jobject {
|
||||
super::tokio().block_on(Client::connect(config))
|
||||
.map(|client| Box::into_raw(Box::new(client)) as jlong)
|
||||
.map(|ptr| {
|
||||
env.find_class("mp/code/Client")
|
||||
.and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
|
||||
.jexcept(&mut env)
|
||||
}).jexcept(&mut env).as_raw()
|
||||
let client = tokio().block_on(Client::connect(config.unwrap()));
|
||||
if let Ok(client) = client {
|
||||
client.jobjectify(&mut env).jexcept(&mut env).as_raw()
|
||||
} else {
|
||||
handle_error!(&mut env, client, std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current [crate::api::User].
|
||||
|
@ -92,20 +44,20 @@ pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
workspace_id: JString<'local>
|
||||
) -> jobject {
|
||||
null_check!(env, workspace_id, std::ptr::null_mut());
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&input) }
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio().block_on(client.join_workspace(workspace_id))
|
||||
.map(|workspace| spawn_updater(workspace.clone()))
|
||||
.map(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
|
||||
.map(|ptr| {
|
||||
env.find_class("mp/code/Workspace")
|
||||
.and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
|
||||
.jexcept(&mut env)
|
||||
}).jexcept(&mut env).as_raw()
|
||||
let workspace = tokio().block_on(client.join_workspace(workspace_id))
|
||||
.map(|workspace| spawn_updater(workspace.clone()));
|
||||
if let Ok(workspace) = workspace {
|
||||
workspace.jobjectify(&mut env).jexcept(&mut env).as_raw()
|
||||
} else {
|
||||
handle_error!(&mut env, workspace, std::ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a workspace on server, if allowed to.
|
||||
|
@ -114,13 +66,14 @@ pub extern "system" fn Java_mp_code_Client_create_1workspace<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
workspace_id: JString<'local>
|
||||
) {
|
||||
null_check!(env, workspace_id, {});
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&input) }
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio()
|
||||
tokio()
|
||||
.block_on(client.create_workspace(workspace_id))
|
||||
.jexcept(&mut env);
|
||||
}
|
||||
|
@ -131,13 +84,14 @@ pub extern "system" fn Java_mp_code_Client_delete_1workspace<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
workspace_id: JString<'local>
|
||||
) {
|
||||
null_check!(env, workspace_id, {});
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&input) }
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio()
|
||||
tokio()
|
||||
.block_on(client.delete_workspace(workspace_id))
|
||||
.jexcept(&mut env);
|
||||
}
|
||||
|
@ -148,17 +102,19 @@ pub extern "system" fn Java_mp_code_Client_invite_1to_1workspace<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
ws: JString<'local>,
|
||||
usr: JString<'local>
|
||||
workspace_id: JString<'local>,
|
||||
user: JString<'local>
|
||||
) {
|
||||
null_check!(env, workspace_id, {});
|
||||
null_check!(env, user, {});
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&ws) }
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
let user_name = unsafe { env.get_string_unchecked(&usr) }
|
||||
let user_name = unsafe { env.get_string_unchecked(&user) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio()
|
||||
tokio()
|
||||
.block_on(client.invite_to_workspace(workspace_id, user_name))
|
||||
.jexcept(&mut env);
|
||||
}
|
||||
|
@ -173,7 +129,7 @@ pub extern "system" fn Java_mp_code_Client_list_1workspaces<'local>(
|
|||
invited: jboolean
|
||||
) -> jobjectArray {
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
let list = super::tokio()
|
||||
let list = tokio()
|
||||
.block_on(client.list_workspaces(owned != 0, invited != 0))
|
||||
.jexcept(&mut env);
|
||||
env.find_class("java/lang/String")
|
||||
|
@ -210,7 +166,7 @@ pub extern "system" fn Java_mp_code_Client_active_1workspaces<'local>(
|
|||
// TODO: this stays until we get rid of the arc then i'll have to find a better way
|
||||
fn spawn_updater(workspace: Workspace) -> Workspace {
|
||||
let w = workspace.clone();
|
||||
super::tokio().spawn(async move {
|
||||
tokio().spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
||||
w.fetch_buffers().await.unwrap();
|
||||
|
@ -226,10 +182,11 @@ pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
workspace_id: JString<'local>
|
||||
) -> jboolean {
|
||||
null_check!(env, workspace_id, false as jboolean);
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
unsafe { env.get_string_unchecked(&input) }
|
||||
unsafe { env.get_string_unchecked(&workspace_id) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.map(|wid| client.leave_workspace(&wid) as jboolean)
|
||||
.jexcept(&mut env)
|
||||
|
@ -241,19 +198,18 @@ pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
workspace_id: JString<'local>
|
||||
) -> jobject {
|
||||
null_check!(env, workspace_id, std::ptr::null_mut());
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&input) }
|
||||
let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) }
|
||||
.map(|wid| wid.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
client.get_workspace(&workspace_id)
|
||||
.map(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
|
||||
.map(|ptr| {
|
||||
env.find_class("mp/code/Workspace")
|
||||
.and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
|
||||
.jexcept(&mut env)
|
||||
}).unwrap_or_default().as_raw()
|
||||
if let Some(workspace) = client.get_workspace(&workspace_id) {
|
||||
workspace.jobjectify(&mut env).jexcept(&mut env).as_raw()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh the client's session token.
|
||||
|
@ -264,7 +220,7 @@ pub extern "system" fn Java_mp_code_Client_refresh<'local>(
|
|||
self_ptr: jlong,
|
||||
) {
|
||||
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) };
|
||||
super::tokio().block_on(client.refresh())
|
||||
tokio().block_on(client.refresh())
|
||||
.jexcept(&mut env);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject}, JNIEnv};
|
||||
use crate::api::Controller;
|
||||
use jni::{objects::{JClass, JObject}, sys::{jboolean, jlong, jobject}, JNIEnv};
|
||||
use crate::api::{Controller, Cursor};
|
||||
|
||||
use super::{JExceptable, JObjectify};
|
||||
use super::{handle_error, null_check, tokio, Deobjectify, JExceptable, JObjectify};
|
||||
|
||||
/// Try to fetch a [crate::api::Cursor], or returns null if there's nothing.
|
||||
/// Try to fetch a [Cursor], or returns null if there's nothing.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_CursorController_try_1recv(
|
||||
mut env: JNIEnv,
|
||||
|
@ -11,13 +11,13 @@ pub extern "system" fn Java_mp_code_CursorController_try_1recv(
|
|||
self_ptr: jlong,
|
||||
) -> jobject {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
|
||||
super::tokio().block_on(controller.try_recv())
|
||||
tokio().block_on(controller.try_recv())
|
||||
.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 [Cursor].
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_CursorController_recv(
|
||||
mut env: JNIEnv,
|
||||
|
@ -25,62 +25,29 @@ pub extern "system" fn Java_mp_code_CursorController_recv(
|
|||
self_ptr: jlong,
|
||||
) -> jobject {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
|
||||
super::tokio().block_on(controller.recv())
|
||||
tokio().block_on(controller.recv())
|
||||
.jexcept(&mut env)
|
||||
.jobjectify(&mut env)
|
||||
.jexcept(&mut env)
|
||||
.as_raw()
|
||||
}
|
||||
|
||||
/// Receive from Java, converts and sends a [crate::api::Cursor].
|
||||
/// Receive from Java, converts and sends a [Cursor].
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_mp_code_CursorController_send<'local>(
|
||||
mut env: JNIEnv,
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JObject<'local>,
|
||||
cursor: JObject<'local>,
|
||||
) {
|
||||
let start_row = env.get_field(&input, "startRow", "I")
|
||||
.and_then(|sr| sr.i())
|
||||
.jexcept(&mut env);
|
||||
let start_col = env.get_field(&input, "startCol", "I")
|
||||
.and_then(|sc| sc.i())
|
||||
.jexcept(&mut env);
|
||||
let end_row = env.get_field(&input, "endRow", "I")
|
||||
.and_then(|er| er.i())
|
||||
.jexcept(&mut env);
|
||||
let end_col = env.get_field(&input, "endCol", "I")
|
||||
.and_then(|ec| ec.i())
|
||||
.jexcept(&mut env);
|
||||
|
||||
let buffer = env.get_field(&input, "buffer", "Ljava/lang/String;")
|
||||
.and_then(|b| b.l())
|
||||
.map(|b| b.into())
|
||||
.jexcept(&mut env);
|
||||
let buffer = env.get_string(&buffer)
|
||||
.map(|b| b.into())
|
||||
.jexcept(&mut env);
|
||||
|
||||
let user: JString = env.get_field(&input, "user", "Ljava/lang/String;")
|
||||
.and_then(|u| u.l())
|
||||
.map(|u| u.into())
|
||||
.jexcept(&mut env);
|
||||
let user = if user.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(env.get_string(&user)
|
||||
.map(|u| u.into())
|
||||
.jexcept(&mut env)
|
||||
)
|
||||
};
|
||||
|
||||
null_check!(env, cursor, {});
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
|
||||
super::tokio().block_on(controller.send(crate::api::Cursor {
|
||||
start: (start_row, start_col),
|
||||
end: (end_row, end_col),
|
||||
buffer,
|
||||
user
|
||||
})).jexcept(&mut env);
|
||||
let cursor = Cursor::deobjectify(&mut env, cursor);
|
||||
if let Ok(cursor) = cursor {
|
||||
tokio().block_on(controller.send(cursor)).jexcept(&mut env)
|
||||
} else {
|
||||
handle_error!(&mut env, cursor, {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a callback for cursor changes.
|
||||
|
@ -91,6 +58,7 @@ pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
|
|||
self_ptr: jlong,
|
||||
cb: JObject<'local>,
|
||||
) {
|
||||
null_check!(env, cb, {});
|
||||
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 {
|
||||
|
@ -142,7 +110,7 @@ pub extern "system" fn Java_mp_code_CursorController_poll(
|
|||
self_ptr: jlong,
|
||||
) {
|
||||
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };
|
||||
super::tokio().block_on(controller.poll())
|
||||
tokio().block_on(controller.poll())
|
||||
.jexcept(&mut env);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use jni::{objects::{JClass, JString}, sys::{jboolean, jlong}, JNIEnv};
|
||||
|
||||
use super::JExceptable;
|
||||
use super::{JExceptable, null_check};
|
||||
|
||||
/// Calculate the XXH3 hash for a given String.
|
||||
#[no_mangle]
|
||||
|
@ -9,6 +9,7 @@ pub extern "system" fn Java_mp_code_Extensions_hash<'local>(
|
|||
_class: JClass<'local>,
|
||||
content: JString<'local>,
|
||||
) -> jlong {
|
||||
null_check!(env, content, 0 as jlong);
|
||||
let content: String = env.get_string(&content)
|
||||
.map(|s| s.into())
|
||||
.jexcept(&mut env);
|
||||
|
|
|
@ -62,93 +62,94 @@ pub(crate) fn setup_logger(debug: bool, path: Option<String>) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Utility macro that attempts to handle an error in a [Result].
|
||||
/// MUST be called within a $result.is_err() block or similar. Failure to do so is UB.
|
||||
/// Will return early with a provided return value, or panic if it fails to throw a Java exception.
|
||||
macro_rules! handle_error {
|
||||
($env: expr, $result: ident, $return: expr) => {
|
||||
{
|
||||
let err = unsafe { $result.unwrap_err_unchecked() };
|
||||
tracing::info!("Attempting to throw error {err:#?} as a Java exception...");
|
||||
if let Err(e) = err.jobjectify($env).map(|t| t.into()).and_then(|t: jni::objects::JThrowable| $env.throw(&t)) {
|
||||
panic!("Failed to throw exception: {e}");
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use handle_error;
|
||||
|
||||
/// Performs a null check on the given variable and throws a NullPointerException on the Java side
|
||||
/// if it is null. Finally, it returns with the given default value.
|
||||
macro_rules! null_check {
|
||||
($env: ident, $var: ident, $return: expr) => {
|
||||
if $var.is_null() {
|
||||
let mut message = stringify!($var).to_string();
|
||||
message.push_str(" cannot be null!");
|
||||
$env.throw_new("java/lang/NullPointerException", message)
|
||||
.expect("Failed to throw exception!");
|
||||
return $return;
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use null_check;
|
||||
|
||||
|
||||
/// A trait meant for our local result type to make converting it to Java easier.
|
||||
/// jni-rs technically has [jni::errors::ToException], but this approach keeps it stream-like.
|
||||
pub(crate) trait JExceptable<T> {
|
||||
pub(crate) trait JExceptable<'local, T: Default> {
|
||||
/// Unwrap it and throws an appropriate Java exception if it's an error.
|
||||
/// Theoretically it returns the type's default value, but the exception makes the value ignored.
|
||||
fn jexcept(self, env: &mut jni::JNIEnv) -> T;
|
||||
fn jexcept(self, env: &mut jni::JNIEnv<'local>) -> T;
|
||||
}
|
||||
|
||||
impl<T> JExceptable<T> for crate::errors::ConnectionResult<T> where T: Default {
|
||||
fn jexcept(self, env: &mut jni::JNIEnv) -> T {
|
||||
if let Err(err) = &self {
|
||||
let msg = format!("{err}");
|
||||
match err {
|
||||
crate::errors::ConnectionError::Transport(_) => env.throw_new("mp/code/exceptions/ConnectionTransportException", msg),
|
||||
crate::errors::ConnectionError::Remote(_) => env.throw_new("mp/code/exceptions/ConnectionRemoteException", msg),
|
||||
}.jexcept(env);
|
||||
impl<'local, T: Default, E: JObjectify<'local> + std::fmt::Debug> JExceptable<'local, T> for Result<T, E> {
|
||||
fn jexcept(self, env: &mut jni::JNIEnv<'local>) -> T {
|
||||
if let Ok(res) = self {
|
||||
res
|
||||
} else {
|
||||
handle_error!(env, self, Default::default());
|
||||
}
|
||||
self.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> JExceptable<T> for crate::errors::RemoteResult<T> where T: Default {
|
||||
fn jexcept(self, env: &mut jni::JNIEnv) -> T {
|
||||
if let Err(err) = &self {
|
||||
let msg = format!("{err}");
|
||||
env.throw_new("mp/code/exceptions/ConnectionRemoteException", msg).jexcept(env);
|
||||
}
|
||||
self.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> JExceptable<T> for crate::errors::ControllerResult<T> where T: Default {
|
||||
fn jexcept(self, env: &mut jni::JNIEnv) -> T {
|
||||
if let Err(err) = &self {
|
||||
let msg = format!("{err}");
|
||||
match err {
|
||||
crate::errors::ControllerError::Stopped => env.throw_new("mp/code/exceptions/ControllerStoppedException", msg),
|
||||
crate::errors::ControllerError::Unfulfilled => env.throw_new("mp/code/exceptions/ControllerUnfulfilledException", msg),
|
||||
}.jexcept(env);
|
||||
}
|
||||
self.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> JExceptable<T> for Result<T, jni::errors::Error> where T: Default {
|
||||
fn jexcept(self, env: &mut jni::JNIEnv) -> T {
|
||||
if let Err(err) = &self {
|
||||
let msg = format!("{err}");
|
||||
if let Err(err) = env.throw_new("mp/code/exceptions/JNIException", msg) {
|
||||
if let Err(err) = env.exception_describe() {
|
||||
tracing::error!("An exception occurred and we failed to even describe it: {err:#?}.");
|
||||
}
|
||||
panic!("A severe error occurred: we were unable to create a JNIException from {err:#?}. This is an unrecoverable state.");
|
||||
}
|
||||
}
|
||||
self.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> JExceptable<T> for Result<T, uuid::Error> where T: Default {
|
||||
fn jexcept(self, env: &mut jni::JNIEnv) -> T {
|
||||
if let Err(err) = &self {
|
||||
let msg = format!("{err}");
|
||||
if let Err(err) = env.throw_new("java/lang/IllegalArgumentException", msg) {
|
||||
if let Err(err) = env.exception_describe() {
|
||||
tracing::error!("An exception occurred and we failed to even describe it: {err:#?}.");
|
||||
}
|
||||
panic!("A severe error occurred: we were unable to create a JNIException from {err:#?}. This is an unrecoverable state.");
|
||||
}
|
||||
}
|
||||
self.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows easy conversion for various types into Java objects.
|
||||
/// This is essentially the same as [TryInto], but that can't be emplemented on non-local types.
|
||||
/// This is similar to [TryInto], but for Java types.
|
||||
pub(crate) trait JObjectify<'local> {
|
||||
/// The error type, likely to be [jni::errors::Error].
|
||||
type Error: std::fmt::Debug;
|
||||
|
||||
/// Attempt to convert the given object to a [jni::objects::JObject].
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error>;
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error>;
|
||||
}
|
||||
|
||||
macro_rules! jobjectify_error {
|
||||
($self: ident, $type: ty, $jclass: expr) => {
|
||||
impl<'local> JObjectify<'local> for $type {
|
||||
fn jobjectify($self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let class = env.find_class($jclass)?;
|
||||
let msg = env.new_string(format!("{:#?}", $self))?;
|
||||
env.new_object(class, "(Ljava/lang/String)V", &[jni::objects::JValueGen::Object(&msg)])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
jobjectify_error!(self, crate::errors::RemoteError, "mp/code/exceptions/ConnectionRemoteException");
|
||||
jobjectify_error!(self, jni::errors::Error, match self {
|
||||
jni::errors::Error::NullPtr(_) => "java/lang/NullPointerException",
|
||||
_ => "mp/code/exceptions/JNIException"
|
||||
});
|
||||
jobjectify_error!(self, uuid::Error, "java/lang/IllegalArgumentException");
|
||||
jobjectify_error!(self, crate::errors::ConnectionError, match self {
|
||||
crate::errors::ConnectionError::Transport(_) => "mp/code/exceptions/ConnectionTransportException",
|
||||
crate::errors::ConnectionError::Remote(_) => "mp/code/exceptions/ConnectionRemoteException"
|
||||
});
|
||||
jobjectify_error!(self, crate::errors::ControllerError, match self {
|
||||
crate::errors::ControllerError::Stopped => "mp/code/exceptions/ControllerStoppedException",
|
||||
crate::errors::ControllerError::Unfulfilled => "mp/code/exceptions/ControllerUnfulfilledException",
|
||||
});
|
||||
|
||||
|
||||
impl<'local> JObjectify<'local> for uuid::Uuid {
|
||||
type Error = jni::errors::Error;
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let class = env.find_class("java/util/UUID")?;
|
||||
let (msb, lsb) = self.as_u64_pair();
|
||||
let msb = i64::from_ne_bytes(msb.to_ne_bytes());
|
||||
|
@ -157,10 +158,29 @@ impl<'local> JObjectify<'local> for uuid::Uuid {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'local> JObjectify<'local> for crate::api::User {
|
||||
type Error = jni::errors::Error;
|
||||
/// Generates a [JObjectify] implementation for a class that is just a holder for a pointer.
|
||||
macro_rules! jobjectify_ptr_class {
|
||||
($type: ty, $jclass: literal) => {
|
||||
impl<'local> JObjectify<'local> for $type {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let class = env.find_class($jclass)?;
|
||||
env.new_object(
|
||||
class,
|
||||
"(J)V",
|
||||
&[jni::objects::JValueGen::Long(Box::into_raw(Box::new(self)) as jni::sys::jlong)]
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||
jobjectify_ptr_class!(crate::Client, "mp/code/Client");
|
||||
jobjectify_ptr_class!(crate::Workspace, "mp/code/Workspace");
|
||||
jobjectify_ptr_class!(crate::cursor::Controller, "mp/code/CursorController");
|
||||
jobjectify_ptr_class!(crate::buffer::Controller, "mp/code/BufferController");
|
||||
|
||||
impl<'local> JObjectify<'local> for crate::api::User {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let id_field = self.id.jobjectify(env)?;
|
||||
let name_field = env.new_string(self.name)?;
|
||||
let class = env.find_class("mp/code/data/User")?;
|
||||
|
@ -175,40 +195,56 @@ impl<'local> JObjectify<'local> for crate::api::User {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'local> JObjectify<'local> for crate::cursor::Controller {
|
||||
type Error = jni::errors::Error;
|
||||
impl<'local> JObjectify<'local> for crate::api::Event {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let (ordinal, arg) = match self {
|
||||
crate::api::Event::UserJoin(arg) => (0, env.new_string(arg)?),
|
||||
crate::api::Event::UserLeave(arg) => (1, env.new_string(arg)?),
|
||||
crate::api::Event::FileTreeUpdated(arg) => (2, env.new_string(arg)?),
|
||||
};
|
||||
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||
let class = env.find_class("mp/code/CursorController")?;
|
||||
let type_class = env.find_class("mp/code/Workspace$Event$Type")?;
|
||||
let variants: jni::objects::JObjectArray = env.call_method(
|
||||
type_class,
|
||||
"getEnumConstants",
|
||||
"()[Ljava/lang/Object;",
|
||||
&[]
|
||||
)?.l()?.into();
|
||||
let event_type = env.get_object_array_element(variants, ordinal)?;
|
||||
|
||||
let event_class = env.find_class("mp/code/Workspace$Event")?;
|
||||
env.new_object(
|
||||
class,
|
||||
"(J)V",
|
||||
event_class,
|
||||
"(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
|
||||
&[
|
||||
jni::objects::JValueGen::Long(Box::into_raw(Box::new(self)) as jni::sys::jlong)
|
||||
jni::objects::JValueGen::Object(&event_type),
|
||||
jni::objects::JValueGen::Object(&arg)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local> JObjectify<'local> for crate::buffer::Controller {
|
||||
type Error = jni::errors::Error;
|
||||
impl<'local> JObjectify<'local> for crate::workspace::DetachResult {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let ordinal = match self {
|
||||
crate::workspace::DetachResult::NotAttached => 0,
|
||||
crate::workspace::DetachResult::Detaching => 1,
|
||||
crate::workspace::DetachResult::AlreadyDetached => 2
|
||||
};
|
||||
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||
let class = env.find_class("mp/code/BufferController")?;
|
||||
env.new_object(
|
||||
let class = env.find_class("mp/code/data/DetachResult")?;
|
||||
let variants: jni::objects::JObjectArray = env.call_method(
|
||||
class,
|
||||
"(J)V",
|
||||
&[
|
||||
jni::objects::JValueGen::Long(Box::into_raw(Box::new(self)) as jni::sys::jlong)
|
||||
]
|
||||
)
|
||||
"getEnumConstants",
|
||||
"()[Ljava/lang/Object;",
|
||||
&[]
|
||||
)?.l()?.into();
|
||||
env.get_object_array_element(variants, ordinal)
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
||||
let content = env.new_string(self.content)?;
|
||||
|
||||
let hash = env.find_class("java/util/OptionalLong").and_then(|class| {
|
||||
|
@ -223,8 +259,8 @@ impl<'local> JObjectify<'local> for crate::api::TextChange {
|
|||
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::Long(self.start.into()),
|
||||
jni::objects::JValueGen::Long(self.end.into()),
|
||||
jni::objects::JValueGen::Object(&content),
|
||||
jni::objects::JValueGen::Object(&hash)
|
||||
]
|
||||
|
@ -234,9 +270,7 @@ impl<'local> JObjectify<'local> for crate::api::TextChange {
|
|||
}
|
||||
|
||||
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> {
|
||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::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 {
|
||||
|
@ -260,3 +294,114 @@ impl<'local> JObjectify<'local> for crate::api::Cursor {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows easy conversion of Java types into their Rust counterparts.
|
||||
pub(crate) trait Deobjectify<'local, T: Sized> {
|
||||
/// Attempt to convert the given [jni::objects::JObject] into its Rust counterpart.
|
||||
fn deobjectify(env: &mut jni::JNIEnv<'local>, jobject: jni::objects::JObject<'local>) -> Result<T, jni::errors::Error>;
|
||||
}
|
||||
|
||||
impl<'local> Deobjectify<'local, Self> for crate::api::Config {
|
||||
fn deobjectify(env: &mut jni::JNIEnv<'local>, config: jni::objects::JObject<'local>) -> Result<Self, jni::errors::Error> {
|
||||
let username = {
|
||||
let jfield = env.get_field(&config, "username", "Ljava/lang/String;")?.l()?;
|
||||
if jfield.is_null() {
|
||||
return Err(jni::errors::Error::NullPtr("Username can never be null!"));
|
||||
}
|
||||
unsafe { env.get_string_unchecked(&jfield.into()) }?.into()
|
||||
};
|
||||
|
||||
let password = {
|
||||
let jfield = env.get_field(&config, "password", "Ljava/lang/String;")?.l()?;
|
||||
if jfield.is_null() {
|
||||
return Err(jni::errors::Error::NullPtr("Password can never be null!"));
|
||||
}
|
||||
unsafe { env.get_string_unchecked(&jfield.into()) }?.into()
|
||||
};
|
||||
|
||||
let host = {
|
||||
let jfield = env.get_field(&config, "host", "Ljava/util/Optional;")?.l()?;
|
||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||
let field = env.call_method(&jfield, "get", "()Ljava/lang/Object;", &[])?.l()?;
|
||||
Some(unsafe { env.get_string_unchecked(&field.into()) }?.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let port = {
|
||||
let jfield = env.get_field(&config, "port", "Ljava/util/OptionalInt;")?.l()?;
|
||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||
let ivalue = env.call_method(&jfield, "get", "()I", &[])?.i()?;
|
||||
Some(ivalue.clamp(0, 65535) as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let tls = {
|
||||
let jfield = env.get_field(&config, "host", "Ljava/util/Optional;")?.l()?;
|
||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||
let field = env.call_method(&jfield, "get", "()Ljava/lang/Object;", &[])?.l()?;
|
||||
Some(env.call_method(field, "booleanValue", "()Z", &[])?.z()?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { username, password, host, port, tls })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local> Deobjectify<'local, Self> for crate::api::Cursor {
|
||||
fn deobjectify(env: &mut jni::JNIEnv<'local>, cursor: jni::objects::JObject<'local>) -> Result<Self, jni::errors::Error> {
|
||||
let start_row = env.get_field(&cursor, "startRow", "I")?.i()?;
|
||||
let start_col = env.get_field(&cursor, "startCol", "I")?.i()?;
|
||||
let end_row = env.get_field(&cursor, "endRow", "I")?.i()?;
|
||||
let end_col = env.get_field(&cursor, "endCol", "I")?.i()?;
|
||||
|
||||
let buffer = {
|
||||
let jfield = env.get_field(&cursor, "buffer", "Ljava/lang/String;")?.l()?;
|
||||
if jfield.is_null() {
|
||||
return Err(jni::errors::Error::NullPtr("Buffer can never be null!"));
|
||||
}
|
||||
unsafe { env.get_string_unchecked(&jfield.into()) }?.into()
|
||||
};
|
||||
|
||||
let user = {
|
||||
let jfield = env.get_field(&cursor, "user", "Ljava/lang/String;")?.l()?;
|
||||
if jfield.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { env.get_string_unchecked(&jfield.into()) }?.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { start: (start_row, start_col), end: (end_row, end_col), buffer, user })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local> Deobjectify<'local, Self> for crate::api::TextChange {
|
||||
fn deobjectify(env: &mut jni::JNIEnv<'local>, change: jni::objects::JObject<'local>) -> Result<Self, jni::errors::Error> {
|
||||
let start = env.get_field(&change, "start", "I")?.j()?.max(0) as u32;
|
||||
let end = env.get_field(&change, "end", "I")?.j()?.max(0) as u32;
|
||||
|
||||
let content = {
|
||||
let jfield = env.get_field(&change, "content", "Ljava/lang/String;")?.l()?;
|
||||
if jfield.is_null() {
|
||||
return Err(jni::errors::Error::NullPtr("Content can never be null!"));
|
||||
}
|
||||
unsafe { env.get_string_unchecked(&jfield.into()) }?.into()
|
||||
};
|
||||
|
||||
let hash = {
|
||||
let jfield = env.get_field(&change, "hash", "Ljava/util/OptionalLong;")?.l()?;
|
||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||
Some(env.call_method(&jfield, "getAsLong", "()J", &[])?.j()?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(Self { start, end, content, hash })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use jni::{objects::{JClass, JObject, JObjectArray, JString, JValueGen}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv};
|
||||
use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv};
|
||||
use crate::Workspace;
|
||||
|
||||
use super::{JExceptable, JObjectify};
|
||||
use super::{handle_error, null_check, JExceptable, JObjectify};
|
||||
|
||||
/// Get the workspace id.
|
||||
#[no_mangle]
|
||||
|
@ -31,14 +31,11 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>(
|
|||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
path: JString<'local>
|
||||
) -> jobject {
|
||||
if input.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
null_check!(env, path, std::ptr::null_mut());
|
||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
let path = unsafe { env.get_string_unchecked(&input) }
|
||||
let path = unsafe { env.get_string_unchecked(&path) }
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
workspace.buffer_by_name(&path)
|
||||
|
@ -105,15 +102,11 @@ pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>(
|
|||
mut env: JNIEnv,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
path: JString<'local>
|
||||
) {
|
||||
if input.is_null() {
|
||||
return env.throw_new("java/lang/NullPointerException", "Buffer name cannot be null!")
|
||||
.expect("Failed to throw exception!");
|
||||
}
|
||||
|
||||
null_check!(env, path, {});
|
||||
let ws = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
let path = unsafe { env.get_string_unchecked(&input) }
|
||||
let path = unsafe { env.get_string_unchecked(&path) }
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio().block_on(ws.create(&path))
|
||||
|
@ -126,10 +119,11 @@ pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>(
|
|||
mut env: JNIEnv,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
path: JString<'local>
|
||||
) -> jobject {
|
||||
null_check!(env, path, std::ptr::null_mut());
|
||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
let path = unsafe { env.get_string_unchecked(&input) }
|
||||
let path = unsafe { env.get_string_unchecked(&path) }
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio().block_on(workspace.attach(&path))
|
||||
|
@ -144,21 +138,15 @@ pub extern "system" fn Java_mp_code_Workspace_detach_1from_1buffer<'local>(
|
|||
mut env: JNIEnv,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>
|
||||
path: JString<'local>
|
||||
) -> jobject {
|
||||
null_check!(env, path, std::ptr::null_mut());
|
||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
let path = unsafe { env.get_string_unchecked(&input) }
|
||||
let path = unsafe { env.get_string_unchecked(&path) }
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
let name = match workspace.detach(&path) {
|
||||
crate::workspace::DetachResult::NotAttached => "NOT_ATTACHED",
|
||||
crate::workspace::DetachResult::Detaching => "DETACHED",
|
||||
crate::workspace::DetachResult::AlreadyDetached => "ALREADY_DETACHED"
|
||||
};
|
||||
|
||||
env.find_class("mp/code/data/DetachResult")
|
||||
.and_then(|class| env.get_static_field(class, name, "Lmp/code/data/DetachResult;"))
|
||||
.and_then(|res| res.l())
|
||||
workspace.detach(&path)
|
||||
.jobjectify(&mut env)
|
||||
.jexcept(&mut env)
|
||||
.as_raw()
|
||||
}
|
||||
|
@ -191,10 +179,11 @@ pub extern "system" fn Java_mp_code_Workspace_list_1buffer_1users<'local>(
|
|||
mut env: JNIEnv,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>,
|
||||
path: JString<'local>,
|
||||
) -> jobjectArray {
|
||||
null_check!(env, path, std::ptr::null_mut());
|
||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
let buffer = unsafe { env.get_string_unchecked(&input) }
|
||||
let buffer = unsafe { env.get_string_unchecked(&path) }
|
||||
.map(|buffer| buffer.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
let users = super::tokio().block_on(workspace.list_buffer_users(&buffer))
|
||||
|
@ -221,10 +210,11 @@ pub extern "system" fn Java_mp_code_Workspace_delete_1buffer<'local>(
|
|||
mut env: JNIEnv,
|
||||
_class: JClass<'local>,
|
||||
self_ptr: jlong,
|
||||
input: JString<'local>,
|
||||
path: JString<'local>,
|
||||
) {
|
||||
null_check!(env, path, {});
|
||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
let buffer = unsafe { env.get_string_unchecked(&input) }
|
||||
let buffer = unsafe { env.get_string_unchecked(&path) }
|
||||
.map(|buffer| buffer.to_string_lossy().to_string())
|
||||
.jexcept(&mut env);
|
||||
super::tokio().block_on(workspace.delete(&buffer))
|
||||
|
@ -239,30 +229,12 @@ pub extern "system" fn Java_mp_code_Workspace_event(
|
|||
self_ptr: jlong
|
||||
) -> jobject {
|
||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||
super::tokio().block_on(workspace.event())
|
||||
.map(|event| {
|
||||
let (ordinal, arg) = match event {
|
||||
crate::api::Event::UserJoin(arg) => (0, env.new_string(arg).unwrap_or_default()),
|
||||
crate::api::Event::UserLeave(arg) => (1, 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")
|
||||
.and_then(|class| env.call_method(class, "getEnumConstants", "()[Ljava/lang/Object;", &[]))
|
||||
.and_then(|enums| enums.l().map(|e| e.into()))
|
||||
.and_then(|enums: JObjectArray| env.get_object_array_element(enums, ordinal))
|
||||
.jexcept(&mut env);
|
||||
env.find_class("mp/code/Workspace$Event").and_then(|class|
|
||||
env.new_object(
|
||||
class,
|
||||
"(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
|
||||
&[
|
||||
JValueGen::Object(&event_type),
|
||||
JValueGen::Object(&arg)
|
||||
]
|
||||
)
|
||||
).jexcept(&mut env)
|
||||
}).jexcept(&mut env).as_raw()
|
||||
let event = super::tokio().block_on(workspace.event());
|
||||
if let Ok(event) = event {
|
||||
event.jobjectify(&mut env).jexcept(&mut env).as_raw()
|
||||
} else {
|
||||
handle_error!(&mut env, event, std::ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the Java GC to drop a [Workspace].
|
||||
|
|
Loading…
Reference in a new issue