diff --git a/AGENTS.md b/AGENTS.md index 409e9c0b..896cc966 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,3 +27,6 @@ - Keep `java_runtime/tests/classes` limited to Java standard library class and API behavior. - Test JVM and interpreter semantics, including class initialization, bytecode execution, and monitor behavior, with compiled Java fixtures under `test_data/src` and expected output under `test_data`, executed by `tests/test_class.rs`. - Do not place JVM core behavior tests in the `java_runtime` standard library test tree. + +## Compatibility Sources +- Implement Java compatibility from public specifications, Javadocs, and observable behavior tests. Do not consult or reproduce OpenJDK or other Java runtime implementation source code; keep the implementation independent to avoid licensing and provenance concerns. diff --git a/java_runtime/src/classes/java/util.rs b/java_runtime/src/classes/java/util.rs index 0adaf164..7ccf408d 100644 --- a/java_runtime/src/classes/java/util.rs +++ b/java_runtime/src/classes/java/util.rs @@ -66,6 +66,12 @@ mod illegal_format_flags_exception; mod illegal_format_precision_exception; mod illegal_format_width_exception; mod iterator; +mod linked_hash_map; +mod linked_hash_map_entry; +mod linked_hash_map_entry_iterator; +mod linked_hash_map_key_iterator; +mod linked_hash_map_linked_hash_iterator; +mod linked_hash_map_value_iterator; mod linked_list; mod linked_list_entry; mod linked_list_itr; @@ -171,6 +177,12 @@ pub use self::{ illegal_format_precision_exception::IllegalFormatPrecisionException, illegal_format_width_exception::IllegalFormatWidthException, iterator::Iterator, + linked_hash_map::LinkedHashMap, + linked_hash_map_entry::LinkedHashMapEntry, + linked_hash_map_entry_iterator::LinkedHashMapEntryIterator, + linked_hash_map_key_iterator::LinkedHashMapKeyIterator, + linked_hash_map_linked_hash_iterator::LinkedHashMapLinkedHashIterator, + linked_hash_map_value_iterator::LinkedHashMapValueIterator, linked_list::LinkedList, linked_list_entry::LinkedListEntry, linked_list_itr::LinkedListItr, diff --git a/java_runtime/src/classes/java/util/hash_map.rs b/java_runtime/src/classes/java/util/hash_map.rs index 8038da37..e43d0271 100644 --- a/java_runtime/src/classes/java/util/hash_map.rs +++ b/java_runtime/src/classes/java/util/hash_map.rs @@ -21,31 +21,55 @@ impl HashMap { parent_class: Some("java/util/AbstractMap"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(IF)V", Self::init_with_capacity_and_load_factor, MethodAccessFlags::PUBLIC), JavaMethodProto::new("", "(Ljava/util/Map;)V", Self::init_from_map, MethodAccessFlags::PUBLIC), - JavaMethodProto::new("size", "()I", Self::size, Default::default()), - JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), - JavaMethodProto::new("containsKey", "(Ljava/lang/Object;)Z", Self::contains_key, Default::default()), - JavaMethodProto::new("containsValue", "(Ljava/lang/Object;)Z", Self::contains_value, Default::default()), - JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsKey", "(Ljava/lang/Object;)Z", Self::contains_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsValue", "(Ljava/lang/Object;)Z", Self::contains_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", Self::put, - Default::default(), + MethodAccessFlags::PUBLIC, ), JavaMethodProto::new("putAll", "(Ljava/util/Map;)V", Self::put_all, MethodAccessFlags::PUBLIC), - JavaMethodProto::new("remove", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::remove, Default::default()), - JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), - JavaMethodProto::new("keySet", "()Ljava/util/Set;", Self::key_set, Default::default()), - JavaMethodProto::new("values", "()Ljava/util/Collection;", Self::values, Default::default()), - JavaMethodProto::new("entrySet", "()Ljava/util/Set;", Self::entry_set, Default::default()), + JavaMethodProto::new( + "remove", + "(Ljava/lang/Object;)Ljava/lang/Object;", + Self::remove, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("keySet", "()Ljava/util/Set;", Self::key_set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("values", "()Ljava/util/Collection;", Self::values, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("entrySet", "()Ljava/util/Set;", Self::entry_set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("initializeMap", "()V", Self::initialize_map, Default::default()), + JavaMethodProto::new( + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::store_new_entry, + Default::default(), + ), + JavaMethodProto::new( + "insertNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::insert_new_entry, + Default::default(), + ), + JavaMethodProto::new("keyIterator", "()Ljava/util/Iterator;", Self::key_iterator, Default::default()), + JavaMethodProto::new("valueIterator", "()Ljava/util/Iterator;", Self::value_iterator, Default::default()), + JavaMethodProto::new("entryIterator", "()Ljava/util/Iterator;", Self::entry_iterator, Default::default()), ], fields: vec![ JavaFieldProto::new("table", "[Ljava/util/HashMap$Entry;", Default::default()), JavaFieldProto::new("size", "I", Default::default()), JavaFieldProto::new("threshold", "I", Default::default()), + JavaFieldProto::new("loadFactor", "F", Default::default()), + JavaFieldProto::new("modCount", "I", Default::default()), ], access_flags: ClassAccessFlags::PUBLIC, } @@ -55,7 +79,13 @@ impl HashMap { tracing::debug!("java.util.HashMap::({this:?})"); let _: () = jvm - .invoke_special(&this, "java/util/HashMap", "", "(I)V", (DEFAULT_INITIAL_CAPACITY,)) + .invoke_special( + &this, + "java/util/HashMap", + "", + "(IF)V", + (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR), + ) .await?; Ok(()) @@ -85,38 +115,60 @@ impl HashMap { Ok(entry_set.into()) } - pub(super) async fn keys_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { + async fn keys_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { Self::snapshot_entries(jvm, this, SnapshotKind::Keys).await } - pub(super) async fn values_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { + async fn values_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { Self::snapshot_entries(jvm, this, SnapshotKind::Values).await } - pub(super) async fn entries_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { + async fn entries_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { Self::snapshot_entries(jvm, this, SnapshotKind::Entries).await } - async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, capacity: i32) -> Result<()> { + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, capacity: i32) -> Result<()> { tracing::debug!("java.util.HashMap::({this:?}, {capacity:?})"); + jvm.invoke_special(&this, "java/util/HashMap", "", "(IF)V", (capacity, DEFAULT_LOAD_FACTOR)) + .await + } + + async fn init_with_capacity_and_load_factor( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + capacity: i32, + load_factor: f32, + ) -> Result<()> { + tracing::debug!("java.util.HashMap::({this:?}, {capacity:?}, {load_factor:?})"); + if capacity < 0 { return Err(jvm .exception("java/lang/IllegalArgumentException", &format!("Illegal Capacity: {capacity}")) .await); } + if load_factor <= 0.0 || load_factor.is_nan() { + return Err(jvm + .exception("java/lang/IllegalArgumentException", &format!("Illegal Load: {load_factor}")) + .await); + } let _: () = jvm.invoke_special(&this, "java/util/AbstractMap", "", "()V", ()).await?; let table: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", capacity as usize).await?.into(); jvm.put_field(&mut this, "table", "[Ljava/util/HashMap$Entry;", table).await?; jvm.put_field(&mut this, "size", "I", 0).await?; - jvm.put_field(&mut this, "threshold", "I", Self::threshold_for_capacity(capacity)).await?; + jvm.put_field(&mut this, "threshold", "I", Self::threshold_for_capacity(capacity, load_factor)) + .await?; + jvm.put_field(&mut this, "loadFactor", "F", load_factor).await?; + jvm.put_field(&mut this, "modCount", "I", 0).await?; + let _: () = jvm.invoke_virtual(&this, "initializeMap", "()V", ()).await?; Ok(()) } - async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.HashMap::({this:?}, {map:?})"); if map.is_null() { @@ -124,8 +176,17 @@ impl HashMap { } let size: i32 = jvm.invoke_virtual(&map, "size", "()I", ()).await?; let capacity = size.saturating_mul(2).max(DEFAULT_INITIAL_CAPACITY); - let _: () = jvm.invoke_special(&this, "java/util/HashMap", "", "(I)V", (capacity,)).await?; - let _: () = jvm.invoke_virtual(&this, "putAll", "(Ljava/util/Map;)V", (map,)).await?; + let _: () = jvm + .invoke_special(&this, "java/util/HashMap", "", "(IF)V", (capacity, DEFAULT_LOAD_FACTOR)) + .await?; + let entry_set: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let entries: ClassInstanceRef> = jvm.invoke_virtual(&entry_set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let count = jvm.array_length(&entries).await?; + for entry in jvm.load_array::>(&entries, 0, count).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + Self::put_for_create(jvm, &mut this, key, value).await?; + } Ok(()) } @@ -195,7 +256,7 @@ impl HashMap { let key_hash = Self::object_hash_or_zero(jvm, &key).await?; Self::ensure_table_for_insert(jvm, &mut this).await?; - let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; + let table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; let table_len = jvm.array_length(&table).await?; let bucket_index = Self::bucket_index(key_hash, table_len); @@ -207,6 +268,7 @@ impl HashMap { if Self::keys_equal(jvm, &key, &entry_key).await? { let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; jvm.put_field(&mut entry, "value", "Ljava/lang/Object;", value).await?; + let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; return Ok(old_value); } } @@ -214,27 +276,14 @@ impl HashMap { entry = jvm.get_field(&entry, "next", "Ljava/util/HashMap$Entry;").await?; } - let size: i32 = jvm.get_field(&this, "size", "I").await?; - let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?; - let bucket_index = if size >= threshold { - Self::rehash(jvm, &mut this).await?; - table = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; - Self::bucket_index(key_hash, jvm.array_length(&table).await?) - } else { - bucket_index - }; - - let existing = Self::load_bucket(jvm, &table, bucket_index).await?; - let new_entry: ClassInstanceRef = jvm - .new_class( - "java/util/HashMap$Entry", - "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", - (key_hash, key, value, existing), + let _: () = jvm + .invoke_virtual( + &this, + "insertNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (key_hash, key, value, bucket_index as i32), ) - .await? - .into(); - jvm.store_array(&mut table, bucket_index, core::iter::once(new_entry)).await?; - jvm.put_field(&mut this, "size", "I", size + 1).await?; + .await?; Ok(None.into()) } @@ -277,11 +326,14 @@ impl HashMap { } let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + let _: () = jvm.invoke_virtual(&entry, "onRemoval", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; let null_entry: ClassInstanceRef = None.into(); jvm.put_field(&mut entry, "next", "Ljava/util/HashMap$Entry;", null_entry).await?; let size: i32 = jvm.get_field(&this, "size", "I").await?; jvm.put_field(&mut this, "size", "I", size - 1).await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; return Ok(old_value); } @@ -304,10 +356,95 @@ impl HashMap { jvm.store_array(&mut table, 0, nulls).await?; } jvm.put_field(&mut this, "size", "I", 0).await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; + + Ok(()) + } + async fn initialize_map(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { Ok(()) } + async fn store_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; + let existing = Self::load_bucket(jvm, &table, bucket_index as usize).await?; + let entry: ClassInstanceRef = jvm + .new_class( + "java/util/HashMap$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (hash, key, value, existing), + ) + .await? + .into(); + jvm.store_array(&mut table, bucket_index as usize, core::iter::once(entry)).await?; + let size: i32 = jvm.get_field(&this, "size", "I").await?; + jvm.put_field(&mut this, "size", "I", size + 1).await + } + + async fn insert_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; + let _: () = jvm + .invoke_virtual( + &this, + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (hash, key, value, bucket_index), + ) + .await?; + let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?; + if size >= threshold { + Self::rehash(jvm, &mut this).await?; + } + + Ok(()) + } + + async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let snapshot = Self::keys_snapshot(jvm, &this).await?; + + Ok(jvm + .new_class("java/util/HashMap$KeyIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .await? + .into()) + } + + async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let snapshot = Self::values_snapshot(jvm, &this).await?; + + Ok(jvm + .new_class("java/util/HashMap$ValueIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .await? + .into()) + } + + async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let snapshot = Self::entries_snapshot(jvm, &this).await?; + + Ok(jvm + .new_class("java/util/HashMap$EntryIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .await? + .into()) + } + pub(super) async fn find_entry( jvm: &Jvm, this: &ClassInstanceRef, @@ -337,6 +474,25 @@ impl HashMap { Ok(None.into()) } + pub(super) async fn put_for_create( + jvm: &Jvm, + this: &mut ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result<()> { + let hash = Self::object_hash_or_zero(jvm, &key).await?; + Self::ensure_table_for_insert(jvm, this).await?; + let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; + let bucket_index = Self::bucket_index(hash, jvm.array_length(&table).await?); + jvm.invoke_virtual( + this, + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (hash, key, value, bucket_index as i32), + ) + .await + } + async fn ensure_table_for_insert(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; if jvm.array_length(&table).await? > 0 { @@ -346,13 +502,14 @@ impl HashMap { let new_capacity = 1; let new_table: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", new_capacity).await?.into(); jvm.put_field(this, "table", "[Ljava/util/HashMap$Entry;", new_table).await?; - jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32)) + let load_factor: f32 = jvm.get_field(this, "loadFactor", "F").await?; + jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32, load_factor)) .await?; Ok(()) } - async fn rehash(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { + pub(super) async fn rehash(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { let old_table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; let old_capacity = jvm.array_length(&old_table).await?; let new_capacity = old_capacity * 2 + 1; @@ -374,7 +531,8 @@ impl HashMap { } jvm.put_field(this, "table", "[Ljava/util/HashMap$Entry;", new_table).await?; - jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32)) + let load_factor: f32 = jvm.get_field(this, "loadFactor", "F").await?; + jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32, load_factor)) .await?; Ok(()) @@ -449,12 +607,8 @@ impl HashMap { Ok(snapshot) } - fn threshold_for_capacity(capacity: i32) -> i32 { - if capacity <= 0 { - 0 - } else { - (capacity as f32 * DEFAULT_LOAD_FACTOR) as i32 - } + fn threshold_for_capacity(capacity: i32, load_factor: f32) -> i32 { + if capacity <= 0 { 0 } else { (capacity as f32 * load_factor) as i32 } } } diff --git a/java_runtime/src/classes/java/util/hash_map_entry.rs b/java_runtime/src/classes/java/util/hash_map_entry.rs index 7ca2d350..4ec0e4ea 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry.rs @@ -6,6 +6,8 @@ use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use super::HashMap; + // class java.util.HashMap$Entry pub struct HashMapEntry; @@ -22,11 +24,18 @@ impl HashMapEntry { Self::init, Default::default(), ), - JavaMethodProto::new("getKey", "()Ljava/lang/Object;", Self::get_key, Default::default()), - JavaMethodProto::new("getValue", "()Ljava/lang/Object;", Self::get_value, Default::default()), - JavaMethodProto::new("setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::set_value, Default::default()), + JavaMethodProto::new("getKey", "()Ljava/lang/Object;", Self::get_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getValue", "()Ljava/lang/Object;", Self::get_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + Self::set_value, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("onAccess", "(Ljava/util/HashMap;)V", Self::on_access, Default::default()), + JavaMethodProto::new("onRemoval", "(Ljava/util/HashMap;)V", Self::on_removal, Default::default()), ], fields: vec![ JavaFieldProto::new("hash", "I", Default::default()), @@ -125,4 +134,12 @@ impl HashMapEntry { }; Ok(key_hash ^ value_hash) } + + async fn on_access(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } + + async fn on_removal(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } } diff --git a/java_runtime/src/classes/java/util/hash_map_entry_set.rs b/java_runtime/src/classes/java/util/hash_map_entry_set.rs index 0ecaf72f..4f51083e 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry_set.rs @@ -126,12 +126,8 @@ impl HashMapEntrySet { tracing::debug!("java.util.HashMap$EntrySet::iterator({this:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let snapshot = HashMap::entries_snapshot(jvm, &map).await?; - let iterator = jvm - .new_class("java/util/HashMap$EntryIterator", "([Ljava/lang/Object;)V", (snapshot,)) - .await?; - Ok(iterator.into()) + jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await } async fn object_equals(jvm: &Jvm, left: &ClassInstanceRef, right: &ClassInstanceRef) -> Result { diff --git a/java_runtime/src/classes/java/util/hash_map_key_set.rs b/java_runtime/src/classes/java/util/hash_map_key_set.rs index 9a8c9a1a..cc083a2a 100644 --- a/java_runtime/src/classes/java/util/hash_map_key_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_key_set.rs @@ -91,11 +91,7 @@ impl HashMapKeySet { tracing::debug!("java.util.HashMap$KeySet::iterator({this:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let snapshot = HashMap::keys_snapshot(jvm, &map).await?; - let iterator = jvm - .new_class("java/util/HashMap$KeyIterator", "([Ljava/lang/Object;)V", (snapshot,)) - .await?; - Ok(iterator.into()) + jvm.invoke_virtual(&map, "keyIterator", "()Ljava/util/Iterator;", ()).await } } diff --git a/java_runtime/src/classes/java/util/hash_map_values.rs b/java_runtime/src/classes/java/util/hash_map_values.rs index 4dd2fed0..323811d6 100644 --- a/java_runtime/src/classes/java/util/hash_map_values.rs +++ b/java_runtime/src/classes/java/util/hash_map_values.rs @@ -68,9 +68,9 @@ impl HashMapValues { tracing::debug!("java.util.HashMap$Values::remove({this:?}, {value:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let entries = HashMap::entries_snapshot(jvm, &map).await?; - let count = jvm.array_length(&entries).await?; - for entry in jvm.load_array::>(&entries, 0, count).await? { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; let entry_value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; let equal = if value.is_null() { entry_value.is_null() @@ -103,11 +103,7 @@ impl HashMapValues { tracing::debug!("java.util.HashMap$Values::iterator({this:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let snapshot = HashMap::values_snapshot(jvm, &map).await?; - let iterator = jvm - .new_class("java/util/HashMap$ValueIterator", "([Ljava/lang/Object;)V", (snapshot,)) - .await?; - Ok(iterator.into()) + jvm.invoke_virtual(&map, "valueIterator", "()Ljava/util/Iterator;", ()).await } } diff --git a/java_runtime/src/classes/java/util/linked_hash_map.rs b/java_runtime/src/classes/java/util/linked_hash_map.rs new file mode 100644 index 00000000..92f7b1d3 --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map.rs @@ -0,0 +1,315 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{HashMap, HashMapEntry, LinkedHashMapEntry}; + +const DEFAULT_INITIAL_CAPACITY: i32 = 16; +const DEFAULT_LOAD_FACTOR: f32 = 0.75; + +// public class java.util.LinkedHashMap +pub struct LinkedHashMap; + +impl LinkedHashMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap", + parent_class: Some("java/util/HashMap"), + interfaces: vec!["java/util/Map"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init_default, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(IF)V", Self::init_with_capacity_and_load_factor, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(IFZ)V", + Self::init_with_capacity_load_factor_and_order, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/util/Map;)V", Self::init_from_map, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsValue", "(Ljava/lang/Object;)Z", Self::contains_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "removeEldestEntry", + "(Ljava/util/Map$Entry;)Z", + Self::remove_eldest_entry, + MethodAccessFlags::PROTECTED, + ), + JavaMethodProto::new("initializeMap", "()V", Self::initialize_map, Default::default()), + JavaMethodProto::new( + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::store_new_entry, + Default::default(), + ), + JavaMethodProto::new( + "insertNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::insert_new_entry, + Default::default(), + ), + JavaMethodProto::new("keyIterator", "()Ljava/util/Iterator;", Self::key_iterator, Default::default()), + JavaMethodProto::new("valueIterator", "()Ljava/util/Iterator;", Self::value_iterator, Default::default()), + JavaMethodProto::new("entryIterator", "()Ljava/util/Iterator;", Self::entry_iterator, Default::default()), + ], + fields: vec![ + JavaFieldProto::new( + "header", + "Ljava/util/LinkedHashMap$Entry;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT, + ), + JavaFieldProto::new("accessOrder", "Z", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init_default(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap", + "", + "(IFZ)V", + (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, false), + ) + .await + } + + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, capacity: i32) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap", + "", + "(IFZ)V", + (capacity, DEFAULT_LOAD_FACTOR, false), + ) + .await + } + + async fn init_with_capacity_and_load_factor( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + capacity: i32, + load_factor: f32, + ) -> Result<()> { + jvm.invoke_special(&this, "java/util/LinkedHashMap", "", "(IFZ)V", (capacity, load_factor, false)) + .await + } + + async fn init_with_capacity_load_factor_and_order( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + capacity: i32, + load_factor: f32, + access_order: bool, + ) -> Result<()> { + let _: () = jvm + .invoke_special(&this, "java/util/HashMap", "", "(IF)V", (capacity, load_factor)) + .await?; + jvm.put_field(&mut this, "accessOrder", "Z", access_order).await + } + + async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + let size: i32 = jvm.invoke_virtual(&map, "size", "()I", ()).await?; + let capacity = size.saturating_mul(2).max(DEFAULT_INITIAL_CAPACITY); + let _: () = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap", + "", + "(IFZ)V", + (capacity, DEFAULT_LOAD_FACTOR, false), + ) + .await?; + + let entry_set: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let entries: ClassInstanceRef> = jvm.invoke_virtual(&entry_set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let count = jvm.array_length(&entries).await?; + let mut hash_map: ClassInstanceRef = ClassInstanceRef::new(this.instance); + for entry in jvm.load_array::>(&entries, 0, count).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + HashMap::put_for_create(jvm, &mut hash_map, key, value).await?; + } + + Ok(()) + } + + async fn initialize_map(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let null: ClassInstanceRef = None.into(); + let next: ClassInstanceRef = None.into(); + let mut header: ClassInstanceRef = jvm + .new_class( + "java/util/LinkedHashMap$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (-1, null.clone(), null, next), + ) + .await? + .into(); + let header_ref = header.clone(); + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", header_ref) + .await?; + let header_ref = header.clone(); + jvm.put_field(&mut header, "after", "Ljava/util/LinkedHashMap$Entry;", header_ref).await?; + jvm.put_field(&mut this, "header", "Ljava/util/LinkedHashMap$Entry;", header).await + } + + async fn contains_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut entry: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + while entry.identity() != header.identity() { + let entry_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + let equal = if value.is_null() { + entry_value.is_null() + } else if entry_value.is_null() { + false + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (entry_value,)).await? + }; + if equal { + return Ok(true); + } + entry = jvm.get_field(&entry, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + } + + Ok(false) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = ClassInstanceRef::new(this.instance.clone()); + let entry = HashMap::find_entry(jvm, &map, &key).await?; + if entry.is_null() { + return Ok(None.into()); + } + let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (map,)).await?; + + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/HashMap", "clear", "()V", ()).await?; + let mut header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let header_ref = header.clone(); + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", header_ref) + .await?; + let header_ref = header.clone(); + jvm.put_field(&mut header, "after", "Ljava/util/LinkedHashMap$Entry;", header_ref).await + } + + async fn store_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; + let existing = jvm + .load_array::>(&table, bucket_index as usize, 1) + .await? + .remove(0); + let mut entry: ClassInstanceRef = jvm + .new_class( + "java/util/LinkedHashMap$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (hash, key, value, existing), + ) + .await? + .into(); + let bucket_entry: ClassInstanceRef = ClassInstanceRef::new(entry.instance.clone()); + jvm.store_array(&mut table, bucket_index as usize, core::iter::once(bucket_entry)).await?; + + let mut header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut tail: ClassInstanceRef = jvm.get_field(&header, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut entry, "before", "Ljava/util/LinkedHashMap$Entry;", tail.clone()) + .await?; + jvm.put_field(&mut entry, "after", "Ljava/util/LinkedHashMap$Entry;", header.clone()) + .await?; + jvm.put_field(&mut tail, "after", "Ljava/util/LinkedHashMap$Entry;", entry.clone()) + .await?; + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", entry).await?; + + let size: i32 = jvm.get_field(&this, "size", "I").await?; + jvm.put_field(&mut this, "size", "I", size + 1).await + } + + async fn insert_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; + let _: () = jvm + .invoke_virtual( + &this, + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (hash, key, value, bucket_index), + ) + .await?; + + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let eldest: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + let eldest_entry: ClassInstanceRef = ClassInstanceRef::new(eldest.instance.clone()); + if jvm + .invoke_virtual::<_, bool>(&this, "removeEldestEntry", "(Ljava/util/Map$Entry;)Z", (eldest_entry,)) + .await? + { + let key: ClassInstanceRef = jvm.get_field(&eldest, "key", "Ljava/lang/Object;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + } else { + let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?; + if size >= threshold { + let mut map: ClassInstanceRef = ClassInstanceRef::new(this.instance.clone()); + HashMap::rehash(jvm, &mut map).await?; + } + } + + Ok(()) + } + + async fn remove_eldest_entry(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/LinkedHashMap$KeyIterator", "(Ljava/util/LinkedHashMap;)V", (this,)) + .await? + .into()) + } + + async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/LinkedHashMap$ValueIterator", "(Ljava/util/LinkedHashMap;)V", (this,)) + .await? + .into()) + } + + async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/LinkedHashMap$EntryIterator", "(Ljava/util/LinkedHashMap;)V", (this,)) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_entry.rs b/java_runtime/src/classes/java/util/linked_hash_map_entry.rs new file mode 100644 index 00000000..e249c35c --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_entry.rs @@ -0,0 +1,92 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{HashMap, HashMapEntry, LinkedHashMap}; + +// class java.util.LinkedHashMap$Entry +pub struct LinkedHashMapEntry; + +impl LinkedHashMapEntry { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$Entry", + parent_class: Some("java/util/HashMap$Entry"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + Self::init, + Default::default(), + ), + JavaMethodProto::new("onAccess", "(Ljava/util/HashMap;)V", Self::on_access, Default::default()), + JavaMethodProto::new("onRemoval", "(Ljava/util/HashMap;)V", Self::on_removal, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("before", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + JavaFieldProto::new("after", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + next: ClassInstanceRef, + ) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/HashMap$Entry", + "", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (hash, key, value, next), + ) + .await + } + + async fn on_access(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let mut map: ClassInstanceRef = ClassInstanceRef::new(map.instance); + if !jvm.get_field::(&map, "accessOrder", "Z").await? { + return Ok(()); + } + + let mut header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut tail: ClassInstanceRef = jvm.get_field(&header, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + if this.identity() == tail.identity() { + return Ok(()); + } + + let mut before: ClassInstanceRef = jvm.get_field(&this, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut after: ClassInstanceRef = jvm.get_field(&this, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut before, "after", "Ljava/util/LinkedHashMap$Entry;", after.clone()) + .await?; + jvm.put_field(&mut after, "before", "Ljava/util/LinkedHashMap$Entry;", before).await?; + + jvm.put_field(&mut this, "before", "Ljava/util/LinkedHashMap$Entry;", tail.clone()) + .await?; + jvm.put_field(&mut this, "after", "Ljava/util/LinkedHashMap$Entry;", header.clone()) + .await?; + jvm.put_field(&mut tail, "after", "Ljava/util/LinkedHashMap$Entry;", this.clone()).await?; + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", this).await?; + + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + jvm.put_field(&mut map, "modCount", "I", mod_count.wrapping_add(1)).await + } + + async fn on_removal(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + let mut before: ClassInstanceRef = jvm.get_field(&this, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut after: ClassInstanceRef = jvm.get_field(&this, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut before, "after", "Ljava/util/LinkedHashMap$Entry;", after.clone()) + .await?; + jvm.put_field(&mut after, "before", "Ljava/util/LinkedHashMap$Entry;", before).await + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs new file mode 100644 index 00000000..5367b4df --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs @@ -0,0 +1,53 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// class java.util.LinkedHashMap$EntryIterator +pub struct LinkedHashMapEntryIterator; + +impl LinkedHashMapEntryIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$EntryIterator", + parent_class: Some("java/util/LinkedHashMap$LinkedHashIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "", + "(Ljava/util/LinkedHashMap;)V", + (map,), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "nextEntry", + "()Ljava/util/LinkedHashMap$Entry;", + (), + ) + .await?; + + Ok(ClassInstanceRef::new(entry.instance)) + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs new file mode 100644 index 00000000..bad0e5be --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs @@ -0,0 +1,53 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// class java.util.LinkedHashMap$KeyIterator +pub struct LinkedHashMapKeyIterator; + +impl LinkedHashMapKeyIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$KeyIterator", + parent_class: Some("java/util/LinkedHashMap$LinkedHashIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "", + "(Ljava/util/LinkedHashMap;)V", + (map,), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "nextEntry", + "()Ljava/util/LinkedHashMap$Entry;", + (), + ) + .await?; + + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs new file mode 100644 index 00000000..ef6ee387 --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs @@ -0,0 +1,106 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// abstract class java.util.LinkedHashMap$LinkedHashIterator +pub struct LinkedHashMapLinkedHashIterator; + +impl LinkedHashMapLinkedHashIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$LinkedHashIterator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextEntry", "()Ljava/util/LinkedHashMap$Entry;", Self::next_entry, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("map", "Ljava/util/LinkedHashMap;", Default::default()), + JavaFieldProto::new("nextEntry", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + JavaFieldProto::new("lastReturned", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + JavaFieldProto::new("expectedModCount", "I", Default::default()), + ], + access_flags: ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + let last_returned: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "map", "Ljava/util/LinkedHashMap;", map).await?; + jvm.put_field(&mut this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;", next).await?; + jvm.put_field(&mut this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;", last_returned) + .await?; + jvm.put_field(&mut this, "expectedModCount", "I", mod_count).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/LinkedHashMap;").await?; + let header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;").await?; + + Ok(next.identity() != header.identity()) + } + + async fn next_entry(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/LinkedHashMap;").await?; + let expected_mod_count: i32 = jvm.get_field(&this, "expectedModCount", "I").await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + if expected_mod_count != mod_count { + return Err(jvm + .exception("java/util/ConcurrentModificationException", "LinkedHashMap modified during iteration") + .await); + } + + let header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;").await?; + if next.identity() == header.identity() { + return Err(jvm + .exception("java/util/NoSuchElementException", "LinkedHashMap iterator exhausted") + .await); + } + let after: ClassInstanceRef = jvm.get_field(&next, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;", next.clone()) + .await?; + jvm.put_field(&mut this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;", after).await?; + + Ok(next) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: ClassInstanceRef = jvm.get_field(&this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;").await?; + if last_returned.is_null() { + return Err(jvm.exception("java/lang/IllegalStateException", "Iterator.remove").await); + } + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/LinkedHashMap;").await?; + let expected_mod_count: i32 = jvm.get_field(&this, "expectedModCount", "I").await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + if expected_mod_count != mod_count { + return Err(jvm + .exception("java/util/ConcurrentModificationException", "LinkedHashMap modified during iteration") + .await); + } + + let key: ClassInstanceRef = jvm.get_field(&last_returned, "key", "Ljava/lang/Object;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + let last_returned: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;", last_returned) + .await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + jvm.put_field(&mut this, "expectedModCount", "I", mod_count).await + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs new file mode 100644 index 00000000..cd8cc76b --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs @@ -0,0 +1,53 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// class java.util.LinkedHashMap$ValueIterator +pub struct LinkedHashMapValueIterator; + +impl LinkedHashMapValueIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$ValueIterator", + parent_class: Some("java/util/LinkedHashMap$LinkedHashIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "", + "(Ljava/util/LinkedHashMap;)V", + (map,), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "nextEntry", + "()Ljava/util/LinkedHashMap$Entry;", + (), + ) + .await?; + + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index c3948f0f..dbc78f5b 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -187,6 +187,12 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::HashMapValueIterator::as_proto(), crate::classes::java::util::HashMapValues::as_proto(), crate::classes::java::util::HashSet::as_proto(), + crate::classes::java::util::LinkedHashMap::as_proto(), + crate::classes::java::util::LinkedHashMapEntry::as_proto(), + crate::classes::java::util::LinkedHashMapLinkedHashIterator::as_proto(), + crate::classes::java::util::LinkedHashMapEntryIterator::as_proto(), + crate::classes::java::util::LinkedHashMapKeyIterator::as_proto(), + crate::classes::java::util::LinkedHashMapValueIterator::as_proto(), crate::classes::java::util::Hashtable::as_proto(), crate::classes::java::util::HashtableEnumerator::as_proto(), crate::classes::java::util::HashtableEntry::as_proto(), diff --git a/java_runtime/tests/classes/java/util/mod.rs b/java_runtime/tests/classes/java/util/mod.rs index 0e75ddc7..adcd73ed 100644 --- a/java_runtime/tests/classes/java/util/mod.rs +++ b/java_runtime/tests/classes/java/util/mod.rs @@ -10,6 +10,7 @@ mod test_gregorian_calendar; mod test_hash_map; mod test_hash_set; mod test_hashtable; +mod test_linked_hash_map; mod test_list_views; mod test_locale; mod test_properties; diff --git a/java_runtime/tests/classes/java/util/test_linked_hash_map.rs b/java_runtime/tests/classes/java/util/test_linked_hash_map.rs new file mode 100644 index 00000000..ae5ff343 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_linked_hash_map.rs @@ -0,0 +1,402 @@ +use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object, get_runtime_class_proto}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; + +struct LimitedLinkedHashMap; + +impl LimitedLinkedHashMap { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "LimitedLinkedHashMap", + parent_class: Some("java/util/LinkedHashMap"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "removeEldestEntry", + "(Ljava/util/Map$Entry;)Z", + Self::remove_eldest_entry, + MethodAccessFlags::PROTECTED, + ), + ], + fields: vec![ + JavaFieldProto::new("limit", "I", Default::default()), + JavaFieldProto::new("callbacks", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, limit: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/LinkedHashMap", "", "()V", ()).await?; + jvm.put_field(&mut this, "limit", "I", limit).await?; + jvm.put_field(&mut this, "callbacks", "I", 0).await + } + + async fn remove_eldest_entry(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + let callbacks: i32 = jvm.get_field(&this, "callbacks", "I").await?; + jvm.put_field(&mut this, "callbacks", "I", callbacks + 1).await?; + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + let limit: i32 = jvm.get_field(&this, "limit", "I").await?; + + Ok(size > limit) + } +} + +async fn view_strings(jvm: &Jvm, map: &ClassInstanceRef, method: &str, descriptor: &str) -> Result>> { + let view: ClassInstanceRef = jvm.invoke_virtual(map, method, descriptor, ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&view, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut values = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + values.push(if value.is_null() { + None + } else { + Some(JavaLangString::to_rust_string(jvm, &value).await?) + }); + } + + Ok(values) +} + +async fn entry_strings(jvm: &Jvm, map: &ClassInstanceRef) -> Result, Option)>> { + let view: ClassInstanceRef = jvm.invoke_virtual(map, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&view, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut entries = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + entries.push(( + if key.is_null() { + None + } else { + Some(JavaLangString::to_rust_string(jvm, &key).await?) + }, + if value.is_null() { + None + } else { + Some(JavaLangString::to_rust_string(jvm, &value).await?) + }, + )); + } + + Ok(entries) +} + +#[tokio::test] +async fn linked_hash_map_exposes_the_cdc_11_class_shape_and_constructor_validation() -> Result<()> { + let proto = get_runtime_class_proto("java/util/LinkedHashMap").expect("LinkedHashMap must be registered"); + assert_eq!(proto.parent_class, Some("java/util/HashMap")); + assert_eq!(proto.interfaces, vec!["java/util/Map"]); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC); + for descriptor in ["()V", "(I)V", "(IF)V", "(IFZ)V", "(Ljava/util/Map;)V"] { + let method = proto + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing LinkedHashMap{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + for (name, descriptor, flags) in [ + ("containsValue", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC), + ("get", "(Ljava/lang/Object;)Ljava/lang/Object;", MethodAccessFlags::PUBLIC), + ("clear", "()V", MethodAccessFlags::PUBLIC), + ("removeEldestEntry", "(Ljava/util/Map$Entry;)Z", MethodAccessFlags::PROTECTED), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing {name}{descriptor}")); + assert_eq!(method.access_flags, flags); + } + + let jvm = test_jvm().await?; + let map = jvm.new_class("java/util/LinkedHashMap", "()V", ()).await?; + assert!(jvm.is_instance(&*map, "java/util/HashMap")); + assert!(jvm.is_instance(&*map, "java/util/Map")); + + for result in [ + jvm.new_class("java/util/LinkedHashMap", "(I)V", (-1,)).await, + jvm.new_class("java/util/LinkedHashMap", "(IF)V", (1, 0.0f32)).await, + jvm.new_class("java/util/LinkedHashMap", "(IF)V", (1, -1.0f32)).await, + jvm.new_class("java/util/LinkedHashMap", "(IFZ)V", (1, f32::NAN, false)).await, + ] { + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid LinkedHashMap constructor arguments must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + } + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_preserves_insertion_order_across_views_nulls_and_rehash() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "(IF)V", (1, 0.75f32)).await?.into(); + + for index in 0..24 { + let key = JavaLangString::from_rust_string(&jvm, &format!("k{index:02}")).await?; + let value = JavaLangString::from_rust_string(&jvm, &format!("v{index:02}")).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + let replacement_key = JavaLangString::from_rust_string(&jvm, "k05").await?; + let replacement = JavaLangString::from_rust_string(&jvm, "changed").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (replacement_key, replacement), + ) + .await?; + let null: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (null.clone(), null), + ) + .await?; + + let mut expected_keys: Vec> = (0..24).map(|index| Some(format!("k{index:02}"))).collect(); + expected_keys.push(None); + assert_eq!(view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, expected_keys); + + let values = view_strings(&jvm, &map, "values", "()Ljava/util/Collection;").await?; + assert_eq!(values[5], Some("changed".into())); + assert_eq!(values.last(), Some(&None)); + let entries = entry_strings(&jvm, &map).await?; + assert_eq!(entries[5], (Some("k05".into()), Some("changed".into()))); + assert_eq!(entries.last(), Some(&(None, None))); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let key = JavaLangString::from_rust_string(&jvm, "k00").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "k00"); + + let text: ClassInstanceRef = jvm.invoke_virtual(&map, "toString", "()Ljava/lang/String;", ()).await?; + let text = JavaLangString::to_rust_string(&jvm, &text).await?; + assert!(text.starts_with("{k00=v00, k01=v01, k02=v02")); + assert!(text.ends_with(", null=null}")); + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_access_order_tracks_only_documented_accesses() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "(IFZ)V", (4, 0.75f32, true)).await?.into(); + for key in ["a", "b", "c"] { + let value = JavaLangString::from_rust_string(&jvm, key).await?; + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let tail = JavaLangString::from_rust_string(&jvm, "c").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (tail,)).await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let tail = JavaLangString::from_rust_string(&jvm, "c").await?; + let value = JavaLangString::from_rust_string(&jvm, "C").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (tail, value)) + .await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let missing = JavaLangString::from_rust_string(&jvm, "missing").await?; + let missing_value: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (missing,)) + .await?; + assert!(missing_value.is_null()); + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let key = JavaLangString::from_rust_string(&jvm, "a").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await?; + let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("access-order get must invalidate an existing iterator"); + }; + assert!(jvm.is_instance(&*exception, "java/util/ConcurrentModificationException")); + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into()), Some("a".into())] + ); + + let key = JavaLangString::from_rust_string(&jvm, "b").await?; + assert!( + jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (key,)) + .await? + ); + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into()), Some("a".into())] + ); + + let key = JavaLangString::from_rust_string(&jvm, "b").await?; + let value = JavaLangString::from_rust_string(&jvm, "B").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("c".into()), Some("a".into()), Some("b".into())] + ); + + let source: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "()V", ()).await?.into(); + for key in ["a", "d"] { + let value = JavaLangString::from_rust_string(&jvm, key).await?; + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&source, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + let _: () = jvm.invoke_virtual(&map, "putAll", "(Ljava/util/Map;)V", (source,)).await?; + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("c".into()), Some("b".into()), Some("a".into()), Some("d".into())] + ); + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_views_and_iterators_remove_in_order_and_fail_fast() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "()V", ()).await?.into(); + for (key, value) in [("a", "same"), ("b", "same"), ("c", "other")] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + let same = JavaLangString::from_rust_string(&jvm, "same").await?; + assert!(jvm.invoke_virtual::<_, bool>(&values, "remove", "(Ljava/lang/Object;)Z", (same,)).await?); + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into())] + ); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "b"); + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + let result: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("repeated iterator remove must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, vec![Some("c".into())]); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let key = JavaLangString::from_rust_string(&jvm, "d").await?; + let value = JavaLangString::from_rust_string(&jvm, "other").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("structural modification must invalidate iterator"); + }; + assert!(jvm.is_instance(&*exception, "java/util/ConcurrentModificationException")); + + let _: () = jvm.invoke_virtual(&map, "clear", "()V", ()).await?; + assert!(view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?.is_empty()); + let key = JavaLangString::from_rust_string(&jvm, "reused").await?; + let value = JavaLangString::from_rust_string(&jvm, "value").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("reused".into())] + ); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("exhausted iterator must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_copy_and_remove_eldest_entry_preserve_policy_and_order() -> Result<()> { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + LimitedLinkedHashMap::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + + let limited: ClassInstanceRef = jvm.new_class("LimitedLinkedHashMap", "(I)V", (2,)).await?.into(); + for key in ["a", "b", "c"] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = key.clone(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&limited, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + assert_eq!( + view_strings(&jvm, &limited, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into())] + ); + assert_eq!(jvm.get_field::(&limited, "callbacks", "I").await?, 3); + + let key = JavaLangString::from_rust_string(&jvm, "c").await?; + let value = JavaLangString::from_rust_string(&jvm, "C").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&limited, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + assert_eq!(jvm.get_field::(&limited, "callbacks", "I").await?, 3); + + let copy: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "(Ljava/util/Map;)V", (limited,)).await?.into(); + assert_eq!( + view_strings(&jvm, ©, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into())] + ); + assert_eq!(entry_strings(&jvm, ©).await?[1], (Some("c".into()), Some("C".into()))); + + let key_set: ClassInstanceRef = jvm.invoke_virtual(©, "keySet", "()Ljava/util/Set;", ()).await?; + let array: ClassInstanceRef> = jvm.invoke_virtual(&key_set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let keys = jvm.load_array::>(&array, 0, 2).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &keys[0]).await?, "b"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &keys[1]).await?, "c"); + + Ok(()) +}