mirror of
https://github.com/hexedtech/jni-toolbox.git
synced 2024-11-22 15:34:56 +01:00
chore: Merge branch 'feat/intermediate-trait'
This commit is contained in:
commit
5259d8716d
17 changed files with 622 additions and 369 deletions
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -2,8 +2,6 @@ name: test
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
@ -28,3 +26,5 @@ jobs:
|
|||
toolchain: ${{ matrix.toolchain }}
|
||||
- run: cargo build --verbose
|
||||
- run: cargo test --verbose
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- run: gradle test
|
||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1 +1,10 @@
|
|||
# rust
|
||||
/target
|
||||
|
||||
# gradle
|
||||
/build
|
||||
/bin
|
||||
/.settings
|
||||
/.gradle
|
||||
.project
|
||||
.classpath
|
||||
|
|
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -57,7 +57,7 @@ name = "jni-toolbox"
|
|||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"jni",
|
||||
"jni-toolbox-macro 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jni-toolbox-macro",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -71,14 +71,11 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-toolbox-macro"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9970cad895bb316f70956593710d675d27a480ddbb8099f7e313042463a16d9b"
|
||||
name = "jni-toolbox-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"jni",
|
||||
"jni-toolbox",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["macro"]
|
||||
members = ["macro", "src/test"]
|
||||
|
||||
[package]
|
||||
name = "jni-toolbox"
|
||||
|
@ -13,6 +13,11 @@ version = "0.1.3"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
jni-toolbox-macro = "0.1.3"
|
||||
#jni-toolbox-macro = "0.1.3"
|
||||
jni-toolbox-macro = { path = "./macro" }
|
||||
jni = "0.21"
|
||||
uuid = { version = "1.10", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
uuid = ["dep:uuid"]
|
||||
|
|
137
README.md
137
README.md
|
@ -3,13 +3,12 @@
|
|||
[![Crates.io Version](https://img.shields.io/crates/v/jni-toolbox)](https://crates.io/crates/jni-toolbox)
|
||||
[![docs.rs](https://img.shields.io/docsrs/jni-toolbox)](https://docs.rs/jni-toolbox)
|
||||
|
||||
This is a simple crate built around [jni-rs](https://github.com/jni-rs/jni-rs) to automatically generate JNI-compatible extern functions.
|
||||
|
||||
this is a simple crate built around [jni-rs](https://github.com/jni-rs/jni-rs) to automatically generate JNI-compatible extern functions
|
||||
It also wraps functions returning `Result<>`, making short-circuiting easy.
|
||||
|
||||
it also wraps functions returning `Result<>`, making short-circuiting easy
|
||||
|
||||
## usage
|
||||
just specify package and class on your function, and done!
|
||||
## Usage
|
||||
Just specify package and class on your function, and done!
|
||||
|
||||
```rust
|
||||
#[jni_toolbox::jni(package = "your.package.path", class = "ContainerClass")]
|
||||
|
@ -18,14 +17,13 @@ fn your_function_name(arg: String) -> Result<Vec<String>, String> {
|
|||
}
|
||||
```
|
||||
|
||||
### conversions
|
||||
every type that must go into/from Java must implement `IntoJava` or `FromJava` (methods will receive a `&mut JNIEnv` and can return errors).
|
||||
most primitives already have them implemented. conversions are automatic and the wrapper function will invoke IntoJava/FromJava for every type,
|
||||
passing an environment reference.
|
||||
### Conversions
|
||||
Every type that is meant to be sent to Java must implement `IntoJavaObject` (or, unlikely, `IntoJavaPrimitive`); every type that is meant to be
|
||||
received from Java must implement `FromJava`. Most primitives and a few common types should already be implemented.
|
||||
|
||||
```rust
|
||||
impl<'j> IntoJava for MyClass {
|
||||
type T = jni::sys::jobject;
|
||||
impl<'j> IntoJavaObject for MyClass {
|
||||
type T = jni::objects::JObject<'j>
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
|
||||
let hello = env.new_string("world")?;
|
||||
// TODO!!
|
||||
|
@ -33,15 +31,14 @@ impl<'j> IntoJava for MyClass {
|
|||
}
|
||||
```
|
||||
|
||||
### pointers
|
||||
to return pointer type values, add the `ptr` attribute
|
||||
### Pointers
|
||||
Note that, while it is possible to pass raw pointers to the JVM, it is not safe by default and must be done with extreme care.
|
||||
|
||||
note that, while possible to pass raw pointers to the JVM, it is not safe by default and must be done with extreme care.
|
||||
|
||||
### exceptions
|
||||
### Exceptions
|
||||
Errors are thrown automatically when a `Result` is an error. For your errors to work, you must implement the `JniToolboxError` trait for your errors,
|
||||
(which just returns the path to your Java error class) and then make a Java error wrapper which can be constructed with a single string argument.
|
||||
functions returning `Result`s will automatically have their return value unwrapped and, if is an err, throw an exception and return early.
|
||||
|
||||
Functions returning `Result`s will automatically have their return value unwrapped and, if is an err, throw an exception and return early.
|
||||
|
||||
```rust
|
||||
impl JniToolboxError for MyError {
|
||||
|
@ -53,113 +50,75 @@ impl JniToolboxError for MyError {
|
|||
|
||||
```java
|
||||
package my.package.some;
|
||||
public class MyError {
|
||||
public class MyError extends Throwable {
|
||||
public MyError(String x) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
to throw simple exceptions, it's possible to use the `exception` attribute. just pass your exception's path (must be constructable with a single string argument!)
|
||||
To throw simple exceptions, it's possible to use the `exception` attribute. Pass the exception's fully qualified name (must have a constructor
|
||||
that takes in a single `String` argument).
|
||||
|
||||
|
||||
|
||||
### examples
|
||||
the following function:
|
||||
### Examples
|
||||
The following function:
|
||||
```rust
|
||||
#[jni(package = "mp.code", class = "Client", ptr)]
|
||||
fn connect(config: Config) -> Result<Client, ConnectionError> {
|
||||
tokio().block_on(Client::connect(config))
|
||||
super::tokio().block_on(Client::connect(config))
|
||||
}
|
||||
```
|
||||
|
||||
gets turned into these two functions:
|
||||
|
||||
<details><summary>show macro expansion</summary>
|
||||
generates a matching expanded function invoking it:
|
||||
|
||||
```rust
|
||||
fn connect(config: Config) -> Result<Client, ConnectionError> {
|
||||
tokio().block_on(Client::connect(config))
|
||||
super::tokio().block_on(Client::connect(config))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(unused_mut)]
|
||||
#[allow(unused_unit)]
|
||||
pub extern "system" fn Java_mp_code_Client_connect<'local>(
|
||||
mut env: jni::JNIEnv<'local>,
|
||||
_class: jni::objects::JClass<'local>,
|
||||
mut config: <Config as jni_toolbox::FromJava<'local>>::T,
|
||||
) -> <Client as jni_toolbox::IntoJava<'local>>::T {
|
||||
config: <Config as jni_toolbox::FromJava<'local>>::From,
|
||||
) -> <Client as jni_toolbox::IntoJava<'local>>::Ret {
|
||||
use jni_toolbox::{FromJava, IntoJava, JniToolboxError};
|
||||
let mut env_copy = unsafe { env.unsafe_clone() };
|
||||
let config_new = match jni_toolbox::from_java_static::<Config>(&mut env, config) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let _ = env.throw_new(
|
||||
"java/lang/RuntimeException",
|
||||
$crate::__export::must_use({
|
||||
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
|
||||
res
|
||||
}),
|
||||
);
|
||||
let _ = env.throw_new(e.jclass(), format!("{e:?}"));
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
match connect(config_new) {
|
||||
Err(e) => match env_copy.find_class(e.jclass()) {
|
||||
Err(e) => {
|
||||
$crate::panicking::panic_fmt($crate::const_format_args!(
|
||||
"error throwing Java exception -- failed resolving error class: {e}"
|
||||
));
|
||||
}
|
||||
Ok(class) => match env_copy.new_string($crate::__export::must_use({
|
||||
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
|
||||
res
|
||||
})) {
|
||||
Err(e) => {
|
||||
$crate::panicking::panic_fmt($crate::const_format_args!(
|
||||
"error throwing Java exception -- failed creating error string: {e}"
|
||||
));
|
||||
}
|
||||
Ok(msg) => match env_copy.new_object(
|
||||
class,
|
||||
"(Ljava/lang/String;)V",
|
||||
&[jni::objects::JValueGen::Object(&msg)],
|
||||
) {
|
||||
Err(e) => {
|
||||
$crate::panicking::panic_fmt($crate::const_format_args!(
|
||||
"error throwing Java exception -- failed creating object: {e}"
|
||||
));
|
||||
}
|
||||
Ok(obj) => match env_copy.throw(jni::objects::JThrowable::from(obj)) {
|
||||
Err(e) => {
|
||||
$crate::panicking::panic_fmt($crate::const_format_args!(
|
||||
"error throwing Java exception -- failed throwing: {e}"
|
||||
));
|
||||
}
|
||||
let result = connect(config_new);
|
||||
let ret = match result {
|
||||
Ok(x) => x,
|
||||
Err(e) => match env.find_class(e.jclass()) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed resolving error class: {e}"),
|
||||
Ok(class) => match env.new_string(format!("{e:?}")) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed creating error string: {e}"),
|
||||
Ok(msg) => match env.new_object(class, "(Ljava/lang/String;)V", &[jni::objects::JValueGen::Object(&msg)]) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed creating object: {e}"));
|
||||
Ok(obj) => match env.throw(jni::objects::JThrowable::from(obj)) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed throwing: {e}"),
|
||||
Ok(_) => return std::ptr::null_mut(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Ok(ret) => match ret.into_java(&mut env_copy) {
|
||||
Ok(fin) => return fin,
|
||||
Err(e) => {
|
||||
let _ = env_copy.throw_new(
|
||||
"java/lang/RuntimeException",
|
||||
$crate::__export::must_use({
|
||||
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
|
||||
res
|
||||
}),
|
||||
);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
},
|
||||
};
|
||||
match ret.into_java(&mut env) {
|
||||
Ok(fin) => fin,
|
||||
Err(e) => {
|
||||
let _ = env.throw_new(e.jclass(), format!("{e:?}"));
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## status
|
||||
this crate is rather early and intended mostly to maintain [`codemp`](https://github.com/hexedtech/codemp) java bindings, however it's also quite small and only runs at comptime, so should be rather safe to use
|
||||
## Status
|
||||
This crate is early and intended mostly to maintain [`codemp`](https://github.com/hexedtech/codemp)'s Java bindings, so things not used
|
||||
there may be missing or slightly broken. However, the crate is also quite small and only runs at compile time, so trying it out in your
|
||||
own project should not be a problem.
|
||||
|
|
24
build.gradle
Normal file
24
build.gradle
Normal file
|
@ -0,0 +1,24 @@
|
|||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
task cargoBuild(type: Exec) {
|
||||
workingDir '.'
|
||||
commandLine 'cargo', 'build', '-p', 'jni-toolbox-test'
|
||||
}
|
||||
|
||||
test {
|
||||
dependsOn cargoBuild
|
||||
outputs.upToDateWhen { false }
|
||||
useJUnitPlatform()
|
||||
systemProperty 'java.library.path','target/debug'
|
||||
}
|
|
@ -22,31 +22,33 @@ fn unpack_pat(pat: syn::Pat) -> Result<TokenStream, syn::Error> {
|
|||
}
|
||||
}
|
||||
|
||||
fn type_equals(ty: Box<syn::Type>, search: impl AsRef<str>) -> bool {
|
||||
fn bare_type(ty: Box<syn::Type>) -> Option<syn::TypePath> {
|
||||
match *ty {
|
||||
syn::Type::Array(_) => false,
|
||||
syn::Type::BareFn(_) => false,
|
||||
syn::Type::ImplTrait(_) => false,
|
||||
syn::Type::Infer(_) => false,
|
||||
syn::Type::Macro(_) => false,
|
||||
syn::Type::Never(_) => false,
|
||||
syn::Type::Ptr(_) => false,
|
||||
syn::Type::Slice(_) => false,
|
||||
syn::Type::TraitObject(_) => false,
|
||||
syn::Type::Tuple(_) => false,
|
||||
syn::Type::Verbatim(_) => false,
|
||||
syn::Type::Group(g) => type_equals(g.elem, search),
|
||||
syn::Type::Paren(p) => type_equals(p.elem, search),
|
||||
syn::Type::Reference(r) => type_equals(r.elem, search),
|
||||
syn::Type::Path(ty) => {
|
||||
ty.path.segments
|
||||
.last()
|
||||
.map_or(false, |e| e.ident == search.as_ref())
|
||||
},
|
||||
_ => false,
|
||||
syn::Type::Array(a) => bare_type(a.elem),
|
||||
syn::Type::BareFn(_) => None,
|
||||
syn::Type::ImplTrait(_) => None,
|
||||
syn::Type::Infer(_) => None,
|
||||
syn::Type::Macro(_) => None,
|
||||
syn::Type::Never(_) => None,
|
||||
syn::Type::TraitObject(_) => None,
|
||||
syn::Type::Verbatim(_) => None,
|
||||
syn::Type::Ptr(p) => bare_type(p.elem),
|
||||
syn::Type::Slice(s) => bare_type(s.elem),
|
||||
syn::Type::Tuple(t) => bare_type(Box::new(t.elems.first()?.clone())), // TODO
|
||||
syn::Type::Group(g) => bare_type(g.elem),
|
||||
syn::Type::Paren(p) => bare_type(p.elem),
|
||||
syn::Type::Reference(r) => bare_type(r.elem),
|
||||
syn::Type::Path(ty) => Some(ty),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_equals(ty: Box<syn::Type>, search: impl AsRef<str>) -> bool {
|
||||
let Some(ty) = bare_type(ty) else { return false };
|
||||
let Some(last) = ty.path.segments.last() else { return false };
|
||||
last.ident == search.as_ref()
|
||||
}
|
||||
|
||||
impl ArgumentOptions {
|
||||
pub(crate) fn parse_args(fn_item: &syn::ItemFn, ret_expr: TokenStream) -> Result<Self, syn::Error> {
|
||||
let mut arguments = Vec::new();
|
||||
|
@ -83,9 +85,9 @@ impl ArgumentOptions {
|
|||
if pass_env {
|
||||
if let Some(arg) = args_iter.next() {
|
||||
let pat = arg.pat;
|
||||
let ty = arg.ty;
|
||||
let ty = bare_type(arg.ty);
|
||||
incoming.append_all(quote::quote!( mut #pat: #ty,));
|
||||
forwarding.append_all(quote::quote!( #pat,));
|
||||
forwarding.append_all(quote::quote!( &mut #pat,));
|
||||
}
|
||||
} else {
|
||||
incoming.append_all(quote::quote!( mut #env: jni::JNIEnv<'local>,));
|
||||
|
@ -104,12 +106,12 @@ impl ArgumentOptions {
|
|||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
// TODO should we panic here instead?
|
||||
let _ = #env.throw_new("java/lang/RuntimeException", format!("{e:?}"));
|
||||
let _ = #env.throw_new(e.jclass(), format!("{e:?}"));
|
||||
return #ret_expr;
|
||||
},
|
||||
};
|
||||
});
|
||||
incoming.append_all(quote::quote!( mut #pat: <#ty as jni_toolbox::FromJava<'local>>::T,));
|
||||
incoming.append_all(quote::quote!( #pat: <#ty as jni_toolbox::FromJava<'local>>::From,));
|
||||
forwarding.append_all(quote::quote!( #new_pat,));
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ pub(crate) struct AttrsOptions {
|
|||
pub(crate) package: String,
|
||||
pub(crate) class: String,
|
||||
pub(crate) exception: Option<String>,
|
||||
pub(crate) return_pointer: bool,
|
||||
pub(crate) inline: bool,
|
||||
}
|
||||
|
||||
impl AttrsOptions {
|
||||
|
@ -14,7 +14,7 @@ impl AttrsOptions {
|
|||
let mut package = None;
|
||||
let mut class = None;
|
||||
let mut exception = None;
|
||||
let mut return_pointer = false;
|
||||
let mut inline = false;
|
||||
|
||||
for attr in attrs {
|
||||
match what_next {
|
||||
|
@ -24,7 +24,8 @@ impl AttrsOptions {
|
|||
"package" => what_next = WhatNext::Package,
|
||||
"class" => what_next = WhatNext::Class,
|
||||
"exception" => what_next = WhatNext::Exception,
|
||||
"ptr" => return_pointer = true,
|
||||
"ptr" => {}, // accepted for backwards compatibility
|
||||
"inline" => inline = true,
|
||||
_ => return Err(syn::Error::new(Span::call_site(), "unexpected attribute on macro: {attr}")),
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ impl AttrsOptions {
|
|||
let Some(package) = package else { return Err(syn::Error::new(Span::call_site(), "missing required attribute 'package'")) };
|
||||
let Some(class) = class else { return Err(syn::Error::new(Span::call_site(), "missing required attribute 'class'")) };
|
||||
|
||||
Ok(Self { package, class, exception, return_pointer })
|
||||
Ok(Self { package, class, exception, inline })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ mod wrapper;
|
|||
mod args;
|
||||
mod ret;
|
||||
|
||||
/// wrap this function in in a JNI exported fn
|
||||
/// Wrap this function in in a JNI exported fn.
|
||||
#[proc_macro_attribute]
|
||||
pub fn jni(
|
||||
attrs: proc_macro::TokenStream,
|
||||
|
|
|
@ -6,13 +6,16 @@ use syn::{ReturnType, Type};
|
|||
pub(crate) struct ReturnOptions {
|
||||
pub(crate) ty: Option<Box<Type>>,
|
||||
pub(crate) result: bool,
|
||||
pub(crate) pointer: bool,
|
||||
pub(crate) void: bool,
|
||||
}
|
||||
|
||||
const PRIMITIVE_TYPES: [&str; 7] = ["i8", "i16", "i32", "i64", "f32", "f64", "bool"];
|
||||
|
||||
impl ReturnOptions {
|
||||
pub(crate) fn parse_signature(ret: &ReturnType) -> Result<Self, syn::Error> {
|
||||
match ret {
|
||||
syn::ReturnType::Default => Ok(Self { ty: None, result: false, void: true }),
|
||||
syn::ReturnType::Default => Ok(Self { ty: None, result: false, void: true, pointer: false }),
|
||||
syn::ReturnType::Type(_tok, ty) => match *ty.clone() {
|
||||
syn::Type::Path(path) => {
|
||||
let Some(last) = path.path.segments.last() else {
|
||||
|
@ -26,14 +29,19 @@ impl ReturnOptions {
|
|||
syn::PathArguments::AngleBracketed(ref generics) => for generic in generics.args.iter() {
|
||||
match generic {
|
||||
syn::GenericArgument::Lifetime(_) => continue,
|
||||
syn::GenericArgument::Type(ty) => return Ok(Self { ty: Some(Box::new(ty.clone())), result: true, void: is_void(ty) }),
|
||||
_ => return Err(syn::Error::new(Span::call_site(), "unexpected type in Result")),
|
||||
syn::GenericArgument::Type(ty) => {
|
||||
// TODO checking by making ty a token stream and then string equals is not exactly great!
|
||||
let pointer = !PRIMITIVE_TYPES.iter().any(|t| ty.to_token_stream().to_string() == *t);
|
||||
return Ok(Self { ty: Some(Box::new(ty.clone())), result: true, void: is_void(ty), pointer });
|
||||
},
|
||||
_ => return Err(syn::Error::new(Span::call_site(), "unexpected type in Result"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { ty: Some(Box::new(Type::Path(path.clone()))), result: false, void: false })
|
||||
let pointer = !PRIMITIVE_TYPES.iter().any(|t| last.ident == t);
|
||||
Ok(Self { ty: Some(Box::new(Type::Path(path.clone()))), result: false, void: false, pointer })
|
||||
},
|
||||
_ => Err(syn::Error::new(Span::call_site(), "unsupported return type")),
|
||||
},
|
||||
|
@ -42,8 +50,8 @@ impl ReturnOptions {
|
|||
|
||||
pub(crate) fn tokens(&self) -> TokenStream {
|
||||
match &self.ty { // TODO why do we need to invoke syn::Token! macro ???
|
||||
Some(t) => quote::quote!( -> <#t as jni_toolbox::IntoJava<'local>>::T ),
|
||||
None => ReturnType::Default.to_token_stream(),
|
||||
Some(t) => quote::quote!( -> <#t as jni_toolbox::IntoJava<'local>>::Ret )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::TokenStreamExt;
|
||||
use syn::Item;
|
||||
|
||||
use crate::{args::ArgumentOptions, attrs::AttrsOptions, ret::ReturnOptions};
|
||||
|
||||
pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
|
||||
let mut out = TokenStream::new();
|
||||
|
||||
let Item::Fn(fn_item) = syn::parse2(input.clone())? else {
|
||||
return Err(syn::Error::new(Span::call_site(), "#[jni] is only supported on functions"));
|
||||
};
|
||||
|
@ -15,7 +12,7 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re
|
|||
let ret = ReturnOptions::parse_signature(&fn_item.sig.output)?;
|
||||
let return_expr = if ret.void {
|
||||
quote::quote!( () )
|
||||
} else if attrs.return_pointer {
|
||||
} else if ret.pointer {
|
||||
quote::quote!( std::ptr::null_mut() )
|
||||
} else {
|
||||
quote::quote!( 0 )
|
||||
|
@ -24,99 +21,103 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re
|
|||
// TODO a bit ugly passing the return expr down... we should probably manage returns here
|
||||
let args = ArgumentOptions::parse_args(&fn_item, return_expr.clone())?;
|
||||
|
||||
|
||||
let return_type = ret.tokens();
|
||||
|
||||
let name = fn_item.sig.ident.to_string();
|
||||
let name_jni = name.replace("_", "_1");
|
||||
let fn_name_inner = syn::Ident::new(&name, Span::call_site());
|
||||
let fn_name = syn::Ident::new(&format!("Java_{}_{}_{name_jni}", attrs.package, attrs.class), Span::call_site());
|
||||
|
||||
|
||||
let incoming = args.incoming;
|
||||
// V----------------------------------V
|
||||
let header = quote::quote! {
|
||||
#[no_mangle]
|
||||
#[allow(unused_mut)]
|
||||
#[allow(unused_unit)]
|
||||
pub extern "system" fn #fn_name<'local>(#incoming) #return_type
|
||||
};
|
||||
// ^----------------------------------^
|
||||
|
||||
let env_ident = args.env;
|
||||
let forwarding = args.forwarding;
|
||||
|
||||
let transforming = args.transforming;
|
||||
let body = if ret.result { // wrap errors
|
||||
let transformations = quote::quote! {
|
||||
use jni_toolbox::{JniToolboxError, FromJava, IntoJava};
|
||||
#transforming
|
||||
};
|
||||
|
||||
|
||||
let env_iden = args.env;
|
||||
let forwarding = args.forwarding;
|
||||
let invocation = quote::quote! {
|
||||
let result = #fn_name_inner(#forwarding);
|
||||
};
|
||||
|
||||
|
||||
let error_handling = if ret.result {
|
||||
if let Some(exception) = attrs.exception {
|
||||
// V----------------------------------V
|
||||
quote::quote! {
|
||||
{
|
||||
use jni_toolbox::{JniToolboxError, FromJava, IntoJava};
|
||||
#transforming
|
||||
match #fn_name_inner(#forwarding) {
|
||||
Ok(ret) => ret,
|
||||
Err(e) => match #env_ident.throw_new(#exception, format!("{e:?}")) {
|
||||
Ok(_) => return #return_expr,
|
||||
Err(e) => panic!("error throwing java exception: {e}"),
|
||||
}
|
||||
let ret = match result {
|
||||
Ok(x) => x,
|
||||
Err(e) => match #env_iden.throw_new(#exception, format!("{e:?}")) {
|
||||
Ok(_) => return #return_expr,
|
||||
Err(e) => panic!("error throwing java exception: {e}"),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// ^----------------------------------^
|
||||
} else {
|
||||
// V----------------------------------V
|
||||
quote::quote! {
|
||||
{
|
||||
use jni_toolbox::{JniToolboxError, FromJava, IntoJava};
|
||||
// NOTE: this should be SAFE! the cloned env reference lives less than the actual one, we just lack a
|
||||
// way to get it back from the called function and thus resort to unsafe cloning
|
||||
let mut env_copy = unsafe { #env_ident.unsafe_clone() };
|
||||
#transforming
|
||||
match #fn_name_inner(#forwarding) {
|
||||
Err(e) => match env_copy.find_class(e.jclass()) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed resolving error class: {e}"),
|
||||
Ok(class) => match env_copy.new_string(format!("{e:?}")) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed creating error string: {e}"),
|
||||
Ok(msg) => match env_copy.new_object(class, "(Ljava/lang/String;)V", &[jni::objects::JValueGen::Object(&msg)]) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed creating object: {e}"),
|
||||
Ok(obj) => match env_copy.throw(jni::objects::JThrowable::from(obj)) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed throwing: {e}"),
|
||||
Ok(_) => return #return_expr,
|
||||
},
|
||||
let ret = match result {
|
||||
Ok(x) => x,
|
||||
Err(e) => match #env_iden.find_class(e.jclass()) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed resolving error class: {e}"),
|
||||
Ok(class) => match #env_iden.new_string(format!("{e:?}")) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed creating error string: {e}"),
|
||||
Ok(msg) => match #env_iden.new_object(class, "(Ljava/lang/String;)V", &[jni::objects::JValueGen::Object(&msg)]) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed creating object: {e}"),
|
||||
Ok(obj) => match #env_iden.throw(jni::objects::JThrowable::from(obj)) {
|
||||
Err(e) => panic!("error throwing Java exception -- failed throwing: {e}"),
|
||||
Ok(_) => return #return_expr,
|
||||
},
|
||||
},
|
||||
}
|
||||
Ok(ret) => match ret.into_java(&mut env_copy) {
|
||||
Ok(fin) => return fin,
|
||||
Err(e) => {
|
||||
// TODO should we panic instead?
|
||||
let _ = env_copy.throw_new("java/lang/RuntimeException", format!("{e:?}"));
|
||||
return #return_expr;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// V----------------------------------V
|
||||
quote::quote! {
|
||||
{
|
||||
use jni_toolbox::{JniToolboxError, FromJava, IntoJava};
|
||||
#transforming
|
||||
match #fn_name_inner(#forwarding).into_java(&mut #env_ident) {
|
||||
Ok(res) => return res,
|
||||
Err(e) => {
|
||||
// TODO should we panic instead?
|
||||
let _ = #env_ident.throw_new("java/lang/RuntimeException", format!("{e:?}"));
|
||||
return #return_expr;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// ^----------------------------------^
|
||||
quote::quote!( let ret = result; )
|
||||
};
|
||||
|
||||
out.append_all(input);
|
||||
out.append_all(header);
|
||||
out.append_all(body);
|
||||
Ok(out)
|
||||
|
||||
let reverse_transformations = quote::quote! {
|
||||
match ret.into_java(&mut #env_iden) {
|
||||
Ok(fin) => fin,
|
||||
Err(e) => {
|
||||
// TODO should we panic instead?
|
||||
let _ = #env_iden.throw_new(e.jclass(), format!("{e:?}"));
|
||||
#return_expr
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let inline_macro = if attrs.inline {
|
||||
quote::quote!(#[inline])
|
||||
} else {
|
||||
quote::quote!()
|
||||
};
|
||||
|
||||
Ok(quote::quote! {
|
||||
#inline_macro
|
||||
#input
|
||||
|
||||
#header {
|
||||
|
||||
#transformations
|
||||
|
||||
#invocation
|
||||
|
||||
#error_handling
|
||||
|
||||
#reverse_transformations
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
121
src/from_java.rs
Normal file
121
src/from_java.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use jni::objects::{JObject, JObjectArray, JPrimitiveArray, JString, TypeArray};
|
||||
|
||||
|
||||
/// Used in the generated code to have proper type bindings. You probably didn't want
|
||||
/// to call this directly.
|
||||
pub fn from_java_static<'j, T: FromJava<'j>>(env: &mut jni::JNIEnv<'j>, val: T::From) -> Result<T, jni::errors::Error> {
|
||||
T::from_java(env, val)
|
||||
}
|
||||
|
||||
/// Specifies how a Java type should be converted before being fed to Rust.
|
||||
pub trait FromJava<'j> : Sized {
|
||||
/// The JNI type representing the input.
|
||||
type From : Sized;
|
||||
/// Attempts to convert this Java object into its Rust counterpart.
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::From) -> Result<Self, jni::errors::Error>;
|
||||
}
|
||||
|
||||
macro_rules! auto_from_java {
|
||||
($t: ty, $j: ty) => {
|
||||
impl<'j> FromJava<'j> for $t {
|
||||
type From = $j;
|
||||
|
||||
#[inline]
|
||||
fn from_java(_: &mut jni::JNIEnv, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto_from_java!(i64, jni::sys::jlong);
|
||||
auto_from_java!(i32, jni::sys::jint);
|
||||
auto_from_java!(i16, jni::sys::jshort);
|
||||
auto_from_java!(i8, jni::sys::jbyte);
|
||||
auto_from_java!(f32, jni::sys::jfloat);
|
||||
auto_from_java!(f64, jni::sys::jdouble);
|
||||
auto_from_java!(JObject<'j>, JObject<'j>);
|
||||
auto_from_java!(JString<'j>, JString<'j>);
|
||||
auto_from_java!(JObjectArray<'j>, JObjectArray<'j>);
|
||||
|
||||
impl<'j, T: TypeArray> FromJava<'j> for JPrimitiveArray<'j, T> {
|
||||
type From = JPrimitiveArray<'j, T>;
|
||||
|
||||
#[inline]
|
||||
fn from_java(_: &mut jni::JNIEnv, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> FromJava<'j> for char {
|
||||
type From = jni::sys::jchar;
|
||||
|
||||
#[inline]
|
||||
fn from_java(_: &mut jni::JNIEnv, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
char::from_u32(value.into()).ok_or_else(|| jni::errors::Error::WrongJValueType("char", "invalid u16"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> FromJava<'j> for bool {
|
||||
type From = jni::sys::jboolean;
|
||||
|
||||
#[inline]
|
||||
fn from_java(_: &mut jni::JNIEnv, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
Ok(value == 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> FromJava<'j> for String {
|
||||
type From = JString<'j>;
|
||||
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
if value.is_null() { return Err(jni::errors::Error::NullPtr("string can't be null")) };
|
||||
Ok(env.get_string(&value)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, T> FromJava<'j> for Option<T>
|
||||
where
|
||||
T: FromJava<'j, From: AsRef<JObject<'j>>>,
|
||||
{
|
||||
type From = T::From;
|
||||
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
if value.as_ref().is_null() { return Ok(None) };
|
||||
Ok(Some(T::from_java(env, value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, T: FromJava<'j, From = JObject<'j>>> FromJava<'j> for Vec<T> {
|
||||
type From = JObjectArray<'j>;
|
||||
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
let len = env.get_array_length(&value)?;
|
||||
let mut out = Vec::new();
|
||||
for i in 0..len {
|
||||
let el = env.get_object_array_element(&value, i)?;
|
||||
out.push(T::from_java(env, el)?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
impl<'j> FromJava<'j> for uuid::Uuid {
|
||||
type From = JObject<'j>;
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, uuid: Self::From) -> Result<Self, jni::errors::Error> {
|
||||
let lsb = u64::from_ne_bytes(
|
||||
env.call_method(&uuid, "getLeastSignificantBits", "()J", &[])?
|
||||
.j()?
|
||||
.to_ne_bytes()
|
||||
);
|
||||
|
||||
let msb = u64::from_ne_bytes(
|
||||
env.call_method(&uuid, "getMostSignificantBits", "()J", &[])?
|
||||
.j()?
|
||||
.to_ne_bytes()
|
||||
);
|
||||
|
||||
Ok(uuid::Uuid::from_u64_pair(msb, lsb))
|
||||
}
|
||||
}
|
139
src/into_java.rs
Normal file
139
src/into_java.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use jni::objects::JObject;
|
||||
|
||||
|
||||
/// Specifies how a Rust type should be converted into a Java primitive.
|
||||
pub trait IntoJava<'j> {
|
||||
/// The JNI type representing the output.
|
||||
type Ret;
|
||||
/// Attempts to convert this Rust object into a Java primitive.
|
||||
fn into_java(self, _: &mut jni::JNIEnv<'j>) -> Result<Self::Ret, jni::errors::Error>;
|
||||
}
|
||||
|
||||
macro_rules! auto_into_java {
|
||||
($t: ty, $j: ty) => {
|
||||
impl<'j> IntoJava<'j> for $t {
|
||||
type Ret = $j;
|
||||
|
||||
#[inline]
|
||||
fn into_java(self, _: &mut jni::JNIEnv<'j>) -> Result<Self::Ret, jni::errors::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: primitive arrays!
|
||||
|
||||
auto_into_java!(i64, jni::sys::jlong);
|
||||
auto_into_java!(i32, jni::sys::jint);
|
||||
auto_into_java!(i16, jni::sys::jshort);
|
||||
auto_into_java!(i8, jni::sys::jbyte);
|
||||
auto_into_java!(f32, jni::sys::jfloat);
|
||||
auto_into_java!(f64, jni::sys::jdouble);
|
||||
auto_into_java!((), ());
|
||||
|
||||
impl<'j> IntoJava<'j> for bool {
|
||||
type Ret = jni::sys::jboolean;
|
||||
|
||||
#[inline]
|
||||
fn into_java(self, _: &mut jni::JNIEnv) -> Result<Self::Ret, jni::errors::Error> {
|
||||
Ok(if self { 1 } else { 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, X: IntoJavaObject<'j>> IntoJava<'j> for X {
|
||||
type Ret = jni::sys::jobject;
|
||||
|
||||
#[inline]
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::Ret, jni::errors::Error> {
|
||||
Ok(self.into_java_object(env)?.as_raw())
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies how a Rust type should be converted into a Java object.
|
||||
pub trait IntoJavaObject<'j> {
|
||||
/// The Java class associated with this type.
|
||||
const CLASS: &'static str;
|
||||
/// Attempts to convert this Rust object into a Java object.
|
||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error>;
|
||||
}
|
||||
|
||||
impl<'j> IntoJavaObject<'j> for JObject<'j> {
|
||||
const CLASS: &'static str = "java/lang/Object";
|
||||
#[inline]
|
||||
fn into_java_object(self, _: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! auto_into_java_object {
|
||||
($t:ty, $cls:literal) => {
|
||||
impl<'j> IntoJavaObject<'j> for $t {
|
||||
const CLASS: &'static str = $cls;
|
||||
#[inline]
|
||||
fn into_java_object(self, _: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
Ok(self.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto_into_java_object!(jni::objects::JString<'j>, "java/lang/String");
|
||||
//auto_into_java_object!(jni::objects::JObjectArray<'j>, "java/lang/Object[]");
|
||||
//auto_into_java_object!(jni::objects::JIntArray<'j>, "java/lang/Integer[]");
|
||||
//auto_into_java_object!(jni::objects::JLongArray<'j>, "java/lang/Long[]");
|
||||
//auto_into_java_object!(jni::objects::JShortArray<'j>, "java/lang/Short[]");
|
||||
//auto_into_java_object!(jni::objects::JByteArray<'j>, "java/lang/Byte[]");
|
||||
//auto_into_java_object!(jni::objects::JCharArray<'j>, "java/lang/Char[]");
|
||||
//auto_into_java_object!(jni::objects::JFloatArray<'j>, "java/lang/Float[]");
|
||||
//auto_into_java_object!(jni::objects::JDoubleArray<'j>, "java/lang/Double[]");
|
||||
//auto_into_java_object!(jni::objects::JBooleanArray<'j>, "java/lang/Boolean[]");
|
||||
|
||||
|
||||
impl<'j> IntoJavaObject<'j> for &str {
|
||||
const CLASS: &'static str = "java/lang/String";
|
||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
Ok(env.new_string(self)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> IntoJavaObject<'j> for String {
|
||||
const CLASS: &'static str = "java/lang/String";
|
||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
self.as_str().into_java_object(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, T: IntoJavaObject<'j>> IntoJavaObject<'j> for Vec<T> {
|
||||
const CLASS: &'static str = T::CLASS; // TODO shouldnt it be 'Object[]' ?
|
||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
let mut array = env.new_object_array(self.len() as i32, T::CLASS, JObject::null())?;
|
||||
for (n, el) in self.into_iter().enumerate() {
|
||||
let el = el.into_java_object(env)?;
|
||||
env.set_object_array_element(&mut array, n as i32, &el)?;
|
||||
}
|
||||
Ok(array.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, T: IntoJavaObject<'j>> IntoJavaObject<'j> for Option<T> {
|
||||
const CLASS: &'static str = T::CLASS;
|
||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
match self {
|
||||
Some(x) => x.into_java_object(env),
|
||||
None => Ok(JObject::null())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
impl<'j> IntoJavaObject<'j> for uuid::Uuid {
|
||||
const CLASS: &'static str = "java/util/UUID";
|
||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<JObject<'j>, jni::errors::Error> {
|
||||
let class = env.find_class(Self::CLASS)?;
|
||||
let (msb, lsb) = self.as_u64_pair();
|
||||
let msb = i64::from_ne_bytes(msb.to_ne_bytes());
|
||||
let lsb = i64::from_ne_bytes(lsb.to_ne_bytes());
|
||||
env.new_object(&class, "(JJ)V", &[jni::objects::JValueGen::Long(msb), jni::objects::JValueGen::Long(lsb)])
|
||||
}
|
||||
}
|
200
src/lib.rs
200
src/lib.rs
|
@ -1,179 +1,53 @@
|
|||
use jni::{objects::{JObject, JObjectArray, JString}, sys::jobject};
|
||||
pub use jni_toolbox_macro::jni;
|
||||
pub mod into_java;
|
||||
pub mod from_java;
|
||||
|
||||
pub use jni_toolbox_macro::jni;
|
||||
pub use into_java::{IntoJavaObject, IntoJava};
|
||||
pub use from_java::{FromJava, from_java_static};
|
||||
|
||||
|
||||
/// An error that is meant to be used with jni-toolbox.
|
||||
pub trait JniToolboxError: std::error::Error {
|
||||
/// The Java class for the matching exception.
|
||||
fn jclass(&self) -> String;
|
||||
}
|
||||
|
||||
impl JniToolboxError for jni::errors::Error {
|
||||
fn jclass(&self) -> String {
|
||||
"java/lang/RuntimeException".to_string()
|
||||
match self {
|
||||
jni::errors::Error::NullPtr(_) => "java/lang/NullPointerException",
|
||||
_ => "java/lang/RuntimeException",
|
||||
// jni::errors::Error::WrongJValueType(_, _) => todo!(),
|
||||
// jni::errors::Error::InvalidCtorReturn => todo!(),
|
||||
// jni::errors::Error::InvalidArgList(_) => todo!(),
|
||||
// jni::errors::Error::MethodNotFound { name, sig } => todo!(),
|
||||
// jni::errors::Error::FieldNotFound { name, sig } => todo!(),
|
||||
// jni::errors::Error::JavaException => todo!(),
|
||||
// jni::errors::Error::JNIEnvMethodNotFound(_) => todo!(),
|
||||
// jni::errors::Error::NullDeref(_) => todo!(),
|
||||
// jni::errors::Error::TryLock => todo!(),
|
||||
// jni::errors::Error::JavaVMMethodNotFound(_) => todo!(),
|
||||
// jni::errors::Error::FieldAlreadySet(_) => todo!(),
|
||||
// jni::errors::Error::ThrowFailed(_) => todo!(),
|
||||
// jni::errors::Error::ParseFailed(_, _) => todo!(),
|
||||
// jni::errors::Error::JniCall(_) => todo!(),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl JniToolboxError for jni::errors::JniError {
|
||||
fn jclass(&self) -> String {
|
||||
"java/lang/RuntimeException".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_java_static<'j, T: FromJava<'j>>(env: &mut jni::JNIEnv<'j>, val: T::T) -> Result<T, jni::errors::Error> {
|
||||
T::from_java(env, val)
|
||||
}
|
||||
|
||||
pub trait FromJava<'j> : Sized {
|
||||
type T : Sized;
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result<Self, jni::errors::Error>;
|
||||
}
|
||||
|
||||
macro_rules! auto_from_java {
|
||||
($t:ty, $j:ty) => {
|
||||
impl<'j> FromJava<'j> for $t {
|
||||
type T = $j;
|
||||
|
||||
#[inline]
|
||||
fn from_java(_: &mut jni::JNIEnv, value: Self::T) -> Result<Self, jni::errors::Error> {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto_from_java!(i64, jni::sys::jlong);
|
||||
auto_from_java!(i32, jni::sys::jint);
|
||||
auto_from_java!(i16, jni::sys::jshort);
|
||||
auto_from_java!(f32, jni::sys::jfloat);
|
||||
auto_from_java!(f64, jni::sys::jdouble);
|
||||
auto_from_java!(JObject<'j>, JObject<'j>);
|
||||
auto_from_java!(JObjectArray<'j>, JObjectArray<'j>);
|
||||
|
||||
impl<'j> FromJava<'j> for bool {
|
||||
type T = jni::sys::jboolean;
|
||||
|
||||
#[inline]
|
||||
fn from_java(_: &mut jni::JNIEnv, value: Self::T) -> Result<Self, jni::errors::Error> {
|
||||
Ok(value == 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> FromJava<'j> for String {
|
||||
type T = jni::objects::JString<'j>;
|
||||
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result<Self, jni::errors::Error> {
|
||||
if value.is_null() { return Err(jni::errors::Error::NullPtr("string can't be null")) };
|
||||
Ok(unsafe { env.get_string_unchecked(&value) }?.into()) // unsafe for efficiency
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, T: FromJava<'j, T = jni::objects::JObject<'j>>> FromJava<'j> for Option<T> {
|
||||
type T = jni::objects::JObject<'j>;
|
||||
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result<Self, jni::errors::Error> {
|
||||
if value.is_null() { return Ok(None) };
|
||||
Ok(Some(T::from_java(env, value)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
impl<'j> FromJava<'j> for uuid::Uuid {
|
||||
type T = jni::objects::JObject<'j>;
|
||||
fn from_java(env: &mut jni::JNIEnv<'j>, uuid: Self::T) -> Result<Self, jni::errors::Error> {
|
||||
let lsb = u64::from_ne_bytes(
|
||||
env.call_method(&uuid, "getLeastSignificantBits", "()J", &[])?
|
||||
.j()?
|
||||
.to_ne_bytes()
|
||||
);
|
||||
|
||||
let msb = u64::from_ne_bytes(
|
||||
env.call_method(&uuid, "getMostSignificantBits", "()J", &[])?
|
||||
.j()?
|
||||
.to_ne_bytes()
|
||||
);
|
||||
|
||||
Ok(uuid::Uuid::from_u64_pair(msb, lsb))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoJava<'j> {
|
||||
type T;
|
||||
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error>;
|
||||
}
|
||||
|
||||
macro_rules! auto_into_java {
|
||||
($t:ty, $j:ty) => {
|
||||
impl<'j> IntoJava<'j> for $t {
|
||||
type T = $j;
|
||||
|
||||
fn into_java(self, _: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto_into_java!(i64, jni::sys::jlong);
|
||||
auto_into_java!(i32, jni::sys::jint);
|
||||
auto_into_java!(i16, jni::sys::jshort);
|
||||
auto_into_java!(f32, jni::sys::jfloat);
|
||||
auto_into_java!(f64, jni::sys::jdouble);
|
||||
auto_into_java!((), ());
|
||||
|
||||
impl<'j> IntoJava<'j> for bool {
|
||||
type T = jni::sys::jboolean;
|
||||
|
||||
#[inline]
|
||||
fn into_java(self, _: &mut jni::JNIEnv) -> Result<Self::T, jni::errors::Error> {
|
||||
Ok(if self { 1 } else { 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> IntoJava<'j> for &str {
|
||||
type T = jni::sys::jstring;
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
|
||||
Ok(env.new_string(self)?.as_raw())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> IntoJava<'j> for String {
|
||||
type T = jni::sys::jstring;
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
|
||||
self.as_str().into_java(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> IntoJava<'j> for Vec<String> {
|
||||
type T = jni::sys::jobjectArray;
|
||||
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
|
||||
let mut array = env.new_object_array(self.len() as i32, "java/lang/String", JObject::null())?;
|
||||
for (n, el) in self.into_iter().enumerate() {
|
||||
let string = env.new_string(el)?;
|
||||
env.set_object_array_element(&mut array, n as i32, string)?;
|
||||
}
|
||||
Ok(array.into_raw())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j, T: IntoJava<'j, T = jni::sys::jobject>> IntoJava<'j> for Option<T> {
|
||||
type T = T::T;
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
|
||||
match self {
|
||||
Some(x) => x.into_java(env),
|
||||
None => Ok(std::ptr::null_mut()),
|
||||
_ => "java/lang/RuntimeException",
|
||||
// jni::errors::JniError::Unknown => todo!(),
|
||||
// jni::errors::JniError::ThreadDetached => todo!(),
|
||||
// jni::errors::JniError::WrongVersion => todo!(),
|
||||
// jni::errors::JniError::NoMemory => todo!(),
|
||||
// jni::errors::JniError::AlreadyCreated => todo!(),
|
||||
// jni::errors::JniError::InvalidArguments => todo!(),
|
||||
// jni::errors::JniError::Other(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
impl<'j> IntoJava<'j> for uuid::Uuid {
|
||||
type T = jni::sys::jobject;
|
||||
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, 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());
|
||||
let lsb = i64::from_ne_bytes(lsb.to_ne_bytes());
|
||||
env.new_object(&class, "(JJ)V", &[jni::objects::JValueGen::Long(msb), jni::objects::JValueGen::Long(lsb)])
|
||||
.map(|j| j.as_raw())
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
|
13
src/test/Cargo.toml
Normal file
13
src/test/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "jni-toolbox-test"
|
||||
description = "test binary for jni-toolbox"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
path = "test.rs"
|
||||
|
||||
[dependencies]
|
||||
jni-toolbox = { path = "../.." }
|
||||
jni = "0.21"
|
65
src/test/java/toolbox/Main.java
Normal file
65
src/test/java/toolbox/Main.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package toolbox;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
public class Main {
|
||||
static {
|
||||
System.loadLibrary("jni_toolbox_test");
|
||||
}
|
||||
|
||||
static native int sum(int a, int b);
|
||||
static native String concat(String a, String b);
|
||||
static native String[] to_vec(String a, String b, String c);
|
||||
static native boolean maybe(String optional);
|
||||
static native String optional(boolean present);
|
||||
static native String raw();
|
||||
|
||||
@Test
|
||||
public void argumentsByValue() {
|
||||
assertEquals(Main.sum(42, 13), 42 + 13);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void argumentsByReference() {
|
||||
assertEquals(Main.concat("hello", "world"), "hello -- world");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checksForNull() {
|
||||
// TODO maybe these should throw NullPtrException
|
||||
assertThrows(NullPointerException.class, () -> Main.concat("a", null));
|
||||
assertThrows(NullPointerException.class, () -> Main.concat(null, "a"));
|
||||
assertThrows(NullPointerException.class, () -> Main.concat(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnVec() {
|
||||
String[] actual = new String[]{"a", "b", "c"};
|
||||
String[] from_rust = Main.to_vec("a", "b", "c");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
assertEquals(actual[i], from_rust[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optional() {
|
||||
assertEquals(Main.maybe(null), false);
|
||||
assertEquals(Main.maybe("aa"), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passEnv() {
|
||||
assertEquals(Main.raw(), "hello world!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullableReturn() {
|
||||
assertNull(Main.optional(false));
|
||||
assertEquals(Main.optional(true), "hello world!");
|
||||
}
|
||||
|
||||
}
|
35
src/test/test.rs
Normal file
35
src/test/test.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use jni_toolbox::jni;
|
||||
|
||||
#[jni(package = "toolbox", class = "Main")]
|
||||
fn sum(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[jni(package = "toolbox", class = "Main")]
|
||||
fn concat(a: String, b: String) -> String {
|
||||
format!("{a} -- {b}")
|
||||
}
|
||||
|
||||
#[jni(package = "toolbox", class = "Main")]
|
||||
fn to_vec(a: String, b: String, c: String) -> Vec<String> {
|
||||
vec![a, b, c]
|
||||
}
|
||||
|
||||
#[jni(package = "toolbox", class = "Main")]
|
||||
fn maybe(idk: Option<String>) -> bool {
|
||||
idk.is_some()
|
||||
}
|
||||
|
||||
#[jni(package = "toolbox", class = "Main")]
|
||||
fn optional(present: bool) -> Option<String> {
|
||||
if present {
|
||||
Some("hello world!".into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[jni(package = "toolbox", class = "Main")]
|
||||
fn raw<'local>(env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JString<'local>, jni::errors::Error> {
|
||||
env.new_string("hello world!")
|
||||
}
|
Loading…
Reference in a new issue