diff --git a/java_runtime/src/classes/java/util.rs b/java_runtime/src/classes/java/util.rs index 06b15506..c79f5121 100644 --- a/java_runtime/src/classes/java/util.rs +++ b/java_runtime/src/classes/java/util.rs @@ -32,6 +32,7 @@ mod hashtable_key_set; mod hashtable_values; mod iterator; mod list; +mod locale; mod map; mod map_entry; mod no_such_element_exception; @@ -55,7 +56,8 @@ pub use self::{ hash_map_hash_iterator::HashMapHashIterator, hash_map_key_iterator::HashMapKeyIterator, hash_map_key_set::HashMapKeySet, hash_map_value_iterator::HashMapValueIterator, hash_map_values::HashMapValues, hash_set::HashSet, hashtable::Hashtable, hashtable_entry::HashtableEntry, hashtable_entry_set::HashtableEntrySet, hashtable_enumerator::HashtableEnumerator, - hashtable_key_set::HashtableKeySet, hashtable_values::HashtableValues, iterator::Iterator, list::List, map::Map, map_entry::MapEntry, - no_such_element_exception::NoSuchElementException, properties::Properties, random::Random, set::Set, simple_time_zone::SimpleTimeZone, - stack::Stack, time_zone::TimeZone, timer::Timer, timer_task::TimerTask, timer_thread::TimerThread, vector::Vector, vector_itr::VectorItr, + hashtable_key_set::HashtableKeySet, hashtable_values::HashtableValues, iterator::Iterator, list::List, locale::Locale, map::Map, + map_entry::MapEntry, no_such_element_exception::NoSuchElementException, properties::Properties, random::Random, set::Set, + simple_time_zone::SimpleTimeZone, stack::Stack, time_zone::TimeZone, timer::Timer, timer_task::TimerTask, timer_thread::TimerThread, + vector::Vector, vector_itr::VectorItr, }; diff --git a/java_runtime/src/classes/java/util/locale.rs b/java_runtime/src/classes/java/util/locale.rs new file mode 100644 index 00000000..abd9ef8a --- /dev/null +++ b/java_runtime/src/classes/java/util/locale.rs @@ -0,0 +1,929 @@ +use core::iter; + +use alloc::{format, string::ToString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// class java.util.Locale +pub struct Locale; + +impl Locale { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Locale", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Cloneable", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_language, Default::default()), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::init_with_language_country, + Default::default(), + ), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + Self::init, + Default::default(), + ), + JavaMethodProto::new("getDefault", "()Ljava/util/Locale;", Self::get_default, MethodAccessFlags::STATIC), + JavaMethodProto::new("setDefault", "(Ljava/util/Locale;)V", Self::set_default, MethodAccessFlags::STATIC), + JavaMethodProto::new( + "getAvailableLocales", + "()[Ljava/util/Locale;", + Self::get_available_locales, + MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getISOCountries", + "()[Ljava/lang/String;", + Self::get_iso_countries, + MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getISOLanguages", + "()[Ljava/lang/String;", + Self::get_iso_languages, + MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("getLanguage", "()Ljava/lang/String;", Self::get_language, Default::default()), + JavaMethodProto::new("getCountry", "()Ljava/lang/String;", Self::get_country, Default::default()), + JavaMethodProto::new("getVariant", "()Ljava/lang/String;", Self::get_variant, Default::default()), + JavaMethodProto::new("getISO3Language", "()Ljava/lang/String;", Self::get_iso3_language, Default::default()), + JavaMethodProto::new("getISO3Country", "()Ljava/lang/String;", Self::get_iso3_country, Default::default()), + JavaMethodProto::new( + "getDisplayLanguage", + "()Ljava/lang/String;", + Self::get_display_language_default, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayLanguage", + "(Ljava/util/Locale;)Ljava/lang/String;", + Self::get_display_language, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayCountry", + "()Ljava/lang/String;", + Self::get_display_country_default, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayCountry", + "(Ljava/util/Locale;)Ljava/lang/String;", + Self::get_display_country, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayVariant", + "()Ljava/lang/String;", + Self::get_display_variant_default, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayVariant", + "(Ljava/util/Locale;)Ljava/lang/String;", + Self::get_display_variant, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayName", + "()Ljava/lang/String;", + Self::get_display_name_default, + Default::default(), + ), + JavaMethodProto::new( + "getDisplayName", + "(Ljava/util/Locale;)Ljava/lang/String;", + Self::get_display_name, + Default::default(), + ), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, Default::default()), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, Default::default()), + JavaMethodProto::new("clone", "()Ljava/lang/Object;", Self::clone, Default::default()), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), + ], + fields: vec![ + JavaFieldProto::new( + "ENGLISH", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FRENCH", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "GERMAN", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "ITALIAN", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "JAPANESE", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "KOREAN", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CHINESE", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "SIMPLIFIED_CHINESE", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TRADITIONAL_CHINESE", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FRANCE", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "GERMANY", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "ITALY", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "JAPAN", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "KOREA", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CHINA", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "PRC", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TAIWAN", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "UK", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "US", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CANADA", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CANADA_FRENCH", + "Ljava/util/Locale;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "defaultLocale", + "Ljava/util/Locale;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC, + ), + JavaFieldProto::new("language", "Ljava/lang/String;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + JavaFieldProto::new("country", "Ljava/lang/String;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + JavaFieldProto::new("variant", "Ljava/lang/String;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + tracing::debug!("java.util.Locale::()"); + + let english = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "en").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "ENGLISH", "Ljava/util/Locale;", english).await?; + + let french = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "fr").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "FRENCH", "Ljava/util/Locale;", french).await?; + + let german = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "de").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "GERMAN", "Ljava/util/Locale;", german).await?; + + let italian = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "it").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "ITALIAN", "Ljava/util/Locale;", italian).await?; + + let japanese = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "ja").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "JAPANESE", "Ljava/util/Locale;", japanese) + .await?; + + let korean = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "ko").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "KOREAN", "Ljava/util/Locale;", korean).await?; + + let chinese = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "zh").await?, + JavaLangString::from_rust_string(jvm, "").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "CHINESE", "Ljava/util/Locale;", chinese).await?; + + let simplified_chinese = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "zh").await?, + JavaLangString::from_rust_string(jvm, "CN").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "SIMPLIFIED_CHINESE", "Ljava/util/Locale;", simplified_chinese.clone()) + .await?; + + let traditional_chinese = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "zh").await?, + JavaLangString::from_rust_string(jvm, "TW").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field( + "java/util/Locale", + "TRADITIONAL_CHINESE", + "Ljava/util/Locale;", + traditional_chinese.clone(), + ) + .await?; + + let france = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "fr").await?, + JavaLangString::from_rust_string(jvm, "FR").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "FRANCE", "Ljava/util/Locale;", france).await?; + + let germany = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "de").await?, + JavaLangString::from_rust_string(jvm, "DE").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "GERMANY", "Ljava/util/Locale;", germany).await?; + + let italy = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "it").await?, + JavaLangString::from_rust_string(jvm, "IT").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "ITALY", "Ljava/util/Locale;", italy).await?; + + let japan = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "ja").await?, + JavaLangString::from_rust_string(jvm, "JP").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "JAPAN", "Ljava/util/Locale;", japan).await?; + + let korea = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "ko").await?, + JavaLangString::from_rust_string(jvm, "KR").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "KOREA", "Ljava/util/Locale;", korea).await?; + + let uk = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "en").await?, + JavaLangString::from_rust_string(jvm, "GB").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "UK", "Ljava/util/Locale;", uk).await?; + + let us = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "en").await?, + JavaLangString::from_rust_string(jvm, "US").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "US", "Ljava/util/Locale;", us.clone()).await?; + + let canada = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "en").await?, + JavaLangString::from_rust_string(jvm, "CA").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "CANADA", "Ljava/util/Locale;", canada).await?; + + let canada_french = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, "fr").await?, + JavaLangString::from_rust_string(jvm, "CA").await?, + JavaLangString::from_rust_string(jvm, "").await?, + ), + ) + .await?; + jvm.put_static_field("java/util/Locale", "CANADA_FRENCH", "Ljava/util/Locale;", canada_french) + .await?; + + jvm.put_static_field("java/util/Locale", "CHINA", "Ljava/util/Locale;", simplified_chinese.clone()) + .await?; + jvm.put_static_field("java/util/Locale", "PRC", "Ljava/util/Locale;", simplified_chinese) + .await?; + jvm.put_static_field("java/util/Locale", "TAIWAN", "Ljava/util/Locale;", traditional_chinese) + .await?; + jvm.put_static_field("java/util/Locale", "defaultLocale", "Ljava/util/Locale;", us) + .await?; + + Ok(()) + } + + async fn init_with_language(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, language: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Locale::({this:?}, {language:?})"); + + let country: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "").await?.into(); + let variant: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "").await?.into(); + jvm.invoke_special( + &this, + "java/util/Locale", + "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (language, country, variant), + ) + .await + } + + async fn init_with_language_country( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + language: ClassInstanceRef, + country: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.Locale::({this:?}, {language:?}, {country:?})"); + + let variant: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "").await?.into(); + jvm.invoke_special( + &this, + "java/util/Locale", + "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (language, country, variant), + ) + .await + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + language: ClassInstanceRef, + country: ClassInstanceRef, + variant: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.Locale::({this:?}, {language:?}, {country:?}, {variant:?})"); + + if language.is_null() || country.is_null() || variant.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "Locale component is null").await); + } + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + + let language = JavaLangString::to_rust_string(jvm, &language).await?.to_ascii_lowercase(); + let country = JavaLangString::to_rust_string(jvm, &country).await?.to_ascii_uppercase(); + let variant = JavaLangString::to_rust_string(jvm, &variant).await?; + + let language = JavaLangString::from_rust_string(jvm, &language).await?; + let country = JavaLangString::from_rust_string(jvm, &country).await?; + let variant = JavaLangString::from_rust_string(jvm, &variant).await?; + + jvm.put_field(&mut this, "language", "Ljava/lang/String;", language).await?; + jvm.put_field(&mut this, "country", "Ljava/lang/String;", country).await?; + jvm.put_field(&mut this, "variant", "Ljava/lang/String;", variant).await?; + + Ok(()) + } + + async fn get_default(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { + tracing::debug!("java.util.Locale::getDefault()"); + + jvm.get_static_field("java/util/Locale", "defaultLocale", "Ljava/util/Locale;").await + } + + async fn set_default(jvm: &Jvm, _: &mut RuntimeContext, locale: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Locale::setDefault({locale:?})"); + + if locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "Locale default is null").await); + } + + jvm.put_static_field("java/util/Locale", "defaultLocale", "Ljava/util/Locale;", locale) + .await + } + + async fn get_available_locales(jvm: &Jvm, _: &mut RuntimeContext) -> Result>> { + tracing::debug!("java.util.Locale::getAvailableLocales()"); + + let fields = [ + "ENGLISH", + "FRENCH", + "GERMAN", + "ITALIAN", + "JAPANESE", + "KOREAN", + "CHINESE", + "SIMPLIFIED_CHINESE", + "TRADITIONAL_CHINESE", + "FRANCE", + "GERMANY", + "ITALY", + "JAPAN", + "KOREA", + "CHINA", + "PRC", + "TAIWAN", + "UK", + "US", + "CANADA", + "CANADA_FRENCH", + ]; + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Locale;", fields.len()).await?.into(); + + for (i, field) in fields.iter().enumerate() { + let locale: ClassInstanceRef = jvm.get_static_field("java/util/Locale", field, "Ljava/util/Locale;").await?; + jvm.store_array(&mut array, i, iter::once(locale)).await?; + } + + Ok(array) + } + + async fn get_iso_countries(jvm: &Jvm, _: &mut RuntimeContext) -> Result>> { + tracing::debug!("java.util.Locale::getISOCountries()"); + + Self::string_array(jvm, &["US", "GB", "CA", "FR", "DE", "IT", "JP", "KR", "CN", "TW"]).await + } + + async fn get_iso_languages(jvm: &Jvm, _: &mut RuntimeContext) -> Result>> { + tracing::debug!("java.util.Locale::getISOLanguages()"); + + Self::string_array(jvm, &["en", "fr", "de", "it", "ja", "ko", "zh"]).await + } + + async fn string_array(jvm: &Jvm, values: &[&str]) -> Result>> { + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", values.len()).await?.into(); + for (i, value) in values.iter().enumerate() { + let value: ClassInstanceRef = JavaLangString::from_rust_string(jvm, value).await?.into(); + jvm.store_array(&mut array, i, iter::once(value)).await?; + } + Ok(array) + } + + async fn get_language(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::getLanguage({this:?})"); + + jvm.get_field(&this, "language", "Ljava/lang/String;").await + } + + async fn get_country(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::getCountry({this:?})"); + + jvm.get_field(&this, "country", "Ljava/lang/String;").await + } + + async fn get_variant(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::getVariant({this:?})"); + + jvm.get_field(&this, "variant", "Ljava/lang/String;").await + } + + async fn get_iso3_language(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::getISO3Language({this:?})"); + + let language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let language = JavaLangString::to_rust_string(jvm, &language).await?; + let iso3 = match language.as_str() { + "" => "", + "en" => "eng", + "fr" => "fra", + "de" => "deu", + "it" => "ita", + "ja" => "jpn", + "ko" => "kor", + "zh" => "zho", + _ => return Err(jvm.exception("java/lang/IllegalArgumentException", "Unsupported ISO3 language").await), + }; + + Ok(JavaLangString::from_rust_string(jvm, iso3).await?.into()) + } + + async fn get_iso3_country(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::getISO3Country({this:?})"); + + let country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let country = JavaLangString::to_rust_string(jvm, &country).await?; + let iso3 = match country.as_str() { + "" => "", + "US" => "USA", + "GB" => "GBR", + "CA" => "CAN", + "FR" => "FRA", + "DE" => "DEU", + "IT" => "ITA", + "JP" => "JPN", + "KR" => "KOR", + "CN" => "CHN", + "TW" => "TWN", + _ => return Err(jvm.exception("java/lang/IllegalArgumentException", "Unsupported ISO3 country").await), + }; + + Ok(JavaLangString::from_rust_string(jvm, iso3).await?.into()) + } + + async fn get_display_language_default(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let default = Self::get_default(jvm, context).await?; + Self::get_display_language(jvm, context, this, default).await + } + + async fn get_display_language( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + display_locale: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.Locale::getDisplayLanguage({this:?})"); + + if display_locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "display locale is null").await); + } + + let language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let language = JavaLangString::to_rust_string(jvm, &language).await?; + + Ok(JavaLangString::from_rust_string(jvm, Self::display_language(&language)).await?.into()) + } + + async fn get_display_country_default(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let default = Self::get_default(jvm, context).await?; + Self::get_display_country(jvm, context, this, default).await + } + + async fn get_display_country( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + display_locale: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.Locale::getDisplayCountry({this:?})"); + + if display_locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "display locale is null").await); + } + + let country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let country = JavaLangString::to_rust_string(jvm, &country).await?; + + Ok(JavaLangString::from_rust_string(jvm, Self::display_country(&country)).await?.into()) + } + + async fn get_display_variant_default(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let default = Self::get_default(jvm, context).await?; + Self::get_display_variant(jvm, context, this, default).await + } + + async fn get_display_variant( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + display_locale: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.Locale::getDisplayVariant({this:?})"); + + if display_locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "display locale is null").await); + } + + jvm.get_field(&this, "variant", "Ljava/lang/String;").await + } + + async fn get_display_name_default(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let default = Self::get_default(jvm, context).await?; + Self::get_display_name(jvm, context, this, default).await + } + + async fn get_display_name( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + display_locale: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.Locale::getDisplayName({this:?})"); + + if display_locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "display locale is null").await); + } + + let language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let variant: ClassInstanceRef = jvm.get_field(&this, "variant", "Ljava/lang/String;").await?; + + let language = JavaLangString::to_rust_string(jvm, &language).await?; + let country = JavaLangString::to_rust_string(jvm, &country).await?; + let variant = JavaLangString::to_rust_string(jvm, &variant).await?; + + let language = Self::display_language(&language); + let country = Self::display_country(&country); + + let result = if language.is_empty() { + if country.is_empty() { + variant + } else if variant.is_empty() { + country.to_string() + } else { + format!("{country} ({variant})") + } + } else if country.is_empty() && variant.is_empty() { + language.to_string() + } else { + let mut suffixes = vec![]; + if !country.is_empty() { + suffixes.push(country.to_string()); + } + if !variant.is_empty() { + suffixes.push(variant); + } + format!("{language} ({})", suffixes.join(", ")) + }; + + Ok(JavaLangString::from_rust_string(jvm, &result).await?.into()) + } + + fn display_language(language: &str) -> &str { + match language { + "en" => "English", + "fr" => "French", + "de" => "German", + "it" => "Italian", + "ja" => "Japanese", + "ko" => "Korean", + "zh" => "Chinese", + _ => language, + } + } + + fn display_country(country: &str) -> &str { + match country { + "US" => "United States", + "GB" => "United Kingdom", + "CA" => "Canada", + "FR" => "France", + "DE" => "Germany", + "IT" => "Italy", + "JP" => "Japan", + "KR" => "South Korea", + "CN" => "China", + "TW" => "Taiwan", + _ => country, + } + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Locale::equals({this:?}, {other:?})"); + + if other.is_null() || !jvm.is_instance(&**other, "java/util/Locale") { + return Ok(false); + } + + let other: ClassInstanceRef = ClassInstanceRef::new(other.instance); + + let this_language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let this_country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let this_variant: ClassInstanceRef = jvm.get_field(&this, "variant", "Ljava/lang/String;").await?; + let other_language: ClassInstanceRef = jvm.get_field(&other, "language", "Ljava/lang/String;").await?; + let other_country: ClassInstanceRef = jvm.get_field(&other, "country", "Ljava/lang/String;").await?; + let other_variant: ClassInstanceRef = jvm.get_field(&other, "variant", "Ljava/lang/String;").await?; + + let this_language = JavaLangString::to_rust_string(jvm, &this_language).await?; + let this_country = JavaLangString::to_rust_string(jvm, &this_country).await?; + let this_variant = JavaLangString::to_rust_string(jvm, &this_variant).await?; + let other_language = JavaLangString::to_rust_string(jvm, &other_language).await?; + let other_country = JavaLangString::to_rust_string(jvm, &other_country).await?; + let other_variant = JavaLangString::to_rust_string(jvm, &other_variant).await?; + + Ok(this_language == other_language && this_country == other_country && this_variant == other_variant) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Locale::hashCode({this:?})"); + + let language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let variant: ClassInstanceRef = jvm.get_field(&this, "variant", "Ljava/lang/String;").await?; + + let language = JavaLangString::to_rust_string(jvm, &language).await?; + let country = JavaLangString::to_rust_string(jvm, &country).await?; + let variant = JavaLangString::to_rust_string(jvm, &variant).await?; + + Ok(Self::string_hash(&language) ^ Self::string_hash(&country) ^ Self::string_hash(&variant)) + } + + fn string_hash(value: &str) -> i32 { + value.encode_utf16().fold(0i32, |acc, ch| acc.wrapping_mul(31).wrapping_add(ch as i32)) + } + + async fn clone(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::clone({this:?})"); + + let language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let variant: ClassInstanceRef = jvm.get_field(&this, "variant", "Ljava/lang/String;").await?; + + let language = JavaLangString::to_rust_string(jvm, &language).await?; + let country = JavaLangString::to_rust_string(jvm, &country).await?; + let variant = JavaLangString::to_rust_string(jvm, &variant).await?; + + Ok(jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(jvm, &language).await?, + JavaLangString::from_rust_string(jvm, &country).await?, + JavaLangString::from_rust_string(jvm, &variant).await?, + ), + ) + .await? + .into()) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Locale::toString({this:?})"); + + let language: ClassInstanceRef = jvm.get_field(&this, "language", "Ljava/lang/String;").await?; + let country: ClassInstanceRef = jvm.get_field(&this, "country", "Ljava/lang/String;").await?; + let variant: ClassInstanceRef = jvm.get_field(&this, "variant", "Ljava/lang/String;").await?; + + let language = JavaLangString::to_rust_string(jvm, &language).await?; + let country = JavaLangString::to_rust_string(jvm, &country).await?; + let variant = JavaLangString::to_rust_string(jvm, &variant).await?; + + let result = if variant.is_empty() { + if country.is_empty() { language } else { format!("{language}_{country}") } + } else { + format!("{language}_{country}_{variant}") + }; + + Ok(JavaLangString::from_rust_string(jvm, &result).await?.into()) + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index 7a667c4b..c11457a8 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -109,6 +109,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::HashtableValues::as_proto(), crate::classes::java::util::Iterator::as_proto(), crate::classes::java::util::List::as_proto(), + crate::classes::java::util::Locale::as_proto(), crate::classes::java::util::Map::as_proto(), crate::classes::java::util::MapEntry::as_proto(), crate::classes::java::util::NoSuchElementException::as_proto(), diff --git a/java_runtime/tests/classes/java/util/mod.rs b/java_runtime/tests/classes/java/util/mod.rs index a792125f..001ae93c 100644 --- a/java_runtime/tests/classes/java/util/mod.rs +++ b/java_runtime/tests/classes/java/util/mod.rs @@ -4,6 +4,7 @@ mod test_gregorian_calendar; mod test_hash_map; mod test_hash_set; mod test_hashtable; +mod test_locale; mod test_properties; mod test_random; mod test_stack; diff --git a/java_runtime/tests/classes/java/util/test_locale.rs b/java_runtime/tests/classes/java/util/test_locale.rs new file mode 100644 index 00000000..f4b0aa33 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_locale.rs @@ -0,0 +1,250 @@ +use java_runtime::classes::java::{lang::String, util::Locale}; +use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +async fn string_array_contains(jvm: &jvm::Jvm, array: &ClassInstanceRef>, expected: &str) -> Result { + let len = jvm.array_length(array).await?; + for value in jvm.load_array::>(array, 0, len).await? { + if JavaLangString::to_rust_string(jvm, &value).await? == expected { + return Ok(true); + } + } + Ok(false) +} + +#[tokio::test] +async fn test_locale_constants_and_accessors() -> Result<()> { + let jvm = test_jvm().await?; + + let english: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "ENGLISH", "Ljava/util/Locale;").await?; + let language = jvm.invoke_virtual(&english, "getLanguage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &language).await?, "en"); + let country = jvm.invoke_virtual(&english, "getCountry", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &country).await?, ""); + let variant = jvm.invoke_virtual(&english, "getVariant", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &variant).await?, ""); + + let english_string = jvm.invoke_virtual(&english, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &english_string).await?, "en"); + + let us: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "US", "Ljava/util/Locale;").await?; + let language = jvm.invoke_virtual(&us, "getLanguage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &language).await?, "en"); + let country = jvm.invoke_virtual(&us, "getCountry", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &country).await?, "US"); + + let us_string = jvm.invoke_virtual(&us, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &us_string).await?, "en_US"); + + Ok(()) +} + +#[tokio::test] +async fn test_locale_constructors_to_string_equals_and_hash_code() -> Result<()> { + let jvm = test_jvm().await?; + + let en = JavaLangString::from_rust_string(&jvm, "en").await?; + let us = JavaLangString::from_rust_string(&jvm, "US").await?; + let posix = JavaLangString::from_rust_string(&jvm, "POSIX").await?; + let locale: ClassInstanceRef = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (en.clone(), us.clone(), posix.clone()), + ) + .await? + .into(); + + let language = jvm.invoke_virtual(&locale, "getLanguage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &language).await?, "en"); + let country = jvm.invoke_virtual(&locale, "getCountry", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &country).await?, "US"); + let variant = jvm.invoke_virtual(&locale, "getVariant", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &variant).await?, "POSIX"); + + let text = jvm.invoke_virtual(&locale, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "en_US_POSIX"); + + let same: ClassInstanceRef = jvm + .new_class("java/util/Locale", "(Ljava/lang/String;Ljava/lang/String;)V", (en, us)) + .await? + .into(); + let equals: bool = jvm.invoke_virtual(&locale, "equals", "(Ljava/lang/Object;)Z", (same.clone(),)).await?; + assert!(!equals); + + let same_with_variant: ClassInstanceRef = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(&jvm, "en").await?, + JavaLangString::from_rust_string(&jvm, "US").await?, + JavaLangString::from_rust_string(&jvm, "POSIX").await?, + ), + ) + .await? + .into(); + let equals: bool = jvm + .invoke_virtual(&locale, "equals", "(Ljava/lang/Object;)Z", (same_with_variant.clone(),)) + .await?; + assert!(equals); + + let hash: i32 = jvm.invoke_virtual(&locale, "hashCode", "()I", ()).await?; + let same_hash: i32 = jvm.invoke_virtual(&same_with_variant, "hashCode", "()I", ()).await?; + assert_eq!(hash, same_hash); + + Ok(()) +} + +#[tokio::test] +async fn test_locale_default_round_trip_and_null_rejection() -> Result<()> { + let jvm = test_jvm().await?; + + let default: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + let language = jvm.invoke_virtual(&default, "getLanguage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &language).await?, "en"); + let country = jvm.invoke_virtual(&default, "getCountry", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &country).await?, "US"); + + let custom: ClassInstanceRef = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(&jvm, "en").await?, + JavaLangString::from_rust_string(&jvm, "GB").await?, + ), + ) + .await? + .into(); + let _: () = jvm + .invoke_static("java/util/Locale", "setDefault", "(Ljava/util/Locale;)V", (custom.clone(),)) + .await?; + + let updated: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + let country = jvm.invoke_virtual(&updated, "getCountry", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &country).await?, "GB"); + + let err = jvm + .invoke_static::<_, ()>( + "java/util/Locale", + "setDefault", + "(Ljava/util/Locale;)V", + (ClassInstanceRef::::new(None),), + ) + .await + .unwrap_err(); + assert!(matches!(err, JavaError::JavaException(_))); + + Ok(()) +} + +#[tokio::test] +async fn test_locale_compatibility_edges() -> Result<()> { + let jvm = test_jvm().await?; + + let english: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "ENGLISH", "Ljava/util/Locale;").await?; + let display: Result> = jvm + .invoke_virtual( + &english, + "getDisplayLanguage", + "(Ljava/util/Locale;)Ljava/lang/String;", + (ClassInstanceRef::::new(None),), + ) + .await; + assert!(matches!(display, Err(JavaError::JavaException(_)))); + + let unsupported: ClassInstanceRef = jvm + .new_class( + "java/util/Locale", + "(Ljava/lang/String;Ljava/lang/String;)V", + ( + JavaLangString::from_rust_string(&jvm, "xx").await?, + JavaLangString::from_rust_string(&jvm, "YY").await?, + ), + ) + .await? + .into(); + let iso3_language = jvm.invoke_virtual::<_, ClassInstanceRef>(&unsupported, "getISO3Language", "()Ljava/lang/String;", ()); + assert!(matches!(iso3_language.await, Err(JavaError::JavaException(_)))); + + Ok(()) +} + +#[tokio::test] +async fn test_locale_available_locales_and_iso_lists() -> Result<()> { + let jvm = test_jvm().await?; + + let locales: ClassInstanceRef> = jvm + .invoke_static("java/util/Locale", "getAvailableLocales", "()[Ljava/util/Locale;", ()) + .await?; + assert_eq!(jvm.array_length(&locales).await?, 21); + + let france: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "FRANCE", "Ljava/util/Locale;").await?; + let germany: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "GERMANY", "Ljava/util/Locale;").await?; + let prc: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "PRC", "Ljava/util/Locale;").await?; + + let mut has_france = false; + let mut has_germany = false; + let mut has_prc = false; + for locale in jvm + .load_array::>(&locales, 0, jvm.array_length(&locales).await?) + .await? + { + let equals_france: bool = jvm.invoke_virtual(&locale, "equals", "(Ljava/lang/Object;)Z", (france.clone(),)).await?; + let equals_germany: bool = jvm.invoke_virtual(&locale, "equals", "(Ljava/lang/Object;)Z", (germany.clone(),)).await?; + let equals_prc: bool = jvm.invoke_virtual(&locale, "equals", "(Ljava/lang/Object;)Z", (prc.clone(),)).await?; + has_france |= equals_france; + has_germany |= equals_germany; + has_prc |= equals_prc; + } + assert!(has_france); + assert!(has_germany); + assert!(has_prc); + + let countries: ClassInstanceRef> = jvm + .invoke_static("java/util/Locale", "getISOCountries", "()[Ljava/lang/String;", ()) + .await?; + assert!(string_array_contains(&jvm, &countries, "US").await?); + assert!(string_array_contains(&jvm, &countries, "FR").await?); + + let languages: ClassInstanceRef> = jvm + .invoke_static("java/util/Locale", "getISOLanguages", "()[Ljava/lang/String;", ()) + .await?; + assert!(string_array_contains(&jvm, &languages, "en").await?); + assert!(string_array_contains(&jvm, &languages, "fr").await?); + + Ok(()) +} + +#[tokio::test] +async fn test_locale_display_iso3_and_clone_helpers() -> Result<()> { + let jvm = test_jvm().await?; + + let us: ClassInstanceRef = jvm.get_static_field("java/util/Locale", "US", "Ljava/util/Locale;").await?; + + let display_language = jvm.invoke_virtual(&us, "getDisplayLanguage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &display_language).await?, "English"); + + let display_country = jvm.invoke_virtual(&us, "getDisplayCountry", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &display_country).await?, "United States"); + + let display_variant = jvm.invoke_virtual(&us, "getDisplayVariant", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &display_variant).await?, ""); + + let display_name = jvm.invoke_virtual(&us, "getDisplayName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &display_name).await?, "English (United States)"); + + let iso3_language = jvm.invoke_virtual(&us, "getISO3Language", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &iso3_language).await?, "eng"); + + let iso3_country = jvm.invoke_virtual(&us, "getISO3Country", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &iso3_country).await?, "USA"); + + let cloned: ClassInstanceRef = jvm.invoke_virtual(&us, "clone", "()Ljava/lang/Object;", ()).await?; + let equals: bool = jvm.invoke_virtual(&us, "equals", "(Ljava/lang/Object;)Z", (cloned,)).await?; + assert!(equals); + + Ok(()) +}