pub mod buffer;
pub mod client;
pub mod cursor;
pub mod ext;
pub mod workspace;
fn tokio() -> &'static tokio::runtime::Runtime {
use std::sync::OnceLock;
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
RT.get_or_init(|| {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("could not create tokio runtime")
})
}
static mut JVM: Option<std::sync::Arc<jni::JavaVM>> = None;
pub(crate) fn jvm() -> std::sync::Arc<jni::JavaVM> {
unsafe { JVM.clone() }.unwrap()
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "system" fn JNI_OnLoad(vm: jni::JavaVM, _: *mut std::ffi::c_void) -> jni::sys::jint {
unsafe { JVM = Some(std::sync::Arc::new(vm)) };
jni::sys::JNI_VERSION_1_1
}
pub(crate) fn setup_logger(debug: bool, path: Option<String>) {
let format = tracing_subscriber::fmt::format()
.with_level(true)
.with_target(true)
.with_thread_ids(false)
.with_thread_names(false)
.with_ansi(false)
.with_file(false)
.with_line_number(false)
.with_source_location(false)
.compact();
let level = if debug {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
};
let builder = tracing_subscriber::fmt()
.event_format(format)
.with_max_level(level);
if let Some(path) = path {
let logfile = std::fs::File::create(path).expect("failed creating logfile");
builder.with_writer(std::sync::Mutex::new(logfile)).init();
} else {
builder
.with_writer(std::sync::Mutex::new(std::io::stdout()))
.init();
}
}
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;
impl jni_toolbox::JniToolboxError for crate::errors::ConnectionError {
fn jclass(&self) -> String {
match self {
crate::errors::ConnectionError::Transport(_) => {
"mp/code/exceptions/ConnectionTransportException"
}
crate::errors::ConnectionError::Remote(_) => {
"mp/code/exceptions/ConnectionRemoteException"
}
}
.to_string()
}
}
impl jni_toolbox::JniToolboxError for crate::errors::RemoteError {
fn jclass(&self) -> String {
"mp/code/exceptions/ConnectionRemoteException".to_string()
}
}
impl jni_toolbox::JniToolboxError for crate::errors::ControllerError {
fn jclass(&self) -> String {
match self {
crate::errors::ControllerError::Stopped => {
"mp/code/exceptions/ControllerStoppedException"
}
crate::errors::ControllerError::Unfulfilled => {
"mp/code/exceptions/ControllerUnfulfilledException"
}
}
.to_string()
}
}
macro_rules! into_java_ptr_class {
($type: ty, $jclass: literal) => {
impl<'j> jni_toolbox::IntoJavaObject<'j> for $type {
const CLASS: &'static str = $jclass;
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let class = env.find_class(Self::CLASS)?;
env.new_object(
class,
"(J)V",
&[jni::objects::JValueGen::Long(
Box::into_raw(Box::new(self)) as jni::sys::jlong
)],
)
}
}
};
}
into_java_ptr_class!(crate::Client, "mp/code/Client");
into_java_ptr_class!(crate::Workspace, "mp/code/Workspace");
into_java_ptr_class!(crate::cursor::Controller, "mp/code/CursorController");
into_java_ptr_class!(crate::buffer::Controller, "mp/code/BufferController");
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User {
const CLASS: &'static str = "mp/code/data/User";
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let id_field = self.id.into_java_object(env)?;
let name_field = env.new_string(self.name)?;
let class = env.find_class(Self::CLASS)?;
env.new_object(
&class,
"(Ljava/util/UUID;Ljava/lang/String;)V",
&[
jni::objects::JValueGen::Object(&id_field),
jni::objects::JValueGen::Object(&name_field),
],
)
}
}
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Event {
const CLASS: &'static str = "mp/code/Workspace$Event";
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let (ordinal, arg) = match self {
crate::api::Event::UserJoin { name: arg } => (0, env.new_string(arg)?),
crate::api::Event::UserLeave { name: arg } => (1, env.new_string(arg)?),
crate::api::Event::FileTreeUpdated { path: arg } => (2, env.new_string(arg)?),
};
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(Self::CLASS)?;
env.new_object(
event_class,
"(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
&[
jni::objects::JValueGen::Object(&event_type),
jni::objects::JValueGen::Object(&arg),
],
)
}
}
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::BufferUpdate {
const CLASS: &'static str = "mp/code/data/BufferUpdate";
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let class = env.find_class(Self::CLASS)?;
let hash_class = env.find_class("java/util/OptionalLong")?;
let hash = if let Some(h) = self.hash {
env.call_static_method(
hash_class,
"of",
"(J)Ljava/util/OptionalLong;",
&[jni::objects::JValueGen::Long(h)],
)
} else {
env.call_static_method(hash_class, "empty", "()Ljava/util/OptionalLong;", &[])
}?
.l()?;
let version = self.version.into_java_object(env)?;
let change = self.change.into_java_object(env)?;
env.new_object(
class,
"(Ljava/util/OptionalLong;[JLmp/code/data/TextChange;)V",
&[
jni::objects::JValueGen::Object(&hash),
jni::objects::JValueGen::Object(&version),
jni::objects::JValueGen::Object(&change),
],
)
}
}
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::TextChange {
const CLASS: &'static str = "mp/code/data/TextChange";
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let content = env.new_string(self.content)?;
let class = env.find_class(Self::CLASS)?;
env.new_object(
class,
"(JJLjava/lang/String;)V",
&[
jni::objects::JValueGen::Long(self.start_idx.into()),
jni::objects::JValueGen::Long(self.end_idx.into()),
jni::objects::JValueGen::Object(&content),
],
)
}
}
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Cursor {
const CLASS: &'static str = "mp/code/data/Cursor";
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let class = env.find_class(Self::CLASS)?;
let user = env.new_string(&self.user)?;
let sel = self.sel.into_java_object(env)?;
env.new_object(
class,
"(Ljava/lang/String;Lmp/code/data/Selection;)V",
&[
jni::objects::JValueGen::Object(&user),
jni::objects::JValueGen::Object(&sel),
],
)
}
}
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Selection {
const CLASS: &'static str = "mp/code/data/Selection";
fn into_java_object(
self,
env: &mut jni::JNIEnv<'j>,
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
let class = env.find_class(Self::CLASS)?;
let buffer = env.new_string(&self.buffer)?;
env.new_object(
class,
"(IIIILjava/lang/String;)V",
&[
jni::objects::JValueGen::Int(self.start_row),
jni::objects::JValueGen::Int(self.start_col),
jni::objects::JValueGen::Int(self.end_row),
jni::objects::JValueGen::Int(self.end_col),
jni::objects::JValueGen::Object(&buffer),
],
)
}
}
macro_rules! from_java_ptr {
($type: ty) => {
impl<'j> jni_toolbox::FromJava<'j> for &mut $type {
type From = jni::sys::jobject;
fn from_java(
_env: &mut jni::JNIEnv<'j>,
value: Self::From,
) -> Result<Self, jni::errors::Error> {
Ok(unsafe { Box::leak(Box::from_raw(value as *mut $type)) })
}
}
};
}
from_java_ptr!(crate::Client);
from_java_ptr!(crate::Workspace);
from_java_ptr!(crate::cursor::Controller);
from_java_ptr!(crate::buffer::Controller);
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
type From = jni::objects::JObject<'j>;
fn from_java(
env: &mut jni::JNIEnv<'j>,
config: Self::From,
) -> 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, "getAsInt", "()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()?;
let bool_true = env
.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;")?
.l()?;
Some(
env.call_method(
field,
"equals",
"(Ljava/lang/Object;)Z",
&[jni::objects::JValueGen::Object(&bool_true)],
)?
.z()?,
) } else {
None
}
};
Ok(Self {
username,
password,
host,
port,
tls,
})
}
}
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Selection {
type From = jni::objects::JObject<'j>;
fn from_java(
env: &mut jni::JNIEnv<'j>,
cursor: Self::From,
) -> 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()
};
Ok(Self {
start_row,
start_col,
end_row,
end_col,
buffer,
})
}
}
impl<'j> jni_toolbox::FromJava<'j> for crate::api::TextChange {
type From = jni::objects::JObject<'j>;
fn from_java(
env: &mut jni::JNIEnv<'j>,
change: Self::From,
) -> Result<Self, jni::errors::Error> {
let start = env
.get_field(&change, "startIdx", "J")?
.j()?
.clamp(0, u32::MAX.into()) as u32;
let end = env
.get_field(&change, "endIdx", "J")?
.j()?
.clamp(0, u32::MAX.into()) 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()
};
Ok(Self {
start_idx: start,
end_idx: end,
content,
})
}
}