rsjni 0.4.0

Rust bindings to the Java Native Interface
// Copyright (c) 2017 rsjni developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

//! Rust JNI Bindings for Java Interop.
//!
//! # Examples
//!
//! ## Java
//! We will be exercising the following Java class via Rust.
//!
//! ```java
//! //
//! //  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
//! ```
//! # #![feature(try_from)]
//! # #[macro_use] extern crate error_chain;
//! # extern crate rsjni;
//! #
//! # use rsjni::{array, Classpath, Input, strng, Version, Jvm, Kind, Opts};
//! # use std::convert::TryFrom;
//! # use std::env;
//! # use std::path::PathBuf;
//! #
//! # error_chain!{
//! #     foreign_links {
//! #         Var(::std::env::VarError);
//! #         RsJni(::rsjni::Error);
//! #     }
//! # }
//! #
//! # fn run() -> Result<()> {
//! // 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);
//!
//! #    Ok(())
//! # }
//! # fn main() {
//! #     run().expect("fail!");
//! # }

#![deny(missing_docs)]
#![feature(ptr_internals, try_from, untagged_unions)]
#![cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
#![recursion_limit = "128"]

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate getset;
#[cfg(test)]
#[macro_use]
extern crate lazy_static;

extern crate core;

mod class;
#[allow(missing_docs)]
mod error;
mod ffi;
mod jni;
mod jvm;
mod object;
mod params;
mod utils;

pub use class::{Class, FieldId, MethodId};
pub use error::Error;
pub use jni::{Env, Version};
pub use jvm::{Classpath, Jvm, Opts};
pub use object::{array, nio, strng, Object, RefType};
pub use params::{Input, Kind, Output};

#[cfg(test)]
mod test_jvm {
    use error::Result;
    use jni::{Env, Version};
    use jvm::{Classpath, Jvm, Opts};
    use std::env;
    // use std::io::{self, Write};
    use std::path::PathBuf;
    use std::process::Command;

    #[cfg(feature = "nine")]
    fn set_version(opts: &mut Opts) {
        opts.set_version(Version::V9);
    }

    #[cfg(not(feature = "nine"))]
    fn set_version(opts: &mut Opts) {
        opts.set_version(Version::V18);
    }

    lazy_static! {
        pub static ref JVM: Jvm = {
            // Build `Test.class` so the tests will pass
            let _output = Command::new("javac")
                    .arg("-verbose")
                    .arg("examples/Test.java")
                    .arg("examples/TestException.java")
                    .arg("examples/TestExt.java")
                    .output()
                    .expect("failed to compile 'examples/Test.java'");

            // writeln!(io::stderr(), "{}", String::from_utf8_lossy(&output.stderr)).expect("unable to write to stderr");

            let manifest = env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string());
            let mut path = PathBuf::from(manifest);
            path.push("examples");
            let ojdbc_path = path.join("ojdbc8.jar");
            let mut classpath: Classpath = Default::default();
            classpath.add(path);
            classpath.add(ojdbc_path);

            let mut opts: Opts = Default::default();
            opts.set_initial_heap_size(256);
            opts.set_max_heap_size(256);
            set_version(&mut opts);
            opts.set_classpath(classpath);

            // Create the Java virtual machine.
            Jvm::new(opts).expect("Unable to start the JVM for testing!")
        };
    }

    pub fn att_det<T>(test: T) -> Result<()>
    where
        T: FnOnce(&Env) -> Result<()>,
    {
        let env = JVM.attach_current_thread()?;
        match test(&env) {
            Ok(_) => {
                JVM.detach_current_thread()?;
                Ok(())
            }
            Err(e) => {
                JVM.detach_current_thread()?;
                Err(e)
            }
        }
    }
}