diff --git a/java/interop-rust/src/main/java/one/tesseract/interop/rust/RustObject.kt b/java/interop-rust/src/main/java/one/tesseract/interop/rust/RustObject.kt new file mode 100644 index 0000000..5494f31 --- /dev/null +++ b/java/interop-rust/src/main/java/one/tesseract/interop/rust/RustObject.kt @@ -0,0 +1,9 @@ +package one.tesseract.interop.rust + +open class RustObject(protected val handle: Long) { + private external fun drop() + + protected fun finalize() { + drop() + } +} \ No newline at end of file diff --git a/rust/interop/src/error.rs b/rust/interop/src/error.rs new file mode 100644 index 0000000..22049d2 --- /dev/null +++ b/rust/interop/src/error.rs @@ -0,0 +1,42 @@ +//===------------ error.rs --------------------------------------------===// +// Copyright 2022, Tesseract Systems, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +use std::{default::Default, error::Error}; + +use jni::JNIEnv; + +pub fn deresultify(env: &JNIEnv, fun: F) -> T +where + T: Default, + I: Into, + F: FnOnce() -> Result>, +{ + match fun() { + Err(err) => { + //temporary solution. need a proper conversion to Exception with a class + let message: &str = &err.to_string(); + + match env.throw(message) { + Ok(_) => T::default(), + Err(e) => { + debug!("Error '{}' occured, but couldn't be thrown as Exception because JNI returned: {}", message, e.to_string()); + panic!("Error '{}' occured, but couldn't be thrown as Exception because JNI returned: {}", message, e.to_string()) + }, + } + } + Ok(value) => value.into() + } +} diff --git a/rust/interop/src/lib.rs b/rust/interop/src/lib.rs index b30c310..7979279 100644 --- a/rust/interop/src/lib.rs +++ b/rust/interop/src/lib.rs @@ -14,6 +14,8 @@ // limitations under the License. //===----------------------------------------------------------------------===// +#![feature(iterator_try_collect)] + #[macro_use] extern crate log; extern crate android_log; @@ -21,17 +23,21 @@ extern crate android_log; pub mod bi_consumer; mod contexted_global; pub mod env; -pub mod errors; +pub mod error; mod exception; mod jfuture; pub mod thread_pool; pub mod pointer; pub mod future; +pub mod object; pub use contexted_global::ContextedGlobal; pub use exception::Exception; +pub use error::deresultify; pub use jfuture::JFuture; +pub use object::{JavaDesc, JavaWrappableDesc, JavaWrappable, JavaConvertibleDesc, JavaConvertible}; + #[cfg(test)] mod tests { #[test] diff --git a/rust/interop/src/object/convertible.rs b/rust/interop/src/object/convertible.rs new file mode 100644 index 0000000..b00816a --- /dev/null +++ b/rust/interop/src/object/convertible.rs @@ -0,0 +1,79 @@ +//===------------ convertible.rs --------------------------------------------===// +// Copyright 2022, Tesseract Systems, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +use crate::env::AndroidEnv; +use jni::{JNIEnv, objects::{JObject, JValue}, errors::Result}; + +pub trait JavaConvertibleDesc: Sized { + fn java_class<'a>(&'a self) -> &'a str; + + fn fields() -> Vec<(&'static str, &'static str)>; //(name, type) + + fn into_values<'a: 'b, 'b>(self, env: &'b JNIEnv<'a>) -> Result>>; + fn from_values<'a: 'b, 'b>(env: &'b JNIEnv<'a>, values: &[JValue<'a>]) -> Result; +} + +pub trait JavaConvertible: Sized { + fn into_java<'a: 'b, 'b>(self, env: &'b JNIEnv<'a>) -> Result>; + fn from_java<'a: 'b, 'b>(env: &'b JNIEnv<'a>, object: JObject<'a>) -> Result; +} + +impl JavaConvertible for T where T: JavaConvertibleDesc { + fn into_java<'a: 'b, 'b>(self, env: &'b JNIEnv<'a>) -> Result> { + let clazz = env.find_class_android(self.java_class())?; + + let types: Vec<&str> = Self::fields().into_iter().map(|field| field.1).collect(); + let sig = format!("({})V", types.join("")); + + let value = self.into_values(env)?; + + env.new_object(clazz, sig, &value) + } + + fn from_java<'a: 'b, 'b>(env: &'b JNIEnv<'a>, object: JObject<'a>) -> Result { + fn uppercase_first_letter(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } + } + + fn getter_prefix(ret: &str) -> &'static str { + let mut c = ret.chars(); + match c.next() { + None => panic!("Put a sig"), + Some(f) => { + match f { + 'Z' => "is", + _ => "get" + } + } + } + } + + let fields = Self::fields(); + + let values : Vec = fields.into_iter().map(|(field_name, ret)| { + let getter = format!("{}{}", getter_prefix(ret), uppercase_first_letter(field_name)); + let sig = format!("(){}", ret); + + env.call_method(object, &getter, sig, &[]) + }).try_collect()?; + + Self::from_values(env, &values) + } +} \ No newline at end of file diff --git a/rust/interop/src/errors.rs b/rust/interop/src/object/desc.rs similarity index 60% rename from rust/interop/src/errors.rs rename to rust/interop/src/object/desc.rs index 7db4ec6..bca723f 100644 --- a/rust/interop/src/errors.rs +++ b/rust/interop/src/object/desc.rs @@ -1,4 +1,4 @@ -//===------------ errors.rs --------------------------------------------===// +//===------------ desc.rs --------------------------------------------===// // Copyright 2022, Tesseract Systems, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,19 +14,6 @@ // limitations under the License. //===----------------------------------------------------------------------===// -/*use thiserror::Error; - -#[derive(Debug, Error)] -enum JError { - #[error("An error came from Rust JNI: {0}")] - Jni(jni::errors::Error), - - #[error("An error came from Rust JNI: {0:?}")] - Exception(jni::objects::JThrowable), -} - -impl From<::jni::errors::Error> for Error { - fn from(_: ::std::sync::TryLockError) -> Self { - Error::TryLock - } -}*/ +pub trait JavaDesc { + fn java_class<'a>(&'a self) -> &'a str; +} \ No newline at end of file diff --git a/rust/interop/src/object/mod.rs b/rust/interop/src/object/mod.rs new file mode 100644 index 0000000..998764b --- /dev/null +++ b/rust/interop/src/object/mod.rs @@ -0,0 +1,7 @@ +mod convertible; +mod wrappable; +mod desc; + +pub use desc::{JavaDesc}; +pub use wrappable::{JavaWrappableDesc, JavaWrappable}; +pub use convertible::{JavaConvertibleDesc, JavaConvertible}; \ No newline at end of file diff --git a/rust/interop/src/object/wrappable.rs b/rust/interop/src/object/wrappable.rs new file mode 100644 index 0000000..7026ea8 --- /dev/null +++ b/rust/interop/src/object/wrappable.rs @@ -0,0 +1,109 @@ +//===------------ wrappable.rs --------------------------------------------===// +// Copyright 2022, Tesseract Systems, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +use std::sync::Arc; + +use jni::objects::{JObject, JValue}; +use jni::JNIEnv; +use jni::errors::Result; + +use jni_fn::jni_fn; + +use crate::env::AndroidEnv; +use crate::pointer::ArcPointer; +use crate::error::deresultify; + +use super::desc::JavaDesc; + +struct WrappableHandle { + pointer: i64, + dropper: Option> +} + +impl WrappableHandle { + fn from_arc(arc: Arc) -> Self { + let long_p: i64 = ArcPointer::new(arc).into(); + + Self { pointer: long_p, dropper: Some(Box::new(|pointer| { + let arc_pointer = ArcPointer::::of(pointer); + arc_pointer.destroy() + }))} + } + + fn from_java_ref(object: JObject, env: &JNIEnv) -> Result> { + let handle_lp = env + .call_method(object, "getHandle", "()J", &[])? + .j()?; + + let handle_p = handle_lp as *mut WrappableHandle; + Ok(unsafe { Box::from_raw(handle_p) }) + } + + fn arc(&self) -> Arc { + ArcPointer::of(self.pointer).arc() + } +} + +impl Drop for WrappableHandle { + fn drop(&mut self) { + let dropper = self.dropper.take(); + + if let Some(dropper) = dropper { + dropper(self.pointer) + } + } +} + +pub trait JavaWrappableDesc: JavaDesc { +} + +pub trait JavaWrappable { + fn java_ref<'a: 'b, 'b, D: JavaDesc>(self: Arc, env: &'b JNIEnv<'a>, desc: Option) -> Result>; + fn from_java_ref(object: JObject, env: &JNIEnv) -> Result>; +} + +impl JavaWrappable for T where T: JavaWrappableDesc { + fn java_ref<'a: 'b, 'b, D: JavaDesc>(self: Arc, env: &'b JNIEnv<'a>, desc: Option) -> Result> { + let clazz = match &desc { + Some(desc) => desc.java_class(), + None => self.java_class(), + }; + + let clazz = env + .find_class_android(clazz)?; + + let handle = WrappableHandle::from_arc(self); + let handle_p = Box::into_raw(Box::new(handle)) as *const () as i64; + + let obj = env.new_object(clazz, "(J)V", &[JValue::from(handle_p)])?; + + Ok(obj) + } + + fn from_java_ref(object: JObject, env: &JNIEnv) -> Result> { + let handle= Box::leak(WrappableHandle::from_java_ref(object, env)?); + Ok(handle.arc()) + } +} + +#[jni_fn("one.tesseract.interop.rust.RustObject")] +pub fn drop(env: JNIEnv, this: JObject) { + deresultify(&env, || { + let handle = WrappableHandle::from_java_ref(this, &env)?; + Ok(drop(handle)) + }) +} +