#java #jni #bindings #language

nightly rsjni

Rust bindings to the Java Native Interface

10 unstable releases (3 breaking)

Uses old Rust 2015

0.4.0 Aug 25, 2018
0.3.2 Mar 29, 2018
0.3.0 Jan 27, 2018
0.2.0 Jan 21, 2018
0.1.2 Sep 30, 2017

#454 in Programming languages

MIT/Apache

570KB
6K SLoC

rsjni

Rust bindings to the Java Native Interface (JNI)


lib.rs:

Rust JNI Bindings for Java Interop.

Examples

Java

We will be exercising the following Java class via Rust.

//
//  Test Class
//
import java.lang.System;
import java.util.Arrays;
import java.util.stream.Stream;
import java.util.stream.IntStream;

public class Test {
	public int current = 0;
	public static String message = "Hello!";

	public Test(int current) {
		this.current = current;
	}

	public void incrementCurrent() {
        this.current += 1;
	}

	public int getCurrent() {
		return this.current;
	}

	public static int add(int a, int b) {
		return a + b;
	}

	public static String append(String input) {
		return input + " there from Java!";
	}

	public static void printMessage() {
		System.out.println("The message is: " + message);
	}

	public boolean allOnes(int iarr[]) {
		final IntStream stream1 = Arrays.stream(iarr);
		return stream1.allMatch(i -> i == 1);
	}

	public int[] iArr() {
   	    int[] a = {1,2,3,4,5,9};
        return a;
    }
}

Rust

#
#
#
// Setup the `Classpath`.   These are the directories or jar files to search for .class files.
let manifest = env::var("CARGO_MANIFEST_DIR")?;
let path = PathBuf::from(manifest).join("examples");
let mut classpath: Classpath = Default::default();
classpath.add(path);

// Setup the `Opts`, i.e. -Xms256m
let mut opts: Opts = Default::default();
opts.set_initial_heap_size(256);
opts.set_max_heap_size(256);
opts.set_version(Version::V18);
opts.set_classpath(classpath);

// Create the JVM.
let jvm = Jvm::new(opts)?;

// Get the JNI environment from the JVM.
let env = jvm.env();

// Load the class `Test` into the JVM if it is found on the classpath.
let test_class = env.find_class("Test")?;

// We are Java-ing.  We need to construct an instance of our class.
//
// The proper constructor is found based on the argument signature, `Test(int)` in this case.
// We are trying to call `Test(5)` here, which stores 5 in the `current` field.
let constructor_id = test_class.get_method_id("<init>", &[Kind::Int], &Kind::Void)?;
let test_object = test_class.new_object(&constructor_id, &[Input::Int(5)])?;

let inc_curr_method_id = test_class.get_method_id("incrementCurrent", &[], &Kind::Void)?;
// Lets change some internal state.  Call 'public void incrementCurrent()'.  Internally, the
// `incrementCurrent` method increments the `current` field by 1.
test_object.call_method(&inc_curr_method_id, &[])?;

//  Lets see what our `current` value is via a getter. Call 'public int getCurrent()'
let get_curr_method_id = test_class.get_method_id("getCurrent", &[], &Kind::Int)?;
let current = test_object.call_method(&get_curr_method_id, &[])?;
// Here we are converting the `Output` object into the type we expect.
assert_eq!(6, current.int());

// Set the 'public int current' field to 10.
let current_field_id = test_class.get_field_id("current", &Kind::Int)?;
test_object.set_field(&current_field_id, &Input::Int(10))?;

// Read back the field to check that it is now 10.
let updated_current = test_object.get_field(&current_field_id)?;
assert_eq!(10, updated_current.int());

// Call 'public static int add(int a, int b)'.
// Notice that we call static methods on the class, not the object instance.
let args = [Input::Int(1), Input::Int(8)];
let add_method_id = test_class.get_static_method_id("add", &[Kind::Int, Kind::Int], &Kind::Int)?;
let value1 = test_class.call_static_method(&add_method_id, &args)?;
assert_eq!(9, value1.int());

// Get the value of the 'public static String message' field.
let message_field_id = test_class.get_static_field_id("message", &Kind::Object("java/lang/String".to_string()))?;
let message = test_class.get_static_field(&message_field_id)?;
// Convert the output object into a string.
let java_str: strng::JavaString = TryFrom::try_from(message.object())?;
let message_str: String = TryFrom::try_from(java_str)?;
assert_eq!(message_str, "Hello!");

// Set the value of the 'public static String message' field to "Hi!".
// We need to create the 'java/lang/String' in the JVM first.
let str_obj = strng::JavaString::new(&env, "Hi!")?;
test_class.set_static_field(&message_field_id, &Input::StringObj(str_obj))?;

// Get the value again to see that the static field is changed.
let new_message = test_class.get_static_field(&message_field_id)?;
let java_str: strng::JavaString = TryFrom::try_from(new_message.object())?;
let new_message_str: String = TryFrom::try_from(java_str)?;
assert_eq!(new_message_str, "Hi!");

// Work with an array input.
// Create an int[5] in the JVM.
let mut int_arr = array::Int::new(&env, 5)?;

// Setup the array in the JVM.
int_arr.set_slice(0, &[1; 5])?;

// Check that the array now contains all 1's.
let args = [Input::IntArr(int_arr)];
let all_ones_method_id = test_class.get_method_id("allOnes", &[Kind::IntArr], &Kind::Boolean)?;
let value = test_object.call_method(&all_ones_method_id, &args)?;
assert!(value.boolean());

// Now we are calling a method that returns an int[].
let int_arr_method_id = test_class.get_method_id("intArr", &[], &Kind::IntArr)?;
let int_arr_ret = test_object.call_method(&int_arr_method_id, &[])?;
// Get the object out of the `Output`.
let int_arr: array::Int = TryFrom::try_from(int_arr_ret.object())?;

// Convert it to an array we can work with.
let mut full_buf = [0; 6];
int_arr.as_slice(0, 6, &mut full_buf)?;

// Release it back to the JVM to allow for GC.
drop(int_arr);

Dependencies

~4–5MB
~110K SLoC