rusty-jvm 0.5.0

An implementation of a Java Virtual Machine (JVM).
Documentation
use crate::vm::error::{Error, Result};
use crate::vm::system_native::properties_provider::properties::{
    endianness, file_separator, java_home, line_separator, os_name, os_version, path_separator,
    tmp_dir, user_dir,
};
use indexmap::IndexMap;
use once_cell::sync::OnceCell;
use std::sync::LazyLock;

static DEFAULT_PLATFORM_PROPERTIES: LazyLock<IndexMap<&str, &str>> = LazyLock::new(|| {
    IndexMap::from([
        ("display.country", "display.country_VALUE"),
        ("display.language", "display.language_VALUE"),
        ("display.script", "display.script_VALUE"),
        ("display.variant", "display.variant_VALUE"),
        ("file.separator", file_separator()),
        ("format.country", "format.country_VALUE"),
        ("format.language", "format.language_VALUE"),
        ("format.script", "format.script_VALUE"),
        ("format.variant", "format.variant_VALUE"),
        ("ftp.nonProxyHosts", "ftp.nonProxyHosts_VALUE"),
        ("ftp.proxyHost", "ftp.proxyHost_VALUE"),
        ("ftp.proxyPort", "ftp.proxyPort_VALUE"),
        ("http.nonProxyHosts", "http.nonProxyHosts_VALUE"),
        ("http.proxyHost", "http.proxyHost_VALUE"),
        ("http.proxyPort", "http.proxyPort_VALUE"),
        ("https.proxyHost", "https.proxyHost_VALUE"),
        ("https.proxyPort", "https.proxyPort_VALUE"),
        ("java.io.tmpdir", tmp_dir()),
        ("line.separator", line_separator()),
        ("native.encoding", "native.encoding_VALUE"),
        ("os.arch", "os.arch_VALUE"),
        ("os.name", os_name()),
        ("os.version", os_version()),
        ("path.separator", path_separator()),
        ("socksNonProxyHosts", "socksNonProxyHosts_VALUE"),
        ("socksProxyHost", "socksProxyHost_VALUE"),
        ("socksProxyPort", "socksProxyPort_VALUE"),
        ("stderr.encoding", "cp437"), // https://github.com/hextriclosan/rusty-jvm/issues/489
        ("stdin.encoding", "native.encoding_VALUE"),
        ("stdout.encoding", "stdout.encoding_VALUE"),
        ("sun.arch.abi", "sun.arch.abi_VALUE"),
        ("sun.arch.data.model", "sun.arch.data.model_VALUE"),
        ("sun.cpu.endian", endianness()),
        ("sun.cpu.isalist", "sun.cpu.isalist_VALUE"),
        ("sun.io.unicode.encoding", "sun.io.unicode.encoding_VALUE"),
        ("sun.jnu.encoding", "sun.jnu.encoding_VALUE"),
        ("sun.os.patch.level", "sun.os.patch.level_VALUE"),
        ("user.dir", user_dir()),
        ("user.home", "user.home_VALUE"),
        ("user.name", "user.name_VALUE"),
    ])
});

static DEFAULT_VM_PROPERTIES: LazyLock<IndexMap<&str, &str>> =
    LazyLock::new(|| IndexMap::from([("java.home", java_home())]));

pub static OVERRIDDEN_PLATFORM_PROPERTIES: OnceCell<IndexMap<String, String>> = OnceCell::new();
pub static OVERRIDDEN_VM_PROPERTIES: OnceCell<IndexMap<String, String>> = OnceCell::new();

pub fn init_system_properties(system_properties: IndexMap<String, String>) -> Result<()> {
    let mut overridden_platform_props = deep_clone(&DEFAULT_PLATFORM_PROPERTIES);
    let mut overridden_vm_props = deep_clone(&DEFAULT_VM_PROPERTIES);

    let (platform, vm): (IndexMap<String, String>, IndexMap<String, String>) = system_properties
        .into_iter()
        .partition(|(key, _value)| overridden_platform_props.contains_key(key));

    overridden_platform_props.extend(platform);
    overridden_vm_props.extend(vm);

    OVERRIDDEN_PLATFORM_PROPERTIES
        .set(overridden_platform_props)
        .map_err(|existing_value| {
            Error::new_execution(&format!(
                "OVERRIDDEN_PLATFORM_PROPERTIES already initialized: {existing_value:?}"
            ))
        })?;
    OVERRIDDEN_VM_PROPERTIES
        .set(overridden_vm_props)
        .map_err(|existing_value| {
            Error::new_execution(&format!(
                "OVERRIDDEN_VM_PROPERTIES already initialized: {existing_value:?}"
            ))
        })
}

fn deep_clone(to_clone: &IndexMap<&str, &str>) -> IndexMap<String, String> {
    to_clone
        .clone()
        .iter()
        .map(|(key, value)| (key.to_string(), value.to_string()))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::vm::JAVA_HOME;
    use std::path::PathBuf;

    #[test]
    fn test_init_system_properties() {
        JAVA_HOME.set(PathBuf::from("default_java_home")).unwrap();
        let system_properties = IndexMap::from([
            ("os.name".to_string(), "new_os_name".to_string()),
            ("java.home".to_string(), "new_java_home".to_string()),
            ("other.property".to_string(), "other_value".to_string()),
        ]);

        assert!(init_system_properties(system_properties).is_ok());
        assert!(OVERRIDDEN_VM_PROPERTIES.get().is_some());
        assert!(OVERRIDDEN_PLATFORM_PROPERTIES.get().is_some());

        let platform_properties = OVERRIDDEN_PLATFORM_PROPERTIES.get().unwrap();
        let vm_properties = OVERRIDDEN_VM_PROPERTIES.get().unwrap();
        assert_eq!(
            platform_properties.get("os.name"),
            Some(&"new_os_name".to_string())
        );
        assert_eq!(platform_properties.len(), 40);

        assert_eq!(
            vm_properties,
            &IndexMap::from([
                ("java.home".to_string(), "new_java_home".to_string()),
                ("other.property".to_string(), "other_value".to_string())
            ])
        );
    }
}