jni_utils/
lib.rs

1//! # Extra Utilities for JNI in Rust
2//!
3//! This crate builds on top of the [`jni`](::jni) crate and provides
4//! higher-level concepts to more easily deal with JNI. While the
5//! [`jni`](::jni) crate implements low-level bindings to JNI,
6//! [`jni-utils`](crate) is more focused on higher-level constructs that get
7//! used frequently. Some of the features provided by [`jni-utils`](crate)
8//! include:
9//!
10//! * Asynchronous calls to Java code using the [`JFuture`](future::JFuture)
11//!   and [`JStream`](stream::JStream) types
12//! * Conversion between various commonly-used Rust types and their
13//!   corresponding Java types
14//! * Emulation of `try`/`catch` blocks with the
15//!   [`try_block`](exceptions::try_block) function
16//!
17//! The overriding principle of [`jni-utils`](crate) is that switches between
18//! Rust and Java code should be minimized, and that it is easier to call Java
19//! code from Rust than it is to call Rust code from Java. Calling Rust from
20//! Java requires creating a class with a `native` method and exporting it from
21//! Rust, either by a combination of `#[nomangle]` and `extern "C"` to export
22//! the function as a symbol in a shared library, or by calling
23//! [`JNIEnv::register_native_methods()`](::jni::JNIEnv::register_native_methods).
24//! In contrast, calling Java from Rust only requires calling
25//! [`JNIEnv::call_method()`](::jni::JNIEnv::call_method) (though you can cache
26//! the method ID and use
27//! [`JNIEnv::call_method_unchecked()`](::jni::JNIEnv::call_method_unchecked)
28//! for a performance improvement.)
29//!
30//! To that end, [`jni-utils`](crate) seeks to minimize the number of holes
31//! that must be poked through the Rust-Java boundary, and the number of
32//! `native` exported-to-Java Rust functions that must be written. In
33//! particular, the async API has been developed to minimize such exports by
34//! allowing Java code to wake an `await` without creating a new `native`
35//! function.
36//!
37//! Some features of [`jni-utils`](crate) require the accompanying Java support
38//! library, which includes some native methods. Therefore,
39//! [`jni_utils::init()`](crate::init) should be called before using
40//! [`jni-utils`](crate).
41
42use ::jni::{errors::Result, JNIEnv};
43
44pub mod arrays;
45pub mod classcache;
46pub mod exceptions;
47pub mod future;
48pub mod ops;
49pub mod stream;
50pub mod task;
51pub mod uuid;
52
53/// Initialize [`jni-utils`](crate) by registering required native methods.
54/// This should be called before using [`jni-utils`](crate).
55///
56/// # Arguments
57///
58/// * `env` - Java environment with which to register native methods.
59pub fn init(env: &JNIEnv) -> Result<()> {
60    ops::jni::init(env)?;
61    Ok(())
62}
63
64#[cfg(test)]
65pub(crate) mod test_utils {
66    use jni::{objects::GlobalRef, JNIEnv, JavaVM};
67    use lazy_static::lazy_static;
68    use std::{
69        sync::{Arc, Mutex},
70        task::{Wake, Waker},
71    };
72
73    pub struct TestWakerData(Mutex<bool>);
74
75    impl TestWakerData {
76        pub fn new() -> Self {
77            Self(Mutex::new(false))
78        }
79
80        pub fn value(&self) -> bool {
81            *self.0.lock().unwrap()
82        }
83
84        pub fn set_value(&self, value: bool) {
85            let mut guard = self.0.lock().unwrap();
86            *guard = value;
87        }
88    }
89
90    impl Wake for TestWakerData {
91        fn wake(self: Arc<Self>) {
92            Self::wake_by_ref(&self);
93        }
94
95        fn wake_by_ref(self: &Arc<Self>) {
96            self.set_value(true);
97        }
98    }
99
100    pub fn test_waker(data: &Arc<TestWakerData>) -> Waker {
101        Waker::from(data.clone())
102    }
103
104    struct GlobalJVM {
105        jvm: JavaVM,
106        class_loader: GlobalRef,
107    }
108
109    thread_local! {
110        pub static JVM_ENV: JNIEnv<'static> = {
111            let env = JVM.jvm.attach_current_thread_permanently().unwrap();
112
113            let thread = env
114                .call_static_method(
115                    "java/lang/Thread",
116                    "currentThread",
117                    "()Ljava/lang/Thread;",
118                    &[],
119                )
120                .unwrap()
121                .l()
122                .unwrap();
123            env.call_method(
124                thread,
125                "setContextClassLoader",
126                "(Ljava/lang/ClassLoader;)V",
127                &[JVM.class_loader.as_obj().into()]
128            ).unwrap();
129
130            env
131        }
132    }
133
134    lazy_static! {
135        static ref JVM: GlobalJVM = {
136            use jni::InitArgsBuilder;
137            use std::{env, path::PathBuf};
138
139            let mut jni_utils_jar = PathBuf::from(env::current_exe().unwrap());
140            jni_utils_jar.pop();
141            jni_utils_jar.pop();
142            jni_utils_jar.push("java");
143            jni_utils_jar.push("libs");
144            jni_utils_jar.push("jni-utils-0.1.0-SNAPSHOT.jar");
145
146            let jvm_args = InitArgsBuilder::new()
147                .option(&format!(
148                    "-Djava.class.path={}",
149                    jni_utils_jar.to_str().unwrap()
150                ))
151                .build()
152                .unwrap();
153            let jvm = JavaVM::new(jvm_args).unwrap();
154
155            let env = jvm.attach_current_thread_permanently().unwrap();
156            crate::init(&env).unwrap();
157
158            let thread = env
159                .call_static_method(
160                    "java/lang/Thread",
161                    "currentThread",
162                    "()Ljava/lang/Thread;",
163                    &[],
164                )
165                .unwrap()
166                .l()
167                .unwrap();
168            let class_loader = env
169                .call_method(
170                    thread,
171                    "getContextClassLoader",
172                    "()Ljava/lang/ClassLoader;",
173                    &[],
174                )
175                .unwrap()
176                .l()
177                .unwrap();
178            let class_loader = env.new_global_ref(class_loader).unwrap();
179
180            GlobalJVM { jvm, class_loader }
181        };
182    }
183}