j4rs stands for 'Java for Rust' and allows effortless calls to Java code from Rust and vice-versa.
```rust use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
// Create a JVM let jvm = JvmBuilder::new().build()?;
// Create a java.lang.String instance
let stringinstance = jvm.createinstance(
"java.lang.String", // The Java class to create an instance for
&Vec::new(), // The 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 booleaninstance = jvm.invoke(
&stringinstance, // The String instance created above
"isEmpty", // The method of the String instance to invoke
&Vec::new(), // The InvocationArg
s to use for the invocation - empty for this example
)?;
// If we need to transform an Instance
to Rust value, the to_rust
should be called
let rustboolean: bool = jvm.torust(booleaninstance)?;
println!("The isEmpty() method of the java.lang.String instance returned {}", rustboolean);
// The above prints:
// The isEmpty() method of the java.lang.String instance returned true
// Static invocation
let staticinvocationresult = jvm.invokestatic(
"java.lang.System", // The Java class to invoke
"currentTimeMillis", // The static method of the Java class to invoke
&Vec::new(), // The InvocationArg
s to use for the invocation - empty for this example
)?;
```
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 myvec: Vec
let i10 = InvocationArg::tryfrom(myvec.as_slice())?; ```
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
&Vec::new(), // The
InvocationArg`s to use for the constructor call - empty for this example
)?;
let i11 = InvocationArg::tryfrom(onemorestringinstance)?; ```
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
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")?;
```rust // Create a Java array of Strings let s1 = InvocationArg::tryfrom("string1")?; let s2 = InvocationArg::tryfrom("string2")?; let s3 = InvocationArg::try_from("string3")?;
let arrinstance = jvm.createjavaarray("java.lang.String", &vec![s1, s2, s3])?;
// Invoke the Arrays.asList(...) and retrieve a java.util.List
rust
// Assuming that the following map_instance is a Map<String, Integer>
// we may invoke its put method
jvm.invoke(&map_instance, "put", &vec![InvocationArg::try_from("one")?, InvocationArg::try_from(1)?])?;
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]);
```rust use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
// Create a JVM let jvm = JvmBuilder::new().build()?;
// Create an instance let stringinstance = jvm.createinstance( "java.lang.String", &vec![InvocationArg::try_from(" a string ")?], )?;
// Perform chained operations on the instance let stringsize: isize = jvm.chain(stringinstance) .invoke("trim", &[])? .invoke("length", &[])? .to_rust()?;
// Assert that the string was trimmed assert!(string_size == 8); ```
j4rs
provides support for Java to Rust callbacks.
These callbacks come to the Rust world via Rust Channels.
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:
```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",
&Vec::new())?;
// Invoke the method
let instancereceiverres = jvm.invoketochannel(
&i, // The instance to invoke asynchronously
"performCallback", // The method to invoke asynchronoysly
&Vec::new() // The InvocationArg
s to use for the invocation - empty for this example
);
// Wait for the response to come let instancereceiver = instancereceiverres?; let _ = instancereceiver.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();
}
} ```
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 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() .withmavensettings(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 that the classpath is properly populated during the actual Rust code execution.
Note: the deployment does not take care the transitive dependencies yet.
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()?;
The jar for j4rs
is available in the Maven Central. It may be used by adding the following dependency in a pom:
xml
<dependency>
<groupId>io.github.astonbitecode</groupId>
<artifactId>j4rs</artifactId>
<version>0.13.0</version>
<scope>provided</scope>
</dependency>
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.
If you encounter any issues when using j4rs in Android, this may be caused by Java 8 compatibility problems. This is why there is a Java 7
version of j4rs
:
xml
<dependency>
<groupId>io.github.astonbitecode</groupId>
<artifactId>j4rs</artifactId>
<version>0.13.0-java7</version>
</dependency>
(v0.13.0 onwards)
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:
```rust use j4rs::JvmBuilder; use j4rs::jfx::JavaFxSupport;
fn main() {
let jvm = JvmBuilder::new().build().unwrap();
jvm.deploy_javafx_dependencies().unwrap();
}
```
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.
```rust // Create a Jvm with JavaFX support let jvm = JvmBuilder::new().withjavafxsupport().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.startjavafxapp()?.rx().recv()?;
// Create a StackPane. Java code: StackPane root = new StackPane(); let root = jvm.create_instance("javafx.scene.layout.StackPane", &[])?;
// Create the button. Java code: Button btn = new Button(); let btn = jvm.createinstance("javafx.scene.control.Button", &[])?; // Get the action channel for this button let btnactionchannel = jvm.getjavafxeventreceiver(&btn, FxEventType::ActionEventAction)?; // 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".tryinto()?])?; // Add the button to the GUI. Java code: root.getChildren().add(btn); jvm.chain(&root)? .invoke("getChildren", &[])? .invoke("add", &[btn.try_into()?])? .collect();
// Create a new Scene. Java code: Scene scene = new Scene(root, 300, 250); let scene = jvm.createinstance("javafx.scene.Scene", &[ root.tryinto()?, InvocationArg::tryfrom(300f64)?.intoprimitive()?, InvocationArg::tryfrom(250f64)?.intoprimitive()?])?; // Set the title for the scene. Java code: stage.setTitle("Hello Rust world!"); jvm.invoke(&stage, "setTitle", &["Hello Rust world!".tryinto()?])?; // Set the scene in the stage. Java code: stage.setScene(scene); jvm.invoke(&stage, "setScene", &[scene.tryinto()?])?; // Show the stage. Java code: stage.show(); jvm.invoke(&stage, "show", &[])?;
```
I personally prefer building the UI with FXMLs, using for example the 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
```
The id
of the elements can be used to retrieve the respective Nodes in Rust and act upon them (eg. adding Event Listeners, changing the texts or effects on them etc).
```rust // Create a Jvm with JavaFX support let jvm = JvmBuilder::new().withjavafxsupport().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.startjavafxapp()?.rx().recv()?;
// Set the title for the scene. Java code: stage.setTitle("Hello Rust world!"); jvm.invoke(&stage, "setTitle", &["Hello JavaFX from Rust!".try_into()?])?; // Show the stage. Java code: stage.show(); jvm.invoke(&stage, "show", &[])?;
// Load a fxml. This returns an FxController
which can be used in order to find Nodes by their id,
// add Event Listeners and more.
let controller = jvm.loadfxml(&PathBuf::from("./fxml/jfxin_rust.fxml"), &stage)?;
// Wait for the controller to be initialized. This is not mandatory, it is here to shoe that the functionality exists. let _ = controller.oninitializedcallback(&jvm)?.rx().recv()?; println!("The controller is initialized!");
// Get the InstanceReceiver to retrieve callbacks from the JavaFX button with id helloButton let hellobuttonactionchannel = controller.geteventreceiverfornode("helloButton", FxEventType::ActionEventAction, &jvm)?;
```
For a complete example, please have a look here.
(v0.12.0 onwards)
Add the two needed dependencies (j4rs
and j4rs_derive
) in the Cargo.toml
and mark the project as a cdylib
, in order to have a shared library as output.
This library will be loaded and used by the Java code to achieve JNI calls.
Annotate the functions that will be accessible from the Java code with the call_from_java
attribute:
```rust
fn myfunctionwithnoargs() { println!("Hello from the Rust world!"); // If you need to have a Jvm here, you need to attach the thread let jvm = Jvm::attach_thread().unwrap(); // Now you may further call Java classes and methods as usual! } ```
For a complete example, please have a look here.
Note: JNI is used behind the scenes, so, any conventions in naming that hold for JNI, should hold for j4rs
too.
For example, underscores (_
) should be escaped and become _1
in the call_from_java
definition.
At your option, under: