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}