diff --git a/rustjava-runtime/src/classes/java/util/hash_map.rs b/rustjava-runtime/src/classes/java/util/hash_map.rs index 75c3d1cd..e529e945 100644 --- a/rustjava-runtime/src/classes/java/util/hash_map.rs +++ b/rustjava-runtime/src/classes/java/util/hash_map.rs @@ -125,18 +125,6 @@ impl HashMap { Ok(entry_set.into()) } - async fn keys_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { - Self::snapshot_entries(jvm, this, SnapshotKind::Keys).await - } - - async fn values_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { - Self::snapshot_entries(jvm, this, SnapshotKind::Values).await - } - - 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, this: ClassInstanceRef, capacity: i32) -> Result<()> { tracing::debug!("java.util.HashMap::({this:?}, {capacity:?})"); @@ -473,28 +461,40 @@ impl HashMap { } async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { - let snapshot = Self::keys_snapshot(jvm, &this).await?; + let snapshot = Self::snapshot_entries(jvm, &this).await?; Ok(jvm - .new_class("java/util/HashMap$KeyIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .new_class( + "java/util/HashMap$KeyIterator", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + (this, snapshot), + ) .await? .into()) } async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { - let snapshot = Self::values_snapshot(jvm, &this).await?; + let snapshot = Self::snapshot_entries(jvm, &this).await?; Ok(jvm - .new_class("java/util/HashMap$ValueIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .new_class( + "java/util/HashMap$ValueIterator", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + (this, snapshot), + ) .await? .into()) } async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { - let snapshot = Self::entries_snapshot(jvm, &this).await?; + let snapshot = Self::snapshot_entries(jvm, &this).await?; Ok(jvm - .new_class("java/util/HashMap$EntryIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .new_class( + "java/util/HashMap$EntryIterator", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + (this, snapshot), + ) .await? .into()) } @@ -655,7 +655,7 @@ impl HashMap { (hash & 0x7FFFFFFF) as usize % table_len } - async fn snapshot_entries(jvm: &Jvm, this: &ClassInstanceRef, kind: SnapshotKind) -> Result>> { + async fn snapshot_entries(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { let table: ClassInstanceRef> = jvm.get_field(this, "java/util/HashMap", "table", "[Ljava/util/HashMap$Entry;").await?; let table_len = jvm.array_length(&table).await?; let size: i32 = jvm.get_field(this, "java/util/HashMap", "size", "I").await?; @@ -664,12 +664,7 @@ impl HashMap { for bucket_index in 0..table_len { let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; while !entry.is_null() { - let element = match kind { - SnapshotKind::Keys => jvm.get_field(&entry, "java/util/HashMap$Entry", "key", "Ljava/lang/Object;").await?, - SnapshotKind::Values => jvm.get_field(&entry, "java/util/HashMap$Entry", "value", "Ljava/lang/Object;").await?, - SnapshotKind::Entries => ClassInstanceRef::new(entry.clone().instance), - }; - elements.push(element); + elements.push(ClassInstanceRef::new(entry.clone().instance)); entry = jvm .get_field(&entry, "java/util/HashMap$Entry", "next", "Ljava/util/HashMap$Entry;") @@ -689,10 +684,3 @@ impl HashMap { if capacity <= 0 { 0 } else { (capacity as f32 * load_factor) as i32 } } } - -#[derive(Copy, Clone)] -enum SnapshotKind { - Keys, - Values, - Entries, -} diff --git a/rustjava-runtime/src/classes/java/util/hash_map_entry_iterator.rs b/rustjava-runtime/src/classes/java/util/hash_map_entry_iterator.rs index 25a994d7..5cc2b7e7 100644 --- a/rustjava-runtime/src/classes/java/util/hash_map_entry_iterator.rs +++ b/rustjava-runtime/src/classes/java/util/hash_map_entry_iterator.rs @@ -6,6 +6,8 @@ use jvm_types::{ClassAccessFlags, MethodAccessFlags}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use super::HashMap; + // class java.util.HashMap$EntryIterator pub struct HashMapEntryIterator; @@ -17,7 +19,7 @@ impl HashMapEntryIterator { interfaces: vec![], methods: vec![JavaMethodProto::new( "", - "([Ljava/lang/Object;)V", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", Self::init, MethodAccessFlags::empty(), )], @@ -26,10 +28,22 @@ impl HashMapEntryIterator { } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + map: ClassInstanceRef, + elements: ClassInstanceRef>, + ) -> Result<()> { tracing::debug!("java.util.HashMap$EntryIterator::({this:?}, {elements:?})"); - jvm.invoke_special(&this, "java/util/HashMap$HashIterator", "", "([Ljava/lang/Object;)V", (elements,)) - .await + jvm.invoke_special( + &this, + "java/util/HashMap$HashIterator", + "", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + (map, elements), + ) + .await } } diff --git a/rustjava-runtime/src/classes/java/util/hash_map_hash_iterator.rs b/rustjava-runtime/src/classes/java/util/hash_map_hash_iterator.rs index 6869064d..01d4a98f 100644 --- a/rustjava-runtime/src/classes/java/util/hash_map_hash_iterator.rs +++ b/rustjava-runtime/src/classes/java/util/hash_map_hash_iterator.rs @@ -1,4 +1,4 @@ -use alloc::vec; +use alloc::{vec, vec::Vec}; use jvm::{Array, ClassInstanceRef, Jvm, Result}; use jvm_class_proto::{JavaFieldProto, JavaMethodProto}; @@ -6,6 +6,8 @@ use jvm_types::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use super::{HashMap, HashMapEntry}; + // abstract class java.util.HashMap$HashIterator pub struct HashMapHashIterator; @@ -16,12 +18,19 @@ impl HashMapHashIterator { parent_class: Some("java/lang/Object"), interfaces: vec!["java/util/Iterator"], methods: vec![ - JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, MethodAccessFlags::empty()), + JavaMethodProto::new( + "", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + Self::init, + MethodAccessFlags::empty(), + ), JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), ], fields: vec![ + JavaFieldProto::new("map", "Ljava/util/HashMap;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("lastReturned", "Ljava/util/HashMap$Entry;", FieldAccessFlags::PRIVATE), JavaFieldProto::new("elements", "[Ljava/lang/Object;", FieldAccessFlags::PRIVATE), JavaFieldProto::new("index", "I", FieldAccessFlags::empty()), ], @@ -29,11 +38,19 @@ impl HashMapHashIterator { } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + map: ClassInstanceRef, + elements: ClassInstanceRef>, + ) -> Result<()> { tracing::debug!("java.util.HashMap$HashIterator::({this:?}, {elements:?})"); let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "java/util/HashMap$HashIterator", "map", "Ljava/util/HashMap;", map) + .await?; jvm.put_field(&mut this, "java/util/HashMap$HashIterator", "elements", "[Ljava/lang/Object;", elements) .await?; jvm.put_field(&mut this, "java/util/HashMap$HashIterator", "index", "I", 0).await?; @@ -66,19 +83,48 @@ impl HashMapHashIterator { return Err(jvm.exception("java/util/NoSuchElementException", "HashMap iterator exhausted").await); } - let mut values = jvm.load_array(&elements, index as usize, 1).await?; + let mut values: Vec> = jvm.load_array(&elements, index as usize, 1).await?; let Some(element) = values.pop() else { return Err(jvm.exception("java/util/NoSuchElementException", "HashMap iterator exhausted").await); }; jvm.put_field(&mut this, "java/util/HashMap$HashIterator", "index", "I", index + 1) .await?; + jvm.put_field( + &mut this, + "java/util/HashMap$HashIterator", + "lastReturned", + "Ljava/util/HashMap$Entry;", + element.clone(), + ) + .await?; Ok(element) } - async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.HashMap$HashIterator::remove({this:?})"); - Err(jvm.exception("java/lang/UnsupportedOperationException", "Iterator.remove").await) + let entry: ClassInstanceRef = jvm + .get_field(&this, "java/util/HashMap$HashIterator", "lastReturned", "Ljava/util/HashMap$Entry;") + .await?; + if entry.is_null() { + return Err(jvm.exception("java/lang/IllegalStateException", "No entry to remove").await); + } + let map = jvm + .get_field(&this, "java/util/HashMap$HashIterator", "map", "Ljava/util/HashMap;") + .await?; + let key: ClassInstanceRef = jvm.get_field(&entry, "java/util/HashMap$Entry", "key", "Ljava/lang/Object;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "java/util/HashMap", "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + let null_entry: ClassInstanceRef = None.into(); + jvm.put_field( + &mut this, + "java/util/HashMap$HashIterator", + "lastReturned", + "Ljava/util/HashMap$Entry;", + null_entry, + ) + .await } } diff --git a/rustjava-runtime/src/classes/java/util/hash_map_key_iterator.rs b/rustjava-runtime/src/classes/java/util/hash_map_key_iterator.rs index a122172f..21761e60 100644 --- a/rustjava-runtime/src/classes/java/util/hash_map_key_iterator.rs +++ b/rustjava-runtime/src/classes/java/util/hash_map_key_iterator.rs @@ -6,6 +6,8 @@ use jvm_types::{ClassAccessFlags, MethodAccessFlags}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use super::HashMap; + // class java.util.HashMap$KeyIterator pub struct HashMapKeyIterator; @@ -15,21 +17,43 @@ impl HashMapKeyIterator { name: "java/util/HashMap$KeyIterator", parent_class: Some("java/util/HashMap$HashIterator"), interfaces: vec![], - methods: vec![JavaMethodProto::new( - "", - "([Ljava/lang/Object;)V", - Self::init, - MethodAccessFlags::empty(), - )], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + Self::init, + MethodAccessFlags::empty(), + ), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], fields: vec![], access_flags: ClassAccessFlags::FINAL, } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = jvm + .invoke_special(&this, "java/util/HashMap$HashIterator", "next", "()Ljava/lang/Object;", ()) + .await?; + jvm.get_field(&entry, "java/util/HashMap$Entry", "key", "Ljava/lang/Object;").await + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + map: ClassInstanceRef, + elements: ClassInstanceRef>, + ) -> Result<()> { tracing::debug!("java.util.HashMap$KeyIterator::({this:?}, {elements:?})"); - jvm.invoke_special(&this, "java/util/HashMap$HashIterator", "", "([Ljava/lang/Object;)V", (elements,)) - .await + jvm.invoke_special( + &this, + "java/util/HashMap$HashIterator", + "", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + (map, elements), + ) + .await } } diff --git a/rustjava-runtime/src/classes/java/util/hash_map_value_iterator.rs b/rustjava-runtime/src/classes/java/util/hash_map_value_iterator.rs index 764ee0e7..b644a760 100644 --- a/rustjava-runtime/src/classes/java/util/hash_map_value_iterator.rs +++ b/rustjava-runtime/src/classes/java/util/hash_map_value_iterator.rs @@ -6,6 +6,8 @@ use jvm_types::{ClassAccessFlags, MethodAccessFlags}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use super::HashMap; + // class java.util.HashMap$ValueIterator pub struct HashMapValueIterator; @@ -15,21 +17,43 @@ impl HashMapValueIterator { name: "java/util/HashMap$ValueIterator", parent_class: Some("java/util/HashMap$HashIterator"), interfaces: vec![], - methods: vec![JavaMethodProto::new( - "", - "([Ljava/lang/Object;)V", - Self::init, - MethodAccessFlags::empty(), - )], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + Self::init, + MethodAccessFlags::empty(), + ), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], fields: vec![], access_flags: ClassAccessFlags::FINAL, } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = jvm + .invoke_special(&this, "java/util/HashMap$HashIterator", "next", "()Ljava/lang/Object;", ()) + .await?; + jvm.get_field(&entry, "java/util/HashMap$Entry", "value", "Ljava/lang/Object;").await + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + map: ClassInstanceRef, + elements: ClassInstanceRef>, + ) -> Result<()> { tracing::debug!("java.util.HashMap$ValueIterator::({this:?}, {elements:?})"); - jvm.invoke_special(&this, "java/util/HashMap$HashIterator", "", "([Ljava/lang/Object;)V", (elements,)) - .await + jvm.invoke_special( + &this, + "java/util/HashMap$HashIterator", + "", + "(Ljava/util/HashMap;[Ljava/lang/Object;)V", + (map, elements), + ) + .await } } diff --git a/rustjava-runtime/src/classes/java/util/jar.rs b/rustjava-runtime/src/classes/java/util/jar.rs index f62512e7..b4b4abe8 100644 --- a/rustjava-runtime/src/classes/java/util/jar.rs +++ b/rustjava-runtime/src/classes/java/util/jar.rs @@ -1,7 +1,11 @@ mod attributes; +mod attributes_name; mod jar_entry; mod jar_file; mod jar_file_entries; mod manifest; -pub use {attributes::Attributes, jar_entry::JarEntry, jar_file::JarFile, jar_file_entries::JarFileEntries, manifest::Manifest}; +pub use { + attributes::Attributes, attributes_name::AttributesName, jar_entry::JarEntry, jar_file::JarFile, jar_file_entries::JarFileEntries, + manifest::Manifest, +}; diff --git a/rustjava-runtime/src/classes/java/util/jar/attributes.rs b/rustjava-runtime/src/classes/java/util/jar/attributes.rs index dff2ebc7..6b3fc2ec 100644 --- a/rustjava-runtime/src/classes/java/util/jar/attributes.rs +++ b/rustjava-runtime/src/classes/java/util/jar/attributes.rs @@ -4,7 +4,13 @@ use jvm::{ClassInstanceRef, Jvm, Result}; use jvm_class_proto::{JavaFieldProto, JavaMethodProto}; use jvm_types::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; -use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + lang::{Object, String}, + util::{Collection, Set, jar::AttributesName}, + }, +}; // class java.util.jar.Attributes pub struct Attributes; @@ -14,7 +20,7 @@ impl Attributes { RuntimeClassProto { name: "java/util/jar/Attributes", parent_class: Some("java/lang/Object"), - interfaces: vec!["java/lang/Cloneable"], + interfaces: vec!["java/lang/Cloneable", "java/util/Map"], methods: vec![ JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), JavaMethodProto::new( @@ -29,6 +35,36 @@ impl Attributes { Self::get_value, MethodAccessFlags::PUBLIC, ), + JavaMethodProto::new( + "getValue", + "(Ljava/util/jar/Attributes$Name;)Ljava/lang/String;", + Self::get_value_by_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("entrySet", "()Ljava/util/Set;", Self::entry_set, MethodAccessFlags::PUBLIC), + 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, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "remove", + "(Ljava/lang/Object;)Ljava/lang/Object;", + Self::remove, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("putAll", "(Ljava/util/Map;)V", Self::put_all, 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("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), ], fields: vec![JavaFieldProto::new("map", "Ljava/util/Map;", FieldAccessFlags::PROTECTED)], access_flags: ClassAccessFlags::PUBLIC, @@ -56,12 +92,11 @@ impl Attributes { ) -> Result> { tracing::debug!("java.util.jar.Attributes::putValue({this:?}, {name:?}, {value:?})"); - // TODO we should store key in Attributes.Name type - let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + let name = jvm.new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (name,)).await?; let old = jvm .invoke_virtual( - &map, - &map.class_definition().name(), + &this, + "java/util/jar/Attributes", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (name, value), @@ -79,11 +114,29 @@ impl Attributes { ) -> Result> { tracing::debug!("java.util.jar.Attributes::getValue({this:?}, {name:?})"); - let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + let name = jvm.new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (name,)).await?; + jvm.invoke_virtual( + &this, + "java/util/jar/Attributes", + "get", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (name,), + ) + .await + } + + async fn get_value_by_name( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + name: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.jar.Attributes::getValue({this:?}, {name:?})"); + let value = jvm .invoke_virtual( - &map, - &map.class_definition().name(), + &this, + "java/util/jar/Attributes", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (name,), @@ -92,4 +145,133 @@ impl Attributes { Ok(value) } + + async fn entry_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.jar.Attributes::entrySet({this:?})"); + + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "entrySet", "()Ljava/util/Set;", ()).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "size", "()I", ()).await + } + + async fn is_empty(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "isEmpty", "()Z", ()).await + } + + async fn contains_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "containsKey", "(Ljava/lang/Object;)Z", (key,)) + .await + } + + async fn contains_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "containsValue", "(Ljava/lang/Object;)Z", (value,)) + .await + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result> { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await + } + + async fn put( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + if !key.is_null() && !jvm.is_instance(&**key, "java/util/jar/Attributes$Name") { + return Err(jvm + .exception("java/lang/ClassCastException", "Attribute key must be Attributes.Name") + .await); + } + if !value.is_null() && !jvm.is_instance(&**value, "java/lang/String") { + return Err(jvm.exception("java/lang/ClassCastException", "Attribute value must be String").await); + } + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual( + &map, + "java/util/Map", + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, value), + ) + .await + } + + async fn remove( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + key: ClassInstanceRef, + ) -> Result> { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await + } + + async fn put_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, source: ClassInstanceRef) -> Result<()> { + if source.is_null() || !jvm.is_instance(&**source, "java/util/jar/Attributes") { + return Err(jvm.exception("java/lang/ClassCastException", "Source must be Attributes").await); + } + let entries = jvm.invoke_virtual(&source, "java/util/Map", "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator = jvm + .invoke_virtual(&entries, "java/util/Set", "iterator", "()Ljava/util/Iterator;", ()) + .await?; + while jvm.invoke_virtual(&iterator, "java/util/Iterator", "hasNext", "()Z", ()).await? { + let entry = jvm + .invoke_virtual(&iterator, "java/util/Iterator", "next", "()Ljava/lang/Object;", ()) + .await?; + let key: ClassInstanceRef = jvm + .invoke_virtual(&entry, "java/util/Map$Entry", "getKey", "()Ljava/lang/Object;", ()) + .await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&entry, "java/util/Map$Entry", "getValue", "()Ljava/lang/Object;", ()) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &this, + "java/util/jar/Attributes", + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, value), + ) + .await?; + } + Ok(()) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "clear", "()V", ()).await + } + + async fn key_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "keySet", "()Ljava/util/Set;", ()).await + } + + async fn values(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/util/Map", "values", "()Ljava/util/Collection;", ()).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", (other,)) + .await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map = jvm.get_field(&this, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "java/lang/Object", "hashCode", "()I", ()).await + } } diff --git a/rustjava-runtime/src/classes/java/util/jar/attributes_name.rs b/rustjava-runtime/src/classes/java/util/jar/attributes_name.rs new file mode 100644 index 00000000..bb67c455 --- /dev/null +++ b/rustjava-runtime/src/classes/java/util/jar/attributes_name.rs @@ -0,0 +1,116 @@ +use alloc::vec; + +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm_class_proto::{JavaFieldProto, JavaMethodProto}; +use jvm_types::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// class java.util.jar.Attributes$Name +pub struct AttributesName; + +impl AttributesName { + const NAMES: [(&str, &str); 17] = [ + ("MANIFEST_VERSION", "Manifest-Version"), + ("SIGNATURE_VERSION", "Signature-Version"), + ("CONTENT_TYPE", "Content-Type"), + ("CLASS_PATH", "Class-Path"), + ("MAIN_CLASS", "Main-Class"), + ("SEALED", "Sealed"), + ("EXTENSION_LIST", "Extension-List"), + ("EXTENSION_NAME", "Extension-Name"), + ("EXTENSION_INSTALLATION", "Extension-Installation"), + ("IMPLEMENTATION_TITLE", "Implementation-Title"), + ("IMPLEMENTATION_VERSION", "Implementation-Version"), + ("IMPLEMENTATION_VENDOR", "Implementation-Vendor"), + ("IMPLEMENTATION_VENDOR_ID", "Implementation-Vendor-Id"), + ("IMPLEMENTATION_URL", "Implementation-URL"), + ("SPECIFICATION_TITLE", "Specification-Title"), + ("SPECIFICATION_VERSION", "Specification-Version"), + ("SPECIFICATION_VENDOR", "Specification-Vendor"), + ]; + + pub fn as_proto() -> RuntimeClassProto { + let mut fields = vec![JavaFieldProto::new( + "name", + "Ljava/lang/String;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )]; + fields.extend(Self::NAMES.iter().map(|(field, _)| { + JavaFieldProto::new( + field, + "Ljava/util/jar/Attributes$Name;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ) + })); + RuntimeClassProto { + name: "java/util/jar/Attributes$Name", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + ], + fields, + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + for (field, spelling) in Self::NAMES { + let name = JavaLangString::from_rust_string(jvm, spelling).await?; + let value = jvm.new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (name,)).await?; + jvm.put_static_field("java/util/jar/Attributes$Name", field, "Ljava/util/jar/Attributes$Name;", value) + .await?; + } + Ok(()) + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, name: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name is null").await); + } + let spelling = JavaLangString::to_rust_string(jvm, &name).await?; + if !(1..=70).contains(&spelling.len()) || !spelling.bytes().all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_') { + return Err(jvm.exception("java/lang/IllegalArgumentException", "invalid attribute name").await); + } + jvm.put_field(&mut this, "java/util/jar/Attributes$Name", "name", "Ljava/lang/String;", name) + .await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/util/jar/Attributes$Name") { + return Ok(false); + } + let name = jvm + .get_field(&this, "java/util/jar/Attributes$Name", "name", "Ljava/lang/String;") + .await?; + let other_name = jvm + .get_field(&other, "java/util/jar/Attributes$Name", "name", "Ljava/lang/String;") + .await?; + Ok(JavaLangString::to_rust_string(jvm, &name) + .await? + .eq_ignore_ascii_case(&JavaLangString::to_rust_string(jvm, &other_name).await?)) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let name = jvm + .get_field(&this, "java/util/jar/Attributes$Name", "name", "Ljava/lang/String;") + .await?; + let spelling = JavaLangString::to_rust_string(jvm, &name).await?; + Ok(spelling + .bytes() + .fold(0i32, |hash, byte| hash.wrapping_mul(31).wrapping_add(byte.to_ascii_lowercase() as i32))) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "java/util/jar/Attributes$Name", "name", "Ljava/lang/String;").await + } +} diff --git a/rustjava-runtime/src/classes/java/util/jar/jar_file.rs b/rustjava-runtime/src/classes/java/util/jar/jar_file.rs index 36998582..9e6322c0 100644 --- a/rustjava-runtime/src/classes/java/util/jar/jar_file.rs +++ b/rustjava-runtime/src/classes/java/util/jar/jar_file.rs @@ -1,4 +1,4 @@ -use alloc::vec; +use alloc::{string::ToString, vec}; use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use jvm_class_proto::JavaMethodProto; @@ -12,7 +12,7 @@ use crate::{ util::{ Enumeration, jar::{JarEntry, Manifest}, - zip::ZipEntry, + zip::{ZipEntry, ZipFile}, }, }, }; @@ -112,7 +112,7 @@ impl JarFile { tracing::debug!("java.util.jar.JarFile::getManifest({this:?})"); let manifest_name = JavaLangString::from_rust_string(jvm, "META-INF/MANIFEST.MF").await?; - let manifest_file: ClassInstanceRef = jvm + let mut manifest_file: ClassInstanceRef = jvm .invoke_virtual( &this, "java/util/jar/JarFile", @@ -122,6 +122,28 @@ impl JarFile { ) .await?; + if manifest_file.is_null() { + let name = { + let zip = ZipFile::get_zip_archive(jvm, &ClassInstanceRef::from(this.instance.clone())).await?; + zip.file_names() + .find(|name| name.eq_ignore_ascii_case("META-INF/MANIFEST.MF")) + .map(ToString::to_string) + }; + let Some(name) = name else { + return Ok(None.into()); + }; + let name = JavaLangString::from_rust_string(jvm, &name).await?; + manifest_file = jvm + .invoke_virtual( + &this, + "java/util/jar/JarFile", + "getJarEntry", + "(Ljava/lang/String;)Ljava/util/jar/JarEntry;", + (name,), + ) + .await?; + } + let input_stream: ClassInstanceRef = jvm .invoke_virtual( &this, diff --git a/rustjava-runtime/src/classes/java/util/jar/manifest.rs b/rustjava-runtime/src/classes/java/util/jar/manifest.rs index 825c9be4..4dbfc658 100644 --- a/rustjava-runtime/src/classes/java/util/jar/manifest.rs +++ b/rustjava-runtime/src/classes/java/util/jar/manifest.rs @@ -1,6 +1,6 @@ use alloc::{vec, vec::Vec}; -use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; use jvm_class_proto::{JavaFieldProto, JavaMethodProto}; use jvm_types::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; @@ -74,7 +74,7 @@ impl Manifest { let key = JavaLangString::from_rust_string(jvm, parts[0].trim()).await?; let value = JavaLangString::from_rust_string(jvm, parts[1].trim()).await?; - let _: ClassInstanceRef = jvm + let result: Result> = jvm .invoke_virtual( &main_attributes, "java/util/jar/Attributes", @@ -82,7 +82,15 @@ impl Manifest { "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (key, value), ) - .await?; + .await; + match result { + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/lang/IllegalArgumentException") => { + return Err(jvm.exception("java/io/IOException", "invalid manifest attribute name").await); + } + result => { + result?; + } + } } jvm.put_field( diff --git a/rustjava-runtime/src/classes/java/util/zip/zip_file.rs b/rustjava-runtime/src/classes/java/util/zip/zip_file.rs index 1d5478f7..11745a6e 100644 --- a/rustjava-runtime/src/classes/java/util/zip/zip_file.rs +++ b/rustjava-runtime/src/classes/java/util/zip/zip_file.rs @@ -50,7 +50,7 @@ impl ZipFile { } } - async fn get_zip_archive(jvm: &Jvm, this: &ClassInstanceRef) -> Result>>> { + pub(crate) async fn get_zip_archive(jvm: &Jvm, this: &ClassInstanceRef) -> Result>>> { let zip_data: ClassInstanceRef> = jvm.get_field(this, "java/util/zip/ZipFile", "zipData", "[B").await?; let length = jvm.array_length(&zip_data).await?; let mut buf = vec![0u8; length]; diff --git a/rustjava-runtime/src/loader.rs b/rustjava-runtime/src/loader.rs index 1e6c312f..3edf38c7 100644 --- a/rustjava-runtime/src/loader.rs +++ b/rustjava-runtime/src/loader.rs @@ -260,6 +260,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::VectorItr::as_proto(), crate::classes::java::util::VectorItr::list_proto(), crate::classes::java::util::jar::Attributes::as_proto(), + crate::classes::java::util::jar::AttributesName::as_proto(), crate::classes::java::util::jar::JarEntry::as_proto(), crate::classes::java::util::jar::JarFile::as_proto(), crate::classes::java::util::jar::JarFileEntries::as_proto(), diff --git a/rustjava-runtime/tests/classes/java/util/jar/test_attributes.rs b/rustjava-runtime/tests/classes/java/util/jar/test_attributes.rs index 800dfb2e..367708c0 100644 --- a/rustjava-runtime/tests/classes/java/util/jar/test_attributes.rs +++ b/rustjava-runtime/tests/classes/java/util/jar/test_attributes.rs @@ -8,10 +8,12 @@ async fn test_attribute_get_set() -> Result<()> { let jvm = test_jvm().await?; let attributes = jvm.new_class("java/util/jar/Attributes", "()V", ()).await?; - let map: ClassInstanceRef = jvm.get_field(&attributes, "java/util/jar/Attributes", "map", "Ljava/util/Map;").await?; - assert!(!map.is_null()); - assert!(jvm.is_instance(&**map, "java/util/HashMap")); - assert!(jvm.is_instance(&**map, "java/util/Map")); + assert!(jvm.is_instance(&*attributes, "java/util/Map")); + let entries = jvm + .invoke_virtual(&attributes, "java/util/Map", "entrySet", "()Ljava/util/Set;", ()) + .await?; + let size: i32 = jvm.invoke_virtual(&entries, "java/util/Set", "size", "()I", ()).await?; + assert_eq!(size, 0); let name = JavaLangString::from_rust_string(&jvm, "Name").await?; let value = JavaLangString::from_rust_string(&jvm, "Value").await?; @@ -40,5 +42,330 @@ async fn test_attribute_get_set() -> Result<()> { let value = JavaLangString::to_rust_string(&jvm, &value).await?; assert_eq!(value, "Value"); + let lowercase = JavaLangString::from_rust_string(&jvm, "name").await?; + let replacement = JavaLangString::from_rust_string(&jvm, "Replacement").await?; + let old = jvm + .invoke_virtual( + &attributes, + "java/util/jar/Attributes", + "putValue", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (lowercase.clone(), replacement), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &old).await?, "Value"); + let size: i32 = jvm.invoke_virtual(&entries, "java/util/Set", "size", "()I", ()).await?; + assert_eq!(size, 1); + + let iterator = jvm + .invoke_virtual(&entries, "java/util/Set", "iterator", "()Ljava/util/Iterator;", ()) + .await?; + let entry = jvm + .invoke_virtual(&iterator, "java/util/Iterator", "next", "()Ljava/lang/Object;", ()) + .await?; + let key: ClassInstanceRef = jvm + .invoke_virtual(&entry, "java/util/Map$Entry", "getKey", "()Ljava/lang/Object;", ()) + .await?; + assert!(jvm.is_instance(&**key, "java/util/jar/Attributes$Name")); + let key_string = jvm + .invoke_virtual(&key, "java/lang/Object", "toString", "()Ljava/lang/String;", ()) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &key_string).await?, "Name"); + let value = jvm + .invoke_virtual( + &attributes, + "java/util/jar/Attributes", + "getValue", + "(Ljava/util/jar/Attributes$Name;)Ljava/lang/String;", + (key,), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "Replacement"); + + let updated = JavaLangString::from_rust_string(&jvm, "Updated").await?; + let old = jvm + .invoke_virtual( + &entry, + "java/util/Map$Entry", + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (updated,), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &old).await?, "Replacement"); + let value = jvm + .invoke_virtual( + &attributes, + "java/util/jar/Attributes", + "getValue", + "(Ljava/lang/String;)Ljava/lang/String;", + (lowercase.clone(),), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "Updated"); + + let _: () = jvm.invoke_virtual(&iterator, "java/util/Iterator", "remove", "()V", ()).await?; + let value: ClassInstanceRef = jvm + .invoke_virtual( + &attributes, + "java/util/jar/Attributes", + "getValue", + "(Ljava/lang/String;)Ljava/lang/String;", + (lowercase,), + ) + .await?; + assert!(value.is_null()); + let size: i32 = jvm.invoke_virtual(&entries, "java/util/Set", "size", "()I", ()).await?; + assert_eq!(size, 0); + + Ok(()) +} + +#[tokio::test] +async fn test_attributes_map_operations() -> Result<()> { + let jvm = test_jvm().await?; + let attributes = jvm.new_class("java/util/jar/Attributes", "()V", ()).await?; + let empty: bool = jvm.invoke_virtual(&attributes, "java/util/Map", "isEmpty", "()Z", ()).await?; + assert!(empty); + let keys = jvm + .invoke_virtual(&attributes, "java/util/Map", "keySet", "()Ljava/util/Set;", ()) + .await?; + let values = jvm + .invoke_virtual(&attributes, "java/util/Map", "values", "()Ljava/util/Collection;", ()) + .await?; + let spelling = JavaLangString::from_rust_string(&jvm, "Name").await?; + let key = jvm + .new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (spelling.clone(),)) + .await?; + let value = JavaLangString::from_rust_string(&jvm, "Value").await?; + let old: ClassInstanceRef = jvm + .invoke_virtual( + &attributes, + "java/util/Map", + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(), value.clone()), + ) + .await?; + assert!(old.is_null()); + let size: i32 = jvm.invoke_virtual(&attributes, "java/util/Map", "size", "()I", ()).await?; + assert_eq!(size, 1); + let empty: bool = jvm.invoke_virtual(&attributes, "java/util/Map", "isEmpty", "()Z", ()).await?; + assert!(!empty); + let found: bool = jvm + .invoke_virtual(&attributes, "java/util/Map", "containsKey", "(Ljava/lang/Object;)Z", (key.clone(),)) + .await?; + assert!(found); + let found: bool = jvm + .invoke_virtual(&attributes, "java/util/Map", "containsValue", "(Ljava/lang/Object;)Z", (value.clone(),)) + .await?; + assert!(found); + let found = jvm + .invoke_virtual( + &attributes, + "java/util/Map", + "get", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(),), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &found).await?, "Value"); + let absent: ClassInstanceRef = jvm + .invoke_virtual( + &attributes, + "java/util/Map", + "get", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (spelling.clone(),), + ) + .await?; + assert!(absent.is_null()); + for view in [&keys, &values] { + let size: i32 = jvm.invoke_virtual(view, "java/util/Collection", "size", "()I", ()).await?; + assert_eq!(size, 1); + } + + let copy = jvm.new_class("java/util/jar/Attributes", "()V", ()).await?; + let _: () = jvm + .invoke_virtual(©, "java/util/Map", "putAll", "(Ljava/util/Map;)V", (attributes.clone(),)) + .await?; + let equal: bool = jvm + .invoke_virtual(&attributes, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", (copy.clone(),)) + .await?; + assert!(equal); + let hash: i32 = jvm.invoke_virtual(&attributes, "java/lang/Object", "hashCode", "()I", ()).await?; + let copy_hash: i32 = jvm.invoke_virtual(©, "java/lang/Object", "hashCode", "()I", ()).await?; + assert_eq!(hash, copy_hash); + let old = jvm + .invoke_virtual( + &attributes, + "java/util/Map", + "remove", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(),), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &old).await?, "Value"); + let _: () = jvm + .invoke_virtual(&attributes, "java/util/Map", "putAll", "(Ljava/util/Map;)V", (copy,)) + .await?; + let _: () = jvm.invoke_virtual(&attributes, "java/util/Map", "clear", "()V", ()).await?; + for view in [&keys, &values] { + let size: i32 = jvm.invoke_virtual(view, "java/util/Collection", "size", "()I", ()).await?; + assert_eq!(size, 0); + } + + for (key, value) in [ + (ClassInstanceRef::::from(spelling), ClassInstanceRef::::from(value)), + ( + ClassInstanceRef::::from(key), + ClassInstanceRef::::from(jvm.new_class("java/lang/Object", "()V", ()).await?), + ), + ] { + let result: Result> = jvm + .invoke_virtual( + &attributes, + "java/util/Map", + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, value), + ) + .await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected ClassCastException"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException")); + } + for source in [ + ClassInstanceRef::::from(jvm.new_class("java/util/HashMap", "()V", ()).await?), + ClassInstanceRef::::new(None), + ] { + let result: Result<()> = jvm + .invoke_virtual(&attributes, "java/util/Map", "putAll", "(Ljava/util/Map;)V", (source,)) + .await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected ClassCastException"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException")); + } + let size: i32 = jvm.invoke_virtual(&attributes, "java/util/Map", "size", "()I", ()).await?; + assert_eq!(size, 0); + let null = ClassInstanceRef::::new(None); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &attributes, + "java/util/Map", + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (null.clone(), null.clone()), + ) + .await?; + let found: bool = jvm + .invoke_virtual(&attributes, "java/util/Map", "containsKey", "(Ljava/lang/Object;)Z", (null,)) + .await?; + assert!(found); + Ok(()) +} + +#[tokio::test] +async fn test_attribute_name_validation() -> Result<()> { + let jvm = test_jvm().await?; + for spelling in ["a", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-", &"A".repeat(70)] { + let name = JavaLangString::from_rust_string(&jvm, spelling).await?; + let name = jvm.new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (name,)).await?; + let text = jvm + .invoke_virtual(&name, "java/util/jar/Attributes$Name", "toString", "()Ljava/lang/String;", ()) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, spelling); + } + for spelling in ["", "a b", "\u{e9}", &"A".repeat(71)] { + let name = JavaLangString::from_rust_string(&jvm, spelling).await?; + let result = jvm.new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (name,)).await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected IllegalArgumentException for {spelling:?}"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + } + let result = jvm + .new_class( + "java/util/jar/Attributes$Name", + "(Ljava/lang/String;)V", + (ClassInstanceRef::::new(None),), + ) + .await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + Ok(()) +} + +#[tokio::test] +async fn test_attribute_name_equality_and_hash() -> Result<()> { + let jvm = test_jvm().await?; + let spelling = JavaLangString::from_rust_string(&jvm, "MiXeD-Name_123").await?; + let name = jvm + .new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (spelling.clone(),)) + .await?; + let hash: i32 = jvm.invoke_virtual(&name, "java/util/jar/Attributes$Name", "hashCode", "()I", ()).await?; + for (text, expected) in [("mixed-name_123", true), ("Other", false)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let other = jvm.new_class("java/util/jar/Attributes$Name", "(Ljava/lang/String;)V", (text,)).await?; + let equal: bool = jvm + .invoke_virtual( + &name, + "java/util/jar/Attributes$Name", + "equals", + "(Ljava/lang/Object;)Z", + (other.clone(),), + ) + .await?; + assert_eq!(equal, expected); + if equal { + let other_hash: i32 = jvm.invoke_virtual(&other, "java/util/jar/Attributes$Name", "hashCode", "()I", ()).await?; + assert_eq!(hash, other_hash); + } + } + for other in [ClassInstanceRef::::from(spelling), ClassInstanceRef::new(None)] { + let equal: bool = jvm + .invoke_virtual(&name, "java/util/jar/Attributes$Name", "equals", "(Ljava/lang/Object;)Z", (other,)) + .await?; + assert!(!equal); + } + Ok(()) +} + +#[tokio::test] +async fn test_attribute_name_constants() -> Result<()> { + let jvm = test_jvm().await?; + for (field, expected) in [ + ("MANIFEST_VERSION", "Manifest-Version"), + ("SIGNATURE_VERSION", "Signature-Version"), + ("CONTENT_TYPE", "Content-Type"), + ("CLASS_PATH", "Class-Path"), + ("MAIN_CLASS", "Main-Class"), + ("SEALED", "Sealed"), + ("EXTENSION_LIST", "Extension-List"), + ("EXTENSION_NAME", "Extension-Name"), + ("EXTENSION_INSTALLATION", "Extension-Installation"), + ("IMPLEMENTATION_TITLE", "Implementation-Title"), + ("IMPLEMENTATION_VERSION", "Implementation-Version"), + ("IMPLEMENTATION_VENDOR", "Implementation-Vendor"), + ("IMPLEMENTATION_VENDOR_ID", "Implementation-Vendor-Id"), + ("IMPLEMENTATION_URL", "Implementation-URL"), + ("SPECIFICATION_TITLE", "Specification-Title"), + ("SPECIFICATION_VERSION", "Specification-Version"), + ("SPECIFICATION_VENDOR", "Specification-Vendor"), + ] { + let name: ClassInstanceRef = jvm + .get_static_field("java/util/jar/Attributes$Name", field, "Ljava/util/jar/Attributes$Name;") + .await?; + assert!(!name.is_null(), "{field}"); + assert!(jvm.is_instance(&**name, "java/util/jar/Attributes$Name")); + let text = jvm + .invoke_virtual(&name, "java/util/jar/Attributes$Name", "toString", "()Ljava/lang/String;", ()) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } Ok(()) } diff --git a/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs b/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs index 98b3aca1..7baea2f2 100644 --- a/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs +++ b/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs @@ -1,44 +1,85 @@ +use std::io::{Cursor, Write}; + use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; -use rustjava_runtime::classes::java::util::jar::JarEntry; +use rustjava_runtime::classes::java::util::{ + jar::{JarEntry, Manifest}, + zip::ZipEntry, +}; +use zip::{ZipWriter, write::SimpleFileOptions}; use test_utils::test_jvm_filesystem; #[tokio::test] async fn test_jar_manifest() -> Result<()> { - let jar = include_bytes!("../../../../../../test-data/test.jar"); - let filesystem = [("test.jar".into(), jar.to_vec())].into_iter().collect(); - let jvm = test_jvm_filesystem(filesystem).await?; - - let name = JavaLangString::from_rust_string(&jvm, "test.jar").await?; - let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (name,)).await?; - let jar = jvm.new_class("java/util/jar/JarFile", "(Ljava/io/File;)V", (file,)).await?; - - let manifest = jvm - .invoke_virtual(&jar, &jar.class_definition().name(), "getManifest", "()Ljava/util/jar/Manifest;", ()) - .await?; - - let main_attributes = jvm - .invoke_virtual( - &manifest, - &manifest.class_definition().name(), - "getMainAttributes", - "()Ljava/util/jar/Attributes;", - (), - ) - .await?; - - let key = JavaLangString::from_rust_string(&jvm, "Main-Class").await?; - let value = jvm - .invoke_virtual( - &main_attributes, - &main_attributes.class_definition().name(), - "getValue", - "(Ljava/lang/String;)Ljava/lang/String;", - (key,), - ) - .await?; - - assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "JarTest"); + for (names, expected) in [ + (&["META-INF/MANIFEST.MF"][..], Some("META-INF/MANIFEST.MF")), + (&["meta-inf/manifest.mf"][..], Some("meta-inf/manifest.mf")), + (&["other.txt", "MeTa-InF/MaNiFeSt.Mf"][..], Some("MeTa-InF/MaNiFeSt.Mf")), + (&["other.txt"][..], None), + (&["meta-inf/manifest.mf", "META-INF/MANIFEST.MF"][..], Some("META-INF/MANIFEST.MF")), + ] { + let mut archive = ZipWriter::new(Cursor::new(Vec::new())); + for name in names { + archive.start_file(*name, SimpleFileOptions::default()).unwrap(); + write!(archive, "Manifest-Version: 1.0\r\nMain-Class: {name}\r\n\r\n").unwrap(); + } + let filesystem = [("test.jar".into(), archive.finish().unwrap().into_inner())].into_iter().collect(); + let jvm = test_jvm_filesystem(filesystem).await?; + + let name = JavaLangString::from_rust_string(&jvm, "test.jar").await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (name,)).await?; + let jar = jvm.new_class("java/util/jar/JarFile", "(Ljava/io/File;)V", (file,)).await?; + let manifest: ClassInstanceRef = jvm + .invoke_virtual(&jar, "java/util/jar/JarFile", "getManifest", "()Ljava/util/jar/Manifest;", ()) + .await?; + + if let Some(expected) = expected { + let attributes = jvm + .invoke_virtual( + &manifest, + "java/util/jar/Manifest", + "getMainAttributes", + "()Ljava/util/jar/Attributes;", + (), + ) + .await?; + let key = JavaLangString::from_rust_string(&jvm, "Main-Class").await?; + let value = jvm + .invoke_virtual( + &attributes, + "java/util/jar/Attributes", + "getValue", + "(Ljava/lang/String;)Ljava/lang/String;", + (key,), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, expected); + } else { + assert!(manifest.is_null()); + } + + let canonical = JavaLangString::from_rust_string(&jvm, "META-INF/MANIFEST.MF").await?; + let entry: ClassInstanceRef = jvm + .invoke_virtual( + &jar, + "java/util/jar/JarFile", + "getJarEntry", + "(Ljava/lang/String;)Ljava/util/jar/JarEntry;", + (canonical.clone(),), + ) + .await?; + assert_eq!(entry.is_null(), !names.contains(&"META-INF/MANIFEST.MF")); + let entry: ClassInstanceRef = jvm + .invoke_virtual( + &jar, + "java/util/zip/ZipFile", + "getEntry", + "(Ljava/lang/String;)Ljava/util/zip/ZipEntry;", + (canonical,), + ) + .await?; + assert_eq!(entry.is_null(), !names.contains(&"META-INF/MANIFEST.MF")); + } Ok(()) } diff --git a/rustjava-runtime/tests/classes/java/util/jar/test_manifest.rs b/rustjava-runtime/tests/classes/java/util/jar/test_manifest.rs index bc5dcbe7..e0b95488 100644 --- a/rustjava-runtime/tests/classes/java/util/jar/test_manifest.rs +++ b/rustjava-runtime/tests/classes/java/util/jar/test_manifest.rs @@ -1,4 +1,4 @@ -use jvm::{Result, runtime::JavaLangString}; +use jvm::{JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -38,5 +38,15 @@ async fn test_manifest_parsing() -> Result<()> { assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "test"); + let data = b"Manifest-Version: 1.0\r\nBad Name: value\r\n\r\n"; + let mut bytes = jvm.instantiate_array("B", data.len()).await?; + jvm.array_raw_buffer_mut(&mut bytes).await?.write(0, data)?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?; + let result = jvm.new_class("java/util/jar/Manifest", "(Ljava/io/InputStream;)V", (input,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid manifest attribute names must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + Ok(()) } diff --git a/rustjava-runtime/tests/classes/java/util/test_hash_map.rs b/rustjava-runtime/tests/classes/java/util/test_hash_map.rs index 48f34028..cd627999 100644 --- a/rustjava-runtime/tests/classes/java/util/test_hash_map.rs +++ b/rustjava-runtime/tests/classes/java/util/test_hash_map.rs @@ -263,18 +263,70 @@ async fn assert_next_throws_no_such_element(jvm: &Jvm, iterator: &ClassInstanceR Ok(()) } -async fn assert_remove_throws_unsupported(jvm: &Jvm, iterator: &ClassInstanceRef) -> Result<()> { +async fn assert_remove_throws_illegal_state(jvm: &Jvm, iterator: &ClassInstanceRef) -> Result<()> { let result: Result<()> = jvm .invoke_virtual(iterator, &iterator.class_definition().name(), "remove", "()V", ()) .await; let Err(JavaError::JavaException(exception)) = result else { panic!("Expected JavaException, got {:?}", result); }; - assert!(jvm.is_instance(&*exception, "java/lang/UnsupportedOperationException")); + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); Ok(()) } +async fn assert_view_iterator_removal(jvm: &Jvm, map: &ClassInstanceRef, view: &ClassInstanceRef) -> Result<()> { + let value: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "duplicate").await?.into(); + let first_key = JavaLangString::from_rust_string(jvm, "first").await?; + let second_key = JavaLangString::from_rust_string(jvm, "second").await?; + let null: ClassInstanceRef = None.into(); + for (key, value) in [(null.clone(), value.clone()), (first_key.into(), value), (second_key.into(), null)] { + let _: ClassInstanceRef = jvm + .invoke_virtual( + map, + "java/util/HashMap", + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, value), + ) + .await?; + } + + let entries: ClassInstanceRef = jvm.invoke_virtual(map, "java/util/HashMap", "entrySet", "()Ljava/util/Set;", ()).await?; + let entry_iterator = jvm + .invoke_virtual(&entries, "java/util/Set", "iterator", "()Ljava/util/Iterator;", ()) + .await?; + let iterator = jvm + .invoke_virtual(view, &view.class_definition().name(), "iterator", "()Ljava/util/Iterator;", ()) + .await?; + assert_remove_throws_illegal_state(jvm, &iterator).await?; + for remaining in (0..3).rev() { + let entry = jvm + .invoke_virtual(&entry_iterator, "java/util/Iterator", "next", "()Ljava/lang/Object;", ()) + .await?; + let key: ClassInstanceRef = jvm + .invoke_virtual(&entry, "java/util/Map$Entry", "getKey", "()Ljava/lang/Object;", ()) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&iterator, "java/util/Iterator", "next", "()Ljava/lang/Object;", ()) + .await?; + if remaining == 0 { + assert_next_throws_no_such_element(jvm, &iterator).await?; + } + let _: () = jvm.invoke_virtual(&iterator, "java/util/Iterator", "remove", "()V", ()).await?; + let contains: bool = jvm + .invoke_virtual(map, "java/util/HashMap", "containsKey", "(Ljava/lang/Object;)Z", (key,)) + .await?; + assert!(!contains); + let size: i32 = jvm.invoke_virtual(map, "java/util/HashMap", "size", "()I", ()).await?; + assert_eq!(size, remaining); + assert_remove_throws_illegal_state(jvm, &iterator).await?; + } + let has_next: bool = jvm.invoke_virtual(&iterator, "java/util/Iterator", "hasNext", "()Z", ()).await?; + assert!(!has_next); + Ok(()) +} + fn sorted_optional_strings(mut values: Vec>) -> Vec> { values.sort(); values @@ -1094,7 +1146,6 @@ async fn test_hash_map_key_set_view_backing_and_iterator_snapshot() -> Result<() let snapshot = sorted_optional_strings(iterator_to_optional_strings(&jvm, &iterator).await?); assert_eq!(snapshot, vec![Some("first".to_string()), Some("second".to_string())]); assert_next_throws_no_such_element(&jvm, &iterator).await?; - assert_remove_throws_unsupported(&jvm, &iterator).await?; let size: i32 = jvm .invoke_virtual(&key_set, &key_set.class_definition().name(), "size", "()I", ()) @@ -1195,6 +1246,8 @@ async fn test_hash_map_key_set_view_backing_and_iterator_snapshot() -> Result<() assert!(!has_next); assert_next_throws_no_such_element(&jvm, &empty_iterator).await?; + assert_view_iterator_removal(&jvm, &hash_map.into(), &key_set).await?; + Ok(()) } @@ -1294,7 +1347,6 @@ async fn test_hash_map_values_view_contains_clear_and_iterator_snapshot() -> Res let snapshot = sorted_optional_strings(iterator_to_optional_strings(&jvm, &iterator).await?); assert_eq!(snapshot, vec![None, Some("same-value".to_string())]); assert_next_throws_no_such_element(&jvm, &iterator).await?; - assert_remove_throws_unsupported(&jvm, &iterator).await?; let size: i32 = jvm.invoke_virtual(&values, &values.class_definition().name(), "size", "()I", ()).await?; assert_eq!(size, 3); @@ -1314,6 +1366,8 @@ async fn test_hash_map_values_view_contains_clear_and_iterator_snapshot() -> Res .await?; assert_eq!(jvm.array_length(&values_array).await?, 0); + assert_view_iterator_removal(&jvm, &hash_map.into(), &values).await?; + Ok(()) } @@ -1382,7 +1436,6 @@ async fn test_hash_map_entry_set_view_backing_clear_and_iterator_snapshot() -> R ] ); assert_next_throws_no_such_element(&jvm, &iterator).await?; - assert_remove_throws_unsupported(&jvm, &iterator).await?; let size: i32 = jvm .invoke_virtual(&entry_set, &entry_set.class_definition().name(), "size", "()I", ()) @@ -1410,6 +1463,8 @@ async fn test_hash_map_entry_set_view_backing_clear_and_iterator_snapshot() -> R assert!(!has_next); assert_next_throws_no_such_element(&jvm, &empty_iterator).await?; + assert_view_iterator_removal(&jvm, &hash_map.into(), &entry_set).await?; + Ok(()) } diff --git a/rustjava-runtime/tests/classes/java/util/test_hash_set.rs b/rustjava-runtime/tests/classes/java/util/test_hash_set.rs index 2269b8ab..917e9b2f 100644 --- a/rustjava-runtime/tests/classes/java/util/test_hash_set.rs +++ b/rustjava-runtime/tests/classes/java/util/test_hash_set.rs @@ -53,18 +53,6 @@ async fn assert_next_throws_no_such_element(jvm: &jvm::Jvm, iterator: &ClassInst Ok(()) } -async fn assert_remove_throws_unsupported(jvm: &jvm::Jvm, iterator: &ClassInstanceRef) -> Result<()> { - let result: Result<()> = jvm - .invoke_virtual(iterator, &iterator.class_definition().name(), "remove", "()V", ()) - .await; - let Err(JavaError::JavaException(exception)) = result else { - panic!("Expected JavaException, got {:?}", result); - }; - assert!(jvm.is_instance(&*exception, "java/lang/UnsupportedOperationException")); - - Ok(()) -} - fn sorted_optional_strings(mut values: Vec>) -> Vec> { values.sort(); values @@ -287,15 +275,23 @@ async fn test_hash_set_to_array_and_iterator_snapshot_exhaustion_remove() -> Res .invoke_virtual(&hash_set, &hash_set.class_definition().name(), "add", "(Ljava/lang/Object;)Z", (third,)) .await?; - let snapshot = sorted_optional_strings(iterator_to_optional_strings(&jvm, &iterator).await?); - assert_eq!(snapshot, vec![Some("first".to_string()), Some("second".to_string())]); + let snapshot = iterator_to_optional_strings(&jvm, &iterator).await?; + let last = JavaLangString::from_rust_string(&jvm, snapshot.last().unwrap().as_deref().unwrap()).await?; + assert_eq!( + sorted_optional_strings(snapshot), + vec![Some("first".to_string()), Some("second".to_string())] + ); assert_next_throws_no_such_element(&jvm, &iterator).await?; - assert_remove_throws_unsupported(&jvm, &iterator).await?; + let _: () = jvm.invoke_virtual(&iterator, "java/util/Iterator", "remove", "()V", ()).await?; + let contains: bool = jvm + .invoke_virtual(&hash_set, "java/util/HashSet", "contains", "(Ljava/lang/Object;)Z", (last,)) + .await?; + assert!(!contains); let size: i32 = jvm .invoke_virtual(&hash_set, &hash_set.class_definition().name(), "size", "()I", ()) .await?; - assert_eq!(size, 3); + assert_eq!(size, 2); let empty_hash_set = jvm.new_class("java/util/HashSet", "()V", ()).await?; let empty_iterator: ClassInstanceRef = jvm