toad_jni/lib.rs
1//! High-level wrapper of [`jni`], making Java & Rust FFI easy & fun
2//!
3//! ## Globals
4//! [`toad_jni::global`](https://docs.rs/toad-jni/latest/toad_jni/global/index.html) offers the option to use a global JVM handle .
5//!
6//! [`toad_jni::global::jvm()`](https://docs.rs/toad-jni/latest/toad_jni/global/fn.jvm.html) must be initialized with
7//! [`toad_jni::global::init()`](https://docs.rs/toad-jni/latest/toad_jni/global/fn.init.html) in order to use Rust trait
8//! impls on java types. Some examples include:
9//!
10//! - [`Iterator`] on [`toad_jni::java::util::ArrayList`](https://docs.rs/toad-jni/latest/toad_jni/java/util/struct.ArrayList.html)
11//! - [`core::fmt::Debug`] on [`toad_jni::java::lang::Throwable`](https://docs.rs/toad-jni/latest/toad_jni/java/lang/struct.Throwable.html)
12//! - [`toad::net::Socket`] on [`toad_jni::java::nio::channels::DatagramChannel`](https://docs.rs/toad-jni/latest/toad_jni/java/nio/channels/struct.DatagramChannel.html)
13//!
14//! ## Types
15//! All java type signatures can be represented by rust types
16//! that implement the [`toad_jni::java::Type`](https://docs.rs/toad-jni/latest/toad_jni/java/trait.Type.html) trait, which is automatically
17//! implemented for all [`toad_jni::java::Class`](https://docs.rs/toad-jni/latest/toad_jni/java/trait.Class.html)es.
18//!
19//! ## Classes
20//! Classes are represented in `toad_jni` by implementing 2 traits:
21//! * [`toad_jni::java::Class`](https://docs.rs/toad-jni/latest/toad_jni/java/trait.Class.html)
22//! * [`toad_jni::java::Object`](https://docs.rs/toad-jni/latest/toad_jni/java/trait.Object.html) (see also [`toad_jni::java::object_newtype`](https://docs.rs/toad-jni/latest/toad_jni/java/macro.object_newtype.html))
23//!
24//! ### Fields and Methods
25//! There are several high-level lens-style structs for interacting with fields, methods and constructors:
26//! * [`toad_jni::java::Constructor`](https://docs.rs/toad-jni/latest/toad_jni/java/struct.Constructor.html)
27//! * [`toad_jni::java::StaticField`](https://docs.rs/toad-jni/latest/toad_jni/java/struct.StaticField.html)
28//! * [`toad_jni::java::StaticMethod`](https://docs.rs/toad-jni/latest/toad_jni/java/struct.StaticMethod.html)
29//! * [`toad_jni::java::Field`](https://docs.rs/toad-jni/latest/toad_jni/java/struct.Field.html)
30//! * [`toad_jni::java::Method`](https://docs.rs/toad-jni/latest/toad_jni/java/struct.Method.html)
31//!
32//! All of these types use [`toad_jni::java::Type`](https://docs.rs/toad-jni/latest/toad_jni/java/trait.Type.html) to transform nice Rust types into the corresponding
33//! JVM type signatures.
34//!
35//! For example, the `StaticMethod` representation of [`java.lang.String.format(String, ..Object)`](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/String.html#format(java.lang.String,java.lang.Object...))
36//! would be:
37//! ```rust,no_run
38//! use toad_jni::java::lang::Object;
39//! use toad_jni::java::StaticMethod;
40//!
41//! static STRING_FORMAT: StaticMethod<String, fn(String, Vec<Object>) -> String> =
42//! StaticMethod::new("format");
43//! ```
44//!
45//! It is recommended that these structs are stored in local `static` variables so that they can cache
46//! the internal JNI IDs of the class and methods, but this is not required.
47//!
48//! ### Example
49//! Consider the following java class:
50//! ```java
51//! package com.foo.bar;
52//!
53//! public class Foo {
54//! public final static long NUMBER = 123;
55//! public String bingus = "bingus";
56//!
57//! public Foo() { }
58//!
59//! public static String bar() {
60//! return "bar";
61//! }
62//!
63//! public void setBingus(String newBingus) {
64//! this.bingus = newBingus;
65//! }
66//! }
67//! ```
68//!
69//! A Rust API to this class would look like:
70//! ```rust,no_run
71//! use toad_jni::java;
72//!
73//! pub struct Foo(java::lang::Object);
74//!
75//! java::object_newtype!(Foo);
76//!
77//! impl java::Class for Foo {
78//! const PATH: &'static str = "com/foo/bar/Foo";
79//! }
80//!
81//! impl Foo {
82//! pub fn new(e: &mut java::Env) -> Self {
83//! static CTOR: java::Constructor<Foo, fn()> = java::Constructor::new();
84//! CTOR.invoke(e)
85//! }
86//!
87//! pub fn number(e: &mut java::Env) -> i64 {
88//! static NUMBER: java::StaticField<Foo, i64> = java::StaticField::new("NUMBER");
89//! NUMBER.get(e)
90//! }
91//!
92//! pub fn bar(e: &mut java::Env) -> String {
93//! static BAR: java::StaticMethod<Foo, fn() -> String> = java::StaticMethod::new("bar");
94//! BAR.invoke(e)
95//! }
96//!
97//! pub fn bingus(&self, e: &mut java::Env) -> String {
98//! static BINGUS: java::Field<Foo, String> = java::Field::new("bingus");
99//! BINGUS.get(e, self)
100//! }
101//!
102//! pub fn set_bingus(&self, e: &mut java::Env, s: String) {
103//! static SET_BINGUS: java::Method<Foo, fn(String)> = java::Method::new("setBingus");
104//! SET_BINGUS.invoke(e, self, s)
105//! }
106//! }
107//! ```
108
109// docs
110#![doc(html_root_url = "https://docs.rs/toad-jni/0.4.1")]
111#![cfg_attr(any(docsrs, feature = "docs"), feature(doc_cfg))]
112// -
113// style
114#![allow(clippy::unused_unit)]
115// -
116// deny
117#![deny(missing_docs)]
118#![deny(missing_copy_implementations)]
119// -
120// warnings
121#![cfg_attr(not(test), warn(unreachable_pub))]
122// -
123// features
124
125/// java language features and class shims
126pub mod java;
127
128/// Global JVM handles
129pub mod global {
130 use jni::{InitArgsBuilder, JavaVM};
131
132 static mut JVM: Option<JavaVM> = None;
133
134 /// Initialize the global jvm handle with an existing handle
135 pub fn init_with(jvm: JavaVM) {
136 unsafe {
137 JVM = Some(jvm);
138 }
139 }
140
141 /// Initialize the global jvm handle by creating a new handle
142 pub fn init() {
143 unsafe {
144 let args = InitArgsBuilder::new().build().unwrap();
145 JVM = Some(JavaVM::new(args).unwrap());
146 }
147
148 jvm().attach_current_thread_permanently().unwrap();
149 }
150
151 /// Get a reference to the global jvm handle
152 pub fn jvm() -> &'static mut JavaVM {
153 unsafe { JVM.as_mut().unwrap() }
154 }
155}
156
157#[cfg(test)]
158mod test {
159 use std::sync::Once;
160
161 use java::Primitive;
162 use jni::{InitArgsBuilder, JavaVM};
163 use toad_jni::java;
164
165 pub use crate as toad_jni;
166 use crate::java::Object;
167
168 pub fn init<'a>() -> java::Env<'a> {
169 static INIT: Once = Once::new();
170 INIT.call_once(|| {
171 std::env::set_var("FOO", "bar");
172 let args = InitArgsBuilder::new().build().unwrap();
173 toad_jni::global::init_with(JavaVM::new(args).unwrap());
174 });
175
176 let jvm = toad_jni::global::jvm();
177 jvm.attach_current_thread_permanently().unwrap()
178 }
179
180 #[test]
181 fn init_works() {
182 init();
183 }
184
185 #[test]
186 fn prim_wrappers() {
187 init();
188 let mut e = java::env();
189 let e = &mut e;
190
191 let i = (32i8).to_primitive_wrapper(e);
192 assert_eq!(i8::from_primitive_wrapper(e, i), 32i8);
193 }
194
195 #[test]
196 fn test_arraylist() {
197 init();
198 assert_eq!(vec![1i8, 2, 3, 4].into_iter()
199 .collect::<java::util::ArrayList<i8>>()
200 .into_iter()
201 .collect::<Vec<i8>>(),
202 vec![1, 2, 3, 4])
203 }
204
205 #[test]
206 fn test_optional() {
207 init();
208
209 let mut e = java::env();
210
211 let o = java::util::Optional::of(&mut e, 12i32);
212 assert_eq!(o.to_option(&mut e).unwrap(), 12);
213
214 let o = java::util::Optional::<i32>::empty(&mut e);
215 assert!(o.is_empty(&mut e));
216 }
217
218 #[test]
219 fn test_time() {
220 init();
221
222 let mut e = java::env();
223
224 let o = java::time::Duration::of_millis(&mut e, 1000);
225 assert_eq!(o.to_millis(&mut e), 1000);
226 }
227
228 #[test]
229 fn test_system() {
230 let mut e = init();
231 let e = &mut e;
232
233 type System = java::lang::System;
234 assert_eq!(System::get_env(e, "FOO"), Some("bar".to_string()));
235 assert_eq!(System::get_env(e, "BAR"), None);
236
237 assert_eq!(System::get_property(e, "foo.bar"), None);
238 assert_eq!(System::set_property(e, "foo.bar", "baz"), None);
239 assert_eq!(System::get_property(e, "foo.bar"), Some("baz".to_string()));
240
241 let args = vec![8329i32, 3281, 8329 + 3281].into_iter()
242 .map(|i| i.to_primitive_wrapper(e).downcast(e))
243 .collect();
244 match System::console(e).into_option(e) {
245 | Some(c) => {
246 c.printf(e, "%d + %d = %d", args);
247 },
248 | None => println!("no console"),
249 }
250 }
251
252 #[test]
253 fn test_inet() {
254 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
255
256 use java::net::*;
257
258 let mut env = init();
259 let e = &mut env;
260
261 assert_eq!(InetSocketAddress::new_wildcard_address(e, 1234).to_std(e),
262 "0.0.0.0:1234".parse().unwrap());
263
264 let local = InetAddress::from_std(e, IpAddr::from(Ipv4Addr::LOCALHOST));
265 assert_eq!(local.to_std(e), "127.0.0.1".parse::<IpAddr>().unwrap());
266 assert_eq!(InetSocketAddress::new(e, local, 1234).to_std(e),
267 "127.0.0.1:1234".parse::<SocketAddr>().unwrap());
268
269 let local = InetAddress::from_std(e, IpAddr::from(Ipv6Addr::LOCALHOST));
270 assert_eq!(local.to_std(e), "::1".parse::<IpAddr>().unwrap());
271 assert_eq!(InetSocketAddress::new(e, local, 1234).to_std(e),
272 "[::1]:1234".parse::<SocketAddr>().unwrap());
273 }
274
275 #[test]
276 fn test_bigint() {
277 init();
278
279 let mut e = java::env();
280 let e = &mut e;
281
282 type BigInt = java::math::BigInteger;
283
284 let bi = BigInt::from_be_bytes(e, &i128::MAX.to_be_bytes());
285 assert_eq!(bi.to_i128(e), i128::MAX);
286
287 let bi = BigInt::from_be_bytes(e, &i8::MAX.to_be_bytes());
288 assert_eq!(bi.to_i128(e), i8::MAX.into());
289
290 let bi = BigInt::from_be_bytes(e, &1i8.to_be_bytes());
291 assert_eq!(bi.to_i8(e), 1);
292
293 let bi = BigInt::from_be_bytes(e, &(-1i8).to_be_bytes());
294 assert_eq!(bi.to_i8(e), -1);
295
296 let bi = BigInt::from_be_bytes(e, &0i128.to_be_bytes());
297 assert_eq!(bi.to_i8(e), 0);
298 assert_eq!(bi.to_i16(e), 0);
299 assert_eq!(bi.to_i32(e), 0);
300 assert_eq!(bi.to_i64(e), 0);
301 assert_eq!(bi.to_i128(e), 0);
302 }
303}