Skip to content

Commit 5259d87

Browse files
committed
chore: Merge branch 'feat/intermediate-trait'
2 parents 2337ef0 + 9160772 commit 5259d87

File tree

17 files changed

+619
-366
lines changed

17 files changed

+619
-366
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ name: test
22

33
on:
44
push:
5-
branches:
6-
- dev
75

86
env:
97
CARGO_TERM_COLOR: always
@@ -28,3 +26,5 @@ jobs:
2826
toolchain: ${{ matrix.toolchain }}
2927
- run: cargo build --verbose
3028
- run: cargo test --verbose
29+
- uses: gradle/actions/setup-gradle@v4
30+
- run: gradle test

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1+
# rust
12
/target
3+
4+
# gradle
5+
/build
6+
/bin
7+
/.settings
8+
/.gradle
9+
.project
10+
.classpath

Cargo.lock

Lines changed: 5 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["macro"]
2+
members = ["macro", "src/test"]
33

44
[package]
55
name = "jni-toolbox"
@@ -13,6 +13,11 @@ version = "0.1.3"
1313
edition = "2021"
1414

1515
[dependencies]
16-
jni-toolbox-macro = "0.1.3"
16+
#jni-toolbox-macro = "0.1.3"
17+
jni-toolbox-macro = { path = "./macro" }
1718
jni = "0.21"
1819
uuid = { version = "1.10", optional = true }
20+
21+
[features]
22+
default = []
23+
uuid = ["dep:uuid"]

README.md

Lines changed: 48 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
[![Crates.io Version](https://img.shields.io/crates/v/jni-toolbox)](https://crates.io/crates/jni-toolbox)
44
[![docs.rs](https://img.shields.io/docsrs/jni-toolbox)](https://docs.rs/jni-toolbox)
55

6+
This is a simple crate built around [jni-rs](https://github.com/jni-rs/jni-rs) to automatically generate JNI-compatible extern functions.
67

7-
this is a simple crate built around [jni-rs](https://github.com/jni-rs/jni-rs) to automatically generate JNI-compatible extern functions
8+
It also wraps functions returning `Result<>`, making short-circuiting easy.
89

9-
it also wraps functions returning `Result<>`, making short-circuiting easy
10-
11-
## usage
12-
just specify package and class on your function, and done!
10+
## Usage
11+
Just specify package and class on your function, and done!
1312

1413
```rust
1514
#[jni_toolbox::jni(package = "your.package.path", class = "ContainerClass")]
@@ -18,30 +17,28 @@ fn your_function_name(arg: String) -> Result<Vec<String>, String> {
1817
}
1918
```
2019

21-
### conversions
22-
every type that must go into/from Java must implement `IntoJava` or `FromJava` (methods will receive a `&mut JNIEnv` and can return errors).
23-
most primitives already have them implemented. conversions are automatic and the wrapper function will invoke IntoJava/FromJava for every type,
24-
passing an environment reference.
20+
### Conversions
21+
Every type that is meant to be sent to Java must implement `IntoJavaObject` (or, unlikely, `IntoJavaPrimitive`); every type that is meant to be
22+
received from Java must implement `FromJava`. Most primitives and a few common types should already be implemented.
2523

2624
```rust
27-
impl<'j> IntoJava for MyClass {
28-
type T = jni::sys::jobject;
25+
impl<'j> IntoJavaObject for MyClass {
26+
type T = jni::objects::JObject<'j>
2927
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
3028
let hello = env.new_string("world")?;
3129
// TODO!!
3230
}
3331
}
3432
```
3533

36-
### pointers
37-
to return pointer type values, add the `ptr` attribute
38-
39-
note that, while possible to pass raw pointers to the JVM, it is not safe by default and must be done with extreme care.
34+
### Pointers
35+
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.
4036

41-
### exceptions
37+
### Exceptions
4238
Errors are thrown automatically when a `Result` is an error. For your errors to work, you must implement the `JniToolboxError` trait for your errors,
4339
(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.
44-
functions returning `Result`s will automatically have their return value unwrapped and, if is an err, throw an exception and return early.
40+
41+
Functions returning `Result`s will automatically have their return value unwrapped and, if is an err, throw an exception and return early.
4542

4643
```rust
4744
impl JniToolboxError for MyError {
@@ -53,113 +50,75 @@ impl JniToolboxError for MyError {
5350

5451
```java
5552
package my.package.some;
56-
public class MyError {
53+
public class MyError extends Throwable {
5754
public MyError(String x) {
5855
// TODO
5956
}
6057
}
6158
```
6259

63-
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!)
64-
60+
To throw simple exceptions, it's possible to use the `exception` attribute. Pass the exception's fully qualified name (must have a constructor
61+
that takes in a single `String` argument).
6562

66-
67-
### examples
68-
the following function:
63+
### Examples
64+
The following function:
6965
```rust
7066
#[jni(package = "mp.code", class = "Client", ptr)]
7167
fn connect(config: Config) -> Result<Client, ConnectionError> {
72-
tokio().block_on(Client::connect(config))
68+
super::tokio().block_on(Client::connect(config))
7369
}
7470
```
7571

76-
gets turned into these two functions:
77-
78-
<details><summary>show macro expansion</summary>
72+
generates a matching expanded function invoking it:
7973

8074
```rust
8175
fn connect(config: Config) -> Result<Client, ConnectionError> {
82-
tokio().block_on(Client::connect(config))
76+
super::tokio().block_on(Client::connect(config))
8377
}
8478

8579
#[no_mangle]
86-
#[allow(unused_mut)]
80+
#[allow(unused_unit)]
8781
pub extern "system" fn Java_mp_code_Client_connect<'local>(
8882
mut env: jni::JNIEnv<'local>,
8983
_class: jni::objects::JClass<'local>,
90-
mut config: <Config as jni_toolbox::FromJava<'local>>::T,
91-
) -> <Client as jni_toolbox::IntoJava<'local>>::T {
84+
config: <Config as jni_toolbox::FromJava<'local>>::From,
85+
) -> <Client as jni_toolbox::IntoJava<'local>>::Ret {
9286
use jni_toolbox::{FromJava, IntoJava, JniToolboxError};
93-
let mut env_copy = unsafe { env.unsafe_clone() };
9487
let config_new = match jni_toolbox::from_java_static::<Config>(&mut env, config) {
9588
Ok(x) => x,
9689
Err(e) => {
97-
let _ = env.throw_new(
98-
"java/lang/RuntimeException",
99-
$crate::__export::must_use({
100-
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
101-
res
102-
}),
103-
);
90+
let _ = env.throw_new(e.jclass(), format!("{e:?}"));
10491
return std::ptr::null_mut();
10592
}
10693
};
107-
match connect(config_new) {
108-
Err(e) => match env_copy.find_class(e.jclass()) {
109-
Err(e) => {
110-
$crate::panicking::panic_fmt($crate::const_format_args!(
111-
"error throwing Java exception -- failed resolving error class: {e}"
112-
));
113-
}
114-
Ok(class) => match env_copy.new_string($crate::__export::must_use({
115-
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
116-
res
117-
})) {
118-
Err(e) => {
119-
$crate::panicking::panic_fmt($crate::const_format_args!(
120-
"error throwing Java exception -- failed creating error string: {e}"
121-
));
122-
}
123-
Ok(msg) => match env_copy.new_object(
124-
class,
125-
"(Ljava/lang/String;)V",
126-
&[jni::objects::JValueGen::Object(&msg)],
127-
) {
128-
Err(e) => {
129-
$crate::panicking::panic_fmt($crate::const_format_args!(
130-
"error throwing Java exception -- failed creating object: {e}"
131-
));
132-
}
133-
Ok(obj) => match env_copy.throw(jni::objects::JThrowable::from(obj)) {
134-
Err(e) => {
135-
$crate::panicking::panic_fmt($crate::const_format_args!(
136-
"error throwing Java exception -- failed throwing: {e}"
137-
));
138-
}
94+
let result = connect(config_new);
95+
let ret = match result {
96+
Ok(x) => x,
97+
Err(e) => match env.find_class(e.jclass()) {
98+
Err(e) => panic!("error throwing Java exception -- failed resolving error class: {e}"),
99+
Ok(class) => match env.new_string(format!("{e:?}")) {
100+
Err(e) => panic!("error throwing Java exception -- failed creating error string: {e}"),
101+
Ok(msg) => match env.new_object(class, "(Ljava/lang/String;)V", &[jni::objects::JValueGen::Object(&msg)]) {
102+
Err(e) => panic!("error throwing Java exception -- failed creating object: {e}"));
103+
Ok(obj) => match env.throw(jni::objects::JThrowable::from(obj)) {
104+
Err(e) => panic!("error throwing Java exception -- failed throwing: {e}"),
139105
Ok(_) => return std::ptr::null_mut(),
140106
},
141107
},
142108
},
143109
},
144-
Ok(ret) => match ret.into_java(&mut env_copy) {
145-
Ok(fin) => return fin,
146-
Err(e) => {
147-
let _ = env_copy.throw_new(
148-
"java/lang/RuntimeException",
149-
$crate::__export::must_use({
150-
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
151-
res
152-
}),
153-
);
154-
return std::ptr::null_mut();
155-
}
156-
},
110+
};
111+
match ret.into_java(&mut env) {
112+
Ok(fin) => fin,
113+
Err(e) => {
114+
let _ = env.throw_new(e.jclass(), format!("{e:?}"));
115+
std::ptr::null_mut()
116+
}
157117
}
158118
}
159119
```
160120

161-
</details>
162-
163-
164-
## status
165-
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
121+
## Status
122+
This crate is early and intended mostly to maintain [`codemp`](https://github.com/hexedtech/codemp)'s Java bindings, so things not used
123+
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
124+
own project should not be a problem.

build.gradle

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
plugins {
2+
id 'java-library'
3+
}
4+
5+
dependencies {
6+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
7+
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
8+
}
9+
10+
repositories {
11+
mavenCentral()
12+
}
13+
14+
task cargoBuild(type: Exec) {
15+
workingDir '.'
16+
commandLine 'cargo', 'build', '-p', 'jni-toolbox-test'
17+
}
18+
19+
test {
20+
dependsOn cargoBuild
21+
outputs.upToDateWhen { false }
22+
useJUnitPlatform()
23+
systemProperty 'java.library.path','target/debug'
24+
}

macro/src/args.rs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,33 @@ fn unpack_pat(pat: syn::Pat) -> Result<TokenStream, syn::Error> {
2222
}
2323
}
2424

25-
fn type_equals(ty: Box<syn::Type>, search: impl AsRef<str>) -> bool {
25+
fn bare_type(ty: Box<syn::Type>) -> Option<syn::TypePath> {
2626
match *ty {
27-
syn::Type::Array(_) => false,
28-
syn::Type::BareFn(_) => false,
29-
syn::Type::ImplTrait(_) => false,
30-
syn::Type::Infer(_) => false,
31-
syn::Type::Macro(_) => false,
32-
syn::Type::Never(_) => false,
33-
syn::Type::Ptr(_) => false,
34-
syn::Type::Slice(_) => false,
35-
syn::Type::TraitObject(_) => false,
36-
syn::Type::Tuple(_) => false,
37-
syn::Type::Verbatim(_) => false,
38-
syn::Type::Group(g) => type_equals(g.elem, search),
39-
syn::Type::Paren(p) => type_equals(p.elem, search),
40-
syn::Type::Reference(r) => type_equals(r.elem, search),
41-
syn::Type::Path(ty) => {
42-
ty.path.segments
43-
.last()
44-
.map_or(false, |e| e.ident == search.as_ref())
45-
},
46-
_ => false,
27+
syn::Type::Array(a) => bare_type(a.elem),
28+
syn::Type::BareFn(_) => None,
29+
syn::Type::ImplTrait(_) => None,
30+
syn::Type::Infer(_) => None,
31+
syn::Type::Macro(_) => None,
32+
syn::Type::Never(_) => None,
33+
syn::Type::TraitObject(_) => None,
34+
syn::Type::Verbatim(_) => None,
35+
syn::Type::Ptr(p) => bare_type(p.elem),
36+
syn::Type::Slice(s) => bare_type(s.elem),
37+
syn::Type::Tuple(t) => bare_type(Box::new(t.elems.first()?.clone())), // TODO
38+
syn::Type::Group(g) => bare_type(g.elem),
39+
syn::Type::Paren(p) => bare_type(p.elem),
40+
syn::Type::Reference(r) => bare_type(r.elem),
41+
syn::Type::Path(ty) => Some(ty),
42+
_ => todo!(),
4743
}
4844
}
4945

46+
fn type_equals(ty: Box<syn::Type>, search: impl AsRef<str>) -> bool {
47+
let Some(ty) = bare_type(ty) else { return false };
48+
let Some(last) = ty.path.segments.last() else { return false };
49+
last.ident == search.as_ref()
50+
}
51+
5052
impl ArgumentOptions {
5153
pub(crate) fn parse_args(fn_item: &syn::ItemFn, ret_expr: TokenStream) -> Result<Self, syn::Error> {
5254
let mut arguments = Vec::new();
@@ -83,9 +85,9 @@ impl ArgumentOptions {
8385
if pass_env {
8486
if let Some(arg) = args_iter.next() {
8587
let pat = arg.pat;
86-
let ty = arg.ty;
88+
let ty = bare_type(arg.ty);
8789
incoming.append_all(quote::quote!( mut #pat: #ty,));
88-
forwarding.append_all(quote::quote!( #pat,));
90+
forwarding.append_all(quote::quote!( &mut #pat,));
8991
}
9092
} else {
9193
incoming.append_all(quote::quote!( mut #env: jni::JNIEnv<'local>,));
@@ -104,12 +106,12 @@ impl ArgumentOptions {
104106
Ok(x) => x,
105107
Err(e) => {
106108
// TODO should we panic here instead?
107-
let _ = #env.throw_new("java/lang/RuntimeException", format!("{e:?}"));
109+
let _ = #env.throw_new(e.jclass(), format!("{e:?}"));
108110
return #ret_expr;
109111
},
110112
};
111113
});
112-
incoming.append_all(quote::quote!( mut #pat: <#ty as jni_toolbox::FromJava<'local>>::T,));
114+
incoming.append_all(quote::quote!( #pat: <#ty as jni_toolbox::FromJava<'local>>::From,));
113115
forwarding.append_all(quote::quote!( #new_pat,));
114116
}
115117

0 commit comments

Comments
 (0)