1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! This crate helps "store" and "retrieve" the current Java Virtual Machine in a safe way, plus it provides routines to help you get the JNIEnv for the current thread you're running on. This way, you set the JVM once and ask for the JNIEnv you need when you need it. Attaching and detaching from the JavaVM should be left to the library, it is none of your business :)
//! Note that you have to set the JavaVM once before you use the functions in this library. Failure to do that will make your program panic!
//! # Example
//! ```
//! javavm::set_jvm(None); // Pass a valid JavaVM instance here (hint: use the jni crate, which this crate already depends on)
//! // ... Other code goes here
//!
//! let handle = std::thread::spawn(|| {
//!     // When you need the JNIEnv
//!     let _ = javavm::get_env();
//! });
//! ```

use std::cell::RefCell;
use std::collections::HashMap;

use jni::objects::JClass;
use jni::JNIEnv;
use jni::JavaVM;

static mut VM: Option<JavaVM> = None;
thread_local! {
    static CACHED_CLASSES: RefCell<HashMap<String, JClass<'static>>> = RefCell::new(HashMap::new());
}

/// Sets the current JavaVM. All JNIEnv instances will come from this JavaVM
pub fn set_jvm(vm: Option<JavaVM>) {
    unsafe {
        VM = vm;
    }
}

/// Retrieve the current JVM as set by set_jvm
pub fn jvm() -> Option<&'static JavaVM> {
    if let Some(vm) = unsafe { VM.as_ref() } {
        return Some(vm);
    }

    None
}

/// Retrieves the current JNIEnv from the JavaVM.
/// # Panics
/// This function will panic if a JavaVM has not already been set
pub fn get_env() -> JNIEnv<'static> {
    let vm = unsafe { VM.as_ref().unwrap() };
    vm.attach_current_thread_as_daemon().unwrap()
}

/// Retrieves the current JNIEnv from the JavaVM.
/// Does not panic if there is no JavaVM currently set and returns None instead
pub fn get_env_safe() -> Option<JNIEnv<'static>> {
    let vm = unsafe { VM.as_ref()? };
    match vm.attach_current_thread_as_daemon() {
        Ok(env) => Some(env),
        Err(_) => None,
    }
}

/// Find and cache the JClass for the current thread. If the class has already been looked up, it returns the class and avoids any other expensive lookup without any other work on your part. The class will be automatically unloaded on program termination. If you want to unload a class before program termination, use `unload_cached_class(&str)`
pub fn load_class_cached(name: &str) -> Option<JClass<'static>> {
    let get_class = || -> Option<JClass> {
        return CACHED_CLASSES.with(|map| -> Option<JClass> {
            if map.borrow().contains_key(name) {
                let res = *map.borrow().get(name).unwrap();
                return Some(res);
            }

            None
        });
    };

    let class = get_class();

    if class.is_some() {
        return class;
    }

    let env = get_env();
    if let Ok(class) = env.find_class(name) {
        CACHED_CLASSES.with(|map| {
            map.borrow_mut().insert(name.to_string(), class);
        });
    }

    get_class()
}

/// Cache this class. You can retrieve the class with the `load_class_cached` function, passing it the name given to this function. Note that this class will only be available for the "current thread". I do not know if the class instances are usable on multiple threads and until I'm sure about this, thread-local storage is what I'll stick to
pub fn cache_class(name: &str, class: JClass<'static>) {
    CACHED_CLASSES.with(|map| {
        map.borrow_mut().insert(name.to_string(), class);
    });
}

/// Unloads a cached class (if one exists). Does nothing if the class has not already been cached
pub fn unload_cached_class(name: &str) {
    CACHED_CLASSES.with(|map| map.borrow_mut().remove(name));
}

#[cfg(test)]
mod tests {

    use crate::*;

    #[test]
    #[should_panic]
    fn no_vm() {
        let _ = jvm().unwrap();
    }

    #[test]
    #[should_panic]
    fn no_env() {
        // Get the env from another thread. This is probably more appropriate for an example
        let handle = std::thread::spawn(|| {
            let _ = get_env();
        });

        handle.join().unwrap();
    }

    #[test]
    fn no_env_safe() {
        assert!(get_env_safe().is_none());
    }
}