# j4rs [![crates.io](https://img.shields.io/crates/v/j4rs.svg)](https://crates.io/crates/j4rs) [![Maven Central](https://img.shields.io/badge/Maven%20Central-0.21.0-blue.svg)](https://central.sonatype.com/artifact/io.github.astonbitecode/j4rs/) ![Build](https://github.com/astonbitecode/j4rs/actions/workflows/ci-workflow.yml/badge.svg) j4rs stands for __'Java for Rust'__ and allows effortless calls to Java code from Rust and vice-versa. ## Features * **Rust to Java direction support (call Java from Rust).** * No special configuration needed (no need to tweak LD_LIBRARY_PATH, PATH etc). * [Easily instantiate and invoke Java classes.](#Basics) * [Support custom types via serialization.](#Passing-custom-arguments-from-Rust-to-Java) * [.async/.await support](#Async-support) * [Casting support.](#Casting) * [Java arrays / variadic support.](#Java-arrays-and-variadics) * [Java generics support.](#Java-Generics) * [Java primitives support.](#Java-primitives) * [Java instances invocations chaining.](#Java-instances-chaining) * [Java -> Rust callbacks support.](#Callback-support) * [Simple Maven artifacts download and deployment.](#Using-Maven-artifacts) * **[Java -> Rust support](#Java-to-Rust-support) (Call Rust from Java).** * **[JavaFX support](#JavaFX-support) (including FXML support).** * **Tested on Linux, Windows and Android.** ## Usage ### Basics ```rust use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; // Create a JVM let jvm = JvmBuilder::new().build()?; // Create a java.lang.String instance let string_instance = jvm.create_instance( "java.lang.String", // The Java class to create an instance for InvocationArg::empty(), // An array of `InvocationArg`s to use for the constructor call - empty for this example )?; // The instances returned from invocations and instantiations can be viewed as pointers to Java Objects. // They can be used for further Java calls. // For example, the following invokes the `isEmpty` method of the created java.lang.String instance let boolean_instance = jvm.invoke( &string_instance, // The String instance created above "isEmpty", // The method of the String instance to invoke InvocationArg::empty(), // The `InvocationArg`s to use for the invocation - empty for this example )?; // If we need to transform an `Instance` to some Rust value, the `to_rust` should be called let rust_boolean: bool = jvm.to_rust(boolean_instance)?; println!("The isEmpty() method of the java.lang.String instance returned {}", rust_boolean); // The above prints: // The isEmpty() method of the java.lang.String instance returned true // Static invocation let _static_invocation_result = jvm.invoke_static( "java.lang.System", // The Java class to invoke "currentTimeMillis", // The static method of the Java class to invoke InvocationArg::empty(), // The `InvocationArg`s to use for the invocation - empty for this example )?; // Access a field of a class let system_class = jvm.static_class("java.lang.System")?; let system_out_field = jvm.field(&system_class, "out"); // Retrieve an enum constant using the field let access_mode_enum = jvm.static_class("java.nio.file.AccessMode")?; let access_mode_write = jvm.field(&access_mode_enum, "WRITE")?; // Retrieve a nested class (note the use of `$` instead of `.`) let state = jvm.static_class("java.lang.Thread$State")?; ``` `Instances`s of Java `List`s and `Map`s can be created with the `java_list` and `java_map` functions: ```rust let rust_vec = vec!["arg1", "arg2", "arg3", "arg33"]; // Generate a Java List. The Java List implementation is the one that is returned by java.util.Arrays#asList let java_list_instance = jvm.java_list( JavaClass::String, rust_vec)?; let rust_map = HashMap::from([ ("Potatoes", 3), ("Tomatoes", 33), ("Carrotoes", 333), ]); // Generate a java.util.HashMap. let java_map_instance = jvm.java_map( JavaClass::String, JavaClass::Integer, rust_map)?; ``` ### Passing arguments from Rust to Java j4rs uses the `InvocationArg` enum to pass arguments to the Java world. Users can benefit of the existing `TryFrom` implementations for several basic types: ```rust let i1 = InvocationArg::try_from("a str")?; // Creates an arg of java.lang.String let my_string = "a string".to_owned(); let i2 = InvocationArg::try_from(my_string)?; // Creates an arg of java.lang.String let i3 = InvocationArg::try_from(true)?; // Creates an arg of java.lang.Boolean let i4 = InvocationArg::try_from(1_i8)?; // Creates an arg of java.lang.Byte let i5 = InvocationArg::try_from('c')?; // Creates an arg of java.lang.Character let i6 = InvocationArg::try_from(1_i16)?; // Creates an arg of java.lang.Short let i7 = InvocationArg::try_from(1_i64)?; // Creates an arg of java.lang.Long let i8 = InvocationArg::try_from(0.1_f32)?; // Creates an arg of java.lang.Float let i9 = InvocationArg::try_from(0.1_f64)?; // Creates an arg of java.lang.Double ``` And for `Vec`s: ```rust let my_vec: Vec = vec![ "abc".to_owned(), "def".to_owned(), "ghi".to_owned()]; let i10 = InvocationArg::try_from(my_vec.as_slice())?; ``` The `j4rs` apis accept `InvocationArg`s either as references, or values: ```rust let inv_args = InvocationArg::try_from("arg from Rust")?; let _ = jvm.create_instance("java.lang.String", &[&inv_args])?; // Pass a reference let _ = jvm.create_instance("java.lang.String", &[inv_args])?; // Move ``` The `Instance`s returned by j4rs can be transformed to `InvocationArg`s and be further used for invoking methods as well: ```rust let one_more_string_instance = jvm.create_instance( "java.lang.String", // The Java class to create an instance for InvocationArg::empty(), // The `InvocationArg`s to use for the constructor call - empty for this example )?; let i11 = InvocationArg::try_from(one_more_string_instance)?; ``` To create an `InvocationArg` that represents a `null` Java value, use the `From` implementation with the `Null` struct: ```rust let null_string = InvocationArg::from(Null::String); // A null String let null_integer = InvocationArg::from(Null::Integer); // A null Integer let null_obj = InvocationArg::from(Null::Of("java.util.List")); // A null object of any other class. E.g. List ``` ### Passing custom arguments from Rust to Java Custom types, for which there is no `TryFrom` implementation, are also supported via serialization. To use a custom struct `MyBean` as an `InvocationArg` it needs to be serializable: ```rust #[derive(Serialize, Deserialize, Debug)] #[allow(non_snake_case)] struct MyBean { someString: String, someInteger: isize, } ``` Then, an `InvocationArg` can be created like: ```rust let my_bean = MyBean { someString: "My String In A Bean".to_string(), someInteger: 33, }; let ia = InvocationArg::new(&my_bean, "org.astonbitecode.j4rs.tests.MyBean"); ``` And it can be used as an argument to a Java method that accepts `org.astonbitecode.j4rs.tests.MyBean` instances. Of course, there should exist a respective Java class in the classpath for the deserialization to work and the custom Java Object to be created: ```java package org.astonbitecode.j4rs.tests; public class MyBean { private String someString; private Integer someInteger; public MyBean() { } public String getSomeString() { return someString; } public void setSomeString(String someString) { this.someString = someString; } public Integer getSomeInteger() { return someInteger; } public void setSomeInteger(Integer someInteger) { this.someInteger = someInteger; } } ``` ### Async support (v0.16.0 onwards) `j4rs` supports `.async/.await` via`Jvm::invoke_async` function. The function returns a [Future](https://docs.rs/futures/latest/futures/future/trait.Future.html), which is completed via the `Receiver` of a [oneshot channel](https://docs.rs/futures/latest/futures/channel/oneshot/fn.channel.html). In Java side, the methods that can be invoked by `invoke_async`, __must__ return a Java [Future](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/Future.html). When the Java Future completes, the Java side of `j4rs` invokes native Rust code that completes the pending Rust `Future` with either success or failure, using the `Sender` of the oneshot channel that was created when the `invoke_async` was called. For example, assuming we have a Java method that returns a Future: ```java package org.astonbitecode.j4rs.tests; public class MyTest { private static ExecutorService executor = Executors.newSingleThreadExecutor(); // Just return the passed String in a Future public Future getStringWithFuture(String string) { CompletableFuture completableFuture = new CompletableFuture<>(); executor.submit(() -> { completableFuture.complete(string); return null; }); return completableFuture; } } ``` We can invoke it like following: ```rust let s_test = "j4rs_rust"; let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?; let instance = jvm.invoke_async(&my_test, "getStringWithFuture", &[InvocationArg::try_from(s_test)?]).await?; let string: String = jvm.to_rust(instance)?; assert_eq!(s_test, string); ``` Please note that it is better for the Java methods that are invoked by the `invoke_async` function to return a [CompletableFuture](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletableFuture.html), as this improves performance. `j4rs` handles simple Java Futures that are not `CompletableFuture`s with [polling](https://github.com/astonbitecode/j4rs/blob/86a2cb7bee10e5941fd0cada00afc355ea9e3ebb/java/src/main/java/org/astonbitecode/j4rs/api/async/J4rsPolledFuture.java#L25), using an internal one-threaded `ScheduledExecutorService`. This has apparent performance issues. #### `invoke_async` and `Send` `Instance`s are `Send` and can be safely sent to other threads. However, because of [Send Approximation](https://rust-lang.github.io/async-book/07_workarounds/03_send_approximation.html), the `Future` returned by `invoke_async` is _not_ `Send`, even if it just contains an `Instance`. This is because the `Jvm` is being captured by the `async` call as well and the `Jvm` is __not__ `Send`. In order to have a `Future` that __is__ `Send`, the `Jvm::invoke_into_sendable_async` can be used. This function does not get a `Jvm` as argument; it creates one internally when needed and applies some scoping workarounds in order to achieve returning a `Future` which is also `Send`. Discussion [here](https://github.com/astonbitecode/j4rs/issues/103). ### Casting An `Instance` may be casted to some other Class: ```rust let instantiation_args = vec![InvocationArg::try_from("Hi")?]; let instance = jvm.create_instance("java.lang.String", instantiation_args.as_ref())?; jvm.cast(&instance, "java.lang.Object")?; ``` ### Java arrays and variadics ```rust // Create a Java array of Strings let s1 = InvocationArg::try_from("string1")?; let s2 = InvocationArg::try_from("string2")?; let s3 = InvocationArg::try_from("string3")?; let arr_instance = jvm.create_java_array("java.lang.String", &[s1, s2, s3])?; // Invoke the Arrays.asList(...) and retrieve a java.util.List let list_instance = jvm.invoke_static("java.util.Arrays", "asList", &[InvocationArg::from(arr_instance)])?; ``` ### Java Generics ```rust // Assuming the following map_instance is a Map // we may invoke its put method jvm.invoke(&map_instance, "put", &[InvocationArg::try_from("one")?, InvocationArg::try_from(1)?])?; ``` ### Java primitives Even if auto boxing and unboxing is in place, `j4rs` cannot invoke methods with _primitive_ int arguments using _Integer_ instances. For example, the following code does not work: ```rust let ia = InvocationArg::try_from(1_i32)?; jvm.create_instance("java.lang.Integer", &[ia])?; ``` It throws an _InstantiationException_ because the constructor of `Integer` takes a primitive `int` as an argument: >Exception in thread "main" org.astonbitecode.j4rs.errors.InstantiationException: Cannot create instance of java.lang.Integer at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:37) Caused by: java.lang.NoSuchMethodException: java.lang.Integer.(java.lang.Integer) at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getConstructor(Class.java:2151) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.createInstance(NativeInstantiationImpl.java:69) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:34) In situations like this, the `java.lang.Integer` instance should be transformed to a primitive `int` first: ```rust let ia = InvocationArg::try_from(1_i32)?.into_primitive()?; jvm.create_instance("java.lang.Integer", &[ia]); ``` ### Java instances chaining ```rust use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; // Create a JVM let jvm = JvmBuilder::new().build()?; // Create an instance let string_instance = jvm.create_instance( "java.lang.String", &[InvocationArg::try_from(" a string ")?], )?; // Perform chained operations on the instance let string_size: isize = jvm.chain(string_instance) .invoke("trim", InvocationArg::empty())? .invoke("length", InvocationArg::empty())? .to_rust()?; // Assert that the string was trimmed assert!(string_size == 8); ``` ### Callback support `j4rs` provides support for _Java to Rust callbacks_. These callbacks come to the Rust world via Rust [Channels](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html). In order to initialize a channel that will provide Java callback values, the `Jvm::invoke_to_channel` should be called. It returns a result of `InstanceReceiver` struct, which contains a Channel [Receiver](https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html): ```rust // Invoke of a method of a Java instance and get the returned value in a Rust Channel. // Create an Instance of a class that supports Native Callbacks // (the class just needs to extend the // `org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport`) let i = jvm.create_instance( "org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?; // Invoke the method let instance_receiver_res = jvm.invoke_to_channel( &i, // The instance to invoke asynchronously "performCallback", // The method to invoke asynchronoysly InvocationArg::empty() // The `InvocationArg`s to use for the invocation - empty for this example ); // Wait for the response to come let instance_receiver = instance_receiver_res?; let _ = instance_receiver.rx().recv(); ``` In the Java world, a Class that can do __Native Callbacks__ must extend the `org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport` For example, consider the following Java class. The `performCallback` method spawns a new Thread and invokes the `doCallback` method in this Thread. The `doCallback` method is inherited by the `NativeCallbackToRustChannelSupport` class. ```java package org.astonbitecode.j4rs.tests; import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport; public class MyTest extends NativeCallbackToRustChannelSupport { public void performCallback() { new Thread(() -> { doCallback("THIS IS FROM CALLBACK!"); }).start(); } } ``` ### Using Maven artifacts Since release 0.6.0 there is the possibility to download Java artifacts from the Maven repositories. While it is possible to define more repos, the [maven central](https://search.maven.org/) is by default and always available. For example, here is how the dropbox dependency can be downloaded and get deployed to be used by the rust code: ```rust let dbx_artifact = MavenArtifact::from("com.dropbox.core:dropbox-core-sdk:3.0.11"); jvm.deploy_artifact(dbx_artifact)?; ``` Additional artifactories can be used as well: ```rust let jvm: Jvm = JvmBuilder::new() .with_maven_settings(MavenSettings::new(vec![ MavenArtifactRepo::from("myrepo1::https://my.repo.io/artifacts"), MavenArtifactRepo::from("myrepo2::https://my.other.repo.io/artifacts")]) ) .build() ?; jvm.deploy_artifact(&MavenArtifact::from("io.my:library:1.2.3"))?; ``` Maven artifacts are added automatically to the classpath and do not need to be explicitly added. A good practice is that the deployment of maven artifacts is done by build scripts, during the crate's compilation. This ensures the classpath is properly populated during the actual Rust code execution. _Note: the deployment does not take care the transitive dependencies yet._ ### Adding jars to the classpath If we have one jar that needs to be accessed using `j4rs`, we need to add it in the classpath during the JVM creation: ```rust let entry = ClasspathEntry::new("/home/myuser/dev/myjar-1.0.0.jar"); let jvm: Jvm = JvmBuilder::new() .classpath_entry(entry) .build()?; ``` ## j4rs Java library The jar for `j4rs` is available in the Maven Central. It may be used by adding the following dependency in a pom: ```xml io.github.astonbitecode j4rs 0.21.0 provided ``` Note that the `scope` is `provided`. This is because the `j4rs` Java resources are always available with the `j4rs` crate. Use like this in order to avoid possible classloading errors. ## j4rs in android ### Rust side 1. Define your crate as cdylib in the `Cargo.toml`: ```toml [lib] name = "myandroidapp" crate-type = ["cdylib"] ``` 2. Implement a `jni_onload` function and apply the provided `JavaVM` to the `j4rs` like following: ```rust const JNI_VERSION_1_6: jint = 0x00010006; #[allow(non_snake_case)] #[no_mangle] pub extern fn jni_onload(env: *mut JavaVM, _reserved: jobject) -> jint { j4rs::set_java_vm(env); jni_version_1_6 } ``` ### Java side Create an `Activity` and define your native methods normally, as described [here](#java-to-rust-support). Note: If you encounter any issues when using j4rs in older Android versions, this may be caused by Java 8 compatibility problems. This is why there is a `Java 7` version of `j4rs`: ```xml io.github.astonbitecode j4rs 0.13.1-java7 ``` Update: Java 7 is no more supported. `j4rs` 0.13.1 is the last version. ## JavaFX support (v0.13.0 onwards) ### Steps to build a JavaFX UI #### 1. Have Rust, cargo and JDK 11 (or above) installed #### 2. Retrieve the JavaFX dependencies for j4rs: A good idea is that this happens during build time, in order the dependencies to be available when the actual Rust application starts and the JVM is initialized. This can happen by adding the following in a [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html?highlight=build,scrpit#build-scripts): ```rust use j4rs::JvmBuilder; use j4rs::jfx::JavaFxSupport; fn main() { let jvm = JvmBuilder::new().build().unwrap(); jvm.deploy_javafx_dependencies().unwrap(); } ``` #### 3. Implement the UI: There are two choices here; either build the UI using FXML, or, build it traditionally, using Java code. In the code snippets below, you may find comments with a short description for each line. ##### 3.a Implement the UI with Java calls to the JavaFX API ```rust // Create a Jvm with JavaFX support let jvm = JvmBuilder::new().with_javafx_support().build()?; // Start the JavaFX application. // When the JavaFX application starts, the `InstanceReceiver` channel that is returned from the `start_javafx_app` invocation // will receive an Instance of `javafx.stage.Stage`. // The UI may start being built using the provided `Stage`. let stage = jvm.start_javafx_app()?.rx().recv()?; // Create a StackPane. Java code: StackPane root = new StackPane(); let root = jvm.create_instance("javafx.scene.layout.StackPane", InvocationArg::empty())?; // Create the button. Java code: Button btn = new Button(); let btn = jvm.create_instance("javafx.scene.control.Button", InvocationArg::empty())?; // Get the action channel for this button let btn_action_channel = jvm.get_javafx_event_receiver(&btn, FxEventType::ActionEvent_Action)?; // Set the text of the button. Java code: btn.setText("Say Hello World to Rust"); jvm.invoke(&btn, "setText", &["A button that sends events to Rust".try_into()?])?; // Add the button to the GUI. Java code: root.getChildren().add(btn); jvm.chain(&root)? .invoke("getChildren", InvocationArg::empty())? .invoke("add", &[btn.try_into()?])? .collect(); // Create a new Scene. Java code: Scene scene = new Scene(root, 300, 250); let scene = jvm.create_instance("javafx.scene.Scene", &[ root.try_into()?, InvocationArg::try_from(300_f64)?.into_primitive()?, InvocationArg::try_from(250_f64)?.into_primitive()?])?; // Set the title for the scene. Java code: stage.setTitle("Hello Rust world!"); jvm.invoke(&stage, "setTitle", &["Hello Rust world!".try_into()?])?; // Set the scene in the stage. Java code: stage.setScene(scene); jvm.invoke(&stage, "setScene", &[scene.try_into()?])?; // Show the stage. Java code: stage.show(); jvm.invoke(&stage, "show", InvocationArg::empty())?; ``` ##### 3.b Implement the UI with [FXML](https://openjfx.io/javadoc/12/javafx.fxml/javafx/fxml/doc-files/introduction_to_fxml.html#overview) I personally prefer building the UI with FXMLs, using for example the [Scene Builder](https://gluonhq.com/products/scene-builder/). The thing to keep in mind is that the controller class should be defined in the root FXML element and it should be `fx:controller="org.astonbitecode.j4rs.api.jfx.controllers.FxController"` Here is an FXML example; it creates a window with a label and a button: ```xml