diff --git a/Cargo.lock b/Cargo.lock index 65491f3a..d94ca007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,6 +342,7 @@ dependencies = [ "java_constants", "jvm", "jvm_rust", + "libm", "parking_lot", "test_utils", "tokio", @@ -399,6 +400,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "litemap" version = "0.8.2" diff --git a/java_runtime/Cargo.toml b/java_runtime/Cargo.toml index f003fbd7..a6db567b 100644 --- a/java_runtime/Cargo.toml +++ b/java_runtime/Cargo.toml @@ -9,6 +9,7 @@ async-trait = { workspace = true } bytemuck = { workspace = true } dyn-clone = { workspace = true } hashbrown = { workspace = true } +libm = { version = "^0.2", default-features = false } parking_lot = { workspace = true } tracing = { workspace = true } diff --git a/java_runtime/src/classes/java/io.rs b/java_runtime/src/classes/java/io.rs index 581d1919..823a91f0 100644 --- a/java_runtime/src/classes/java/io.rs +++ b/java_runtime/src/classes/java/io.rs @@ -1,6 +1,11 @@ +mod buffered_input_stream; +mod buffered_output_stream; mod buffered_reader; +mod buffered_writer; mod byte_array_input_stream; mod byte_array_output_stream; +mod char_array_reader; +mod char_array_writer; mod data_input; mod data_input_stream; mod data_output; @@ -11,6 +16,8 @@ mod file_descriptor; mod file_input_stream; mod file_not_found_exception; mod file_output_stream; +mod file_reader; +mod file_writer; mod filter_input_stream; mod filter_output_stream; mod input_stream; @@ -24,19 +31,21 @@ mod print_writer; mod random_access_file; mod reader; mod serializable; +mod string_reader; mod string_writer; mod unsupported_encoding_exception; mod utf_data_format_exception; mod writer; pub use self::{ - buffered_reader::BufferedReader, byte_array_input_stream::ByteArrayInputStream, byte_array_output_stream::ByteArrayOutputStream, - data_input::DataInput, data_input_stream::DataInputStream, data_output::DataOutput, data_output_stream::DataOutputStream, - eof_exception::EOFException, file::File, file_descriptor::FileDescriptor, file_input_stream::FileInputStream, - file_not_found_exception::FileNotFoundException, file_output_stream::FileOutputStream, filter_input_stream::FilterInputStream, - filter_output_stream::FilterOutputStream, input_stream::InputStream, input_stream_reader::InputStreamReader, - interrupted_io_exception::InterruptedIOException, io_exception::IOException, output_stream::OutputStream, - output_stream_writer::OutputStreamWriter, print_stream::PrintStream, print_writer::PrintWriter, random_access_file::RandomAccessFile, - reader::Reader, serializable::Serializable, string_writer::StringWriter, unsupported_encoding_exception::UnsupportedEncodingException, - utf_data_format_exception::UTFDataFormatException, writer::Writer, + buffered_input_stream::BufferedInputStream, buffered_output_stream::BufferedOutputStream, buffered_reader::BufferedReader, + buffered_writer::BufferedWriter, byte_array_input_stream::ByteArrayInputStream, byte_array_output_stream::ByteArrayOutputStream, + char_array_reader::CharArrayReader, char_array_writer::CharArrayWriter, data_input::DataInput, data_input_stream::DataInputStream, + data_output::DataOutput, data_output_stream::DataOutputStream, eof_exception::EOFException, file::File, file_descriptor::FileDescriptor, + file_input_stream::FileInputStream, file_not_found_exception::FileNotFoundException, file_output_stream::FileOutputStream, + file_reader::FileReader, file_writer::FileWriter, filter_input_stream::FilterInputStream, filter_output_stream::FilterOutputStream, + input_stream::InputStream, input_stream_reader::InputStreamReader, interrupted_io_exception::InterruptedIOException, io_exception::IOException, + output_stream::OutputStream, output_stream_writer::OutputStreamWriter, print_stream::PrintStream, print_writer::PrintWriter, + random_access_file::RandomAccessFile, reader::Reader, serializable::Serializable, string_reader::StringReader, string_writer::StringWriter, + unsupported_encoding_exception::UnsupportedEncodingException, utf_data_format_exception::UTFDataFormatException, writer::Writer, }; diff --git a/java_runtime/src/classes/java/io/buffered_input_stream.rs b/java_runtime/src/classes/java/io/buffered_input_stream.rs new file mode 100644 index 00000000..09fcf1bd --- /dev/null +++ b/java_runtime/src/classes/java/io/buffered_input_stream.rs @@ -0,0 +1,332 @@ +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::io::InputStream}; + +const DEFAULT_BUFFER_SIZE: i32 = 8192; + +// class java.io.BufferedInputStream +pub struct BufferedInputStream; + +impl BufferedInputStream { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/BufferedInputStream", + parent_class: Some("java/io/FilterInputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/InputStream;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/InputStream;I)V", Self::init_with_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "read", + "()I", + Self::read_byte, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("read", "([BII)I", Self::read, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("skip", "(J)J", Self::skip, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new( + "available", + "()I", + Self::available, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("mark", "(I)V", Self::mark, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("markSupported", "()Z", Self::mark_supported, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("buf", "[B", FieldAccessFlags::PROTECTED | FieldAccessFlags::VOLATILE), + JavaFieldProto::new("count", "I", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("pos", "I", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("markpos", "I", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("marklimit", "I", FieldAccessFlags::PROTECTED), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, r#in: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedInputStream::({this:?}, {in:?})", in = &r#in); + + jvm.invoke_special( + &this, + "java/io/BufferedInputStream", + "", + "(Ljava/io/InputStream;I)V", + (r#in, DEFAULT_BUFFER_SIZE), + ) + .await + } + + async fn init_with_size( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + r#in: ClassInstanceRef, + size: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedInputStream::({this:?}, {in:?}, {size})", in = &r#in); + + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "input is null").await); + } + if size <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Buffer size <= 0").await); + } + + let _: () = jvm + .invoke_special(&this, "java/io/FilterInputStream", "", "(Ljava/io/InputStream;)V", (r#in,)) + .await?; + let buffer = jvm.instantiate_array("B", size as usize).await?; + jvm.put_field(&mut this, "buf", "[B", buffer).await?; + jvm.put_field(&mut this, "count", "I", 0).await?; + jvm.put_field(&mut this, "pos", "I", 0).await?; + jvm.put_field(&mut this, "markpos", "I", -1).await?; + jvm.put_field(&mut this, "marklimit", "I", 0).await + } + + async fn fill(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result { + let r#in: ClassInstanceRef = jvm.get_field(this, "in", "Ljava/io/InputStream;").await?; + let mut buffer: ClassInstanceRef> = jvm.get_field(this, "buf", "[B").await?; + if r#in.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + + let mut position: i32 = jvm.get_field(this, "pos", "I").await?; + let mut mark_position: i32 = jvm.get_field(this, "markpos", "I").await?; + let mark_limit: i32 = jvm.get_field(this, "marklimit", "I").await?; + let mut buffer_length = jvm.array_length(&buffer).await? as i32; + + if mark_position < 0 { + position = 0; + } else if position >= buffer_length { + if mark_position > 0 { + let preserved = position - mark_position; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (buffer.clone(), mark_position, buffer.clone(), 0, preserved), + ) + .await?; + position = preserved; + mark_position = 0; + } else if buffer_length >= mark_limit { + mark_position = -1; + position = 0; + } else { + let new_length = (buffer_length.saturating_mul(2)).min(mark_limit).max(buffer_length + 1); + let new_buffer: ClassInstanceRef> = jvm.instantiate_array("B", new_length as usize).await?.into(); + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (buffer, 0, new_buffer.clone(), 0, position), + ) + .await?; + buffer = new_buffer; + buffer_length = new_length; + jvm.put_field(this, "buf", "[B", buffer.clone()).await?; + } + } + + jvm.put_field(this, "pos", "I", position).await?; + jvm.put_field(this, "markpos", "I", mark_position).await?; + jvm.put_field(this, "count", "I", position).await?; + + let read: i32 = jvm + .invoke_virtual(&r#in, "read", "([BII)I", (buffer, position, buffer_length - position)) + .await?; + if read > 0 { + jvm.put_field(this, "count", "I", position + read).await?; + } + Ok(read) + } + + async fn read_byte(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.BufferedInputStream::read({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/InputStream;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if r#in.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + + let mut position: i32 = jvm.get_field(&this, "pos", "I").await?; + let mut count: i32 = jvm.get_field(&this, "count", "I").await?; + if position >= count { + if Self::fill(jvm, &mut this).await? == -1 { + return Ok(-1); + } + position = jvm.get_field(&this, "pos", "I").await?; + count = jvm.get_field(&this, "count", "I").await?; + if position >= count { + return Ok(-1); + } + } + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + let value = jvm.load_array::(&buffer, position as usize, 1).await?[0]; + jvm.put_field(&mut this, "pos", "I", position + 1).await?; + Ok(value as u8 as i32) + } + + async fn read( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + tracing::debug!("java.io.BufferedInputStream::read({this:?}, {target:?}, {offset}, {length})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/InputStream;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if r#in.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if target.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "target is null").await); + } + let target_length = jvm.array_length(&target).await? as i32; + if offset < 0 || length < 0 || offset > target_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + if length == 0 { + return Ok(0); + } + + let mut total = 0; + while total < length { + let mut position: i32 = jvm.get_field(&this, "pos", "I").await?; + let mut count: i32 = jvm.get_field(&this, "count", "I").await?; + if position >= count { + if Self::fill(jvm, &mut this).await? == -1 { + break; + } + position = jvm.get_field(&this, "pos", "I").await?; + count = jvm.get_field(&this, "count", "I").await?; + if position >= count { + break; + } + } + + let copied = (count - position).min(length - total); + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (buffer, position, target.clone(), offset + total, copied), + ) + .await?; + jvm.put_field(&mut this, "pos", "I", position + copied).await?; + total += copied; + + if total < length { + let available: i32 = jvm.invoke_virtual(&r#in, "available", "()I", ()).await?; + if available == 0 { + break; + } + } + } + + if total == 0 { Ok(-1) } else { Ok(total) } + } + + async fn skip(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, amount: i64) -> Result { + tracing::debug!("java.io.BufferedInputStream::skip({this:?}, {amount})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/InputStream;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if r#in.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if amount <= 0 { + return Ok(0); + } + + let mut position: i32 = jvm.get_field(&this, "pos", "I").await?; + let mut count: i32 = jvm.get_field(&this, "count", "I").await?; + if position >= count { + let mark_position: i32 = jvm.get_field(&this, "markpos", "I").await?; + if mark_position < 0 { + return jvm.invoke_virtual(&r#in, "skip", "(J)J", (amount,)).await; + } + if Self::fill(jvm, &mut this).await? == -1 { + return Ok(0); + } + position = jvm.get_field(&this, "pos", "I").await?; + count = jvm.get_field(&this, "count", "I").await?; + } + + let skipped = amount.min((count - position) as i64); + jvm.put_field(&mut this, "pos", "I", position + skipped as i32).await?; + Ok(skipped) + } + + async fn available(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.BufferedInputStream::available({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/InputStream;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if r#in.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let underlying: i32 = jvm.invoke_virtual(&r#in, "available", "()I", ()).await?; + Ok((count - position).saturating_add(underlying)) + } + + async fn mark(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, read_limit: i32) -> Result<()> { + tracing::debug!("java.io.BufferedInputStream::mark({this:?}, {read_limit})"); + + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + jvm.put_field(&mut this, "marklimit", "I", read_limit).await?; + jvm.put_field(&mut this, "markpos", "I", position).await + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedInputStream::reset({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/InputStream;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if r#in.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let mark_position: i32 = jvm.get_field(&this, "markpos", "I").await?; + if mark_position < 0 { + return Err(jvm.exception("java/io/IOException", "Resetting to invalid mark").await); + } + jvm.put_field(&mut this, "pos", "I", mark_position).await + } + + async fn mark_supported(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.BufferedInputStream::markSupported({this:?})"); + Ok(true) + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedInputStream::close({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Ok(()); + } + let null_input: ClassInstanceRef = None.into(); + let null_buffer: ClassInstanceRef> = None.into(); + jvm.put_field(&mut this, "in", "Ljava/io/InputStream;", null_input).await?; + jvm.put_field(&mut this, "buf", "[B", null_buffer).await?; + jvm.invoke_virtual(&r#in, "close", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/io/buffered_output_stream.rs b/java_runtime/src/classes/java/io/buffered_output_stream.rs new file mode 100644 index 00000000..1e9b24ac --- /dev/null +++ b/java_runtime/src/classes/java/io/buffered_output_stream.rs @@ -0,0 +1,161 @@ +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::io::OutputStream}; + +const DEFAULT_BUFFER_SIZE: i32 = 8192; + +// class java.io.BufferedOutputStream +pub struct BufferedOutputStream; + +impl BufferedOutputStream { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/BufferedOutputStream", + parent_class: Some("java/io/FilterOutputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/OutputStream;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/OutputStream;I)V", Self::init_with_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new( + "write", + "([BII)V", + Self::write_bytes, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + ], + fields: vec![ + JavaFieldProto::new("buf", "[B", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("count", "I", FieldAccessFlags::PROTECTED), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedOutputStream::({this:?}, {out:?})"); + jvm.invoke_special( + &this, + "java/io/BufferedOutputStream", + "", + "(Ljava/io/OutputStream;I)V", + (out, DEFAULT_BUFFER_SIZE), + ) + .await + } + + async fn init_with_size( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + out: ClassInstanceRef, + size: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedOutputStream::({this:?}, {out:?}, {size})"); + + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); + } + if size <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Buffer size <= 0").await); + } + + let _: () = jvm + .invoke_special(&this, "java/io/FilterOutputStream", "", "(Ljava/io/OutputStream;)V", (out,)) + .await?; + let buffer = jvm.instantiate_array("B", size as usize).await?; + jvm.put_field(&mut this, "buf", "[B", buffer).await?; + jvm.put_field(&mut this, "count", "I", 0).await + } + + async fn flush_buffer(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { + let out: ClassInstanceRef = jvm.get_field(this, "out", "Ljava/io/OutputStream;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(this, "buf", "[B").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let count: i32 = jvm.get_field(this, "count", "I").await?; + if count > 0 { + let _: () = jvm.invoke_virtual(&out, "write", "([BII)V", (buffer, 0, count)).await?; + jvm.put_field(this, "count", "I", 0).await?; + } + Ok(()) + } + + async fn write(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result<()> { + tracing::debug!("java.io.BufferedOutputStream::write({this:?}, {value})"); + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + let mut buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let mut count: i32 = jvm.get_field(&this, "count", "I").await?; + if count >= jvm.array_length(&buffer).await? as i32 { + Self::flush_buffer(jvm, &mut this).await?; + count = 0; + buffer = jvm.get_field(&this, "buf", "[B").await?; + } + jvm.store_array(&mut buffer, count as usize, [value as i8]).await?; + jvm.put_field(&mut this, "count", "I", count + 1).await + } + + async fn write_bytes( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + bytes: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedOutputStream::write({this:?}, {bytes:?}, {offset}, {length})"); + + let source_length = jvm.array_length(&bytes).await? as i32; + if offset < 0 || length < 0 || offset > source_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + let mut buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if length == 0 { + return Ok(()); + } + + let buffer_length = jvm.array_length(&buffer).await? as i32; + if length >= buffer_length { + Self::flush_buffer(jvm, &mut this).await?; + return jvm.invoke_virtual(&out, "write", "([BII)V", (bytes, offset, length)).await; + } + + let mut count: i32 = jvm.get_field(&this, "count", "I").await?; + if length > buffer_length - count { + Self::flush_buffer(jvm, &mut this).await?; + count = 0; + buffer = jvm.get_field(&this, "buf", "[B").await?; + } + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (bytes, offset, buffer, count, length), + ) + .await?; + jvm.put_field(&mut this, "count", "I", count + length).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedOutputStream::flush({this:?})"); + + Self::flush_buffer(jvm, &mut this).await?; + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + jvm.invoke_virtual(&out, "flush", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/io/buffered_reader.rs b/java_runtime/src/classes/java/io/buffered_reader.rs index 74d7fda3..478c1c0b 100644 --- a/java_runtime/src/classes/java/io/buffered_reader.rs +++ b/java_runtime/src/classes/java/io/buffered_reader.rs @@ -1,16 +1,24 @@ +use core::{cmp::min, future::Future}; + use alloc::{vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::{io::Reader, lang::String}, + classes::java::{ + io::Reader, + lang::{Object, String}, + }, }; -// class java.io.BufferedReader +const DEFAULT_BUFFER_SIZE: usize = 8192; +const INVALIDATED: i32 = -2; +const UNMARKED: i32 = -1; -const BUF_SIZE: usize = 1024; +// class java.io.BufferedReader pub struct BufferedReader; impl BufferedReader { @@ -20,94 +28,519 @@ impl BufferedReader { parent_class: Some("java/io/Reader"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "(Ljava/io/Reader;)V", Self::init, Default::default()), - JavaMethodProto::new("readLine", "()Ljava/lang/String;", Self::read_line, Default::default()), - JavaMethodProto::new("close", "()V", Self::close, Default::default()), + JavaMethodProto::new("", "(Ljava/io/Reader;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/Reader;I)V", Self::init_with_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "()I", Self::read_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "([CII)I", Self::read, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("readLine", "()Ljava/lang/String;", Self::read_line, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("skip", "(J)J", Self::skip, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("ready", "()Z", Self::ready, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("markSupported", "()Z", Self::mark_supported, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("mark", "(I)V", Self::mark, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), ], fields: vec![ - JavaFieldProto::new("in", "Ljava/io/Reader;", Default::default()), - JavaFieldProto::new("buf", "[C", Default::default()), - JavaFieldProto::new("bufSize", "I", Default::default()), + JavaFieldProto::new("in", "Ljava/io/Reader;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("cb", "[C", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("nChars", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("nextChar", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("markedChar", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("readAheadLimit", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("skipLF", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("markedSkipLF", "Z", FieldAccessFlags::PRIVATE), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, r#in: ClassInstanceRef) -> Result<()> { - tracing::debug!("java.io.BufferedReader::({this:?}, {:?})", &r#in); + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, r#in: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedReader::({this:?}, {in:?})", in = &r#in); - let _: () = jvm.invoke_special(&this, "java/io/Reader", "", "()V", ()).await?; + jvm.invoke_special( + &this, + "java/io/BufferedReader", + "", + "(Ljava/io/Reader;I)V", + (r#in, DEFAULT_BUFFER_SIZE as i32), + ) + .await + } - jvm.put_field(&mut this, "in", "Ljava/io/Reader;", r#in).await?; + async fn init_with_size( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + r#in: ClassInstanceRef, + size: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedReader::({this:?}, {in:?}, {size})", in = &r#in); + + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "reader is null").await); + } + if size <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "buffer size is not positive").await); + } - let buf = jvm.instantiate_array("C", BUF_SIZE).await?; - jvm.put_field(&mut this, "buf", "[C", buf).await?; - jvm.put_field(&mut this, "bufSize", "I", 0).await?; + let _: () = jvm + .invoke_special(&this, "java/io/Reader", "", "(Ljava/lang/Object;)V", (r#in.clone(),)) + .await?; + let cb = jvm.instantiate_array("C", size as usize).await?; + jvm.put_field(&mut this, "in", "Ljava/io/Reader;", r#in).await?; + jvm.put_field(&mut this, "cb", "[C", cb).await?; + jvm.put_field(&mut this, "nChars", "I", 0).await?; + jvm.put_field(&mut this, "nextChar", "I", 0).await?; + jvm.put_field(&mut this, "markedChar", "I", UNMARKED).await?; + jvm.put_field(&mut this, "readAheadLimit", "I", 0).await?; + jvm.put_field(&mut this, "skipLF", "Z", false).await?; + jvm.put_field(&mut this, "markedSkipLF", "Z", false).await?; Ok(()) } - async fn read_line(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { - tracing::debug!("java.io.BufferedReader::readLine({this:?})"); + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(lock).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(lock).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(lock).await { + tracing::error!(?exit_error, "failed to release BufferedReader lock"); + } + Err(error) + } + } + } - let buf: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + async fn fill(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result { + let r#in: ClassInstanceRef = jvm.get_field(this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + + let mut cb: ClassInstanceRef> = jvm.get_field(this, "cb", "[C").await?; + let mut destination = 0; + let marked_char: i32 = jvm.get_field(this, "markedChar", "I").await?; + if marked_char >= 0 { + let next_char: i32 = jvm.get_field(this, "nextChar", "I").await?; + let delta = next_char - marked_char; + let read_ahead_limit: i32 = jvm.get_field(this, "readAheadLimit", "I").await?; + if delta >= read_ahead_limit { + jvm.put_field(this, "markedChar", "I", INVALIDATED).await?; + jvm.put_field(this, "readAheadLimit", "I", 0).await?; + } else { + let capacity = jvm.array_length(&cb).await?; + if read_ahead_limit as usize > capacity { + let expanded_capacity = min(capacity.saturating_mul(2), read_ahead_limit as usize); + let expanded = jvm.instantiate_array("C", expanded_capacity).await?; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (cb, marked_char, expanded.clone(), 0, delta), + ) + .await?; + cb = expanded.into(); + jvm.put_field(this, "cb", "[C", cb.clone()).await?; + } else if delta > 0 { + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (cb.clone(), marked_char, cb.clone(), 0, delta), + ) + .await?; + } - // fill buf - let mut buf_size: i32 = jvm.get_field(&this, "bufSize", "I").await?; - let mut pos = None; - while pos.is_none() { - let r#in = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; - let read: i32 = jvm - .invoke_virtual(&r#in, "read", "([CII)I", (buf.clone(), buf_size, (BUF_SIZE as i32) - buf_size)) + jvm.put_field(this, "markedChar", "I", 0).await?; + destination = delta; + } + } + + jvm.put_field(this, "nextChar", "I", destination).await?; + jvm.put_field(this, "nChars", "I", destination).await?; + + let capacity = jvm.array_length(&cb).await? as i32; + let mut read; + loop { + read = jvm + .invoke_virtual(&r#in, "read", "([CII)I", (cb.clone(), destination, capacity - destination)) .await?; - if read == -1 { + if read != 0 { break; } + } + if read > 0 { + jvm.put_field(this, "nChars", "I", destination + read).await?; + jvm.put_field(this, "nextChar", "I", destination).await?; + } + + Ok(read) + } + + async fn read_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_char_locked(jvm, this)).await + } + + async fn read_char_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.BufferedReader::read({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + + loop { + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let mut n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + if next_char >= n_chars { + if Self::fill(jvm, &mut this).await? <= 0 { + return Ok(-1); + } + next_char = jvm.get_field(&this, "nextChar", "I").await?; + n_chars = jvm.get_field(&this, "nChars", "I").await?; + } + + if jvm.get_field::(&this, "skipLF", "Z").await? { + jvm.put_field(&mut this, "skipLF", "Z", false).await?; + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if jvm.load_array::(&cb, next_char as usize, 1).await?[0] == '\n' as JavaChar { + next_char += 1; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + if next_char >= n_chars { + continue; + } + } + } + + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + let value = jvm.load_array::(&cb, next_char as usize, 1).await?[0]; + jvm.put_field(&mut this, "nextChar", "I", next_char + 1).await?; + return Ok(value as i32); + } + } - buf_size += read; + async fn read( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_locked(jvm, this, target, offset, length)).await + } - jvm.put_field(&mut this, "bufSize", "I", buf_size).await?; + async fn read_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + tracing::debug!("java.io.BufferedReader::read({this:?}, {target:?}, {offset}, {length})"); - let char_buf: Vec = jvm.load_array(&buf, 0, buf_size as _).await?; - pos = char_buf.iter().position(|&c| c == b'\n' as _); + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + if target.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "target is null").await); } - // can't fill buffer - if buf_size == 0 { - return Ok(None.into()); + let target_length = jvm.array_length(&target).await? as i32; + if offset < 0 || length < 0 || offset > target_length || length > target_length - offset { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid offset or length").await); + } + if length == 0 { + return Ok(0); } - Ok(if let Some(x) = pos { - // found newline - let result = jvm.new_class("java/lang/String", "([CII)V", (buf.clone(), 0, x as i32)).await?; + let mut total = 0; + while total < length { + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let mut n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + if next_char >= n_chars { + if total > 0 && !jvm.invoke_virtual::<_, bool>(&r#in, "ready", "()Z", ()).await? { + break; + } + if Self::fill(jvm, &mut this).await? <= 0 { + break; + } + next_char = jvm.get_field(&this, "nextChar", "I").await?; + n_chars = jvm.get_field(&this, "nChars", "I").await?; + } + + if jvm.get_field::(&this, "skipLF", "Z").await? { + jvm.put_field(&mut this, "skipLF", "Z", false).await?; + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if jvm.load_array::(&cb, next_char as usize, 1).await?[0] == '\n' as JavaChar { + next_char += 1; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + if next_char >= n_chars { + continue; + } + } + } - // advance buffer + let count = min(length - total, n_chars - next_char); + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; let _: () = jvm .invoke_static( "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (buf.clone(), (x + 1) as i32, buf, 0, buf_size - x as i32), + (cb, next_char, target.clone(), offset + total, count), ) .await?; - jvm.put_field(&mut this, "bufSize", "I", buf_size - x as i32 - 1).await?; + total += count; + jvm.put_field(&mut this, "nextChar", "I", next_char + count).await?; + } + + if total == 0 { Ok(-1) } else { Ok(total) } + } + + async fn read_line(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_line_locked(jvm, this)).await + } + + async fn read_line_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result> { + tracing::debug!("java.io.BufferedReader::readLine({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + + let mut omit_lf: bool = jvm.get_field(&this, "skipLF", "Z").await?; + jvm.put_field(&mut this, "skipLF", "Z", false).await?; + let mut line = Vec::new(); + + loop { + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let mut n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + if next_char >= n_chars { + if Self::fill(jvm, &mut this).await? <= 0 { + if line.is_empty() { + return Ok(None.into()); + } + + let line_length = line.len(); + let mut chars = jvm.instantiate_array("C", line_length).await?; + jvm.store_array(&mut chars, 0, line).await?; + let value = jvm.new_class("java/lang/String", "([CII)V", (chars, 0, line_length as i32)).await?; + return Ok(value.into()); + } + next_char = jvm.get_field(&this, "nextChar", "I").await?; + n_chars = jvm.get_field(&this, "nChars", "I").await?; + } + + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if omit_lf { + omit_lf = false; + if jvm.load_array::(&cb, next_char as usize, 1).await?[0] == '\n' as JavaChar { + next_char += 1; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + if next_char >= n_chars { + continue; + } + } + } + + let buffered: Vec = jvm.load_array(&cb, next_char as usize, (n_chars - next_char) as usize).await?; + if let Some(index) = buffered.iter().position(|value| *value == '\n' as JavaChar || *value == '\r' as JavaChar) { + line.extend_from_slice(&buffered[..index]); + let terminator = buffered[index]; + jvm.put_field(&mut this, "nextChar", "I", next_char + index as i32 + 1).await?; + if terminator == '\r' as JavaChar { + jvm.put_field(&mut this, "skipLF", "Z", true).await?; + } + + let line_length = line.len(); + let mut chars = jvm.instantiate_array("C", line_length).await?; + jvm.store_array(&mut chars, 0, line).await?; + let value = jvm.new_class("java/lang/String", "([CII)V", (chars, 0, line_length as i32)).await?; + return Ok(value.into()); + } + + line.extend(buffered); + jvm.put_field(&mut this, "nextChar", "I", n_chars).await?; + } + } + + async fn skip(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, count: i64) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::skip_locked(jvm, this, count)).await + } + + async fn skip_locked(jvm: &Jvm, mut this: ClassInstanceRef, count: i64) -> Result { + tracing::debug!("java.io.BufferedReader::skip({this:?}, {count})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + if count < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "skip value is negative").await); + } + + let mut remaining = count; + while remaining > 0 { + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let mut n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + if next_char >= n_chars { + if Self::fill(jvm, &mut this).await? <= 0 { + break; + } + next_char = jvm.get_field(&this, "nextChar", "I").await?; + n_chars = jvm.get_field(&this, "nChars", "I").await?; + } + + if jvm.get_field::(&this, "skipLF", "Z").await? { + jvm.put_field(&mut this, "skipLF", "Z", false).await?; + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if jvm.load_array::(&cb, next_char as usize, 1).await?[0] == '\n' as JavaChar { + next_char += 1; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + if next_char >= n_chars { + continue; + } + } + } + + let skipped = min(remaining, (n_chars - next_char) as i64); + remaining -= skipped; + jvm.put_field(&mut this, "nextChar", "I", next_char + skipped as i32).await?; + } + + Ok(count - remaining) + } + + async fn ready(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::ready_locked(jvm, this)).await + } + + async fn ready_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.BufferedReader::ready({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let mut n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + if jvm.get_field::(&this, "skipLF", "Z").await? { + if next_char >= n_chars && jvm.invoke_virtual::<_, bool>(&r#in, "ready", "()Z", ()).await? { + let _ = Self::fill(jvm, &mut this).await?; + next_char = jvm.get_field(&this, "nextChar", "I").await?; + n_chars = jvm.get_field(&this, "nChars", "I").await?; + } - result.into() - } else { - // end of stream, and no newline - jvm.put_field(&mut this, "bufSize", "I", 0).await?; + if next_char < n_chars { + let cb: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if jvm.load_array::(&cb, next_char as usize, 1).await?[0] == '\n' as JavaChar { + next_char += 1; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + } + jvm.put_field(&mut this, "skipLF", "Z", false).await?; + } + } + + if next_char < n_chars { + return Ok(true); + } + jvm.invoke_virtual(&r#in, "ready", "()Z", ()).await + } + + async fn mark_supported(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.BufferedReader::markSupported({this:?})"); + Ok(true) + } + + async fn mark(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, read_ahead_limit: i32) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::mark_locked(jvm, this, read_ahead_limit)).await + } + + async fn mark_locked(jvm: &Jvm, mut this: ClassInstanceRef, read_ahead_limit: i32) -> Result<()> { + tracing::debug!("java.io.BufferedReader::mark({this:?}, {read_ahead_limit})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } + if read_ahead_limit < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "read-ahead limit is negative").await); + } + + let next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let skip_lf: bool = jvm.get_field(&this, "skipLF", "Z").await?; + jvm.put_field(&mut this, "readAheadLimit", "I", read_ahead_limit).await?; + jvm.put_field(&mut this, "markedChar", "I", next_char).await?; + jvm.put_field(&mut this, "markedSkipLF", "Z", skip_lf).await?; + Ok(()) + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::reset_locked(jvm, this)).await + } + + async fn reset_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedReader::reset({this:?})"); + + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/io/IOException", "stream is closed").await); + } - let result = jvm.new_class("java/lang/String", "([CII)V", (buf, 0, buf_size)).await?; - result.into() - }) + let marked_char: i32 = jvm.get_field(&this, "markedChar", "I").await?; + if marked_char < 0 { + let message = if marked_char == INVALIDATED { + "mark invalid" + } else { + "stream not marked" + }; + return Err(jvm.exception("java/io/IOException", message).await); + } + + let marked_skip_lf: bool = jvm.get_field(&this, "markedSkipLF", "Z").await?; + jvm.put_field(&mut this, "nextChar", "I", marked_char).await?; + jvm.put_field(&mut this, "skipLF", "Z", marked_skip_lf).await?; + Ok(()) } async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::close_locked(jvm, this)).await + } + + async fn close_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.BufferedReader::close({this:?})"); - let r#in = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; - let _: () = jvm.invoke_virtual(&r#in, "close", "()V", ()).await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "in", "Ljava/io/Reader;").await?; + if r#in.is_null() { + return Ok(()); + } - Ok(()) + let result = jvm.invoke_virtual(&r#in, "close", "()V", ()).await; + let closed: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "in", "Ljava/io/Reader;", closed).await?; + let released: ClassInstanceRef> = None.into(); + jvm.put_field(&mut this, "cb", "[C", released).await?; + result } } diff --git a/java_runtime/src/classes/java/io/buffered_writer.rs b/java_runtime/src/classes/java/io/buffered_writer.rs new file mode 100644 index 00000000..9f4a63b5 --- /dev/null +++ b/java_runtime/src/classes/java/io/buffered_writer.rs @@ -0,0 +1,327 @@ +use core::future::Future; + +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::Writer, + lang::{Object, String}, + }, +}; + +const DEFAULT_CHAR_BUFFER_SIZE: i32 = 8192; + +// class java.io.BufferedWriter +pub struct BufferedWriter; + +impl BufferedWriter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/BufferedWriter", + parent_class: Some("java/io/Writer"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/Writer;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/Writer;I)V", Self::init_with_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([CII)V", Self::write_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(Ljava/lang/String;II)V", Self::write_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("newLine", "()V", Self::new_line, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("out", "Ljava/io/Writer;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("cb", "[C", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("nChars", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("nextChar", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("lineSeparator", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::({this:?}, {out:?})"); + jvm.invoke_special( + &this, + "java/io/BufferedWriter", + "", + "(Ljava/io/Writer;I)V", + (out, DEFAULT_CHAR_BUFFER_SIZE), + ) + .await + } + + async fn init_with_size( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + out: ClassInstanceRef, + size: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::({this:?}, {out:?}, {size})"); + + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "writer is null").await); + } + if size <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Buffer size <= 0").await); + } + + let _: () = jvm + .invoke_special(&this, "java/io/Writer", "", "(Ljava/lang/Object;)V", (out.clone(),)) + .await?; + let buffer = jvm.instantiate_array("C", size as usize).await?; + let key = JavaLangString::from_rust_string(jvm, "line.separator").await?; + let mut line_separator: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if line_separator.is_null() { + line_separator = JavaLangString::from_rust_string(jvm, "\n").await?.into(); + } + + jvm.put_field(&mut this, "out", "Ljava/io/Writer;", out).await?; + jvm.put_field(&mut this, "cb", "[C", buffer).await?; + jvm.put_field(&mut this, "nChars", "I", size).await?; + jvm.put_field(&mut this, "nextChar", "I", 0).await?; + jvm.put_field(&mut this, "lineSeparator", "Ljava/lang/String;", line_separator).await + } + + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(lock).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(lock).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(lock).await { + tracing::error!(?exit_error, "failed to release BufferedWriter lock"); + } + Err(error) + } + } + } + + async fn flush_buffer(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { + let out: ClassInstanceRef = jvm.get_field(this, "out", "Ljava/io/Writer;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(this, "cb", "[C").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let next_char: i32 = jvm.get_field(this, "nextChar", "I").await?; + if next_char > 0 { + let _: () = jvm.invoke_virtual(&out, "write", "([CII)V", (buffer, 0, next_char)).await?; + jvm.put_field(this, "nextChar", "I", 0).await?; + } + Ok(()) + } + + async fn write_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_char_locked(jvm, this, value)).await + } + + async fn write_char_locked(jvm: &Jvm, mut this: ClassInstanceRef, value: i32) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::write({this:?}, {value})"); + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + let mut buffer: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + if next_char >= n_chars { + Self::flush_buffer(jvm, &mut this).await?; + next_char = 0; + buffer = jvm.get_field(&this, "cb", "[C").await?; + } + jvm.store_array(&mut buffer, next_char as usize, [value as JavaChar]).await?; + jvm.put_field(&mut this, "nextChar", "I", next_char + 1).await + } + + async fn write_chars( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + chars: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_chars_locked(jvm, this, chars, offset, length)).await + } + + async fn write_chars_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + chars: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::write({this:?}, {chars:?}, {offset}, {length})"); + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "character array is null").await); + } + let source_length = jvm.array_length(&chars).await? as i32; + if offset < 0 || length < 0 || offset > source_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + if length == 0 { + return Ok(()); + } + let n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + if length >= n_chars { + Self::flush_buffer(jvm, &mut this).await?; + return jvm.invoke_virtual(&out, "write", "([CII)V", (chars, offset, length)).await; + } + + let mut source_position = offset; + let end = offset + length; + while source_position < end { + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let copied = (n_chars - next_char).min(end - source_position); + let buffer: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (chars.clone(), source_position, buffer, next_char, copied), + ) + .await?; + source_position += copied; + next_char += copied; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + if next_char >= n_chars { + Self::flush_buffer(jvm, &mut this).await?; + } + } + Ok(()) + } + + async fn write_string( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + value: ClassInstanceRef, + offset: i32, + length: i32, + ) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_string_locked(jvm, this, value, offset, length)).await + } + + async fn write_string_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::write({this:?}, {value:?}, {offset}, {length})"); + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + if out.is_null() || buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "string is null").await); + } + let string_length: i32 = jvm.invoke_virtual(&value, "length", "()I", ()).await?; + if offset < 0 || length < 0 || offset > string_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + let n_chars: i32 = jvm.get_field(&this, "nChars", "I").await?; + let mut source_position = offset; + let end = offset + length; + while source_position < end { + let mut next_char: i32 = jvm.get_field(&this, "nextChar", "I").await?; + let copied = (n_chars - next_char).min(end - source_position); + let buffer: ClassInstanceRef> = jvm.get_field(&this, "cb", "[C").await?; + let _: () = jvm + .invoke_virtual( + &value, + "getChars", + "(II[CI)V", + (source_position, source_position + copied, buffer, next_char), + ) + .await?; + source_position += copied; + next_char += copied; + jvm.put_field(&mut this, "nextChar", "I", next_char).await?; + if next_char >= n_chars { + Self::flush_buffer(jvm, &mut this).await?; + } + } + Ok(()) + } + + async fn new_line(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::new_line_locked(jvm, this)).await + } + + async fn new_line_locked(jvm: &Jvm, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::newLine({this:?})"); + + let line_separator: ClassInstanceRef = jvm.get_field(&this, "lineSeparator", "Ljava/lang/String;").await?; + let length: i32 = jvm.invoke_virtual(&line_separator, "length", "()I", ()).await?; + Self::write_string_locked(jvm, this, line_separator, 0, length).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::flush_locked(jvm, this)).await + } + + async fn flush_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::flush({this:?})"); + + Self::flush_buffer(jvm, &mut this).await?; + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + jvm.invoke_virtual(&out, "flush", "()V", ()).await + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::close_locked(jvm, this)).await + } + + async fn close_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.BufferedWriter::close({this:?})"); + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + return Ok(()); + } + let flush_result = Self::flush_buffer(jvm, &mut this).await; + let close_result: Result<()> = jvm.invoke_virtual(&out, "close", "()V", ()).await; + let null_writer: ClassInstanceRef = None.into(); + let null_buffer: ClassInstanceRef> = None.into(); + let clear_out_result = jvm.put_field(&mut this, "out", "Ljava/io/Writer;", null_writer).await; + let clear_buffer_result = jvm.put_field(&mut this, "cb", "[C", null_buffer).await; + + flush_result?; + close_result?; + clear_out_result?; + clear_buffer_result + } +} diff --git a/java_runtime/src/classes/java/io/byte_array_output_stream.rs b/java_runtime/src/classes/java/io/byte_array_output_stream.rs index 591d1147..a8aaed45 100644 --- a/java_runtime/src/classes/java/io/byte_array_output_stream.rs +++ b/java_runtime/src/classes/java/io/byte_array_output_stream.rs @@ -1,9 +1,13 @@ use alloc::{vec, vec::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::String}; +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{io::OutputStream, lang::String}, +}; // class java.io.ByteArrayOutputStream pub struct ByteArrayOutputStream; @@ -15,21 +19,48 @@ impl ByteArrayOutputStream { parent_class: Some("java/io/OutputStream"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(I)V", Self::init_with_size, Default::default()), - JavaMethodProto::new("write", "(I)V", Self::write, Default::default()), - JavaMethodProto::new("write", "([BII)V", Self::write_bytes, Default::default()), - JavaMethodProto::new("toByteArray", "()[B", Self::to_byte_array, Default::default()), - JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), - JavaMethodProto::new("size", "()I", Self::size, Default::default()), - JavaMethodProto::new("reset", "()V", Self::reset, Default::default()), - JavaMethodProto::new("close", "()V", Self::close, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new( + "write", + "([BII)V", + Self::write_bytes, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "writeTo", + "(Ljava/io/OutputStream;)V", + Self::write_to, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "toByteArray", + "()[B", + Self::to_byte_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "toString", + "()Ljava/lang/String;", + Self::to_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "toString", + "(Ljava/lang/String;)Ljava/lang/String;", + Self::to_string_with_encoding, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), ], fields: vec![ - JavaFieldProto::new("buf", "[B", Default::default()), - JavaFieldProto::new("pos", "I", Default::default()), + JavaFieldProto::new("buf", "[B", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("count", "I", FieldAccessFlags::PROTECTED), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } @@ -37,7 +68,7 @@ impl ByteArrayOutputStream { tracing::debug!("java.io.ByteArrayOutputStream::({this:?})"); let _: () = jvm - .invoke_special(&this, "java/io/ByteArrayOutputStream", "", "(I)V", (1024,)) + .invoke_special(&this, "java/io/ByteArrayOutputStream", "", "(I)V", (32,)) .await?; Ok(()) @@ -55,7 +86,7 @@ impl ByteArrayOutputStream { let array = jvm.instantiate_array("B", size as usize).await?; jvm.put_field(&mut this, "buf", "[B", array).await?; - jvm.put_field(&mut this, "pos", "I", 0).await?; + jvm.put_field(&mut this, "count", "I", 0).await?; Ok(()) } @@ -75,41 +106,53 @@ impl ByteArrayOutputStream { return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); } - let pos: i32 = jvm.get_field(&this, "pos", "I").await?; - Self::ensure_capacity(jvm, &mut this, (pos + len) as usize).await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Self::ensure_capacity(jvm, &mut this, (count + len) as usize).await?; let mut buf = jvm.get_field(&this, "buf", "[B").await?; let values: Vec = jvm.load_array(&bytes, off as usize, len as usize).await?; - jvm.store_array(&mut buf, pos as usize, values).await?; - jvm.put_field(&mut this, "pos", "I", pos + len).await + jvm.store_array(&mut buf, count as usize, values).await?; + jvm.put_field(&mut this, "count", "I", count + len).await } async fn write(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, b: i32) -> Result<()> { tracing::debug!("java.io.ByteArrayOutputStream::write({this:?}, {b:?})"); - let pos: i32 = jvm.get_field(&this, "pos", "I").await?; - Self::ensure_capacity(jvm, &mut this, (pos + 1) as _).await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Self::ensure_capacity(jvm, &mut this, (count + 1) as _).await?; let mut buf = jvm.get_field(&this, "buf", "[B").await?; - jvm.store_array(&mut buf, pos as _, vec![b as i8]).await?; + jvm.store_array(&mut buf, count as _, vec![b as i8]).await?; - jvm.put_field(&mut this, "pos", "I", pos + 1).await?; + jvm.put_field(&mut this, "count", "I", count + 1).await?; Ok(()) } + async fn write_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.ByteArrayOutputStream::writeTo({this:?}, {out:?})"); + + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); + } + + let buf: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + jvm.invoke_virtual(&out, "write", "([BII)V", (buf, 0, count)).await + } + async fn to_byte_array(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { tracing::debug!("java.io.ByteArrayOutputStream::to_byte_array({this:?})"); let buf: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; - let pos: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; - let dest = jvm.instantiate_array("B", pos as _).await?; + let dest = jvm.instantiate_array("B", count as _).await?; let _: () = jvm .invoke_static( "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (buf.clone(), 0, dest.clone(), 0, pos), + (buf.clone(), 0, dest.clone(), 0, count), ) .await?; @@ -119,31 +162,51 @@ impl ByteArrayOutputStream { async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.io.ByteArrayOutputStream::size({this:?})"); - let pos: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; - Ok(pos) + Ok(count) } async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.io.ByteArrayOutputStream::toString({this:?})"); let buf: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; - let pos: i32 = jvm.get_field(&this, "pos", "I").await?; - let bytes = jvm.instantiate_array("B", pos as usize).await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let bytes = jvm.instantiate_array("B", count as usize).await?; let _: () = jvm .invoke_static( "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (buf, 0, bytes.clone(), 0, pos), + (buf, 0, bytes.clone(), 0, count), ) .await?; Ok(jvm.new_class("java/lang/String", "([B)V", (bytes,)).await?.into()) } + async fn to_string_with_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.io.ByteArrayOutputStream::toString({this:?}, {encoding:?})"); + + if encoding.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "encoding is null").await); + } + + let buf: ClassInstanceRef> = jvm.get_field(&this, "buf", "[B").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Ok(jvm + .new_class("java/lang/String", "([BIILjava/lang/String;)V", (buf, 0, count, encoding)) + .await? + .into()) + } + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.ByteArrayOutputStream::reset({this:?})"); - jvm.put_field(&mut this, "pos", "I", 0).await?; + jvm.put_field(&mut this, "count", "I", 0).await?; Ok(()) } @@ -159,15 +222,16 @@ impl ByteArrayOutputStream { let current_capacity = jvm.array_length(&old_buf).await?; if current_capacity < capacity { - let new_capacity = capacity * 2; + let new_capacity = current_capacity.saturating_mul(2).max(capacity); let new_buf = jvm.instantiate_array("B", new_capacity).await?; + let count: i32 = jvm.get_field(this, "count", "I").await?; let _: () = jvm .invoke_static( "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (old_buf.clone(), 0, new_buf.clone(), 0, current_capacity as i32), + (old_buf.clone(), 0, new_buf.clone(), 0, count), ) .await?; diff --git a/java_runtime/src/classes/java/io/char_array_reader.rs b/java_runtime/src/classes/java/io/char_array_reader.rs new file mode 100644 index 00000000..fe9d83f2 --- /dev/null +++ b/java_runtime/src/classes/java/io/char_array_reader.rs @@ -0,0 +1,260 @@ +use core::future::Future; + +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.io.CharArrayReader +pub struct CharArrayReader; + +impl CharArrayReader { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/CharArrayReader", + parent_class: Some("java/io/Reader"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "([C)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "([CII)V", Self::init_with_range, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "()I", Self::read_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "([CII)I", Self::read, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("skip", "(J)J", Self::skip, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("ready", "()Z", Self::ready, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("markSupported", "()Z", Self::mark_supported, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("mark", "(I)V", Self::mark, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("buf", "[C", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("pos", "I", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("markedPos", "I", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("count", "I", FieldAccessFlags::PROTECTED), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, buffer: ClassInstanceRef>) -> Result<()> { + tracing::debug!("java.io.CharArrayReader::({this:?}, {buffer:?})"); + + if buffer.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "buffer is null").await); + } + let length = jvm.array_length(&buffer).await? as i32; + jvm.invoke_special(&this, "java/io/CharArrayReader", "", "([CII)V", (buffer, 0, length)) + .await + } + + async fn init_with_range( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + buffer: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.CharArrayReader::({this:?}, {buffer:?}, {offset}, {length})"); + + if buffer.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "buffer is null").await); + } + let buffer_length = jvm.array_length(&buffer).await? as i32; + let end = offset as i64 + length as i64; + if offset < 0 || offset > buffer_length || length < 0 || end < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Invalid offset or length").await); + } + + let _: () = jvm.invoke_special(&this, "java/io/Reader", "", "()V", ()).await?; + jvm.put_field(&mut this, "buf", "[C", buffer).await?; + jvm.put_field(&mut this, "pos", "I", offset).await?; + jvm.put_field(&mut this, "markedPos", "I", offset).await?; + jvm.put_field(&mut this, "count", "I", end.min(buffer_length as i64) as i32).await + } + + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(lock).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(lock).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(lock).await { + tracing::error!(?exit_error, "failed to release CharArrayReader lock"); + } + Err(error) + } + } + } + + async fn read_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_char_locked(jvm, this)).await + } + + async fn read_char_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.CharArrayReader::read({this:?})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + if buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if position >= count { + return Ok(-1); + } + let value = jvm.load_array::(&buffer, position as usize, 1).await?[0]; + jvm.put_field(&mut this, "pos", "I", position + 1).await?; + Ok(value as i32) + } + + async fn read( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_locked(jvm, this, target, offset, length)).await + } + + async fn read_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + tracing::debug!("java.io.CharArrayReader::read({this:?}, {target:?}, {offset}, {length})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + if buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if target.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "target is null").await); + } + let target_length = jvm.array_length(&target).await? as i32; + if offset < 0 || length < 0 || offset > target_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + if length == 0 { + return Ok(0); + } + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if position >= count { + return Ok(-1); + } + let copied = length.min(count - position); + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (buffer, position, target, offset, copied), + ) + .await?; + jvm.put_field(&mut this, "pos", "I", position + copied).await?; + Ok(copied) + } + + async fn skip(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, amount: i64) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::skip_locked(jvm, this, amount)).await + } + + async fn skip_locked(jvm: &Jvm, mut this: ClassInstanceRef, amount: i64) -> Result { + tracing::debug!("java.io.CharArrayReader::skip({this:?}, {amount})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + if buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if amount <= 0 { + return Ok(0); + } + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let skipped = amount.min((count - position) as i64); + jvm.put_field(&mut this, "pos", "I", position + skipped as i32).await?; + Ok(skipped) + } + + async fn ready(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::ready_locked(jvm, this)).await + } + + async fn ready_locked(jvm: &Jvm, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.CharArrayReader::ready({this:?})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + if buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Ok(count - position > 0) + } + + async fn mark_supported(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.CharArrayReader::markSupported({this:?})"); + Ok(true) + } + + async fn mark(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, read_ahead_limit: i32) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::mark_locked(jvm, this, read_ahead_limit)).await + } + + async fn mark_locked(jvm: &Jvm, mut this: ClassInstanceRef, read_ahead_limit: i32) -> Result<()> { + tracing::debug!("java.io.CharArrayReader::mark({this:?}, {read_ahead_limit})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + if buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let position: i32 = jvm.get_field(&this, "pos", "I").await?; + jvm.put_field(&mut this, "markedPos", "I", position).await + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::reset_locked(jvm, this)).await + } + + async fn reset_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayReader::reset({this:?})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + if buffer.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let marked_position: i32 = jvm.get_field(&this, "markedPos", "I").await?; + jvm.put_field(&mut this, "pos", "I", marked_position).await + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::close_locked(jvm, this)).await + } + + async fn close_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayReader::close({this:?})"); + + let null_buffer: ClassInstanceRef> = None.into(); + jvm.put_field(&mut this, "buf", "[C", null_buffer).await + } +} diff --git a/java_runtime/src/classes/java/io/char_array_writer.rs b/java_runtime/src/classes/java/io/char_array_writer.rs new file mode 100644 index 00000000..92a12c58 --- /dev/null +++ b/java_runtime/src/classes/java/io/char_array_writer.rs @@ -0,0 +1,281 @@ +use core::future::Future; + +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::Writer, + lang::{Object, String}, + }, +}; + +const DEFAULT_INITIAL_BUFFER_SIZE: i32 = 32; + +// class java.io.CharArrayWriter +pub struct CharArrayWriter; + +impl CharArrayWriter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/CharArrayWriter", + parent_class: Some("java/io/Writer"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([CII)V", Self::write_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(Ljava/lang/String;II)V", Self::write_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("writeTo", "(Ljava/io/Writer;)V", Self::write_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toCharArray", "()[C", Self::to_char_array, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("buf", "[C", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("count", "I", FieldAccessFlags::PROTECTED), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::({this:?})"); + jvm.invoke_special(&this, "java/io/CharArrayWriter", "", "(I)V", (DEFAULT_INITIAL_BUFFER_SIZE,)) + .await + } + + async fn init_with_size(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, size: i32) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::({this:?}, {size})"); + + if size < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Negative initial size").await); + } + let _: () = jvm.invoke_special(&this, "java/io/Writer", "", "()V", ()).await?; + let buffer = jvm.instantiate_array("C", size as usize).await?; + jvm.put_field(&mut this, "buf", "[C", buffer).await?; + jvm.put_field(&mut this, "count", "I", 0).await + } + + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(lock).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(lock).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(lock).await { + tracing::error!(?exit_error, "failed to release CharArrayWriter lock"); + } + Err(error) + } + } + } + + async fn ensure_capacity(jvm: &Jvm, this: &mut ClassInstanceRef, minimum: i32) -> Result<()> { + let buffer: ClassInstanceRef> = jvm.get_field(this, "buf", "[C").await?; + let current = jvm.array_length(&buffer).await? as i32; + if minimum > current { + let new_length = current.saturating_mul(2).max(minimum).max(1); + let new_buffer = jvm.instantiate_array("C", new_length as usize).await?; + let count: i32 = jvm.get_field(this, "count", "I").await?; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (buffer, 0, new_buffer.clone(), 0, count), + ) + .await?; + jvm.put_field(this, "buf", "[C", new_buffer).await?; + } + Ok(()) + } + + async fn write_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_char_locked(jvm, this, value)).await + } + + async fn write_char_locked(jvm: &Jvm, mut this: ClassInstanceRef, value: i32) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::write({this:?}, {value})"); + + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Self::ensure_capacity(jvm, &mut this, count + 1).await?; + let mut buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + jvm.store_array(&mut buffer, count as usize, [value as JavaChar]).await?; + jvm.put_field(&mut this, "count", "I", count + 1).await + } + + async fn write_chars( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + chars: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_chars_locked(jvm, this, chars, offset, length)).await + } + + async fn write_chars_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + chars: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::write({this:?}, {chars:?}, {offset}, {length})"); + + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "character array is null").await); + } + let source_length = jvm.array_length(&chars).await? as i32; + if offset < 0 || length < 0 || offset > source_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Self::ensure_capacity(jvm, &mut this, count + length).await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (chars, offset, buffer, count, length), + ) + .await?; + jvm.put_field(&mut this, "count", "I", count + length).await + } + + async fn write_string( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + value: ClassInstanceRef, + offset: i32, + length: i32, + ) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_string_locked(jvm, this, value, offset, length)).await + } + + async fn write_string_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::write({this:?}, {value:?}, {offset}, {length})"); + + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "string is null").await); + } + let source_length: i32 = jvm.invoke_virtual(&value, "length", "()I", ()).await?; + if offset < 0 || length < 0 || offset > source_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Self::ensure_capacity(jvm, &mut this, count + length).await?; + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + let _: () = jvm + .invoke_virtual(&value, "getChars", "(II[CI)V", (offset, offset + length, buffer, count)) + .await?; + jvm.put_field(&mut this, "count", "I", count + length).await + } + + async fn write_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::write_to_locked(jvm, this, out)).await + } + + async fn write_to_locked(jvm: &Jvm, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::writeTo({this:?}, {out:?})"); + + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "writer is null").await); + } + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + jvm.invoke_virtual(&out, "write", "([CII)V", (buffer, 0, count)).await + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::reset_locked(jvm, this)).await + } + + async fn reset_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::reset({this:?})"); + jvm.put_field(&mut this, "count", "I", 0).await + } + + async fn to_char_array(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::to_char_array_locked(jvm, this)).await + } + + async fn to_char_array_locked(jvm: &Jvm, this: ClassInstanceRef) -> Result>> { + tracing::debug!("java.io.CharArrayWriter::toCharArray({this:?})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let copy = jvm.instantiate_array("C", count as usize).await?; + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (buffer, 0, copy.clone(), 0, count), + ) + .await?; + Ok(copy.into()) + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::size_locked(jvm, this)).await + } + + async fn size_locked(jvm: &Jvm, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.CharArrayWriter::size({this:?})"); + jvm.get_field(&this, "count", "I").await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::to_string_locked(jvm, this)).await + } + + async fn to_string_locked(jvm: &Jvm, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.io.CharArrayWriter::toString({this:?})"); + + let buffer: ClassInstanceRef> = jvm.get_field(&this, "buf", "[C").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Ok(jvm.new_class("java/lang/String", "([CII)V", (buffer, 0, count)).await?.into()) + } + + async fn flush(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::flush({this:?})"); + Ok(()) + } + + async fn close(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.CharArrayWriter::close({this:?})"); + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/io/file_input_stream.rs b/java_runtime/src/classes/java/io/file_input_stream.rs index 8b923f07..627e1fd1 100644 --- a/java_runtime/src/classes/java/io/file_input_stream.rs +++ b/java_runtime/src/classes/java/io/file_input_stream.rs @@ -6,7 +6,7 @@ use java_class_proto::{JavaFieldProto, JavaMethodProto}; use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ - RuntimeClassProto, RuntimeContext, + FileOpenOptions, RuntimeClassProto, RuntimeContext, classes::java::io::{File, FileDescriptor}, }; @@ -43,7 +43,15 @@ impl FileInputStream { let path = jvm.invoke_virtual(&file, "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; - let fd = context.open(&path, false).await; + let fd = context + .open( + &path, + FileOpenOptions { + read: true, + ..Default::default() + }, + ) + .await; if fd.is_err() { return Err(jvm.exception("java/io/FileNotFoundException", "File not found").await); } diff --git a/java_runtime/src/classes/java/io/file_output_stream.rs b/java_runtime/src/classes/java/io/file_output_stream.rs index 02b5fcc3..255c355a 100644 --- a/java_runtime/src/classes/java/io/file_output_stream.rs +++ b/java_runtime/src/classes/java/io/file_output_stream.rs @@ -3,10 +3,11 @@ use alloc::vec; use bytemuck::cast_slice; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ - RuntimeClassProto, RuntimeContext, + FileOpenOptions, RuntimeClassProto, RuntimeContext, classes::java::io::{File, FileDescriptor}, }; @@ -21,6 +22,7 @@ impl FileOutputStream { interfaces: vec![], methods: vec![ JavaMethodProto::new("", "(Ljava/io/File;)V", Self::init, Default::default()), + JavaMethodProto::new("", "(Ljava/io/File;Z)V", Self::init_with_append, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "", "(Ljava/io/FileDescriptor;)V", @@ -31,42 +33,66 @@ impl FileOutputStream { JavaMethodProto::new("write", "(I)V", Self::write, Default::default()), JavaMethodProto::new("close", "()V", Self::close, Default::default()), ], - fields: vec![JavaFieldProto::new("fd", "Ljava/io/FileDescriptor;", Default::default())], + fields: vec![ + JavaFieldProto::new("fd", "Ljava/io/FileDescriptor;", Default::default()), + JavaFieldProto::new("append", "Z", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], access_flags: Default::default(), } } - async fn init(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.FileOutputStream::({this:?}, {file:?})"); + jvm.invoke_special(&this, "java/io/FileOutputStream", "", "(Ljava/io/File;Z)V", (file, false)) + .await + } + + async fn init_with_append( + jvm: &Jvm, + context: &mut RuntimeContext, + mut this: ClassInstanceRef, + file: ClassInstanceRef, + append: bool, + ) -> Result<()> { + tracing::debug!("java.io.FileOutputStream::({this:?}, {file:?}, {append})"); let path = jvm.invoke_virtual(&file, "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; - let fd = context.open(&path, true).await; + let fd = context + .open( + &path, + FileOpenOptions { + write: true, + append, + truncate: !append, + create: true, + ..Default::default() + }, + ) + .await; if fd.is_err() { return Err(jvm.exception("java/io/FileNotFoundException", "File not found").await); } let fd = FileDescriptor::from_fd(jvm, fd.unwrap()).await?; - - let _: () = jvm - .invoke_special(&this, "java/io/FileOutputStream", "", "(Ljava/io/FileDescriptor;)V", (fd,)) - .await?; - - Ok(()) + let _: () = jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await?; + jvm.put_field(&mut this, "fd", "Ljava/io/FileDescriptor;", fd).await?; + jvm.put_field(&mut this, "append", "Z", append).await } async fn init_with_file_descriptor( jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, - file_descriptor: ClassInstanceRef, + file_descriptor: ClassInstanceRef, ) -> Result<()> { tracing::debug!("java.io.FileOutputStream::({this:?}, {file_descriptor:?})"); let _: () = jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await?; jvm.put_field(&mut this, "fd", "Ljava/io/FileDescriptor;", file_descriptor).await?; + jvm.put_field(&mut this, "append", "Z", false).await?; Ok(()) } @@ -87,8 +113,14 @@ impl FileOutputStream { let mut buf = vec![0; length as _]; jvm.array_raw_buffer(&buffer).await?.read(offset as _, &mut buf)?; - if file.write(cast_slice(&buf)).await.is_err() { - return Err(jvm.exception("java/io/IOException", "I/O error").await); + let bytes = cast_slice(&buf); + let mut written = 0; + while written < bytes.len() { + match file.write(&bytes[written..]).await { + Ok(0) | Err(_) => return Err(jvm.exception("java/io/IOException", "I/O error").await), + Ok(length) if length > bytes.len() - written => return Err(jvm.exception("java/io/IOException", "I/O error").await), + Ok(length) => written += length, + } } Ok(()) @@ -100,7 +132,7 @@ impl FileOutputStream { let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?; let mut file = FileDescriptor::file(jvm, context, fd).await?; - if file.write(&[byte as u8]).await.is_err() { + if !matches!(file.write(&[byte as u8]).await, Ok(1)) { return Err(jvm.exception("java/io/IOException", "I/O error").await); } diff --git a/java_runtime/src/classes/java/io/file_reader.rs b/java_runtime/src/classes/java/io/file_reader.rs new file mode 100644 index 00000000..7b7eb504 --- /dev/null +++ b/java_runtime/src/classes/java/io/file_reader.rs @@ -0,0 +1,78 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{File, FileDescriptor}, + lang::String, + }, +}; + +// class java.io.FileReader +pub struct FileReader; + +impl FileReader { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/FileReader", + parent_class: Some("java/io/InputStreamReader"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_path, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/File;)V", Self::init_with_file, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/FileDescriptor;)V", + Self::init_with_file_descriptor, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init_with_path(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, path: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.FileReader::({this:?}, {path:?})"); + + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "path is null").await); + } + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?; + jvm.invoke_special(&this, "java/io/FileReader", "", "(Ljava/io/File;)V", (file,)) + .await + } + + async fn init_with_file(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.FileReader::({this:?}, {file:?})"); + + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + let input = jvm.new_class("java/io/FileInputStream", "(Ljava/io/File;)V", (file,)).await?; + jvm.invoke_special(&this, "java/io/InputStreamReader", "", "(Ljava/io/InputStream;)V", (input,)) + .await + } + + async fn init_with_file_descriptor( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + descriptor: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.io.FileReader::({this:?}, {descriptor:?})"); + + if descriptor.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file descriptor is null").await); + } + let input = jvm + .new_class("java/io/FileInputStream", "(Ljava/io/FileDescriptor;)V", (descriptor,)) + .await?; + jvm.invoke_special(&this, "java/io/InputStreamReader", "", "(Ljava/io/InputStream;)V", (input,)) + .await + } +} diff --git a/java_runtime/src/classes/java/io/file_writer.rs b/java_runtime/src/classes/java/io/file_writer.rs new file mode 100644 index 00000000..4e77e4a2 --- /dev/null +++ b/java_runtime/src/classes/java/io/file_writer.rs @@ -0,0 +1,105 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{File, FileDescriptor}, + lang::String, + }, +}; + +// class java.io.FileWriter +pub struct FileWriter; + +impl FileWriter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/FileWriter", + parent_class: Some("java/io/OutputStreamWriter"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_path, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;Z)V", Self::init_with_path_append, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/File;)V", Self::init_with_file, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/File;Z)V", Self::init_with_file_append, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/FileDescriptor;)V", + Self::init_with_file_descriptor, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init_with_path(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, path: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.FileWriter::({this:?}, {path:?})"); + jvm.invoke_special(&this, "java/io/FileWriter", "", "(Ljava/lang/String;Z)V", (path, false)) + .await + } + + async fn init_with_path_append( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + path: ClassInstanceRef, + append: bool, + ) -> Result<()> { + tracing::debug!("java.io.FileWriter::({this:?}, {path:?}, {append})"); + + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "path is null").await); + } + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?; + jvm.invoke_special(&this, "java/io/FileWriter", "", "(Ljava/io/File;Z)V", (file, append)) + .await + } + + async fn init_with_file(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.FileWriter::({this:?}, {file:?})"); + jvm.invoke_special(&this, "java/io/FileWriter", "", "(Ljava/io/File;Z)V", (file, false)) + .await + } + + async fn init_with_file_append( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + file: ClassInstanceRef, + append: bool, + ) -> Result<()> { + tracing::debug!("java.io.FileWriter::({this:?}, {file:?}, {append})"); + + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + let output = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;Z)V", (file, append)).await?; + + jvm.invoke_special(&this, "java/io/OutputStreamWriter", "", "(Ljava/io/OutputStream;)V", (output,)) + .await + } + + async fn init_with_file_descriptor( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + descriptor: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.io.FileWriter::({this:?}, {descriptor:?})"); + + if descriptor.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file descriptor is null").await); + } + let output = jvm + .new_class("java/io/FileOutputStream", "(Ljava/io/FileDescriptor;)V", (descriptor,)) + .await?; + jvm.invoke_special(&this, "java/io/OutputStreamWriter", "", "(Ljava/io/OutputStream;)V", (output,)) + .await + } +} diff --git a/java_runtime/src/classes/java/io/filter_output_stream.rs b/java_runtime/src/classes/java/io/filter_output_stream.rs index 7931204a..00b5c2c9 100644 --- a/java_runtime/src/classes/java/io/filter_output_stream.rs +++ b/java_runtime/src/classes/java/io/filter_output_stream.rs @@ -1,8 +1,8 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::FieldAccessFlags; -use jvm::{Array, ClassInstanceRef, Jvm, Result}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::io::OutputStream}; @@ -20,7 +20,7 @@ impl FilterOutputStream { JavaMethodProto::new("write", "([BII)V", Self::write_bytes_offset, Default::default()), JavaMethodProto::new("write", "(I)V", Self::write, Default::default()), JavaMethodProto::new("flush", "()V", Self::flush, Default::default()), - JavaMethodProto::new("close", "()V", Self::close, Default::default()), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), ], fields: vec![JavaFieldProto::new("out", "Ljava/io/OutputStream;", FieldAccessFlags::PROTECTED)], access_flags: Default::default(), @@ -68,9 +68,27 @@ impl FilterOutputStream { jvm.invoke_virtual(&out, "flush", "()V", ()).await } - async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.FilterOutputStream::close({this:?})"); - let out = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; - jvm.invoke_virtual(&out, "close", "()V", ()).await + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Ok(()); + } + + match jvm.invoke_virtual::<_, ()>(&this, "flush", "()V", ()).await { + Ok(()) => {} + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => {} + Err(error) => { + let null_output: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "out", "Ljava/io/OutputStream;", null_output).await?; + return Err(error); + } + } + + let close_result: Result<()> = jvm.invoke_virtual(&out, "close", "()V", ()).await; + let null_output: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "out", "Ljava/io/OutputStream;", null_output).await?; + close_result } } diff --git a/java_runtime/src/classes/java/io/print_stream.rs b/java_runtime/src/classes/java/io/print_stream.rs index ce3d9a68..c4ea09d2 100644 --- a/java_runtime/src/classes/java/io/print_stream.rs +++ b/java_runtime/src/classes/java/io/print_stream.rs @@ -1,19 +1,17 @@ -use alloc::{ - format, - string::{String as RustString, ToString}, - vec, - vec::Vec, -}; -use core::char; +use core::future::Future; + +use alloc::{vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, classes::java::{ - io::OutputStream, + io::{OutputStream, OutputStreamWriter}, lang::{Object, String}, + util::Properties, }, }; @@ -27,228 +25,484 @@ impl PrintStream { parent_class: Some("java/io/FilterOutputStream"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "(Ljava/io/OutputStream;)V", Self::init, Default::default()), - JavaMethodProto::new("checkError", "()Z", Self::check_error, Default::default()), - JavaMethodProto::new("close", "()V", Self::close, Default::default()), - JavaMethodProto::new("flush", "()V", Self::flush, Default::default()), - JavaMethodProto::new("write", "(I)V", Self::write_byte, Default::default()), - JavaMethodProto::new("write", "([BII)V", Self::write_bytes, Default::default()), - JavaMethodProto::new("print", "(Ljava/lang/Object;)V", Self::print_object, Default::default()), - JavaMethodProto::new("print", "(Ljava/lang/String;)V", Self::print_string, Default::default()), - JavaMethodProto::new("print", "(I)V", Self::print_int, Default::default()), - JavaMethodProto::new("print", "(J)V", Self::print_long, Default::default()), - JavaMethodProto::new("print", "(C)V", Self::print_char, Default::default()), - JavaMethodProto::new("print", "([C)V", Self::print_chars, Default::default()), - JavaMethodProto::new("print", "(Z)V", Self::print_bool, Default::default()), - JavaMethodProto::new("print", "(F)V", Self::print_float, Default::default()), - JavaMethodProto::new("print", "(D)V", Self::print_double, Default::default()), - JavaMethodProto::new("println", "()V", Self::println, Default::default()), - JavaMethodProto::new("println", "(Ljava/lang/Object;)V", Self::println_object, Default::default()), - JavaMethodProto::new("println", "(Ljava/lang/String;)V", Self::println_string, Default::default()), - JavaMethodProto::new("println", "(I)V", Self::println_int, Default::default()), - JavaMethodProto::new("println", "(J)V", Self::println_long, Default::default()), - JavaMethodProto::new("println", "(C)V", Self::println_char, Default::default()), - JavaMethodProto::new("println", "([C)V", Self::println_chars, Default::default()), - JavaMethodProto::new("println", "(B)V", Self::println_byte, Default::default()), - JavaMethodProto::new("println", "(S)V", Self::println_short, Default::default()), - JavaMethodProto::new("println", "(Z)V", Self::println_bool, Default::default()), - JavaMethodProto::new("println", "(F)V", Self::println_float, Default::default()), - JavaMethodProto::new("println", "(D)V", Self::println_double, Default::default()), + JavaMethodProto::new("", "(Ljava/io/OutputStream;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/OutputStream;Z)V", Self::init_auto_flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("checkError", "()Z", Self::check_error, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write_byte, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([BII)V", Self::write_bytes, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(Ljava/lang/Object;)V", Self::print_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(Ljava/lang/String;)V", Self::print_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(I)V", Self::print_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(J)V", Self::print_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(C)V", Self::print_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "([C)V", Self::print_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(Z)V", Self::print_bool, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(F)V", Self::print_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(D)V", Self::print_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "()V", Self::println, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(Ljava/lang/Object;)V", Self::println_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(Ljava/lang/String;)V", Self::println_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(I)V", Self::println_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(J)V", Self::println_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(C)V", Self::println_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "([C)V", Self::println_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(B)V", Self::println_byte, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(S)V", Self::println_short, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(Z)V", Self::println_bool, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(F)V", Self::println_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(D)V", Self::println_double, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("autoFlush", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("trouble", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("charOut", "Ljava/io/OutputStreamWriter;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("closing", "Z", FieldAccessFlags::PRIVATE), ], - fields: vec![JavaFieldProto::new("trouble", "Z", Default::default())], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintStream::({this:?}, {out:?})"); + jvm.invoke_special(&this, "java/io/PrintStream", "", "(Ljava/io/OutputStream;Z)V", (out, false)) + .await + } + + async fn init_auto_flush( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + out: ClassInstanceRef, + auto_flush: bool, + ) -> Result<()> { + tracing::debug!("java.io.PrintStream::({this:?}, {out:?}, {auto_flush})"); if out.is_null() { return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); } let _: () = jvm - .invoke_special(&this, "java/io/FilterOutputStream", "", "(Ljava/io/OutputStream;)V", (out,)) + .invoke_special(&this, "java/io/FilterOutputStream", "", "(Ljava/io/OutputStream;)V", (out.clone(),)) .await?; - jvm.put_field(&mut this, "trouble", "Z", false).await + let this_output: ClassInstanceRef = this.instance.clone().into(); + let props: ClassInstanceRef = jvm.get_static_field("java/lang/System", "props", "Ljava/util/Properties;").await?; + let encoding: ClassInstanceRef = if props.is_null() { + JavaLangString::from_rust_string(jvm, "UTF-8").await?.into() + } else { + let key = JavaLangString::from_rust_string(jvm, "file.encoding").await?; + let encoding: ClassInstanceRef = jvm + .invoke_virtual(&props, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if encoding.is_null() { + JavaLangString::from_rust_string(jvm, "UTF-8").await?.into() + } else { + encoding + } + }; + let char_out = jvm + .new_class( + "java/io/OutputStreamWriter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (this_output, encoding), + ) + .await?; + jvm.put_field(&mut this, "autoFlush", "Z", auto_flush).await?; + jvm.put_field(&mut this, "trouble", "Z", false).await?; + jvm.put_field(&mut this, "charOut", "Ljava/io/OutputStreamWriter;", char_out).await?; + jvm.put_field(&mut this, "closing", "Z", false).await + } + + async fn with_monitor(jvm: &Jvm, this: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(this).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(this).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(this).await { + tracing::error!(?exit_error, "failed to release PrintStream monitor"); + } + Err(error) + } + } } async fn check_error(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.io.PrintStream::checkError({this:?})"); - let _: () = jvm.invoke_virtual(&this, "flush", "()V", ()).await?; - jvm.get_field(&this, "trouble", "Z").await + + Self::with_monitor(jvm, &this, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + if !out.is_null() { + let _: () = jvm.invoke_virtual(&this, "flush", "()V", ()).await?; + if jvm.is_instance(&**out, "java/io/PrintStream") { + return jvm.invoke_virtual(&out, "checkError", "()Z", ()).await; + } + } + jvm.get_field(&this, "trouble", "Z").await + }) + .await } - async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintStream::close({this:?})"); - let out = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; - if jvm.invoke_virtual::<_, ()>(&out, "close", "()V", ()).await.is_err() { - jvm.put_field(&mut this, "trouble", "Z", true).await?; - } - Ok(()) + + Self::with_monitor(jvm, &this, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() || jvm.get_field::(&this, "closing", "Z").await? { + return Ok(()); + } + + let mut this = this.clone(); + jvm.put_field(&mut this, "closing", "Z", true).await?; + let char_out: ClassInstanceRef = jvm.get_field(&this, "charOut", "Ljava/io/OutputStreamWriter;").await?; + let char_out_result = jvm.invoke_virtual(&char_out, "close", "()V", ()).await; + let close_out = match char_out_result { + Ok(()) => true, + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + jvm.put_field(&mut this, "trouble", "Z", true).await?; + false + } + Err(error) => return Err(error), + }; + + if close_out { + match jvm.invoke_virtual(&out, "close", "()V", ()).await { + Ok(()) => {} + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + jvm.put_field(&mut this, "trouble", "Z", true).await?; + } + Err(error) => return Err(error), + } + } + + let closed_output: ClassInstanceRef = None.into(); + let closed_writer: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "charOut", "Ljava/io/OutputStreamWriter;", closed_writer).await?; + jvm.put_field(&mut this, "out", "Ljava/io/OutputStream;", closed_output).await + }) + .await } - async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintStream::flush({this:?})"); - let out = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; - if jvm.invoke_virtual::<_, ()>(&out, "flush", "()V", ()).await.is_err() { - jvm.put_field(&mut this, "trouble", "Z", true).await?; - } - Ok(()) + + Self::with_monitor(jvm, &this, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, &this, result).await.map(|_| ()) + }) + .await } - async fn write_byte(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result<()> { + async fn write_byte(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { tracing::debug!("java.io.PrintStream::write({this:?}, {value})"); - let out = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; - if jvm.invoke_virtual::<_, ()>(&out, "write", "(I)V", (value,)).await.is_err() { - jvm.put_field(&mut this, "trouble", "Z", true).await?; - } - Ok(()) + + Self::with_monitor(jvm, &this, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm.invoke_virtual(&out, "write", "(I)V", (value,)).await; + if Self::suppress_io_exception(jvm, &this, result).await? + && value == b'\n' as i32 + && jvm.get_field::(&this, "autoFlush", "Z").await? + { + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, &this, result).await?; + } + Ok(()) + }) + .await } async fn write_bytes( jvm: &Jvm, _: &mut RuntimeContext, - mut this: ClassInstanceRef, + this: ClassInstanceRef, bytes: ClassInstanceRef>, off: i32, len: i32, ) -> Result<()> { tracing::debug!("java.io.PrintStream::write({this:?}, {bytes:?}, {off}, {len})"); - let out = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; - if jvm.invoke_virtual::<_, ()>(&out, "write", "([BII)V", (bytes, off, len)).await.is_err() { - jvm.put_field(&mut this, "trouble", "Z", true).await?; - } - Ok(()) + + Self::with_monitor(jvm, &this, async { + if bytes.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "bytes is null").await); + } + let array_length = jvm.array_length(&bytes).await? as i32; + if off < 0 || len < 0 || off > array_length - len { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid offset or length").await); + } + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm.invoke_virtual(&out, "write", "([BII)V", (bytes, off, len)).await; + if Self::suppress_io_exception(jvm, &this, result).await? && jvm.get_field::(&this, "autoFlush", "Z").await? { + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, &this, result).await?; + } + Ok(()) + }) + .await } async fn print_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintStream::print({this:?}, {value:?})"); - if value.is_null() { - return Self::write_text(jvm, &this, "null").await; - } - - let value: ClassInstanceRef = jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; - let value = JavaLangString::to_rust_string(jvm, &value).await?; - Self::write_text(jvm, &this, &value).await + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", (value,)) + .await?; + Self::write_string(jvm, &this, value).await } async fn print_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintStream::print({this:?}, {value:?})"); - if value.is_null() { - Self::write_text(jvm, &this, "null").await + let value = if value.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() } else { - let value = JavaLangString::to_rust_string(jvm, &value).await?; - Self::write_text(jvm, &this, &value).await - } + value + }; + Self::write_string(jvm, &this, value).await } async fn print_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { - Self::write_text(jvm, &this, &format!("{value}")).await + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(I)Ljava/lang/String;", (value,)) + .await?; + Self::write_string(jvm, &this, value).await } async fn print_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result<()> { - Self::write_text(jvm, &this, &format!("{value}")).await + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(J)Ljava/lang/String;", (value,)) + .await?; + Self::write_string(jvm, &this, value).await } async fn print_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: JavaChar) -> Result<()> { - let value = char::from_u32(value as u32).unwrap_or('?'); - Self::write_text(jvm, &this, &value.to_string()).await + let mut chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut chars, 0, [value]).await?; + Self::write_characters(jvm, &this, chars.into()).await } async fn print_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef>) -> Result<()> { - let length = jvm.array_length(&value).await?; - let value: Vec = jvm.load_array(&value, 0, length).await?; - let value: RustString = char::decode_utf16(value).map(|value| value.unwrap_or('?')).collect(); - Self::write_text(jvm, &this, &value).await + Self::write_characters(jvm, &this, value).await } async fn print_bool(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result<()> { - Self::write_text(jvm, &this, if value { "true" } else { "false" }).await + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(Z)Ljava/lang/String;", (value,)) + .await?; + Self::write_string(jvm, &this, value).await } async fn print_float(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result<()> { let value: ClassInstanceRef = jvm - .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .invoke_static("java/lang/String", "valueOf", "(F)Ljava/lang/String;", (value,)) .await?; - let value = JavaLangString::to_rust_string(jvm, &value).await?; - Self::write_text(jvm, &this, &value).await + Self::write_string(jvm, &this, value).await } async fn print_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result<()> { let value: ClassInstanceRef = jvm - .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .invoke_static("java/lang/String", "valueOf", "(D)Ljava/lang/String;", (value,)) .await?; - let value = JavaLangString::to_rust_string(jvm, &value).await?; - Self::write_text(jvm, &this, &value).await + Self::write_string(jvm, &this, value).await } async fn println(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { - Self::write_text(jvm, &this, "\n").await + Self::new_line(jvm, &this).await } - async fn println_object(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { - Self::print_object(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", (value,)) + .await?; + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(Ljava/lang/String;)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_string(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { - Self::print_string(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(Ljava/lang/String;)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_int(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { - Self::print_int(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(I)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_long(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result<()> { - Self::print_long(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(J)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_char(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: JavaChar) -> Result<()> { - Self::print_char(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: JavaChar) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(C)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_chars( - jvm: &Jvm, - context: &mut RuntimeContext, - this: ClassInstanceRef, - value: ClassInstanceRef>, - ) -> Result<()> { - Self::print_chars(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef>) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "([C)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await + } + + async fn println_byte(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i8) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(I)V", (value as i32,)).await?; + Self::new_line(jvm, &this).await + }) + .await + } + + async fn println_short(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i16) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(I)V", (value as i32,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_byte(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: i8) -> Result<()> { - Self::println_int(jvm, context, this, value as i32).await + async fn println_bool(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(Z)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_short(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: i16) -> Result<()> { - Self::println_int(jvm, context, this, value as i32).await + async fn println_float(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(F)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_bool(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result<()> { - Self::print_bool(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn println_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result<()> { + Self::with_monitor(jvm, &this, async { + let _: () = jvm.invoke_virtual(&this, "print", "(D)V", (value,)).await?; + Self::new_line(jvm, &this).await + }) + .await } - async fn println_float(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result<()> { - Self::print_float(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn write_string(jvm: &Jvm, this: &ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let chars: ClassInstanceRef> = jvm.invoke_virtual(&value, "toCharArray", "()[C", ()).await?; + Self::write_characters(jvm, this, chars).await } - async fn println_double(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result<()> { - Self::print_double(jvm, context, this.clone(), value).await?; - Self::write_text(jvm, &this, "\n").await + async fn write_characters(jvm: &Jvm, this: &ClassInstanceRef, chars: ClassInstanceRef>) -> Result<()> { + Self::with_monitor(jvm, this, async { + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "chars is null").await); + } + + let out: ClassInstanceRef = jvm.get_field(this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let length = jvm.array_length(&chars).await?; + let values: Vec = jvm.load_array(&chars, 0, length).await?; + let char_out: ClassInstanceRef = jvm.get_field(this, "charOut", "Ljava/io/OutputStreamWriter;").await?; + let result = jvm.invoke_virtual(&char_out, "write", "([CII)V", (chars, 0, length as i32)).await; + if !Self::suppress_io_exception(jvm, this, result).await? { + return Ok(()); + } + + if jvm.get_field::(this, "autoFlush", "Z").await? && values.contains(&('\n' as JavaChar)) { + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, this, result).await?; + } + Ok(()) + }) + .await } - async fn write_text(jvm: &Jvm, this: &ClassInstanceRef, value: &str) -> Result<()> { - let bytes = value.as_bytes(); - let mut java_bytes = jvm.instantiate_array("B", bytes.len()).await?; - jvm.store_array(&mut java_bytes, 0, bytes.iter().map(|value| *value as i8)).await?; - jvm.invoke_virtual(this, "write", "([BII)V", (java_bytes, 0, bytes.len() as i32)).await + async fn new_line(jvm: &Jvm, this: &ClassInstanceRef) -> Result<()> { + Self::with_monitor(jvm, this, async { + let out: ClassInstanceRef = jvm.get_field(this, "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let key = JavaLangString::from_rust_string(jvm, "line.separator").await?; + let separator: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + let separator = if separator.is_null() { + JavaLangString::from_rust_string(jvm, "\n").await?.into() + } else { + separator + }; + let chars: ClassInstanceRef> = jvm.invoke_virtual(&separator, "toCharArray", "()[C", ()).await?; + let length = jvm.array_length(&chars).await?; + let char_out: ClassInstanceRef = jvm.get_field(this, "charOut", "Ljava/io/OutputStreamWriter;").await?; + let result = jvm.invoke_virtual(&char_out, "write", "([CII)V", (chars, 0, length as i32)).await; + if !Self::suppress_io_exception(jvm, this, result).await? { + return Ok(()); + } + + if jvm.get_field::(this, "autoFlush", "Z").await? { + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, this, result).await?; + } + Ok(()) + }) + .await + } + + async fn suppress_io_exception(jvm: &Jvm, this: &ClassInstanceRef, result: Result<()>) -> Result { + match result { + Ok(()) => Ok(true), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + Ok(false) + } + Err(error) => Err(error), + } } } diff --git a/java_runtime/src/classes/java/io/print_writer.rs b/java_runtime/src/classes/java/io/print_writer.rs index d422a04b..f93ab88e 100644 --- a/java_runtime/src/classes/java/io/print_writer.rs +++ b/java_runtime/src/classes/java/io/print_writer.rs @@ -1,11 +1,17 @@ -use alloc::{format, vec}; +use core::future::Future; + +use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::{io::Writer, lang::String}, + classes::java::{ + io::{OutputStream, Writer}, + lang::{Object, String}, + }, }; // class java.io.PrintWriter @@ -18,64 +24,498 @@ impl PrintWriter { parent_class: Some("java/io/Writer"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "(Ljava/io/Writer;)V", Self::init, Default::default()), - JavaMethodProto::new("write", "([CII)V", Self::write, Default::default()), - JavaMethodProto::new("flush", "()V", Self::flush, Default::default()), - JavaMethodProto::new("close", "()V", Self::close, Default::default()), - JavaMethodProto::new("println", "(Ljava/lang/String;)V", Self::println, Default::default()), + JavaMethodProto::new("", "(Ljava/io/Writer;)V", Self::init_writer, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/Writer;Z)V", Self::init_writer_auto_flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/io/OutputStream;)V", Self::init_output_stream, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/OutputStream;Z)V", + Self::init_output_stream_auto_flush, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("write", "(I)V", Self::write_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([C)V", Self::write_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([CII)V", Self::write_chars_range, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(Ljava/lang/String;)V", Self::write_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(Ljava/lang/String;II)V", Self::write_string_range, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(Z)V", Self::print_boolean, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(C)V", Self::print_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(I)V", Self::print_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(J)V", Self::print_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(F)V", Self::print_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(D)V", Self::print_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "([C)V", Self::print_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(Ljava/lang/String;)V", Self::print_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(Ljava/lang/Object;)V", Self::print_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "()V", Self::println, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(Z)V", Self::println_boolean, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(C)V", Self::println_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(I)V", Self::println_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(J)V", Self::println_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(F)V", Self::println_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(D)V", Self::println_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "([C)V", Self::println_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(Ljava/lang/String;)V", Self::println_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "(Ljava/lang/Object;)V", Self::println_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("checkError", "()Z", Self::check_error, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("out", "Ljava/io/Writer;", FieldAccessFlags::PROTECTED), + JavaFieldProto::new("autoFlush", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("trouble", "Z", FieldAccessFlags::PRIVATE), ], - fields: vec![JavaFieldProto::new("out", "Ljava/io/Writer;", Default::default())], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { - tracing::debug!("java.io.PrintWriter::({this:?})"); + async fn init_writer(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.PrintWriter::({this:?}, {out:?})"); + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/Writer;Z)V", (out, false)) + .await + } - let _: () = jvm.invoke_special(&this, "java/io/Writer", "", "()V", ()).await?; + async fn init_writer_auto_flush( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + out: ClassInstanceRef, + auto_flush: bool, + ) -> Result<()> { + tracing::debug!("java.io.PrintWriter::({this:?}, {out:?}, {auto_flush})"); + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); + } + + let _: () = jvm + .invoke_special(&this, "java/io/Writer", "", "(Ljava/lang/Object;)V", (out.clone(),)) + .await?; jvm.put_field(&mut this, "out", "Ljava/io/Writer;", out).await?; + jvm.put_field(&mut this, "autoFlush", "Z", auto_flush).await?; + jvm.put_field(&mut this, "trouble", "Z", false).await + } + + async fn init_output_stream(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.PrintWriter::({this:?}, {out:?})"); + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/OutputStream;Z)V", (out, false)) + .await + } + + async fn init_output_stream_auto_flush( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + out: ClassInstanceRef, + auto_flush: bool, + ) -> Result<()> { + tracing::debug!("java.io.PrintWriter::({this:?}, {out:?}, {auto_flush})"); + + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); + } + + let writer: ClassInstanceRef = jvm + .new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (out,)) + .await? + .into(); + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/Writer;Z)V", (writer, auto_flush)) + .await + } + + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(lock).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(lock).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(lock).await { + tracing::error!(?exit_error, "failed to release PrintWriter lock"); + } + Err(error) + } + } + } + + async fn write_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + tracing::debug!("java.io.PrintWriter::write({this:?}, {value})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm.invoke_virtual(&out, "write", "(I)V", (value,)).await; + Self::suppress_io_exception(jvm, &this, result).await + }) + .await + } + + async fn write_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, chars: ClassInstanceRef>) -> Result<()> { + tracing::debug!("java.io.PrintWriter::write({this:?}, {chars:?})"); - Ok(()) + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "chars is null").await); + } + let length = jvm.array_length(&chars).await? as i32; + jvm.invoke_virtual(&this, "write", "([CII)V", (chars, 0, length)).await + }) + .await } - async fn write( + async fn write_chars_range( jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, chars: ClassInstanceRef>, - off: i32, - len: i32, + offset: i32, + length: i32, + ) -> Result<()> { + tracing::debug!("java.io.PrintWriter::write({this:?}, {chars:?}, {offset}, {length})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "chars is null").await); + } + let array_length = jvm.array_length(&chars).await? as i32; + if offset < 0 || length < 0 || offset > array_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid offset or length").await); + } + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm.invoke_virtual(&out, "write", "([CII)V", (chars, offset, length)).await; + Self::suppress_io_exception(jvm, &this, result).await + }) + .await + } + + async fn write_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.PrintWriter::write({this:?}, {string:?})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "string is null").await); + } + let length: i32 = jvm.invoke_virtual(&string, "length", "()I", ()).await?; + jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;II)V", (string, 0, length)).await + }) + .await + } + + async fn write_string_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + offset: i32, + length: i32, ) -> Result<()> { - tracing::debug!("java.io.PrintWriter::write({this:?}, {chars:?}, {off:?}, {len:?})"); + tracing::debug!("java.io.PrintWriter::write({this:?}, {string:?}, {offset}, {length})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "string is null").await); + } + let string_length: i32 = jvm.invoke_virtual(&string, "length", "()I", ()).await?; + if offset < 0 || length < 0 || offset > string_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid offset or length").await); + } + + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm + .invoke_virtual(&out, "write", "(Ljava/lang/String;II)V", (string, offset, length)) + .await; + Self::suppress_io_exception(jvm, &this, result).await + }) + .await + } - let out = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + async fn print_boolean(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value})"); + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(Z)Ljava/lang/String;", (value,)) + .await?; + Self::print_string(jvm, context, this, value).await + } + + async fn print_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: JavaChar) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value})"); + jvm.invoke_virtual(&this, "write", "(I)V", (value as i32,)).await + } + + async fn print_int(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value})"); + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(I)Ljava/lang/String;", (value,)) + .await?; + Self::print_string(jvm, context, this, value).await + } + + async fn print_long(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value})"); + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(J)Ljava/lang/String;", (value,)) + .await?; + Self::print_string(jvm, context, this, value).await + } + + async fn print_float(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value})"); + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(F)Ljava/lang/String;", (value,)) + .await?; + Self::print_string(jvm, context, this, value).await + } + + async fn print_double(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value})"); + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(D)Ljava/lang/String;", (value,)) + .await?; + Self::print_string(jvm, context, this, value).await + } + + async fn print_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef>) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value:?})"); + jvm.invoke_virtual(&this, "write", "([C)V", (value,)).await + } + + async fn print_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value:?})"); + let value = if value.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + value + }; + jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;)V", (value,)).await + } + + async fn print_object(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.PrintWriter::print({this:?}, {value:?})"); + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", (value,)) + .await?; + Self::print_string(jvm, context, this, value).await + } + + async fn println(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.PrintWriter::println({this:?})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let key = JavaLangString::from_rust_string(jvm, "line.separator").await?; + let separator: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + let separator = if separator.is_null() { + JavaLangString::from_rust_string(jvm, "\n").await?.into() + } else { + separator + }; + match jvm.invoke_virtual(&out, "write", "(Ljava/lang/String;)V", (separator,)).await { + Ok(()) => {} + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + Err(error) => return Err(error), + } + + if jvm.get_field::(&this, "autoFlush", "Z").await? { + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, &this, result).await + } else { + Ok(()) + } + }) + .await + } - let _: () = jvm.invoke_virtual(&out, "write", "([CII)V", (chars, off, len)).await?; + async fn println_boolean(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(Z)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: JavaChar) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(C)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(I)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(J)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_float(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(F)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(D)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } - Ok(()) + async fn println_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef>) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "([C)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(Ljava/lang/String;)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await + } + + async fn println_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let _: () = jvm.invoke_virtual(&this, "print", "(Ljava/lang/Object;)V", (value,)).await?; + jvm.invoke_virtual(&this, "println", "()V", ()).await + }) + .await } async fn flush(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintWriter::flush({this:?})"); - let out = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; - jvm.invoke_virtual(&out, "flush", "()V", ()).await + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await?; + return Ok(()); + } + + let result = jvm.invoke_virtual(&out, "flush", "()V", ()).await; + Self::suppress_io_exception(jvm, &this, result).await + }) + .await } async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintWriter::close({this:?})"); - let out = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; - jvm.invoke_virtual(&out, "close", "()V", ()).await - } - async fn println(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { - tracing::debug!("java.io.PrintWriter::println({this:?}, {string:?})"); + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if out.is_null() { + return Ok(()); + } - let string = format!("{}\n", JavaLangString::to_rust_string(jvm, &string).await?); - let string = JavaLangString::from_rust_string(jvm, &string).await?; + match jvm.invoke_virtual(&out, "close", "()V", ()).await { + Ok(()) => { + let mut this = this.clone(); + let closed: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "out", "Ljava/io/Writer;", closed).await + } + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await + } + Err(error) => Err(error), + } + }) + .await + } - let _: () = jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;)V", (string,)).await?; + async fn check_error(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.PrintWriter::checkError({this:?})"); - Ok(()) + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let out: ClassInstanceRef = jvm.get_field(&this, "out", "Ljava/io/Writer;").await?; + if !out.is_null() { + let _: () = jvm.invoke_virtual(&this, "flush", "()V", ()).await?; + if jvm.is_instance(&**out, "java/io/PrintWriter") { + return jvm.invoke_virtual(&out, "checkError", "()Z", ()).await; + } + } + jvm.get_field(&this, "trouble", "Z").await + }) + .await + } + + async fn suppress_io_exception(jvm: &Jvm, this: &ClassInstanceRef, result: Result<()>) -> Result<()> { + match result { + Ok(()) => Ok(()), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + let mut this = this.clone(); + jvm.put_field(&mut this, "trouble", "Z", true).await + } + Err(error) => Err(error), + } } } diff --git a/java_runtime/src/classes/java/io/random_access_file.rs b/java_runtime/src/classes/java/io/random_access_file.rs index 2defa325..4f4801d9 100644 --- a/java_runtime/src/classes/java/io/random_access_file.rs +++ b/java_runtime/src/classes/java/io/random_access_file.rs @@ -6,7 +6,7 @@ use java_class_proto::{JavaFieldProto, JavaMethodProto}; use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ - RuntimeClassProto, RuntimeContext, + FileOpenOptions, RuntimeClassProto, RuntimeContext, classes::java::{ io::{File, FileDescriptor}, lang::String, @@ -57,7 +57,17 @@ impl RandomAccessFile { let write = mode.contains('w'); - let fd_id = context.open(&name, write).await; + let fd_id = context + .open( + &name, + FileOpenOptions { + read: true, + write, + create: write, + ..Default::default() + }, + ) + .await; if fd_id.is_err() { return Err(jvm.exception("java/io/FileNotFoundException", "File not found").await); } @@ -147,8 +157,14 @@ impl RandomAccessFile { let mut rust_buf = vec![0; length as usize]; jvm.array_raw_buffer(&buf).await?.read(offset as _, &mut rust_buf)?; - if rust_file.write(&cast_vec(rust_buf)).await.is_err() { - return Err(jvm.exception("java/io/IOException", "I/O error").await); + let rust_buf = cast_vec(rust_buf); + let mut written = 0; + while written < rust_buf.len() { + match rust_file.write(&rust_buf[written..]).await { + Ok(0) | Err(_) => return Err(jvm.exception("java/io/IOException", "I/O error").await), + Ok(length) if length > rust_buf.len() - written => return Err(jvm.exception("java/io/IOException", "I/O error").await), + Ok(length) => written += length, + } } Ok(()) diff --git a/java_runtime/src/classes/java/io/string_reader.rs b/java_runtime/src/classes/java/io/string_reader.rs new file mode 100644 index 00000000..4e8c7be7 --- /dev/null +++ b/java_runtime/src/classes/java/io/string_reader.rs @@ -0,0 +1,236 @@ +use core::future::Future; + +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// class java.io.StringReader +pub struct StringReader; + +impl StringReader { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/StringReader", + parent_class: Some("java/io/Reader"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "()I", Self::read_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "([CII)I", Self::read, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("skip", "(J)J", Self::skip, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("ready", "()Z", Self::ready, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("markSupported", "()Z", Self::mark_supported, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("mark", "(I)V", Self::mark, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("str", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("length", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("next", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("mark", "I", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.StringReader::({this:?}, {value:?})"); + + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "string is null").await); + } + let _: () = jvm.invoke_special(&this, "java/io/Reader", "", "()V", ()).await?; + let length: i32 = jvm.invoke_virtual(&value, "length", "()I", ()).await?; + jvm.put_field(&mut this, "str", "Ljava/lang/String;", value).await?; + jvm.put_field(&mut this, "length", "I", length).await?; + jvm.put_field(&mut this, "next", "I", 0).await?; + jvm.put_field(&mut this, "mark", "I", 0).await + } + + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result + where + F: Future>, + { + jvm.monitor_enter(lock).await?; + match operation.await { + Ok(value) => { + jvm.monitor_exit(lock).await?; + Ok(value) + } + Err(error) => { + if let Err(exit_error) = jvm.monitor_exit(lock).await { + tracing::error!(?exit_error, "failed to release StringReader lock"); + } + Err(error) + } + } + } + + async fn read_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_char_locked(jvm, this)).await + } + + async fn read_char_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.StringReader::read({this:?})"); + + let value: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + if value.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let next: i32 = jvm.get_field(&this, "next", "I").await?; + let length: i32 = jvm.get_field(&this, "length", "I").await?; + if next >= length { + return Ok(-1); + } + let result: JavaChar = jvm.invoke_virtual(&value, "charAt", "(I)C", (next,)).await?; + jvm.put_field(&mut this, "next", "I", next + 1).await?; + Ok(result as i32) + } + + async fn read( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::read_locked(jvm, this, target, offset, length)).await + } + + async fn read_locked( + jvm: &Jvm, + mut this: ClassInstanceRef, + target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + tracing::debug!("java.io.StringReader::read({this:?}, {target:?}, {offset}, {length})"); + + let value: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + if value.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if target.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "target is null").await); + } + let target_length = jvm.array_length(&target).await? as i32; + if offset < 0 || length < 0 || offset > target_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + if length == 0 { + return Ok(0); + } + + let next: i32 = jvm.get_field(&this, "next", "I").await?; + let source_length: i32 = jvm.get_field(&this, "length", "I").await?; + if next >= source_length { + return Ok(-1); + } + let copied = length.min(source_length - next); + let _: () = jvm + .invoke_virtual(&value, "getChars", "(II[CI)V", (next, next + copied, target, offset)) + .await?; + jvm.put_field(&mut this, "next", "I", next + copied).await?; + Ok(copied) + } + + async fn skip(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, amount: i64) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::skip_locked(jvm, this, amount)).await + } + + async fn skip_locked(jvm: &Jvm, mut this: ClassInstanceRef, amount: i64) -> Result { + tracing::debug!("java.io.StringReader::skip({this:?}, {amount})"); + + let value: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + if value.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + if amount <= 0 { + return Ok(0); + } + let next: i32 = jvm.get_field(&this, "next", "I").await?; + let length: i32 = jvm.get_field(&this, "length", "I").await?; + let skipped = amount.min((length - next) as i64); + jvm.put_field(&mut this, "next", "I", next + skipped as i32).await?; + Ok(skipped) + } + + async fn ready(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::ready_locked(jvm, this)).await + } + + async fn ready_locked(jvm: &Jvm, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.StringReader::ready({this:?})"); + + let value: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + if value.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + Ok(true) + } + + async fn mark_supported(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.io.StringReader::markSupported({this:?})"); + Ok(true) + } + + async fn mark(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, read_ahead_limit: i32) -> Result<()> { + tracing::debug!("java.io.StringReader::mark({this:?}, {read_ahead_limit})"); + + if read_ahead_limit < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Read-ahead limit < 0").await); + } + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::mark_locked(jvm, this)).await + } + + async fn mark_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + let value: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + if value.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let next: i32 = jvm.get_field(&this, "next", "I").await?; + jvm.put_field(&mut this, "mark", "I", next).await + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::reset_locked(jvm, this)).await + } + + async fn reset_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.StringReader::reset({this:?})"); + + let value: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + if value.is_null() { + return Err(jvm.exception("java/io/IOException", "Stream closed").await); + } + let mark: i32 = jvm.get_field(&this, "mark", "I").await?; + jvm.put_field(&mut this, "next", "I", mark).await + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, Self::close_locked(jvm, this)).await + } + + async fn close_locked(jvm: &Jvm, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.io.StringReader::close({this:?})"); + + let null_string: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "str", "Ljava/lang/String;", null_string).await + } +} diff --git a/java_runtime/src/classes/java/lang.rs b/java_runtime/src/classes/java/lang.rs index d7b8f6ae..65dcd6b6 100644 --- a/java_runtime/src/classes/java/lang.rs +++ b/java_runtime/src/classes/java/lang.rs @@ -7,6 +7,7 @@ mod byte; mod character; mod class; mod class_cast_exception; +mod class_circularity_error; mod class_format_error; mod class_loader; mod class_not_found_exception; @@ -18,15 +19,18 @@ mod error; mod exception; mod exception_in_initializer_error; mod float; +mod illegal_access_error; mod illegal_access_exception; mod illegal_argument_exception; mod illegal_monitor_state_exception; +mod illegal_state_exception; mod illegal_thread_state_exception; mod incompatible_class_change_error; mod index_out_of_bounds_exception; mod instantiation_error; mod instantiation_exception; mod integer; +mod internal_error; mod interrupted_exception; mod linkage_error; mod long; @@ -34,7 +38,9 @@ mod math; mod negative_array_size_exception; mod no_class_def_found_error; mod no_such_field_error; +mod no_such_field_exception; mod no_such_method_error; +mod no_such_method_exception; mod null_pointer_exception; mod number; mod number_format_exception; @@ -45,12 +51,15 @@ mod runtime; mod runtime_exception; mod security_exception; mod short; +mod stack_overflow_error; mod string; mod string_buffer; mod string_index_out_of_bounds_exception; mod system; mod thread; +mod thread_death; mod throwable; +mod unknown_error; mod unsatisfied_link_error; mod unsupported_class_version_error; mod unsupported_operation_exception; @@ -60,19 +69,23 @@ mod virtual_machine_error; pub use self::{ abstract_method_error::AbstractMethodError, arithmetic_exception::ArithmeticException, array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, boolean::Boolean, byte::Byte, - character::Character, class::Class, class_cast_exception::ClassCastException, class_format_error::ClassFormatError, class_loader::ClassLoader, - class_not_found_exception::ClassNotFoundException, clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, - comparable::Comparable, double::Double, error::Error, exception::Exception, exception_in_initializer_error::ExceptionInInitializerError, - float::Float, illegal_access_exception::IllegalAccessException, illegal_argument_exception::IllegalArgumentException, - illegal_monitor_state_exception::IllegalMonitorStateException, illegal_thread_state_exception::IllegalThreadStateException, - incompatible_class_change_error::IncompatibleClassChangeError, index_out_of_bounds_exception::IndexOutOfBoundsException, - instantiation_error::InstantiationError, instantiation_exception::InstantiationException, integer::Integer, - interrupted_exception::InterruptedException, linkage_error::LinkageError, long::Long, math::Math, - negative_array_size_exception::NegativeArraySizeException, no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, - no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number::Number, - number_format_exception::NumberFormatException, object::Object, out_of_memory_error::OutOfMemoryError, runnable::Runnable, runtime::Runtime, - runtime_exception::RuntimeException, security_exception::SecurityException, short::Short, string::String, string_buffer::StringBuffer, - string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, throwable::Throwable, - unsatisfied_link_error::UnsatisfiedLinkError, unsupported_class_version_error::UnsupportedClassVersionError, - unsupported_operation_exception::UnsupportedOperationException, verify_error::VerifyError, virtual_machine_error::VirtualMachineError, + character::Character, class::Class, class_cast_exception::ClassCastException, class_circularity_error::ClassCircularityError, + class_format_error::ClassFormatError, class_loader::ClassLoader, class_not_found_exception::ClassNotFoundException, + clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, comparable::Comparable, double::Double, error::Error, + exception::Exception, exception_in_initializer_error::ExceptionInInitializerError, float::Float, illegal_access_error::IllegalAccessError, + illegal_access_exception::IllegalAccessException, illegal_argument_exception::IllegalArgumentException, + illegal_monitor_state_exception::IllegalMonitorStateException, illegal_state_exception::IllegalStateException, + illegal_thread_state_exception::IllegalThreadStateException, incompatible_class_change_error::IncompatibleClassChangeError, + index_out_of_bounds_exception::IndexOutOfBoundsException, instantiation_error::InstantiationError, + instantiation_exception::InstantiationException, integer::Integer, internal_error::InternalError, interrupted_exception::InterruptedException, + linkage_error::LinkageError, long::Long, math::Math, negative_array_size_exception::NegativeArraySizeException, + no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, no_such_field_exception::NoSuchFieldException, + no_such_method_error::NoSuchMethodError, no_such_method_exception::NoSuchMethodException, null_pointer_exception::NullPointerException, + number::Number, number_format_exception::NumberFormatException, object::Object, out_of_memory_error::OutOfMemoryError, runnable::Runnable, + runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, short::Short, + stack_overflow_error::StackOverflowError, string::String, string_buffer::StringBuffer, + string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, thread_death::ThreadDeath, + throwable::Throwable, unknown_error::UnknownError, unsatisfied_link_error::UnsatisfiedLinkError, + unsupported_class_version_error::UnsupportedClassVersionError, unsupported_operation_exception::UnsupportedOperationException, + verify_error::VerifyError, virtual_machine_error::VirtualMachineError, }; diff --git a/java_runtime/src/classes/java/lang/class.rs b/java_runtime/src/classes/java/lang/class.rs index c2242dd4..eec466a3 100644 --- a/java_runtime/src/classes/java/lang/class.rs +++ b/java_runtime/src/classes/java/lang/class.rs @@ -34,6 +34,20 @@ impl Class { JavaMethodProto::new("isAssignableFrom", "(Ljava/lang/Class;)Z", Self::is_assignable_from, Default::default()), JavaMethodProto::new("newInstance", "()Ljava/lang/Object;", Self::new_instance, MethodAccessFlags::PUBLIC), JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getSuperclass", "()Ljava/lang/Class;", Self::get_superclass, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getClassLoader", + "()Ljava/lang/ClassLoader;", + Self::get_class_loader, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getComponentType", + "()Ljava/lang/Class;", + Self::get_component_type, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("getInterfaces", "()[Ljava/lang/Class;", Self::get_interfaces, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "getResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;", @@ -173,6 +187,97 @@ impl Class { Ok(jvm.is_type_assignable(&JavaType::from_class_name(&other_name), &JavaType::from_class_name(&class_name))) } + async fn get_superclass(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.lang.Class::getSuperclass({this:?})"); + + let name = JavaLangClass::name(jvm, &this).await?; + if matches!( + name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" | "void" | "java/lang/Object" + ) { + return Ok(None.into()); + } + if name.starts_with('[') { + return Ok(jvm.resolve_class("java/lang/Object").await?.java_class().into()); + } + + let class = JavaLangClass::to_rust_class(jvm, &this).await?; + if class.access_flags().contains(ClassAccessFlags::INTERFACE) { + return Ok(None.into()); + } + + match class.super_class_name() { + Some(super_class_name) => Ok(jvm.resolve_class(&super_class_name).await?.java_class().into()), + None => Ok(None.into()), + } + } + + async fn get_class_loader(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.lang.Class::getClassLoader({this:?})"); + + jvm.get_field(&this, "classLoader", "Ljava/lang/ClassLoader;").await + } + + async fn get_component_type(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.lang.Class::getComponentType({this:?})"); + + let name = JavaLangClass::name(jvm, &this).await?; + let Some(component_descriptor) = name.strip_prefix('[') else { + return Ok(None.into()); + }; + + let primitive_wrapper = match component_descriptor { + "Z" => Some("java/lang/Boolean"), + "B" => Some("java/lang/Byte"), + "C" => Some("java/lang/Character"), + "S" => Some("java/lang/Short"), + "I" => Some("java/lang/Integer"), + "J" => Some("java/lang/Long"), + "F" => Some("java/lang/Float"), + "D" => Some("java/lang/Double"), + _ => None, + }; + if let Some(wrapper) = primitive_wrapper { + return jvm.get_static_field(wrapper, "TYPE", "Ljava/lang/Class;").await; + } + + let component_name = if let Some(reference_name) = component_descriptor.strip_prefix('L').and_then(|name| name.strip_suffix(';')) { + reference_name + } else { + component_descriptor + }; + let defining_loader: ClassInstanceRef = jvm.get_field(&this, "classLoader", "Ljava/lang/ClassLoader;").await?; + if defining_loader.is_null() { + return Ok(jvm.resolve_class(component_name).await?.java_class().into()); + } + + let component_name = JavaLangString::from_rust_string(jvm, component_name).await?; + jvm.invoke_virtual(&defining_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", (component_name,)) + .await + } + + async fn get_interfaces(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + tracing::debug!("java.lang.Class::getInterfaces({this:?})"); + + let name = JavaLangClass::name(jvm, &this).await?; + let interface_names = if matches!( + name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" | "void" + ) { + vec![] + } else { + JavaLangClass::to_rust_class(jvm, &this).await?.interface_names() + }; + + let mut interfaces = jvm.instantiate_array("Ljava/lang/Class;", interface_names.len()).await?; + for (index, interface_name) in interface_names.iter().enumerate() { + let interface = jvm.resolve_class(interface_name).await?.java_class(); + jvm.store_array(&mut interfaces, index, [interface]).await?; + } + + Ok(interfaces.into()) + } + async fn get_resource_as_stream( jvm: &Jvm, _context: &mut RuntimeContext, diff --git a/java_runtime/src/classes/java/lang/class_circularity_error.rs b/java_runtime/src/classes/java/lang/class_circularity_error.rs new file mode 100644 index 00000000..eb04f0f4 --- /dev/null +++ b/java_runtime/src/classes/java/lang/class_circularity_error.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.ClassCircularityError +pub struct ClassCircularityError; + +impl ClassCircularityError { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/ClassCircularityError", + parent_class: Some("java/lang/LinkageError"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.ClassCircularityError::({this:?})"); + + jvm.invoke_special(&this, "java/lang/LinkageError", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.ClassCircularityError::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/LinkageError", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/class_loader.rs b/java_runtime/src/classes/java/lang/class_loader.rs index 4f900db7..d9e8c999 100644 --- a/java_runtime/src/classes/java/lang/class_loader.rs +++ b/java_runtime/src/classes/java/lang/class_loader.rs @@ -188,9 +188,19 @@ impl ClassLoader { let name_str = JavaLangString::to_rust_string(jvm, &name).await?; if let Some(element_type_name) = name_str.strip_prefix('[') { - // TODO do we need another class loader for array? + let ultimate_element_type = element_type_name.trim_start_matches('['); + let defining_loader = if let Some(element_class_name) = ultimate_element_type.strip_prefix('L').and_then(|name| name.strip_suffix(';')) { + let element_class_name = JavaLangString::from_rust_string(jvm, element_class_name).await?; + let element_class: ClassInstanceRef = jvm + .invoke_virtual(&this, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", (element_class_name,)) + .await?; + jvm.get_field(&element_class, "classLoader", "Ljava/lang/ClassLoader;").await? + } else { + None + }; + let class = runtime.define_array_class(jvm, element_type_name).await?; - let java_class = jvm.register_class(class, Some(this.into())).await?; + let java_class = jvm.register_class(class, defining_loader).await?; return Ok(java_class.into()); } diff --git a/java_runtime/src/classes/java/lang/illegal_access_error.rs b/java_runtime/src/classes/java/lang/illegal_access_error.rs new file mode 100644 index 00000000..c3a68d97 --- /dev/null +++ b/java_runtime/src/classes/java/lang/illegal_access_error.rs @@ -0,0 +1,46 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.IllegalAccessError +pub struct IllegalAccessError; + +impl IllegalAccessError { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/IllegalAccessError", + parent_class: Some("java/lang/IncompatibleClassChangeError"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.IllegalAccessError::({this:?})"); + + jvm.invoke_special(&this, "java/lang/IncompatibleClassChangeError", "", "()V", ()) + .await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.IllegalAccessError::({this:?}, {message:?})"); + + jvm.invoke_special( + &this, + "java/lang/IncompatibleClassChangeError", + "", + "(Ljava/lang/String;)V", + (message,), + ) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/illegal_state_exception.rs b/java_runtime/src/classes/java/lang/illegal_state_exception.rs new file mode 100644 index 00000000..c82a261a --- /dev/null +++ b/java_runtime/src/classes/java/lang/illegal_state_exception.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.IllegalStateException +pub struct IllegalStateException; + +impl IllegalStateException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/IllegalStateException", + parent_class: Some("java/lang/RuntimeException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.IllegalStateException::({this:?})"); + + jvm.invoke_special(&this, "java/lang/RuntimeException", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.IllegalStateException::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/RuntimeException", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/internal_error.rs b/java_runtime/src/classes/java/lang/internal_error.rs new file mode 100644 index 00000000..92bbc08c --- /dev/null +++ b/java_runtime/src/classes/java/lang/internal_error.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.InternalError +pub struct InternalError; + +impl InternalError { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/InternalError", + parent_class: Some("java/lang/VirtualMachineError"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.InternalError::({this:?})"); + + jvm.invoke_special(&this, "java/lang/VirtualMachineError", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.InternalError::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/VirtualMachineError", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/math.rs b/java_runtime/src/classes/java/lang/math.rs index d10fdf08..c9031925 100644 --- a/java_runtime/src/classes/java/lang/math.rs +++ b/java_runtime/src/classes/java/lang/math.rs @@ -1,12 +1,12 @@ use alloc::vec; -use java_class_proto::JavaMethodProto; -use java_constants::MethodAccessFlags; -use jvm::{Jvm, Result}; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; -use crate::{RuntimeClassProto, RuntimeContext}; +use crate::{RuntimeClassProto, RuntimeContext, classes::java::util::Random}; -// class java.lang.Math +// public final class java.lang.Math pub struct Math; impl Math { @@ -16,65 +16,314 @@ impl Math { parent_class: Some("java/lang/Object"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("abs", "(I)I", Self::abs, MethodAccessFlags::STATIC), - JavaMethodProto::new("abs", "(J)J", Self::abs_long, MethodAccessFlags::STATIC), - JavaMethodProto::new("abs", "(F)F", Self::abs_float, MethodAccessFlags::STATIC), - JavaMethodProto::new("abs", "(D)D", Self::abs_double, MethodAccessFlags::STATIC), - JavaMethodProto::new("max", "(II)I", Self::max, MethodAccessFlags::STATIC), - JavaMethodProto::new("max", "(JJ)J", Self::max_long, MethodAccessFlags::STATIC), - JavaMethodProto::new("min", "(II)I", Self::min, MethodAccessFlags::STATIC), - JavaMethodProto::new("min", "(JJ)J", Self::min_long, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("abs", "(I)I", Self::abs, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("abs", "(J)J", Self::abs_long, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("abs", "(F)F", Self::abs_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("abs", "(D)D", Self::abs_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("ceil", "(D)D", Self::ceil, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("floor", "(D)D", Self::floor, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("sqrt", "(D)D", Self::sqrt, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("sin", "(D)D", Self::sin, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("cos", "(D)D", Self::cos, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("tan", "(D)D", Self::tan, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "toDegrees", + "(D)D", + Self::to_degrees, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toRadians", + "(D)D", + Self::to_radians, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("max", "(II)I", Self::max, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("max", "(JJ)J", Self::max_long, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("max", "(FF)F", Self::max_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("max", "(DD)D", Self::max_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("min", "(II)I", Self::min, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("min", "(JJ)J", Self::min_long, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("min", "(FF)F", Self::min_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("min", "(DD)D", Self::min_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("acos", "(D)D", Self::acos, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("asin", "(D)D", Self::asin, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("atan", "(D)D", Self::atan, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("atan2", "(DD)D", Self::atan2, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("exp", "(D)D", Self::exp, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("log", "(D)D", Self::log, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("pow", "(DD)D", Self::pow, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("rint", "(D)D", Self::rint, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "IEEEremainder", + "(DD)D", + Self::ieee_remainder, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("round", "(F)I", Self::round_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new("round", "(D)J", Self::round_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "random", + "()D", + Self::random, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::SYNCHRONIZED, + ), ], - fields: vec![], - access_flags: Default::default(), + fields: vec![ + JavaFieldProto::new("E", "D", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL), + JavaFieldProto::new("PI", "D", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL), + JavaFieldProto::new( + "randomNumberGenerator", + "Ljava/util/Random;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC, + ), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, } } + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Math", "E", "D", core::f64::consts::E).await?; + jvm.put_static_field("java/lang/Math", "PI", "D", core::f64::consts::PI).await + } + async fn abs(_: &Jvm, _: &mut RuntimeContext, x: i32) -> Result { tracing::debug!("java.lang.Math::abs({x:?})"); - - Ok(x.abs()) + Ok(x.wrapping_abs()) } async fn abs_long(_: &Jvm, _: &mut RuntimeContext, x: i64) -> Result { tracing::debug!("java.lang.Math::abs({x:?})"); - - Ok(x.abs()) + Ok(x.wrapping_abs()) } async fn abs_float(_: &Jvm, _: &mut RuntimeContext, x: f32) -> Result { tracing::debug!("java.lang.Math::abs({x:?})"); - - Ok(x.abs()) + Ok(libm::fabsf(x)) } async fn abs_double(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { tracing::debug!("java.lang.Math::abs({x:?})"); + Ok(libm::fabs(x)) + } + + async fn ceil(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::ceil({x:?})"); + Ok(libm::ceil(x)) + } - Ok(x.abs()) + async fn floor(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::floor({x:?})"); + Ok(libm::floor(x)) + } + + async fn sqrt(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::sqrt({x:?})"); + Ok(libm::sqrt(x)) + } + + async fn sin(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::sin({x:?})"); + Ok(libm::sin(x)) + } + + async fn cos(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::cos({x:?})"); + Ok(libm::cos(x)) + } + + async fn tan(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::tan({x:?})"); + Ok(libm::tan(x)) + } + + async fn to_degrees(_: &Jvm, _: &mut RuntimeContext, radians: f64) -> Result { + tracing::debug!("java.lang.Math::toDegrees({radians:?})"); + Ok(radians * 180.0 / core::f64::consts::PI) + } + + async fn to_radians(_: &Jvm, _: &mut RuntimeContext, degrees: f64) -> Result { + tracing::debug!("java.lang.Math::toRadians({degrees:?})"); + Ok(degrees / 180.0 * core::f64::consts::PI) } async fn max(_: &Jvm, _: &mut RuntimeContext, x: i32, y: i32) -> Result { tracing::debug!("java.lang.Math::max({x:?}, {y:?})"); - Ok(x.max(y)) } async fn max_long(_: &Jvm, _: &mut RuntimeContext, x: i64, y: i64) -> Result { tracing::debug!("java.lang.Math::max({x:?}, {y:?})"); - Ok(x.max(y)) } + async fn max_float(_: &Jvm, _: &mut RuntimeContext, x: f32, y: f32) -> Result { + tracing::debug!("java.lang.Math::max({x:?}, {y:?})"); + + if x.is_nan() { + return Ok(x); + } + if y.is_nan() { + return Ok(y); + } + if x == 0.0 && y == 0.0 { + return Ok(if x.is_sign_positive() || y.is_sign_positive() { 0.0 } else { -0.0 }); + } + Ok(if x >= y { x } else { y }) + } + + async fn max_double(_: &Jvm, _: &mut RuntimeContext, x: f64, y: f64) -> Result { + tracing::debug!("java.lang.Math::max({x:?}, {y:?})"); + + if x.is_nan() { + return Ok(x); + } + if y.is_nan() { + return Ok(y); + } + if x == 0.0 && y == 0.0 { + return Ok(if x.is_sign_positive() || y.is_sign_positive() { 0.0 } else { -0.0 }); + } + Ok(if x >= y { x } else { y }) + } + async fn min(_: &Jvm, _: &mut RuntimeContext, x: i32, y: i32) -> Result { tracing::debug!("java.lang.Math::min({x:?}, {y:?})"); - Ok(x.min(y)) } async fn min_long(_: &Jvm, _: &mut RuntimeContext, x: i64, y: i64) -> Result { tracing::debug!("java.lang.Math::min({x:?}, {y:?})"); - Ok(x.min(y)) } + + async fn min_float(_: &Jvm, _: &mut RuntimeContext, x: f32, y: f32) -> Result { + tracing::debug!("java.lang.Math::min({x:?}, {y:?})"); + + if x.is_nan() { + return Ok(x); + } + if y.is_nan() { + return Ok(y); + } + if x == 0.0 && y == 0.0 { + return Ok(if x.is_sign_negative() || y.is_sign_negative() { -0.0 } else { 0.0 }); + } + Ok(if x <= y { x } else { y }) + } + + async fn min_double(_: &Jvm, _: &mut RuntimeContext, x: f64, y: f64) -> Result { + tracing::debug!("java.lang.Math::min({x:?}, {y:?})"); + + if x.is_nan() { + return Ok(x); + } + if y.is_nan() { + return Ok(y); + } + if x == 0.0 && y == 0.0 { + return Ok(if x.is_sign_negative() || y.is_sign_negative() { -0.0 } else { 0.0 }); + } + Ok(if x <= y { x } else { y }) + } + + async fn acos(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::acos({x:?})"); + Ok(libm::acos(x)) + } + + async fn asin(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::asin({x:?})"); + Ok(libm::asin(x)) + } + + async fn atan(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::atan({x:?})"); + Ok(libm::atan(x)) + } + + async fn atan2(_: &Jvm, _: &mut RuntimeContext, y: f64, x: f64) -> Result { + tracing::debug!("java.lang.Math::atan2({y:?}, {x:?})"); + Ok(libm::atan2(y, x)) + } + + async fn exp(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::exp({x:?})"); + Ok(libm::exp(x)) + } + + async fn log(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::log({x:?})"); + Ok(libm::log(x)) + } + + async fn pow(_: &Jvm, _: &mut RuntimeContext, x: f64, y: f64) -> Result { + tracing::debug!("java.lang.Math::pow({x:?}, {y:?})"); + + if y == 0.0 { + return Ok(1.0); + } + if y.is_nan() { + return Ok(f64::NAN); + } + if (x == 1.0 || x == -1.0) && y.is_infinite() { + return Ok(f64::NAN); + } + Ok(libm::pow(x, y)) + } + + async fn rint(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::rint({x:?})"); + Ok(libm::rint(x)) + } + + async fn ieee_remainder(_: &Jvm, _: &mut RuntimeContext, x: f64, y: f64) -> Result { + tracing::debug!("java.lang.Math::IEEEremainder({x:?}, {y:?})"); + Ok(libm::remainder(x, y)) + } + + async fn round_float(_: &Jvm, _: &mut RuntimeContext, x: f32) -> Result { + tracing::debug!("java.lang.Math::round({x:?})"); + + if x.is_nan() { + return Ok(0); + } + if x >= i32::MAX as f32 { + return Ok(i32::MAX); + } + if x <= i32::MIN as f32 { + return Ok(i32::MIN); + } + Ok(libm::floorf(x + 0.5) as i32) + } + + async fn round_double(_: &Jvm, _: &mut RuntimeContext, x: f64) -> Result { + tracing::debug!("java.lang.Math::round({x:?})"); + + if x.is_nan() { + return Ok(0); + } + if x >= i64::MAX as f64 { + return Ok(i64::MAX); + } + if x <= i64::MIN as f64 { + return Ok(i64::MIN); + } + Ok(libm::floor(x + 0.5) as i64) + } + + async fn random(jvm: &Jvm, _: &mut RuntimeContext) -> Result { + tracing::debug!("java.lang.Math::random()"); + + let mut random: ClassInstanceRef = jvm + .get_static_field("java/lang/Math", "randomNumberGenerator", "Ljava/util/Random;") + .await?; + if random.is_null() { + random = jvm.new_class("java/util/Random", "()V", ()).await?.into(); + jvm.put_static_field("java/lang/Math", "randomNumberGenerator", "Ljava/util/Random;", random.clone()) + .await?; + } + jvm.invoke_virtual(&random, "nextDouble", "()D", ()).await + } } diff --git a/java_runtime/src/classes/java/lang/no_such_field_exception.rs b/java_runtime/src/classes/java/lang/no_such_field_exception.rs new file mode 100644 index 00000000..b52a7f29 --- /dev/null +++ b/java_runtime/src/classes/java/lang/no_such_field_exception.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.NoSuchFieldException +pub struct NoSuchFieldException; + +impl NoSuchFieldException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/NoSuchFieldException", + parent_class: Some("java/lang/Exception"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.NoSuchFieldException::({this:?})"); + + jvm.invoke_special(&this, "java/lang/Exception", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.NoSuchFieldException::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/Exception", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/no_such_method_exception.rs b/java_runtime/src/classes/java/lang/no_such_method_exception.rs new file mode 100644 index 00000000..a921ba2f --- /dev/null +++ b/java_runtime/src/classes/java/lang/no_such_method_exception.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.NoSuchMethodException +pub struct NoSuchMethodException; + +impl NoSuchMethodException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/NoSuchMethodException", + parent_class: Some("java/lang/Exception"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.NoSuchMethodException::({this:?})"); + + jvm.invoke_special(&this, "java/lang/Exception", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.NoSuchMethodException::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/Exception", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/object.rs b/java_runtime/src/classes/java/lang/object.rs index 59539419..a64a1ebd 100644 --- a/java_runtime/src/classes/java/lang/object.rs +++ b/java_runtime/src/classes/java/lang/object.rs @@ -37,6 +37,12 @@ impl Hasher for IdentityHasher { } impl Object { + pub(crate) fn identity_hash_code(object: &ClassInstanceRef) -> i32 { + let mut hasher = IdentityHasher::default(); + object.hash(&mut hasher); + hasher.finish() as i32 + } + pub fn as_proto() -> RuntimeClassProto { RuntimeClassProto { name: "java/lang/Object", @@ -82,13 +88,7 @@ impl Object { async fn hash_code(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.lang.Object::hashCode({this:?})"); - let rust_this: Box = this.into(); - - let mut hasher = IdentityHasher::default(); - rust_this.hash(&mut hasher); - let hash = hasher.finish(); - - Ok(hash as _) + Ok(Self::identity_hash_code(&this)) } async fn equals(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { diff --git a/java_runtime/src/classes/java/lang/runtime.rs b/java_runtime/src/classes/java/lang/runtime.rs index 9fbfcda9..71b9a17c 100644 --- a/java_runtime/src/classes/java/lang/runtime.rs +++ b/java_runtime/src/classes/java/lang/runtime.rs @@ -21,6 +21,7 @@ impl Runtime { JavaMethodProto::new("totalMemory", "()J", Self::total_memory, Default::default()), JavaMethodProto::new("freeMemory", "()J", Self::free_memory, Default::default()), JavaMethodProto::new("gc", "()V", Self::gc, Default::default()), + JavaMethodProto::new("exit", "(I)V", Self::exit, MethodAccessFlags::PUBLIC), ], fields: vec![], access_flags: Default::default(), @@ -60,4 +61,11 @@ impl Runtime { Ok(()) } + + async fn exit(_: &Jvm, context: &mut RuntimeContext, _: ClassInstanceRef, status: i32) -> Result<()> { + tracing::debug!("java.lang.Runtime::exit({status})"); + + context.exit(status); + Ok(()) + } } diff --git a/java_runtime/src/classes/java/lang/stack_overflow_error.rs b/java_runtime/src/classes/java/lang/stack_overflow_error.rs new file mode 100644 index 00000000..04c9e89c --- /dev/null +++ b/java_runtime/src/classes/java/lang/stack_overflow_error.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.StackOverflowError +pub struct StackOverflowError; + +impl StackOverflowError { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/StackOverflowError", + parent_class: Some("java/lang/VirtualMachineError"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.StackOverflowError::({this:?})"); + + jvm.invoke_special(&this, "java/lang/VirtualMachineError", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.StackOverflowError::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/VirtualMachineError", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/lang/string.rs b/java_runtime/src/classes/java/lang/string.rs index 3f3907fd..38841b66 100644 --- a/java_runtime/src/classes/java/lang/string.rs +++ b/java_runtime/src/classes/java/lang/string.rs @@ -1,5 +1,3 @@ -use core::cmp::Ordering; - use alloc::{ format, string::{String as RustString, ToString}, @@ -10,7 +8,7 @@ use alloc::{ use bytemuck::{cast_slice, cast_vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::MethodAccessFlags; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; use crate::{ @@ -28,7 +26,7 @@ impl String { RuntimeClassProto { name: "java/lang/String", parent_class: Some("java/lang/Object"), - interfaces: vec![], + interfaces: vec!["java/io/Serializable", "java/lang/Comparable"], methods: vec![ JavaMethodProto::new("", "()V", Self::init_empty, Default::default()), JavaMethodProto::new("", "([B)V", Self::init_with_byte_array, Default::default()), @@ -51,7 +49,14 @@ impl String { JavaMethodProto::new("", "(Ljava/lang/StringBuffer;)V", Self::init_with_string_buffer, Default::default()), JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, Default::default()), JavaMethodProto::new("equalsIgnoreCase", "(Ljava/lang/String;)Z", Self::equals_ignore_case, Default::default()), - JavaMethodProto::new("compareTo", "(Ljava/lang/String;)I", Self::compare_to, Default::default()), + JavaMethodProto::new("compareTo", "(Ljava/lang/String;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "compareToIgnoreCase", + "(Ljava/lang/String;)I", + Self::compare_to_ignore_case, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("hashCode", "()I", Self::hash_code, Default::default()), JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), JavaMethodProto::new("charAt", "(I)C", Self::char_at, Default::default()), @@ -60,19 +65,47 @@ impl String { JavaMethodProto::new("getChars", "(II[CI)V", Self::get_chars, Default::default()), JavaMethodProto::new("toCharArray", "()[C", Self::to_char_array, Default::default()), JavaMethodProto::new("toUpperCase", "()Ljava/lang/String;", Self::to_upper_case, Default::default()), + JavaMethodProto::new( + "toUpperCase", + "(Ljava/util/Locale;)Ljava/lang/String;", + Self::to_upper_case_locale, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("toLowerCase", "()Ljava/lang/String;", Self::to_lower_case, Default::default()), + JavaMethodProto::new( + "toLowerCase", + "(Ljava/util/Locale;)Ljava/lang/String;", + Self::to_lower_case_locale, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("length", "()I", Self::length, Default::default()), JavaMethodProto::new("concat", "(Ljava/lang/String;)Ljava/lang/String;", Self::concat, Default::default()), JavaMethodProto::new("substring", "(I)Ljava/lang/String;", Self::substring, Default::default()), JavaMethodProto::new("substring", "(II)Ljava/lang/String;", Self::substring_with_end, Default::default()), JavaMethodProto::new("replace", "(CC)Ljava/lang/String;", Self::replace, Default::default()), + JavaMethodProto::new( + "regionMatches", + "(ILjava/lang/String;II)Z", + Self::region_matches_case_sensitive, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("regionMatches", "(ZILjava/lang/String;II)Z", Self::region_matches, Default::default()), JavaMethodProto::new("valueOf", "(Z)Ljava/lang/String;", Self::value_of_boolean, MethodAccessFlags::STATIC), JavaMethodProto::new("valueOf", "(C)Ljava/lang/String;", Self::value_of_char, MethodAccessFlags::STATIC), JavaMethodProto::new("valueOf", "(I)Ljava/lang/String;", Self::value_of_integer, MethodAccessFlags::STATIC), JavaMethodProto::new("valueOf", "(J)Ljava/lang/String;", Self::value_of_long, MethodAccessFlags::STATIC), - JavaMethodProto::new("valueOf", "(F)Ljava/lang/String;", Self::value_of_float, MethodAccessFlags::STATIC), - JavaMethodProto::new("valueOf", "(D)Ljava/lang/String;", Self::value_of_double, MethodAccessFlags::STATIC), + JavaMethodProto::new( + "valueOf", + "(F)Ljava/lang/String;", + Self::value_of_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(D)Ljava/lang/String;", + Self::value_of_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new("valueOf", "([C)Ljava/lang/String;", Self::value_of_char_array, MethodAccessFlags::STATIC), JavaMethodProto::new( "valueOf", @@ -92,14 +125,38 @@ impl String { JavaMethodProto::new("indexOf", "(Ljava/lang/String;I)I", Self::index_of_string_from, Default::default()), JavaMethodProto::new("lastIndexOf", "(I)I", Self::last_index_of, Default::default()), JavaMethodProto::new("lastIndexOf", "(II)I", Self::last_index_of_from, Default::default()), + JavaMethodProto::new( + "lastIndexOf", + "(Ljava/lang/String;)I", + Self::last_index_of_string, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "lastIndexOf", + "(Ljava/lang/String;I)I", + Self::last_index_of_string_from, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "copyValueOf", + "([C)Ljava/lang/String;", + Self::copy_value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "copyValueOf", + "([CII)Ljava/lang/String;", + Self::copy_value_of_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new("trim", "()Ljava/lang/String;", Self::trim, Default::default()), JavaMethodProto::new("startsWith", "(Ljava/lang/String;)Z", Self::starts_with, Default::default()), JavaMethodProto::new("startsWith", "(Ljava/lang/String;I)Z", Self::starts_with_offset, Default::default()), JavaMethodProto::new("endsWith", "(Ljava/lang/String;)Z", Self::ends_with, Default::default()), JavaMethodProto::new("intern", "()Ljava/lang/String;", Self::intern, Default::default()), ], - fields: vec![JavaFieldProto::new("value", "[C", Default::default())], - access_flags: Default::default(), + fields: vec![JavaFieldProto::new("value", "[C", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, } } @@ -144,6 +201,19 @@ impl String { let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let length = jvm.array_length(&value).await? as i32; + if offset < 0 || count < 0 || offset > length - count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &format!("offset {offset}, count {count}, length {length}"), + ) + .await); + } + let mut array = jvm.instantiate_array("C", count as _).await?; jvm.put_field(&mut this, "value", "[C", array.clone()).await?; @@ -218,19 +288,76 @@ impl String { if this_string == other_string { Ok(true) } else { Ok(false) } } - async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { tracing::debug!("java.lang.String::compareTo({this:?}, {other:?})"); - let other_string = JavaLangString::to_rust_string(jvm, &other).await?; - let this_string = JavaLangString::to_rust_string(jvm, &this).await?; + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "anotherString is null").await); + } + if !jvm.is_instance(&**other, "java/lang/String") { + return Err(jvm.exception("java/lang/ClassCastException", &other.class_definition().name()).await); + } - let compare_result = this_string.cmp(&other_string); + let other: ClassInstanceRef = ClassInstanceRef::new(other.instance); + let this_value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let other_value: ClassInstanceRef> = jvm.get_field(&other, "value", "[C").await?; + let this_length = jvm.array_length(&this_value).await?; + let other_length = jvm.array_length(&other_value).await?; + let this_chars: Vec = jvm.load_array(&this_value, 0, this_length).await?; + let other_chars: Vec = jvm.load_array(&other_value, 0, other_length).await?; + + for (&this_char, &other_char) in this_chars.iter().zip(&other_chars) { + if this_char != other_char { + return Ok(this_char as i32 - other_char as i32); + } + } - match compare_result { - Ordering::Less => Ok(-1), - Ordering::Equal => Ok(0), - Ordering::Greater => Ok(1), + Ok(this_length as i32 - other_length as i32) + } + + async fn compare_to_ignore_case(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.String::compareToIgnoreCase({this:?}, {other:?})"); + + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); } + + let this_value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let other_value: ClassInstanceRef> = jvm.get_field(&other, "value", "[C").await?; + let this_length = jvm.array_length(&this_value).await?; + let other_length = jvm.array_length(&other_value).await?; + let this_chars: Vec = jvm.load_array(&this_value, 0, this_length).await?; + let other_chars: Vec = jvm.load_array(&other_value, 0, other_length).await?; + + for (&this_char, &other_char) in this_chars.iter().zip(&other_chars) { + if this_char == other_char { + continue; + } + + let this_folded = if this_char <= 0x7f { + (this_char as u8).to_ascii_lowercase() as JavaChar + } else { + char::from_u32(this_char as u32) + .and_then(|value| value.to_uppercase().next()) + .and_then(|value| value.to_lowercase().next()) + .map(|value| value as JavaChar) + .unwrap_or(this_char) + }; + let other_folded = if other_char <= 0x7f { + (other_char as u8).to_ascii_lowercase() as JavaChar + } else { + char::from_u32(other_char as u32) + .and_then(|value| value.to_uppercase().next()) + .and_then(|value| value.to_lowercase().next()) + .map(|value| value as JavaChar) + .unwrap_or(other_char) + }; + if this_folded != other_folded { + return Ok(this_folded as i32 - other_folded as i32); + } + } + + Ok(this_length as i32 - other_length as i32) } async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { @@ -510,6 +637,21 @@ impl String { Ok(JavaLangString::from_rust_string(jvm, &upper).await?.into()) } + async fn to_upper_case_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + locale: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.lang.String::toUpperCase({this:?}, {locale:?})"); + + if locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "locale is null").await); + } + + jvm.invoke_virtual(&this, "toUpperCase", "()Ljava/lang/String;", ()).await + } + async fn starts_with(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, prefix: ClassInstanceRef) -> Result { tracing::debug!("java.lang.String::startsWith({this:?}, {prefix:?})"); @@ -657,6 +799,21 @@ impl String { Ok(JavaLangString::from_rust_string(jvm, &lower).await?.into()) } + async fn to_lower_case_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + locale: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.lang.String::toLowerCase({this:?}, {locale:?})"); + + if locale.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "locale is null").await); + } + + jvm.invoke_virtual(&this, "toLowerCase", "()Ljava/lang/String;", ()).await + } + async fn replace( jvm: &Jvm, _: &mut RuntimeContext, @@ -701,33 +858,46 @@ impl String { return Ok(false); } - let this_string = JavaLangString::to_rust_string(jvm, &this).await?; - let other_string = JavaLangString::to_rust_string(jvm, &other).await?; - - let this_chars: Vec = this_string.encode_utf16().collect(); - let other_chars: Vec = other_string.encode_utf16().collect(); - + let this_value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let other_value: ClassInstanceRef> = jvm.get_field(&other, "value", "[C").await?; + let this_length = jvm.array_length(&this_value).await?; + let other_length = jvm.array_length(&other_value).await?; let end_t = toffset as usize + len as usize; let end_o = ooffset as usize + len as usize; - if end_t > this_chars.len() || end_o > other_chars.len() { + if end_t > this_length || end_o > other_length { return Ok(false); } - let this_slice = &this_chars[toffset as usize..end_t]; - let other_slice = &other_chars[ooffset as usize..end_o]; + let this_chars: Vec = jvm.load_array(&this_value, toffset as usize, len as usize).await?; + let other_chars: Vec = jvm.load_array(&other_value, ooffset as usize, len as usize).await?; if ignore_case { - let to_lower = |c: u16| -> u16 { + let to_lower = |c: JavaChar| -> JavaChar { char::from_u32(c as u32) .map(|ch| ch.to_lowercase().next().unwrap_or(ch) as u32 as u16) .unwrap_or(c) }; - Ok(this_slice.iter().copied().map(to_lower).eq(other_slice.iter().copied().map(to_lower))) + Ok(this_chars.iter().copied().map(to_lower).eq(other_chars.iter().copied().map(to_lower))) } else { - Ok(this_slice == other_slice) + Ok(this_chars == other_chars) } } + async fn region_matches_case_sensitive( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + toffset: i32, + other: ClassInstanceRef, + ooffset: i32, + len: i32, + ) -> Result { + tracing::debug!("java.lang.String::regionMatches({this:?}, {toffset}, {other:?}, {ooffset}, {len})"); + + jvm.invoke_virtual(&this, "regionMatches", "(ZILjava/lang/String;II)Z", (false, toffset, other, ooffset, len)) + .await + } + async fn last_index_of_from(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, ch: i32, from_index: i32) -> Result { tracing::debug!("java.lang.String::lastIndexOf({this:?}, {ch}, {from_index})"); @@ -749,6 +919,54 @@ impl String { Ok(index.unwrap_or(-1)) } + async fn last_index_of_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, str: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.String::lastIndexOf({this:?}, {str:?})"); + + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let length = jvm.array_length(&value).await? as i32; + jvm.invoke_virtual(&this, "lastIndexOf", "(Ljava/lang/String;I)I", (str, length)).await + } + + async fn last_index_of_string_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + str: ClassInstanceRef, + from_index: i32, + ) -> Result { + tracing::debug!("java.lang.String::lastIndexOf({this:?}, {str:?}, {from_index})"); + + if str.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + if from_index < 0 { + return Ok(-1); + } + + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let pattern_value: ClassInstanceRef> = jvm.get_field(&str, "value", "[C").await?; + let length = jvm.array_length(&value).await?; + let pattern_length = jvm.array_length(&pattern_value).await?; + let chars: Vec = jvm.load_array(&value, 0, length).await?; + let pattern: Vec = jvm.load_array(&pattern_value, 0, pattern_length).await?; + + if pattern.is_empty() { + return Ok((from_index as usize).min(chars.len()) as i32); + } + if pattern.len() > chars.len() { + return Ok(-1); + } + + let last_start = (from_index as usize).min(chars.len() - pattern.len()); + for index in (0..=last_start).rev() { + if chars[index..].starts_with(&pattern) { + return Ok(index as i32); + } + } + + Ok(-1) + } + async fn ends_with(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, suffix: ClassInstanceRef) -> Result { tracing::debug!("java.lang.String::endsWith({this:?}, {suffix:?})"); @@ -790,13 +1008,13 @@ impl String { async fn value_of_float(jvm: &Jvm, _: &mut RuntimeContext, value: f32) -> Result> { tracing::debug!("java.lang.String::valueOf({value})"); - Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into()) + jvm.invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)).await } async fn value_of_double(jvm: &Jvm, _: &mut RuntimeContext, value: f64) -> Result> { tracing::debug!("java.lang.String::valueOf({value})"); - Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into()) + jvm.invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)).await } async fn value_of_char_array(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef>) -> Result> { @@ -821,6 +1039,42 @@ impl String { Ok(new_string.into()) } + async fn copy_value_of(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef>) -> Result> { + tracing::debug!("java.lang.String::copyValueOf({value:?})"); + + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "data is null").await); + } + + let length = jvm.array_length(&value).await? as i32; + Ok(jvm.new_class("java/lang/String", "([CII)V", (value, 0, length)).await?.into()) + } + + async fn copy_value_of_range( + jvm: &Jvm, + _: &mut RuntimeContext, + value: ClassInstanceRef>, + offset: i32, + count: i32, + ) -> Result> { + tracing::debug!("java.lang.String::copyValueOf({value:?}, {offset}, {count})"); + + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "data is null").await); + } + let length = jvm.array_length(&value).await? as i32; + if offset < 0 || count < 0 || offset > length - count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &format!("offset {offset}, count {count}, length {length}"), + ) + .await); + } + + Ok(jvm.new_class("java/lang/String", "([CII)V", (value, offset, count)).await?.into()) + } + fn decode_str(charset: &str, bytes: &[u8]) -> Option { Some(match charset.to_ascii_uppercase().replace('_', "-").as_str() { "UTF-8" | "UTF8" => RustString::from_utf8_lossy(bytes).into_owned(), diff --git a/java_runtime/src/classes/java/lang/string_buffer.rs b/java_runtime/src/classes/java/lang/string_buffer.rs index 23efd99d..068327da 100644 --- a/java_runtime/src/classes/java/lang/string_buffer.rs +++ b/java_runtime/src/classes/java/lang/string_buffer.rs @@ -1,6 +1,7 @@ use alloc::{string::ToString, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; use crate::{ @@ -8,7 +9,7 @@ use crate::{ classes::java::lang::{Object, String}, }; -// class java.lang.StringBuffer +// public final class java.lang.StringBuffer pub struct StringBuffer; impl StringBuffer { @@ -18,57 +19,228 @@ impl StringBuffer { parent_class: Some("java/lang/Object"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(I)V", Self::init_with_buffer_length, Default::default()), - JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_string, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_string, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", Self::append_string, - Default::default(), + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), JavaMethodProto::new( "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", Self::append_object, - Default::default(), - ), - JavaMethodProto::new("append", "(Z)Ljava/lang/StringBuffer;", Self::append_boolean, Default::default()), - JavaMethodProto::new("append", "(I)Ljava/lang/StringBuffer;", Self::append_integer, Default::default()), - JavaMethodProto::new("append", "(J)Ljava/lang/StringBuffer;", Self::append_long, Default::default()), - JavaMethodProto::new("append", "(C)Ljava/lang/StringBuffer;", Self::append_character, Default::default()), - JavaMethodProto::new("append", "([CII)Ljava/lang/StringBuffer;", Self::append_char_array, Default::default()), - JavaMethodProto::new("delete", "(II)Ljava/lang/StringBuffer;", Self::delete, Default::default()), - JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), - JavaMethodProto::new("setLength", "(I)V", Self::set_length, Default::default()), - JavaMethodProto::new("length", "()I", Self::length, Default::default()), - JavaMethodProto::new("charAt", "(I)C", Self::char_at, Default::default()), + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(Z)Ljava/lang/StringBuffer;", + Self::append_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/StringBuffer;", + Self::append_character, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(I)Ljava/lang/StringBuffer;", + Self::append_integer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(J)Ljava/lang/StringBuffer;", + Self::append_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(F)Ljava/lang/StringBuffer;", + Self::append_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(D)Ljava/lang/StringBuffer;", + Self::append_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "([C)Ljava/lang/StringBuffer;", + Self::append_char_array_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "([CII)Ljava/lang/StringBuffer;", + Self::append_char_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/String;)Ljava/lang/StringBuffer;", + Self::insert_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/Object;)Ljava/lang/StringBuffer;", + Self::insert_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(IZ)Ljava/lang/StringBuffer;", + Self::insert_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(IC)Ljava/lang/StringBuffer;", + Self::insert_character, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(II)Ljava/lang/StringBuffer;", + Self::insert_integer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(IJ)Ljava/lang/StringBuffer;", + Self::insert_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(IF)Ljava/lang/StringBuffer;", + Self::insert_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(ID)Ljava/lang/StringBuffer;", + Self::insert_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "insert", + "(I[C)Ljava/lang/StringBuffer;", + Self::insert_char_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "delete", + "(II)Ljava/lang/StringBuffer;", + Self::delete, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "deleteCharAt", + "(I)Ljava/lang/StringBuffer;", + Self::delete_char_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "replace", + "(IILjava/lang/String;)Ljava/lang/StringBuffer;", + Self::replace, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "substring", + "(I)Ljava/lang/String;", + Self::substring, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "substring", + "(II)Ljava/lang/String;", + Self::substring_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "reverse", + "()Ljava/lang/StringBuffer;", + Self::reverse, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "toString", + "()Ljava/lang/String;", + Self::to_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "capacity", + "()I", + Self::capacity, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "ensureCapacity", + "(I)V", + Self::ensure_capacity, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "setLength", + "(I)V", + Self::set_length, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("length", "()I", Self::length, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new( + "charAt", + "(I)C", + Self::char_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "setCharAt", + "(IC)V", + Self::set_char_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "getChars", + "(II[CI)V", + Self::get_chars, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), ], fields: vec![ - JavaFieldProto::new("value", "[C", Default::default()), - JavaFieldProto::new("count", "I", Default::default()), + JavaFieldProto::new("value", "[C", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("count", "I", FieldAccessFlags::PRIVATE), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, } } async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.StringBuffer::({this:?})"); - let _: () = jvm.invoke_special(&this, "java/lang/StringBuffer", "", "(I)V", (16,)).await?; - - Ok(()) + jvm.invoke_special(&this, "java/lang/StringBuffer", "", "(I)V", (16,)).await } - async fn init_with_buffer_length(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, length: i32) -> Result<()> { - tracing::debug!("java.lang.StringBuffer::({this:?}, {length:?})"); + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, capacity: i32) -> Result<()> { + tracing::debug!("java.lang.StringBuffer::({this:?}, {capacity})"); let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + if capacity < 0 { + return Err(jvm.exception("java/lang/NegativeArraySizeException", &capacity.to_string()).await); + } - let array = jvm.instantiate_array("C", length as _).await?; - jvm.put_field(&mut this, "value", "[C", array).await?; + let value = jvm.instantiate_array("C", capacity as usize).await?; + jvm.put_field(&mut this, "value", "[C", value).await?; jvm.put_field(&mut this, "count", "I", 0).await?; - Ok(()) } @@ -76,13 +248,17 @@ impl StringBuffer { tracing::debug!("java.lang.StringBuffer::({this:?}, {string:?})"); let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } - let value_array = jvm.invoke_virtual(&string, "toCharArray", "()[C", ()).await?; - let length = jvm.array_length(&value_array).await? as i32; - - jvm.put_field(&mut this, "value", "[C", value_array).await?; - jvm.put_field(&mut this, "count", "I", length).await?; - + let string_value: ClassInstanceRef> = jvm.get_field(&string, "value", "[C").await?; + let count = jvm.array_length(&string_value).await?; + let chars: Vec = jvm.load_array(&string_value, 0, count).await?; + let mut value = jvm.instantiate_array("C", count + 16).await?; + jvm.store_array(&mut value, 0, chars).await?; + jvm.put_field(&mut this, "value", "[C", value).await?; + jvm.put_field(&mut this, "count", "I", count as i32).await?; Ok(()) } @@ -94,89 +270,274 @@ impl StringBuffer { ) -> Result> { tracing::debug!("java.lang.StringBuffer::append({this:?}, {string:?})"); - let string = if string.is_null() { - "null".into() + let chars = if string.is_null() { + "null".encode_utf16().collect() } else { - JavaLangString::to_rust_string(jvm, &string).await? + let value: ClassInstanceRef> = jvm.get_field(&string, "value", "[C").await?; + jvm.load_array(&value, 0, jvm.array_length(&value).await?).await? }; - - Self::append(jvm, &mut this, &string).await?; - + Self::append_utf16(jvm, &mut this, chars).await?; Ok(this) } async fn append_object( jvm: &Jvm, _: &mut RuntimeContext, - mut this: ClassInstanceRef, + this: ClassInstanceRef, object: ClassInstanceRef, ) -> Result> { tracing::debug!("java.lang.StringBuffer::append({this:?}, {object:?})"); - let string = if object.is_null() { - "null".into() + let string: ClassInstanceRef = if object.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() } else { - let string = jvm.invoke_virtual(&object, "toString", "()Ljava/lang/String;", ()).await?; - JavaLangString::to_rust_string(jvm, &string).await? + jvm.invoke_virtual(&object, "toString", "()Ljava/lang/String;", ()).await? }; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await + } - Self::append(jvm, &mut this, &string).await?; + async fn append_boolean(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); + let string = JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await + } + + async fn append_character( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + value: JavaChar, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); + + Self::append_utf16(jvm, &mut this, vec![value]).await?; Ok(this) } - async fn append_integer(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result> { - tracing::debug!("java.lang.StringBuffer::append({this:?}, {value:?})"); + async fn append_integer(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); - let digits = value.to_string(); + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await + } - Self::append(jvm, &mut this, &digits).await?; + async fn append_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); - Ok(this) + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await + } + + async fn append_float(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); + + let string: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await } - async fn append_boolean(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: bool) -> Result> { - tracing::debug!("java.lang.StringBuffer::append({this:?}, {value:?})"); + async fn append_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); - let value = value.to_string(); + let string: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await + } - Self::append(jvm, &mut this, &value).await?; + async fn append_char_array_all( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + array: ClassInstanceRef>, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {array:?})"); + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let length = jvm.array_length(&array).await?; + let chars = jvm.load_array(&array, 0, length).await?; + Self::append_utf16(jvm, &mut this, chars).await?; Ok(this) } - async fn append_long(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i64) -> Result> { - tracing::debug!("java.lang.StringBuffer::append({this:?}, {value:?})"); + async fn append_char_array( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + array: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::append({this:?}, {array:?}, {offset}, {length})"); - let digits = value.to_string(); + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let array_length = jvm.array_length(&array).await? as i32; + if offset < 0 || length < 0 || offset > array_length - length { + return Err(jvm + .exception( + "java/lang/IndexOutOfBoundsException", + &alloc::format!("offset {offset}, length {length}, array length {array_length}"), + ) + .await); + } + let chars = jvm.load_array(&array, offset as usize, length as usize).await?; + Self::append_utf16(jvm, &mut this, chars).await?; + Ok(this) + } - Self::append(jvm, &mut this, &digits).await?; + async fn insert_string( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + string: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {string:?})"); + let chars = if string.is_null() { + "null".encode_utf16().collect() + } else { + let value: ClassInstanceRef> = jvm.get_field(&string, "value", "[C").await?; + jvm.load_array(&value, 0, jvm.array_length(&value).await?).await? + }; + Self::insert_utf16(jvm, &mut this, offset, chars).await?; Ok(this) } - async fn append_character(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: u16) -> Result> { - tracing::debug!("java.lang.StringBuffer::append({this:?}, {value:?})"); + async fn insert_object( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + object: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {object:?})"); - Self::append_utf16(jvm, &mut this, vec![value]).await?; + let string: ClassInstanceRef = if object.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + jvm.invoke_virtual(&object, "toString", "()Ljava/lang/String;", ()).await? + }; + jvm.invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, string)) + .await + } - Ok(this) + async fn insert_boolean( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: bool, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {value})"); + + let string = JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?; + jvm.invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, string)) + .await } - async fn append_char_array( + async fn insert_character( jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, - array: ClassInstanceRef>, offset: i32, - length: i32, + value: JavaChar, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {value})"); + + Self::insert_utf16(jvm, &mut this, offset, vec![value]).await?; + Ok(this) + } + + async fn insert_integer( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: i32, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {value})"); + + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, string)) + .await + } + + async fn insert_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, offset: i32, value: i64) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {value})"); + + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, string)) + .await + } + + async fn insert_float( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: f32, ) -> Result> { - tracing::debug!("java.lang.StringBuffer::append({this:?}, {array:?}, {offset:?}, {length:?})"); + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {value})"); - let value: Vec = jvm.load_array(&array, offset as _, length as _).await?; + let string: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .await?; + jvm.invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, string)) + .await + } - Self::append_utf16(jvm, &mut this, value).await?; + async fn insert_double( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: f64, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {value})"); + + let string: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .await?; + jvm.invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, string)) + .await + } + + async fn insert_char_array( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + array: ClassInstanceRef>, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::insert({this:?}, {offset}, {array:?})"); + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if offset < 0 || offset > count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("offset {offset}, length {count}"), + ) + .await); + } + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let chars = jvm.load_array(&array, 0, jvm.array_length(&array).await?).await?; + Self::insert_utf16(jvm, &mut this, offset, chars).await?; Ok(this) } @@ -184,91 +545,289 @@ impl StringBuffer { tracing::debug!("java.lang.StringBuffer::delete({this:?}, {start}, {end})"); let count: i32 = jvm.get_field(&this, "count", "I").await?; + let end = end.min(count); + if start < 0 || start > end { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("start {start}, end {end}, length {count}"), + ) + .await); + } - let mut java_value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; - let chars: Vec = jvm.load_array(&java_value, 0, count as _).await?; + if start != end { + let mut value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let tail: Vec = jvm.load_array(&value, end as usize, (count - end) as usize).await?; + jvm.store_array(&mut value, start as usize, tail).await?; + jvm.put_field(&mut this, "count", "I", count - (end - start)).await?; + } + Ok(this) + } - let new_chars = chars - .iter() - .take(start as _) - .chain(chars.iter().skip(end as _)) - .cloned() - .collect::>(); - let new_count = new_chars.len() as i32; + async fn delete_char_at(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, index: i32) -> Result> { + tracing::debug!("java.lang.StringBuffer::deleteCharAt({this:?}, {index})"); - jvm.store_array(&mut java_value, 0, new_chars).await?; - jvm.put_field(&mut this, "count", "I", new_count).await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if index < 0 || index >= count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("index {index}, length {count}"), + ) + .await); + } + let mut value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let tail: Vec = jvm.load_array(&value, (index + 1) as usize, (count - index - 1) as usize).await?; + jvm.store_array(&mut value, index as usize, tail).await?; + jvm.put_field(&mut this, "count", "I", count - 1).await?; Ok(this) } - async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { - tracing::debug!("java.lang.StringBuffer::toString({this:?})"); + async fn replace( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + start: i32, + end: i32, + string: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::replace({this:?}, {start}, {end}, {string:?})"); - let java_value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; let count: i32 = jvm.get_field(&this, "count", "I").await?; + let end = end.min(count); + if start < 0 || start > count || start > end { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("start {start}, end {end}, length {count}"), + ) + .await); + } + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } - let string = jvm.new_class("java/lang/String", "([CII)V", (java_value, 0, count)).await?; + let string_value: ClassInstanceRef> = jvm.get_field(&string, "value", "[C").await?; + let replacement: Vec = jvm.load_array(&string_value, 0, jvm.array_length(&string_value).await?).await?; + let replacement_length = replacement.len() as i32; + let new_count = count + replacement_length - (end - start); + Self::expand_capacity(jvm, &mut this, new_count).await?; - Ok(string.into()) + let mut value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let tail: Vec = jvm.load_array(&value, end as usize, (count - end) as usize).await?; + jvm.store_array(&mut value, (start + replacement_length) as usize, tail).await?; + jvm.store_array(&mut value, start as usize, replacement).await?; + jvm.put_field(&mut this, "count", "I", new_count).await?; + Ok(this) } - async fn ensure_capacity(jvm: &Jvm, this: &mut ClassInstanceRef, capacity: usize) -> Result<()> { - let java_value_array = jvm.get_field(this, "value", "[C").await?; - let current_capacity = jvm.array_length(&java_value_array).await?; + async fn substring(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, start: i32) -> Result> { + tracing::debug!("java.lang.StringBuffer::substring({this:?}, {start})"); - if current_capacity < capacity { - let old_values: Vec = jvm.load_array(&java_value_array, 0, current_capacity).await?; - let new_capacity = capacity * 2; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + jvm.invoke_virtual(&this, "substring", "(II)Ljava/lang/String;", (start, count)).await + } + + async fn substring_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + tracing::debug!("java.lang.StringBuffer::substring({this:?}, {start}, {end})"); - let mut java_new_value_array = jvm.instantiate_array("C", new_capacity).await?; - jvm.put_field(this, "value", "[C", java_new_value_array.clone()).await?; - jvm.store_array(&mut java_new_value_array, 0, old_values).await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if start < 0 || end > count || start > end { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("start {start}, end {end}, length {count}"), + ) + .await); } - Ok(()) + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + Ok(jvm.new_class("java/lang/String", "([CII)V", (value, start, end - start)).await?.into()) } - async fn append(jvm: &Jvm, this: &mut ClassInstanceRef, string: &str) -> Result<()> { - Self::append_utf16(jvm, this, string.encode_utf16().collect()).await + async fn reverse(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.lang.StringBuffer::reverse({this:?})"); + + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let mut value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let mut chars: Vec = jvm.load_array(&value, 0, count as usize).await?; + chars.reverse(); + jvm.store_array(&mut value, 0, chars).await?; + Ok(this) } - async fn append_utf16(jvm: &Jvm, this: &mut ClassInstanceRef, value_to_add: Vec) -> Result<()> { - let current_count: i32 = jvm.get_field(this, "count", "I").await?; + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.lang.StringBuffer::toString({this:?})"); - let count_to_add = value_to_add.len() as i32; + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let count: i32 = jvm.get_field(&this, "count", "I").await?; + Ok(jvm.new_class("java/lang/String", "([CII)V", (value, 0, count)).await?.into()) + } + + async fn capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.StringBuffer::capacity({this:?})"); - StringBuffer::ensure_capacity(jvm, this, (current_count + count_to_add) as _).await?; + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + Ok(jvm.array_length(&value).await? as i32) + } - let mut java_value_array = jvm.get_field(this, "value", "[C").await?; - jvm.store_array(&mut java_value_array, current_count as _, value_to_add).await?; - jvm.put_field(this, "count", "I", current_count + count_to_add).await?; + async fn ensure_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, minimum_capacity: i32) -> Result<()> { + tracing::debug!("java.lang.StringBuffer::ensureCapacity({this:?}, {minimum_capacity})"); + if minimum_capacity > 0 { + Self::expand_capacity(jvm, &mut this, minimum_capacity).await?; + } Ok(()) } - async fn set_length(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, length: i32) -> Result<()> { - tracing::debug!("java.lang.StringBuffer::setLength({this:?}, {length:?})"); + async fn set_length(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, new_length: i32) -> Result<()> { + tracing::debug!("java.lang.StringBuffer::setLength({this:?}, {new_length})"); - jvm.put_field(&mut this, "count", "I", length).await?; + if new_length < 0 { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", &new_length.to_string()).await); + } + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if new_length > count { + Self::expand_capacity(jvm, &mut this, new_length).await?; + let mut value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + jvm.store_array(&mut value, count as usize, vec![0 as JavaChar; (new_length - count) as usize]) + .await?; + } + jvm.put_field(&mut this, "count", "I", new_length).await?; Ok(()) } async fn length(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.lang.StringBuffer::length({this:?})"); + jvm.get_field(&this, "count", "I").await + } + + async fn char_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { + tracing::debug!("java.lang.StringBuffer::charAt({this:?}, {index})"); + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if index < 0 || index >= count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("index {index}, length {count}"), + ) + .await); + } - Ok(count) + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + Ok(jvm.load_array(&value, index as usize, 1).await?[0]) } - async fn char_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { - tracing::debug!("java.lang.StringBuffer::charAt({this:?}, {index:?})"); + async fn set_char_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32, character: JavaChar) -> Result<()> { + tracing::debug!("java.lang.StringBuffer::setCharAt({this:?}, {index}, {character})"); + + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if index < 0 || index >= count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("index {index}, length {count}"), + ) + .await); + } + + let mut value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + jvm.store_array(&mut value, index as usize, [character]).await + } + + async fn get_chars( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + src_begin: i32, + src_end: i32, + mut destination: ClassInstanceRef>, + dst_begin: i32, + ) -> Result<()> { + tracing::debug!("java.lang.StringBuffer::getChars({this:?}, {src_begin}, {src_end}, {destination:?}, {dst_begin})"); + + let count: i32 = jvm.get_field(&this, "count", "I").await?; + if src_begin < 0 || src_end > count || src_begin > src_end { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("start {src_begin}, end {src_end}, length {count}"), + ) + .await); + } + if destination.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "dst is null").await); + } + + let copy_length = src_end - src_begin; + let destination_length = jvm.array_length(&destination).await? as i32; + if dst_begin < 0 || dst_begin > destination_length - copy_length { + return Err(jvm + .exception( + "java/lang/ArrayIndexOutOfBoundsException", + &alloc::format!("offset {dst_begin}, length {copy_length}, array length {destination_length}"), + ) + .await); + } + + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + let chars: Vec = jvm.load_array(&value, src_begin as usize, copy_length as usize).await?; + jvm.store_array(&mut destination, dst_begin as usize, chars).await + } - let java_value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; - let char_at: JavaChar = jvm.load_array(&java_value, index as _, 1).await?.into_iter().next().unwrap(); + async fn expand_capacity(jvm: &Jvm, this: &mut ClassInstanceRef, minimum_capacity: i32) -> Result<()> { + let value: ClassInstanceRef> = jvm.get_field(this, "value", "[C").await?; + let old_capacity = jvm.array_length(&value).await? as i32; + if minimum_capacity <= old_capacity { + return Ok(()); + } + + let new_capacity = minimum_capacity.max(old_capacity.saturating_mul(2).saturating_add(2)); + let count: i32 = jvm.get_field(this, "count", "I").await?; + let chars: Vec = jvm.load_array(&value, 0, count as usize).await?; + let mut new_value = jvm.instantiate_array("C", new_capacity as usize).await?; + jvm.store_array(&mut new_value, 0, chars).await?; + jvm.put_field(this, "value", "[C", new_value).await + } + + async fn append_utf16(jvm: &Jvm, this: &mut ClassInstanceRef, chars: Vec) -> Result<()> { + let count: i32 = jvm.get_field(this, "count", "I").await?; + let new_count = count + chars.len() as i32; + Self::expand_capacity(jvm, this, new_count).await?; + + let mut value: ClassInstanceRef> = jvm.get_field(this, "value", "[C").await?; + jvm.store_array(&mut value, count as usize, chars).await?; + jvm.put_field(this, "count", "I", new_count).await + } + + async fn insert_utf16(jvm: &Jvm, this: &mut ClassInstanceRef, offset: i32, chars: Vec) -> Result<()> { + let count: i32 = jvm.get_field(this, "count", "I").await?; + if offset < 0 || offset > count { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &alloc::format!("offset {offset}, length {count}"), + ) + .await); + } - Ok(char_at) + let inserted_length = chars.len() as i32; + let new_count = count + inserted_length; + Self::expand_capacity(jvm, this, new_count).await?; + let mut value: ClassInstanceRef> = jvm.get_field(this, "value", "[C").await?; + let tail: Vec = jvm.load_array(&value, offset as usize, (count - offset) as usize).await?; + jvm.store_array(&mut value, (offset + inserted_length) as usize, tail).await?; + jvm.store_array(&mut value, offset as usize, chars).await?; + jvm.put_field(this, "count", "I", new_count).await } } diff --git a/java_runtime/src/classes/java/lang/system.rs b/java_runtime/src/classes/java/lang/system.rs index dcdfe7c7..f94667a8 100644 --- a/java_runtime/src/classes/java/lang/system.rs +++ b/java_runtime/src/classes/java/lang/system.rs @@ -8,7 +8,11 @@ use jvm::{Array, ClassInstanceRef, Jvm, Result}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::{io::FileDescriptor, lang::String}, + classes::java::{ + io::{FileDescriptor, InputStream, PrintStream}, + lang::{Object, String}, + util::Properties, + }, }; // class java.lang.System @@ -48,11 +52,60 @@ impl System { MethodAccessFlags::STATIC, ), JavaMethodProto::new("exit", "(I)V", Self::exit, MethodAccessFlags::STATIC), + JavaMethodProto::new( + "identityHashCode", + "(Ljava/lang/Object;)I", + Self::identity_hash_code, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "setIn", + "(Ljava/io/InputStream;)V", + Self::set_in, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "setOut", + "(Ljava/io/PrintStream;)V", + Self::set_out, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "setErr", + "(Ljava/io/PrintStream;)V", + Self::set_err, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getProperties", + "()Ljava/util/Properties;", + Self::get_properties, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + Self::get_property_with_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), ], fields: vec![ - JavaFieldProto::new("out", "Ljava/io/PrintStream;", FieldAccessFlags::STATIC), - JavaFieldProto::new("err", "Ljava/io/PrintStream;", FieldAccessFlags::STATIC), - JavaFieldProto::new("props", "Ljava/util/Properties;", FieldAccessFlags::STATIC), + JavaFieldProto::new( + "in", + "Ljava/io/InputStream;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "out", + "Ljava/io/PrintStream;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "err", + "Ljava/io/PrintStream;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("props", "Ljava/util/Properties;", FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC), ], access_flags: Default::default(), } @@ -61,6 +114,15 @@ impl System { async fn cl_init(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { tracing::debug!("java.lang.System::()"); + let in_descriptor: ClassInstanceRef = + jvm.get_static_field("java/io/FileDescriptor", "in", "Ljava/io/FileDescriptor;").await?; + if !in_descriptor.is_null() { + let input = jvm + .new_class("java/io/FileInputStream", "(Ljava/io/FileDescriptor;)V", (in_descriptor,)) + .await?; + jvm.put_static_field("java/lang/System", "in", "Ljava/io/InputStream;", input).await?; + } + let out_descriptor: ClassInstanceRef = jvm.get_static_field("java/io/FileDescriptor", "out", "Ljava/io/FileDescriptor;").await?; let out_file_output_stream = jvm @@ -123,6 +185,10 @@ impl System { async fn get_property(jvm: &Jvm, _: &mut RuntimeContext, key: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.System::getProperty({key:?})"); + if key.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "key").await); + } + let props = jvm.get_static_field("java/lang/System", "props", "Ljava/util/Properties;").await?; let value = jvm .invoke_virtual(&props, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) @@ -131,6 +197,62 @@ impl System { Ok(value) } + async fn get_property_with_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default_value: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.lang.System::getProperty({key:?}, {default_value:?})"); + + if key.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "key").await); + } + + let props = jvm.get_static_field("java/lang/System", "props", "Ljava/util/Properties;").await?; + jvm.invoke_virtual( + &props, + "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (key, default_value), + ) + .await + } + + async fn get_properties(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { + tracing::debug!("java.lang.System::getProperties()"); + + jvm.get_static_field("java/lang/System", "props", "Ljava/util/Properties;").await + } + + async fn set_in(jvm: &Jvm, _: &mut RuntimeContext, input: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.System::setIn({input:?})"); + + jvm.put_static_field("java/lang/System", "in", "Ljava/io/InputStream;", input).await + } + + async fn set_out(jvm: &Jvm, _: &mut RuntimeContext, output: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.System::setOut({output:?})"); + + jvm.put_static_field("java/lang/System", "out", "Ljava/io/PrintStream;", output).await + } + + async fn set_err(jvm: &Jvm, _: &mut RuntimeContext, error: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.System::setErr({error:?})"); + + jvm.put_static_field("java/lang/System", "err", "Ljava/io/PrintStream;", error).await + } + + async fn identity_hash_code(_: &Jvm, _: &mut RuntimeContext, object: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.System::identityHashCode({object:?})"); + + if object.is_null() { + return Ok(0); + } + + Ok(Object::identity_hash_code(&object)) + } + async fn set_property( jvm: &Jvm, _: &mut RuntimeContext, diff --git a/java_runtime/src/classes/java/lang/thread.rs b/java_runtime/src/classes/java/lang/thread.rs index 5574c2c6..09877191 100644 --- a/java_runtime/src/classes/java/lang/thread.rs +++ b/java_runtime/src/classes/java/lang/thread.rs @@ -21,32 +21,93 @@ impl Thread { interfaces: vec!["java/lang/Runnable"], methods: vec![ JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(Ljava/lang/Runnable;)V", Self::init_with_runnable, Default::default()), - JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_name, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/Runnable;)V", Self::init_with_runnable, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_name, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "", "(Ljava/lang/Runnable;Ljava/lang/String;)V", Self::init_with_runnable_and_name, - Default::default(), + MethodAccessFlags::PUBLIC, ), - JavaMethodProto::new("start", "()V", Self::start, MethodAccessFlags::SYNCHRONIZED), - JavaMethodProto::new("join", "()V", Self::join, MethodAccessFlags::SYNCHRONIZED), - JavaMethodProto::new("run", "()V", Self::run, Default::default()), - JavaMethodProto::new("isAlive", "()Z", Self::is_alive, Default::default()), - JavaMethodProto::new("getName", "()Ljava/lang/String;", Self::get_name, Default::default()), - JavaMethodProto::new("getPriority", "()I", Self::get_priority, Default::default()), - JavaMethodProto::new("interrupt", "()V", Self::interrupt, Default::default()), - JavaMethodProto::new("activeCount", "()I", Self::active_count, MethodAccessFlags::STATIC), - JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), - JavaMethodProto::new("sleep", "(J)V", Self::sleep, MethodAccessFlags::NATIVE | MethodAccessFlags::STATIC), - JavaMethodProto::new("yield", "()V", Self::r#yield, MethodAccessFlags::NATIVE | MethodAccessFlags::STATIC), - JavaMethodProto::new("setPriority", "(I)V", Self::set_priority, Default::default()), + JavaMethodProto::new("start", "()V", Self::start, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new( + "join", + "()V", + Self::join, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "join", + "(J)V", + Self::join_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("run", "()V", Self::run, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("isAlive", "()Z", Self::is_alive, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + JavaMethodProto::new( + "getName", + "()Ljava/lang/String;", + Self::get_name, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new( + "setName", + "(Ljava/lang/String;)V", + Self::set_name, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new( + "getPriority", + "()I", + Self::get_priority, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new("interrupt", "()V", Self::interrupt, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("isInterrupted", "()Z", Self::is_interrupted, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "interrupted", + "()Z", + Self::interrupted, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "activeCount", + "()I", + Self::active_count, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "sleep", + "(J)V", + Self::sleep, + MethodAccessFlags::PUBLIC | MethodAccessFlags::NATIVE | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "yield", + "()V", + Self::r#yield, + MethodAccessFlags::PUBLIC | MethodAccessFlags::NATIVE | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "setPriority", + "(I)V", + Self::set_priority, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new( + "setDaemon", + "(Z)V", + Self::set_daemon, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new("isDaemon", "()Z", Self::is_daemon, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), JavaMethodProto::new( "currentThread", "()Ljava/lang/Thread;", Self::current_thread, - MethodAccessFlags::NATIVE | MethodAccessFlags::STATIC, + MethodAccessFlags::PUBLIC | MethodAccessFlags::NATIVE | MethodAccessFlags::STATIC, ), // rustjava internal JavaMethodProto::new("", "(Z)V", Self::init_internal, Default::default()), @@ -67,16 +128,17 @@ impl Thread { "I", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, ), - JavaFieldProto::new("threadInitNumber", "I", FieldAccessFlags::STATIC), - JavaFieldProto::new("id", "J", Default::default()), - JavaFieldProto::new("target", "Ljava/lang/Runnable;", Default::default()), - JavaFieldProto::new("name", "Ljava/lang/String;", Default::default()), - JavaFieldProto::new("priority", "I", Default::default()), - JavaFieldProto::new("interrupted", "Z", Default::default()), - JavaFieldProto::new("started", "Z", Default::default()), - JavaFieldProto::new("alive", "Z", Default::default()), + JavaFieldProto::new("threadInitNumber", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC), + JavaFieldProto::new("id", "J", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("target", "Ljava/lang/Runnable;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("name", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("priority", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("interrupted", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("started", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("alive", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("daemon", "Z", FieldAccessFlags::PRIVATE), ], - access_flags: Default::default(), + access_flags: java_constants::ClassAccessFlags::PUBLIC, } } @@ -163,6 +225,9 @@ impl Thread { jvm.put_field(&mut this, "interrupted", "Z", false).await?; jvm.put_field(&mut this, "started", "Z", false).await?; jvm.put_field(&mut this, "alive", "Z", false).await?; + let current_thread = jvm.current_java_thread(); + let daemon: bool = jvm.get_field(¤t_thread, "daemon", "Z").await?; + jvm.put_field(&mut this, "daemon", "Z", daemon).await?; Ok(()) } @@ -176,6 +241,7 @@ impl Thread { jvm.put_field(&mut this, "interrupted", "Z", false).await?; jvm.put_field(&mut this, "started", "Z", true).await?; jvm.put_field(&mut this, "alive", "Z", internal).await?; + jvm.put_field(&mut this, "daemon", "Z", false).await?; Ok(()) } @@ -283,12 +349,34 @@ impl Thread { async fn join(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.Thread::join({this:?})"); + let _: () = jvm.invoke_virtual(&this, "join", "(J)V", (0i64,)).await?; + + Ok(()) + } + + async fn join_long(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, millis: i64) -> Result<()> { + tracing::debug!("java.lang.Thread::join({this:?}, {millis:?})"); + + if millis < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "timeout value is negative").await); + } + + let start = context.now(); loop { let alive: bool = jvm.get_field(&this, "alive", "Z").await?; if !alive { return Ok(()); } - let _: () = jvm.invoke_virtual(&this, "wait", "()V", ()).await?; + + if millis == 0 { + let _: () = jvm.invoke_virtual(&this, "wait", "(J)V", (0i64,)).await?; + } else { + let elapsed = context.now().saturating_sub(start); + if elapsed >= millis as u64 { + return Ok(()); + } + let _: () = jvm.invoke_virtual(&this, "wait", "(J)V", (millis - elapsed as i64,)).await?; + } } } @@ -309,6 +397,16 @@ impl Thread { Ok(name) } + async fn set_name(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, name: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.Thread::setName({this:?}, {name:?})"); + + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + + jvm.put_field(&mut this, "name", "Ljava/lang/String;", name).await + } + async fn get_priority(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.lang.Thread::getPriority({this:?})"); jvm.get_field(&this, "priority", "I").await @@ -316,7 +414,17 @@ impl Thread { async fn interrupt(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.Thread::interrupt({this:?})"); - jvm.put_field(&mut this, "interrupted", "Z", true).await + jvm.interrupt_java_thread(&mut this).await + } + + async fn is_interrupted(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.Thread::isInterrupted({this:?})"); + jvm.is_java_thread_interrupted(&this).await + } + + async fn interrupted(jvm: &Jvm, _: &mut RuntimeContext) -> Result { + tracing::debug!("java.lang.Thread::interrupted()"); + jvm.current_java_thread_interrupted().await } async fn active_count(jvm: &Jvm, _: &mut RuntimeContext) -> Result { @@ -339,9 +447,7 @@ impl Thread { return Err(jvm.exception("java/lang/IllegalArgumentException", "timeout value is negative").await); } - context.sleep(Duration::from_millis(duration as _)).await; - - Ok(()) + jvm.sleep_interruptibly(context.sleep(Duration::from_millis(duration as _))).await } async fn r#yield(_: &Jvm, context: &mut RuntimeContext) -> Result<()> { @@ -363,6 +469,22 @@ impl Thread { Ok(()) } + async fn set_daemon(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, daemon: bool) -> Result<()> { + tracing::debug!("java.lang.Thread::setDaemon({this:?}, {daemon:?})"); + + let alive: bool = jvm.get_field(&this, "alive", "Z").await?; + if alive { + return Err(jvm.exception("java/lang/IllegalThreadStateException", "thread is active").await); + } + + jvm.put_field(&mut this, "daemon", "Z", daemon).await + } + + async fn is_daemon(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.Thread::isDaemon({this:?})"); + jvm.get_field(&this, "daemon", "Z").await + } + async fn current_thread(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { tracing::debug!("java.lang.Thread::currentThread()"); diff --git a/java_runtime/src/classes/java/lang/thread_death.rs b/java_runtime/src/classes/java/lang/thread_death.rs new file mode 100644 index 00000000..d8ac69e5 --- /dev/null +++ b/java_runtime/src/classes/java/lang/thread_death.rs @@ -0,0 +1,29 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext}; + +// class java.lang.ThreadDeath +pub struct ThreadDeath; + +impl ThreadDeath { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/ThreadDeath", + parent_class: Some("java/lang/Error"), + interfaces: vec![], + methods: vec![JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC)], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.ThreadDeath::({this:?})"); + + jvm.invoke_special(&this, "java/lang/Error", "", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/lang/throwable.rs b/java_runtime/src/classes/java/lang/throwable.rs index 30ace64c..12ef890c 100644 --- a/java_runtime/src/classes/java/lang/throwable.rs +++ b/java_runtime/src/classes/java/lang/throwable.rs @@ -1,6 +1,7 @@ use alloc::{boxed::Box, format, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{Array, ClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ @@ -32,6 +33,12 @@ impl Throwable { ), JavaMethodProto::new("getCause", "()Ljava/lang/Throwable;", Self::get_cause, Default::default()), JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, Default::default()), + JavaMethodProto::new( + "getLocalizedMessage", + "()Ljava/lang/String;", + Self::get_localized_message, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new( "initCause", "(Ljava/lang/Throwable;)Ljava/lang/Throwable;", @@ -139,6 +146,12 @@ impl Throwable { jvm.get_field(&this, "detailMessage", "Ljava/lang/String;").await } + async fn get_localized_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.lang.Throwable::getLocalizedMessage({this:?})"); + + jvm.invoke_virtual(&this, "getMessage", "()Ljava/lang/String;", ()).await + } + async fn init_cause( jvm: &Jvm, _: &mut RuntimeContext, diff --git a/java_runtime/src/classes/java/lang/unknown_error.rs b/java_runtime/src/classes/java/lang/unknown_error.rs new file mode 100644 index 00000000..8e979b7b --- /dev/null +++ b/java_runtime/src/classes/java/lang/unknown_error.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.UnknownError +pub struct UnknownError; + +impl UnknownError { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/UnknownError", + parent_class: Some("java/lang/VirtualMachineError"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.UnknownError::({this:?})"); + + jvm.invoke_special(&this, "java/lang/VirtualMachineError", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.UnknownError::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/VirtualMachineError", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/util.rs b/java_runtime/src/classes/java/util.rs index c79f5121..096c9263 100644 --- a/java_runtime/src/classes/java/util.rs +++ b/java_runtime/src/classes/java/util.rs @@ -3,12 +3,33 @@ pub mod zip; mod abstract_collection; mod abstract_list; +mod abstract_list_sub_list; mod abstract_map; mod abstract_set; mod array_list; mod array_list_itr; +mod arrays; +mod arrays_array_list; mod calendar; mod collection; +mod collections; +mod collections_copies_list; +mod collections_empty_list; +mod collections_empty_set; +mod collections_singleton_set; +mod collections_unmodifiable_collection; +mod collections_unmodifiable_collection_iterator; +mod collections_unmodifiable_list; +mod collections_unmodifiable_list_iterator; +mod collections_unmodifiable_map; +mod collections_unmodifiable_map_entry; +mod collections_unmodifiable_map_entry_set; +mod collections_unmodifiable_map_entry_set_iterator; +mod collections_unmodifiable_set; +mod collections_unmodifiable_sorted_map; +mod collections_unmodifiable_sorted_set; +mod comparator; +mod concurrent_modification_exception; mod date; mod dictionary; mod empty_stack_exception; @@ -31,7 +52,11 @@ mod hashtable_enumerator; mod hashtable_key_set; mod hashtable_values; mod iterator; +mod linked_list; +mod linked_list_entry; +mod linked_list_itr; mod list; +mod list_iterator; mod locale; mod map; mod map_entry; @@ -40,24 +65,114 @@ mod properties; mod random; mod set; mod simple_time_zone; +mod sorted_map; +mod sorted_set; mod stack; +mod string_tokenizer; mod time_zone; mod timer; mod timer_task; +mod timer_task_queue; mod timer_thread; +mod tree_map; +mod tree_map_entry; +mod tree_map_entry_iterator; +mod tree_map_entry_set; +mod tree_map_key_iterator; +mod tree_map_key_set; +mod tree_map_private_entry_iterator; +mod tree_map_sub_map; +mod tree_map_value_iterator; +mod tree_map_values; +mod tree_set; mod vector; mod vector_itr; pub use self::{ - abstract_collection::AbstractCollection, abstract_list::AbstractList, abstract_map::AbstractMap, abstract_set::AbstractSet, - array_list::ArrayList, array_list_itr::ArrayListItr, calendar::Calendar, collection::Collection, date::Date, dictionary::Dictionary, - empty_stack_exception::EmptyStackException, enumeration::Enumeration, gregorian_calendar::GregorianCalendar, hash_map::HashMap, - hash_map_entry::HashMapEntry, hash_map_entry_iterator::HashMapEntryIterator, hash_map_entry_set::HashMapEntrySet, - hash_map_hash_iterator::HashMapHashIterator, hash_map_key_iterator::HashMapKeyIterator, hash_map_key_set::HashMapKeySet, - hash_map_value_iterator::HashMapValueIterator, hash_map_values::HashMapValues, hash_set::HashSet, hashtable::Hashtable, - hashtable_entry::HashtableEntry, hashtable_entry_set::HashtableEntrySet, hashtable_enumerator::HashtableEnumerator, - hashtable_key_set::HashtableKeySet, hashtable_values::HashtableValues, iterator::Iterator, list::List, locale::Locale, map::Map, - map_entry::MapEntry, no_such_element_exception::NoSuchElementException, properties::Properties, random::Random, set::Set, - simple_time_zone::SimpleTimeZone, stack::Stack, time_zone::TimeZone, timer::Timer, timer_task::TimerTask, timer_thread::TimerThread, - vector::Vector, vector_itr::VectorItr, + abstract_collection::AbstractCollection, + abstract_list::{AbstractList, AbstractListItr}, + abstract_list_sub_list::AbstractListSubList, + abstract_map::AbstractMap, + abstract_set::AbstractSet, + array_list::ArrayList, + array_list_itr::ArrayListItr, + arrays::Arrays, + arrays_array_list::ArraysArrayList, + calendar::Calendar, + collection::Collection, + collections::Collections, + collections_copies_list::CollectionsCopiesList, + collections_empty_list::CollectionsEmptyList, + collections_empty_set::CollectionsEmptySet, + collections_singleton_set::CollectionsSingletonSet, + collections_unmodifiable_collection::CollectionsUnmodifiableCollection, + collections_unmodifiable_collection_iterator::CollectionsUnmodifiableCollectionIterator, + collections_unmodifiable_list::CollectionsUnmodifiableList, + collections_unmodifiable_list_iterator::CollectionsUnmodifiableListIterator, + collections_unmodifiable_map::CollectionsUnmodifiableMap, + collections_unmodifiable_map_entry::CollectionsUnmodifiableMapEntry, + collections_unmodifiable_map_entry_set::CollectionsUnmodifiableMapEntrySet, + collections_unmodifiable_map_entry_set_iterator::CollectionsUnmodifiableMapEntrySetIterator, + collections_unmodifiable_set::CollectionsUnmodifiableSet, + collections_unmodifiable_sorted_map::CollectionsUnmodifiableSortedMap, + collections_unmodifiable_sorted_set::CollectionsUnmodifiableSortedSet, + comparator::Comparator, + concurrent_modification_exception::ConcurrentModificationException, + date::Date, + dictionary::Dictionary, + empty_stack_exception::EmptyStackException, + enumeration::Enumeration, + gregorian_calendar::GregorianCalendar, + hash_map::HashMap, + hash_map_entry::HashMapEntry, + hash_map_entry_iterator::HashMapEntryIterator, + hash_map_entry_set::HashMapEntrySet, + hash_map_hash_iterator::HashMapHashIterator, + hash_map_key_iterator::HashMapKeyIterator, + hash_map_key_set::HashMapKeySet, + hash_map_value_iterator::HashMapValueIterator, + hash_map_values::HashMapValues, + hash_set::HashSet, + hashtable::Hashtable, + hashtable_entry::HashtableEntry, + hashtable_entry_set::HashtableEntrySet, + hashtable_enumerator::HashtableEnumerator, + hashtable_key_set::HashtableKeySet, + hashtable_values::HashtableValues, + iterator::Iterator, + linked_list::LinkedList, + linked_list_entry::LinkedListEntry, + linked_list_itr::LinkedListItr, + list::List, + list_iterator::ListIterator, + locale::Locale, + map::Map, + map_entry::MapEntry, + no_such_element_exception::NoSuchElementException, + properties::Properties, + random::Random, + set::Set, + simple_time_zone::SimpleTimeZone, + sorted_map::SortedMap, + sorted_set::SortedSet, + stack::Stack, + string_tokenizer::StringTokenizer, + time_zone::TimeZone, + timer::Timer, + timer_task::TimerTask, + timer_task_queue::TimerTaskQueue, + timer_thread::TimerThread, + tree_map::TreeMap, + tree_map_entry::TreeMapEntry, + tree_map_entry_iterator::TreeMapEntryIterator, + tree_map_entry_set::TreeMapEntrySet, + tree_map_key_iterator::TreeMapKeyIterator, + tree_map_key_set::TreeMapKeySet, + tree_map_private_entry_iterator::TreeMapPrivateEntryIterator, + tree_map_sub_map::TreeMapSubMap, + tree_map_value_iterator::TreeMapValueIterator, + tree_map_values::TreeMapValues, + tree_set::TreeSet, + vector::Vector, + vector_itr::VectorItr, }; diff --git a/java_runtime/src/classes/java/util/abstract_collection.rs b/java_runtime/src/classes/java/util/abstract_collection.rs index 5234ac5a..4705258d 100644 --- a/java_runtime/src/classes/java/util/abstract_collection.rs +++ b/java_runtime/src/classes/java/util/abstract_collection.rs @@ -1,8 +1,8 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; -use jvm::{Array, ClassInstanceRef, Jvm, Result}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, AsClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -16,18 +16,33 @@ impl AbstractCollection { parent_class: Some("java/lang/Object"), interfaces: vec!["java/util/Collection"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new_abstract("size", "()I", Default::default()), - JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), - JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), - JavaMethodProto::new_abstract("iterator", "()Ljava/util/Iterator;", Default::default()), - JavaMethodProto::new("toArray", "()[Ljava/lang/Object;", Self::to_array, Default::default()), - JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, Default::default()), - JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, Default::default()), - JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new_abstract("size", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract( + "iterator", + "()Ljava/util/Iterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new("toArray", "()[Ljava/lang/Object;", Self::to_array, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + Self::to_typed_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsAll", "(Ljava/util/Collection;)Z", Self::contains_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addAll", "(Ljava/util/Collection;)Z", Self::add_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("removeAll", "(Ljava/util/Collection;)Z", Self::remove_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("retainAll", "(Ljava/util/Collection;)Z", Self::retain_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), ], fields: vec![], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } @@ -87,6 +102,48 @@ impl AbstractCollection { Ok(array) } + async fn to_typed_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + destination: ClassInstanceRef>, + ) -> Result>> { + tracing::debug!("java.util.AbstractCollection::toArray({this:?}, {destination:?})"); + + if destination.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + + let snapshot: ClassInstanceRef> = jvm.invoke_virtual(&this, "toArray", "()[Ljava/lang/Object;", ()).await?; + let size = jvm.array_length(&snapshot).await?; + let elements: alloc::vec::Vec> = if size == 0 { + alloc::vec::Vec::new() + } else { + jvm.load_array(&snapshot, 0, size).await? + }; + let destination_length = jvm.array_length(&destination).await?; + let mut result = if destination_length < size { + let class_name = destination.class_definition().name(); + let component_descriptor = class_name.strip_prefix('[').unwrap(); + ClassInstanceRef::from(jvm.instantiate_array(component_descriptor, size).await?) + } else { + destination + }; + + for (index, element) in elements.into_iter().enumerate() { + if !element.is_null() && !jvm.array_store_allowed(result.as_class_instance(), element.as_class_instance()) { + return Err(jvm.exception("java/lang/ArrayStoreException", &element.class_definition().name()).await); + } + jvm.store_array(&mut result, index, core::iter::once(element)).await?; + } + if destination_length > size { + let null: ClassInstanceRef = None.into(); + jvm.store_array(&mut result, size, core::iter::once(null)).await?; + } + + Ok(result) + } + async fn add(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { tracing::debug!("java.util.AbstractCollection::add({this:?}, {element:?})"); @@ -101,12 +158,145 @@ impl AbstractCollection { .await) } + async fn contains_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.AbstractCollection::containsAll({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&collection, "toArray", "()[Ljava/lang/Object;", ()).await?; + let size = jvm.array_length(&elements).await?; + for element in jvm.load_array::>(&elements, 0, size).await? { + let contains: bool = jvm.invoke_virtual(&this, "contains", "(Ljava/lang/Object;)Z", (element,)).await?; + if !contains { + return Ok(false); + } + } + + Ok(true) + } + + async fn add_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.AbstractCollection::addAll({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&collection, "toArray", "()[Ljava/lang/Object;", ()).await?; + let size = jvm.array_length(&elements).await?; + let mut modified = false; + for element in jvm.load_array::>(&elements, 0, size).await? { + modified |= jvm.invoke_virtual::<_, bool>(&this, "add", "(Ljava/lang/Object;)Z", (element,)).await?; + } + + Ok(modified) + } + + async fn remove_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.AbstractCollection::removeAll({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&this, "toArray", "()[Ljava/lang/Object;", ()).await?; + let size = jvm.array_length(&elements).await?; + let same_collection = this.identity() == collection.identity(); + let mut modified = false; + for element in jvm.load_array::>(&elements, 0, size).await? { + let remove = if same_collection { + true + } else { + jvm.invoke_virtual(&collection, "contains", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await? + }; + if remove { + while jvm + .invoke_virtual::<_, bool>(&this, "remove", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await? + { + modified = true; + } + } + } + + Ok(modified) + } + + async fn retain_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.AbstractCollection::retainAll({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + if this.identity() == collection.identity() { + return Ok(false); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&this, "toArray", "()[Ljava/lang/Object;", ()).await?; + let size = jvm.array_length(&elements).await?; + let mut modified = false; + for element in jvm.load_array::>(&elements, 0, size).await? { + let retain: bool = jvm + .invoke_virtual(&collection, "contains", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await?; + if !retain { + while jvm + .invoke_virtual::<_, bool>(&this, "remove", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await? + { + modified = true; + } + } + } + + Ok(modified) + } + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.AbstractCollection::clear({this:?})"); Err(jvm.exception("java/lang/UnsupportedOperationException", "AbstractCollection.clear").await) } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let buffer: ClassInstanceRef = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?.into(); + let open = JavaLangString::from_rust_string(jvm, "[").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (open,)) + .await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&this, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut first = true; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + if first { + first = false; + } else { + let separator = JavaLangString::from_rust_string(jvm, ", ").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (separator,)) + .await?; + } + let element: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + if !element.is_null() && element.identity() == this.identity() { + let recursive = JavaLangString::from_rust_string(jvm, "(this Collection)").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (recursive,)) + .await?; + } else { + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", (element,)) + .await?; + } + } + let close = JavaLangString::from_rust_string(jvm, "]").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (close,)) + .await?; + jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await + } + async fn object_equals(jvm: &Jvm, left: &ClassInstanceRef, right: &ClassInstanceRef) -> Result { if left.is_null() { return Ok(right.is_null()); diff --git a/java_runtime/src/classes/java/util/abstract_list.rs b/java_runtime/src/classes/java/util/abstract_list.rs index 459144bb..ab85e6b2 100644 --- a/java_runtime/src/classes/java/util/abstract_list.rs +++ b/java_runtime/src/classes/java/util/abstract_list.rs @@ -1,10 +1,10 @@ use alloc::vec; -use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; -use jvm::{ClassInstanceRef, Jvm, Result}; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; -use crate::{RuntimeClassProto, RuntimeContext}; +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; // abstract class java.util.AbstractList pub struct AbstractList; @@ -15,12 +15,40 @@ impl AbstractList { name: "java/util/AbstractList", parent_class: Some("java/util/AbstractCollection"), interfaces: vec!["java/util/List"], - methods: vec![JavaMethodProto::new("", "()V", Self::init, Default::default())], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addAll", "(ILjava/util/Collection;)Z", Self::add_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/Object;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/Object;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("subList", "(II)Ljava/util/List;", Self::sub_list, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + ], fields: vec![], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } + async fn add(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + let _: () = jvm.invoke_virtual(&this, "add", "(ILjava/lang/Object;)V", (size, element)).await?; + Ok(true) + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.AbstractList::({this:?})"); @@ -28,4 +56,297 @@ impl AbstractList { Ok(()) } + + async fn add_all( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + collection: ClassInstanceRef, + ) -> Result { + tracing::debug!("java.util.AbstractList::addAll({this:?}, {index:?}, {collection:?})"); + + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "index").await); + } + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&collection, "toArray", "()[Ljava/lang/Object;", ()).await?; + let count = jvm.array_length(&elements).await?; + for (offset, element) in jvm + .load_array::>(&elements, 0, count) + .await? + .into_iter() + .enumerate() + { + let _: () = jvm + .invoke_virtual(&this, "add", "(ILjava/lang/Object;)V", (index + offset as i32, element)) + .await?; + } + + Ok(count != 0) + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + tracing::debug!("java.util.AbstractList::lastIndexOf({this:?}, {element:?})"); + + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + for index in (0..size).rev() { + let current: ClassInstanceRef = jvm.invoke_virtual(&this, "get", "(I)Ljava/lang/Object;", (index,)).await?; + if element.is_null() { + if current.is_null() { + return Ok(index); + } + } else if !current.is_null() + && jvm + .invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await? + { + return Ok(index); + } + } + + Ok(-1) + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + for index in 0..size { + let current: ClassInstanceRef = jvm.invoke_virtual(&this, "get", "(I)Ljava/lang/Object;", (index,)).await?; + if element.is_null() { + if current.is_null() { + return Ok(index); + } + } else if !current.is_null() + && jvm + .invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await? + { + return Ok(index); + } + } + Ok(-1) + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm.new_class("java/util/AbstractList$Itr", "(Ljava/util/List;I)V", (this, 0)).await?; + Ok(iterator.into()) + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm.new_class("java/util/AbstractList$ListItr", "(Ljava/util/List;I)V", (this, 0)).await?; + Ok(iterator.into()) + } + + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let iterator = jvm + .new_class("java/util/AbstractList$ListItr", "(Ljava/util/List;I)V", (this, index)) + .await?; + Ok(iterator.into()) + } + + async fn sub_list(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, from: i32, to: i32) -> Result> { + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + if from < 0 || to > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList range").await); + } + if from > to { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromIndex > toIndex").await); + } + let parent: ClassInstanceRef = None.into(); + let sub_list = jvm + .new_class( + "java/util/AbstractList$SubList", + "(Ljava/util/List;Ljava/util/AbstractList$SubList;II)V", + (this, parent, from, to - from), + ) + .await?; + Ok(sub_list.into()) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Ok(false); + } + if this.identity() == other.identity() { + return Ok(true); + } + if !jvm.is_instance(other.as_ref(), "java/util/List") { + return Ok(false); + } + + let left: ClassInstanceRef = jvm.invoke_virtual(&this, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + let right: ClassInstanceRef = jvm.invoke_virtual(&other, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + loop { + let left_has_next: bool = jvm.invoke_virtual(&left, "hasNext", "()Z", ()).await?; + let right_has_next: bool = jvm.invoke_virtual(&right, "hasNext", "()Z", ()).await?; + if !left_has_next || !right_has_next { + return Ok(left_has_next == right_has_next); + } + + let left_element: ClassInstanceRef = jvm.invoke_virtual(&left, "next", "()Ljava/lang/Object;", ()).await?; + let right_element: ClassInstanceRef = jvm.invoke_virtual(&right, "next", "()Ljava/lang/Object;", ()).await?; + let equal = if left_element.is_null() { + right_element.is_null() + } else if right_element.is_null() { + false + } else { + jvm.invoke_virtual::<_, bool>(&left_element, "equals", "(Ljava/lang/Object;)Z", (right_element,)) + .await? + }; + if !equal { + return Ok(false); + } + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&this, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut hash = 1i32; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let element: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let element_hash = if element.is_null() { + 0 + } else { + jvm.invoke_virtual(&element, "hashCode", "()I", ()).await? + }; + hash = hash.wrapping_mul(31).wrapping_add(element_hash); + } + Ok(hash) + } +} + +// classes java.util.AbstractList$Itr and java.util.AbstractList$ListItr +pub struct AbstractListItr; + +impl AbstractListItr { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/AbstractList$Itr", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/List;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("list", "Ljava/util/List;", Default::default()), + JavaFieldProto::new("cursor", "I", Default::default()), + JavaFieldProto::new("lastReturned", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + pub fn list_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/AbstractList$ListItr", + parent_class: Some("java/util/AbstractList$Itr"), + interfaces: vec!["java/util/ListIterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/List;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasPrevious", "()Z", Self::has_previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previous", "()Ljava/lang/Object;", Self::previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextIndex", "()I", Self::next_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previousIndex", "()I", Self::previous_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(Ljava/lang/Object;)V", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)V", Self::add, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, list: ClassInstanceRef, index: i32) -> Result<()> { + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "list iterator index").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "list", "Ljava/util/List;", list).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + Ok(cursor < jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?) + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor >= jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await? { + return Err(jvm.exception("java/util/NoSuchElementException", "AbstractList iterator exhausted").await); + } + let element = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (cursor,)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", cursor).await?; + Ok(element) + } + + async fn has_previous(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? > 0) + } + + async fn previous(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor <= 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "AbstractList iterator exhausted").await); + } + let index = cursor - 1; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let element = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (index,)).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", index).await?; + Ok(element) + } + + async fn next_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "cursor", "I").await + } + + async fn previous_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? - 1) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&list, "remove", "(I)Ljava/lang/Object;", (last_returned,)).await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if last_returned < cursor { + jvm.put_field(&mut this, "cursor", "I", cursor - 1).await?; + } + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } + + async fn set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (last_returned, element)) + .await?; + Ok(()) + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let _: () = jvm.invoke_virtual(&list, "add", "(ILjava/lang/Object;)V", (cursor, element)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } } diff --git a/java_runtime/src/classes/java/util/abstract_list_sub_list.rs b/java_runtime/src/classes/java/util/abstract_list_sub_list.rs new file mode 100644 index 00000000..300a4c49 --- /dev/null +++ b/java_runtime/src/classes/java/util/abstract_list_sub_list.rs @@ -0,0 +1,269 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.AbstractList$SubList +pub struct AbstractListSubList; + +impl AbstractListSubList { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/AbstractList$SubList", + parent_class: Some("java/util/AbstractList"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/List;Ljava/util/AbstractList$SubList;II)V", + Self::init, + Default::default(), + ), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addAll", "(Ljava/util/Collection;)Z", Self::add_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addAll", "(ILjava/util/Collection;)Z", Self::add_all_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/Object;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/Object;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("subList", "(II)Ljava/util/List;", Self::sub_list, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("root", "Ljava/util/List;", Default::default()), + JavaFieldProto::new("parent", "Ljava/util/AbstractList$SubList;", Default::default()), + JavaFieldProto::new("offset", "I", Default::default()), + JavaFieldProto::new("size", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + root: ClassInstanceRef, + parent: ClassInstanceRef, + offset: i32, + size: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractList", "", "()V", ()).await?; + jvm.put_field(&mut this, "root", "Ljava/util/List;", root).await?; + jvm.put_field(&mut this, "parent", "Ljava/util/AbstractList$SubList;", parent).await?; + jvm.put_field(&mut this, "offset", "I", offset).await?; + jvm.put_field(&mut this, "size", "I", size).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "size", "I").await + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if index < 0 || index >= size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList index").await); + } + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/List;").await?; + let offset: i32 = jvm.get_field(&this, "offset", "I").await?; + jvm.invoke_virtual(&root, "get", "(I)Ljava/lang/Object;", (offset + index,)).await + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + element: ClassInstanceRef, + ) -> Result> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if index < 0 || index >= size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList index").await); + } + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/List;").await?; + let offset: i32 = jvm.get_field(&this, "offset", "I").await?; + jvm.invoke_virtual(&root, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (offset + index, element)) + .await + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + let _: () = jvm.invoke_virtual(&this, "add", "(ILjava/lang/Object;)V", (size, element)).await?; + Ok(true) + } + + async fn add_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32, element: ClassInstanceRef) -> Result<()> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList index").await); + } + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/List;").await?; + let offset: i32 = jvm.get_field(&this, "offset", "I").await?; + let _: () = jvm + .invoke_virtual(&root, "add", "(ILjava/lang/Object;)V", (offset + index, element)) + .await?; + Self::update_sizes(jvm, this, 1).await + } + + async fn add_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + jvm.invoke_virtual(&this, "addAll", "(ILjava/util/Collection;)Z", (size, collection)) + .await + } + + async fn add_all_at( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + collection: ClassInstanceRef, + ) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList index").await); + } + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + if jvm.invoke_virtual::<_, i32>(&collection, "size", "()I", ()).await? == 0 { + return Ok(false); + } + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/List;").await?; + let offset: i32 = jvm.get_field(&this, "offset", "I").await?; + let old_root_size: i32 = jvm.invoke_virtual(&root, "size", "()I", ()).await?; + let modified: bool = jvm + .invoke_virtual(&root, "addAll", "(ILjava/util/Collection;)Z", (offset + index, collection)) + .await?; + if modified { + let new_root_size: i32 = jvm.invoke_virtual(&root, "size", "()I", ()).await?; + Self::update_sizes(jvm, this, new_root_size - old_root_size).await?; + } + Ok(modified) + } + + async fn remove_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if index < 0 || index >= size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList index").await); + } + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/List;").await?; + let offset: i32 = jvm.get_field(&this, "offset", "I").await?; + let removed = jvm.invoke_virtual(&root, "remove", "(I)Ljava/lang/Object;", (offset + index,)).await?; + Self::update_sizes(jvm, this, -1).await?; + Ok(removed) + } + + async fn remove_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let index: i32 = jvm.invoke_virtual(&this, "indexOf", "(Ljava/lang/Object;)I", (element,)).await?; + if index < 0 { + return Ok(false); + } + let _: ClassInstanceRef = jvm.invoke_virtual(&this, "remove", "(I)Ljava/lang/Object;", (index,)).await?; + Ok(true) + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + for index in 0..size { + let current: ClassInstanceRef = jvm.invoke_virtual(&this, "get", "(I)Ljava/lang/Object;", (index,)).await?; + if (element.is_null() && current.is_null()) + || (!element.is_null() + && jvm + .invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await?) + { + return Ok(index); + } + } + Ok(-1) + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + for index in (0..size).rev() { + let current: ClassInstanceRef = jvm.invoke_virtual(&this, "get", "(I)Ljava/lang/Object;", (index,)).await?; + if (element.is_null() && current.is_null()) + || (!element.is_null() + && jvm + .invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await?) + { + return Ok(index); + } + } + Ok(-1) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + while jvm.get_field::(&this, "size", "I").await? > 0 { + let _: ClassInstanceRef = jvm.invoke_virtual(&this, "remove", "(I)Ljava/lang/Object;", (0,)).await?; + } + Ok(()) + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm.new_class("java/util/AbstractList$Itr", "(Ljava/util/List;I)V", (this, 0)).await?; + Ok(iterator.into()) + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm.new_class("java/util/AbstractList$ListItr", "(Ljava/util/List;I)V", (this, 0)).await?; + Ok(iterator.into()) + } + + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let iterator = jvm + .new_class("java/util/AbstractList$ListItr", "(Ljava/util/List;I)V", (this, index)) + .await?; + Ok(iterator.into()) + } + + async fn sub_list(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, from: i32, to: i32) -> Result> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if from < 0 || to > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "subList range").await); + } + if from > to { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromIndex > toIndex").await); + } + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/List;").await?; + let offset: i32 = jvm.get_field(&this, "offset", "I").await?; + let sub_list = jvm + .new_class( + "java/util/AbstractList$SubList", + "(Ljava/util/List;Ljava/util/AbstractList$SubList;II)V", + (root, this, offset + from, to - from), + ) + .await?; + Ok(sub_list.into()) + } + + async fn update_sizes(jvm: &Jvm, mut current: ClassInstanceRef, delta: i32) -> Result<()> { + while !current.is_null() { + let size: i32 = jvm.get_field(¤t, "size", "I").await?; + jvm.put_field(&mut current, "size", "I", size + delta).await?; + current = jvm.get_field(¤t, "parent", "Ljava/util/AbstractList$SubList;").await?; + } + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/util/abstract_map.rs b/java_runtime/src/classes/java/util/abstract_map.rs index 44c9688f..c964d23b 100644 --- a/java_runtime/src/classes/java/util/abstract_map.rs +++ b/java_runtime/src/classes/java/util/abstract_map.rs @@ -1,10 +1,10 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; -use jvm::{ClassInstanceRef, Jvm, Result}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; -use crate::{RuntimeClassProto, RuntimeContext}; +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; // abstract class java.util.AbstractMap pub struct AbstractMap; @@ -16,12 +16,16 @@ impl AbstractMap { parent_class: Some("java/lang/Object"), interfaces: vec!["java/util/Map"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new_abstract("size", "()I", Default::default()), - JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new_abstract("size", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("putAll", "(Ljava/util/Map;)V", Self::put_all, 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: vec![], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } @@ -40,4 +44,144 @@ impl AbstractMap { Ok(size == 0) } + + async fn put_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.AbstractMap::putAll({this:?}, {map:?})"); + + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + if this.identity() == map.identity() { + return Ok(()); + } + + 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?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + + Ok(()) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Map") { + return Ok(false); + } + if this.identity() == other.identity() { + return Ok(true); + } + let this_size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + let other_size: i32 = jvm.invoke_virtual(&other, "size", "()I", ()).await?; + if this_size != other_size { + return Ok(false); + } + + let comparison: Result = async { + let entries: ClassInstanceRef = jvm.invoke_virtual(&this, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()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 key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + let other_value: ClassInstanceRef = jvm + .invoke_virtual(&other, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await?; + if value.is_null() { + if !other_value.is_null() + || !jvm + .invoke_virtual::<_, bool>(&other, "containsKey", "(Ljava/lang/Object;)Z", (key,)) + .await? + { + return Ok(false); + } + } else if !jvm + .invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (other_value,)) + .await? + { + return Ok(false); + } + } + Ok(true) + } + .await; + match comparison { + Err(JavaError::JavaException(exception)) + if jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException") + || jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException") => + { + Ok(false) + } + result => result, + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let entries: ClassInstanceRef = jvm.invoke_virtual(&this, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut hash = 0i32; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + hash = hash.wrapping_add(jvm.invoke_virtual::<_, i32>(&entry, "hashCode", "()I", ()).await?); + } + Ok(hash) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let buffer: ClassInstanceRef = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?.into(); + let open = JavaLangString::from_rust_string(jvm, "{").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (open,)) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&this, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut first = true; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + if first { + first = false; + } else { + let separator = JavaLangString::from_rust_string(jvm, ", ").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (separator,)) + .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?; + if !key.is_null() && key.identity() == this.identity() { + let recursive = JavaLangString::from_rust_string(jvm, "(this Map)").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (recursive,)) + .await?; + } else { + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", (key,)) + .await?; + } + let equals = JavaLangString::from_rust_string(jvm, "=").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (equals,)) + .await?; + if !value.is_null() && value.identity() == this.identity() { + let recursive = JavaLangString::from_rust_string(jvm, "(this Map)").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (recursive,)) + .await?; + } else { + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", (value,)) + .await?; + } + } + let close = JavaLangString::from_rust_string(jvm, "}").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (close,)) + .await?; + jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await + } } diff --git a/java_runtime/src/classes/java/util/abstract_set.rs b/java_runtime/src/classes/java/util/abstract_set.rs index edd7f2a6..c6674b08 100644 --- a/java_runtime/src/classes/java/util/abstract_set.rs +++ b/java_runtime/src/classes/java/util/abstract_set.rs @@ -1,10 +1,10 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; -use jvm::{ClassInstanceRef, Jvm, Result}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, JavaError, Jvm, Result}; -use crate::{RuntimeClassProto, RuntimeContext}; +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; // abstract class java.util.AbstractSet pub struct AbstractSet; @@ -15,9 +15,13 @@ impl AbstractSet { name: "java/util/AbstractSet", parent_class: Some("java/util/AbstractCollection"), interfaces: vec!["java/util/Set"], - methods: vec![JavaMethodProto::new("", "()V", Self::init, Default::default())], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + ], fields: vec![], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } @@ -28,4 +32,41 @@ impl AbstractSet { Ok(()) } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Ok(false); + } + if this.identity() == other.identity() { + return Ok(true); + } + if !jvm.is_instance(other.as_ref(), "java/util/Set") { + return Ok(false); + } + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + if jvm.invoke_virtual::<_, i32>(&other, "size", "()I", ()).await? != size { + return Ok(false); + } + match jvm.invoke_virtual(&this, "containsAll", "(Ljava/util/Collection;)Z", (other,)).await { + Ok(equal) => Ok(equal), + Err(JavaError::JavaException(exception)) + if jvm.is_instance(&*exception, "java/lang/ClassCastException") || jvm.is_instance(&*exception, "java/lang/NullPointerException") => + { + Ok(false) + } + Err(error) => Err(error), + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&this, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut hash = 0i32; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let element: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + if !element.is_null() { + hash = hash.wrapping_add(jvm.invoke_virtual::<_, i32>(&element, "hashCode", "()I", ()).await?); + } + } + Ok(hash) + } } diff --git a/java_runtime/src/classes/java/util/array_list.rs b/java_runtime/src/classes/java/util/array_list.rs index 984f5f3b..21c9264a 100644 --- a/java_runtime/src/classes/java/util/array_list.rs +++ b/java_runtime/src/classes/java/util/array_list.rs @@ -1,6 +1,7 @@ use alloc::{format, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -17,6 +18,12 @@ impl ArrayList { methods: vec![ JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + JavaMethodProto::new( + "", + "(Ljava/util/Collection;)V", + Self::init_from_collection, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, Default::default()), JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, Default::default()), JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, Default::default()), @@ -30,12 +37,24 @@ impl ArrayList { JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), JavaMethodProto::new("toArray", "()[Ljava/lang/Object;", Self::to_array, Default::default()), JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, Default::default()), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC, + ), ], fields: vec![ JavaFieldProto::new("elementData", "[Ljava/lang/Object;", Default::default()), JavaFieldProto::new("size", "I", Default::default()), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } @@ -65,6 +84,24 @@ impl ArrayList { Ok(()) } + async fn init_from_collection( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + collection: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.ArrayList::({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + let size: i32 = jvm.invoke_virtual(&collection, "size", "()I", ()).await?; + let _: () = jvm.invoke_special(&this, "java/util/ArrayList", "", "(I)V", (size,)).await?; + let _: bool = jvm.invoke_virtual(&this, "addAll", "(Ljava/util/Collection;)Z", (collection,)).await?; + + Ok(()) + } + async fn add(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef) -> Result { tracing::debug!("java.util.ArrayList::add({this:?}, {element:?})"); @@ -245,9 +282,20 @@ impl ArrayList { async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.util.ArrayList::iterator({this:?})"); - let snapshot: ClassInstanceRef> = jvm.invoke_virtual(&this, "toArray", "()[Ljava/lang/Object;", ()).await?; - let iterator = jvm.new_class("java/util/ArrayList$Itr", "([Ljava/lang/Object;)V", (snapshot,)).await?; + let iterator = jvm.new_class("java/util/ArrayList$Itr", "(Ljava/util/List;I)V", (this, 0)).await?; + + Ok(iterator.into()) + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm.new_class("java/util/ArrayList$ListItr", "(Ljava/util/List;I)V", (this, 0)).await?; + Ok(iterator.into()) + } + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let iterator = jvm + .new_class("java/util/ArrayList$ListItr", "(Ljava/util/List;I)V", (this, index)) + .await?; Ok(iterator.into()) } diff --git a/java_runtime/src/classes/java/util/array_list_itr.rs b/java_runtime/src/classes/java/util/array_list_itr.rs index 465d1230..684db14d 100644 --- a/java_runtime/src/classes/java/util/array_list_itr.rs +++ b/java_runtime/src/classes/java/util/array_list_itr.rs @@ -1,7 +1,8 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{Array, ClassInstanceRef, Jvm, Result}; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -15,63 +16,128 @@ impl ArrayListItr { parent_class: Some("java/lang/Object"), interfaces: vec!["java/util/Iterator"], methods: vec![ - JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, Default::default()), - JavaMethodProto::new("hasNext", "()Z", Self::has_next, Default::default()), - JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, Default::default()), - JavaMethodProto::new("remove", "()V", Self::remove, Default::default()), + JavaMethodProto::new("", "(Ljava/util/List;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), ], fields: vec![ - JavaFieldProto::new("elements", "[Ljava/lang/Object;", Default::default()), - JavaFieldProto::new("index", "I", Default::default()), + JavaFieldProto::new("list", "Ljava/util/List;", Default::default()), + JavaFieldProto::new("cursor", "I", Default::default()), + JavaFieldProto::new("lastReturned", "I", Default::default()), ], access_flags: Default::default(), } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { - tracing::debug!("java.util.ArrayList$Itr::({this:?}, {elements:?})"); - - let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + pub fn list_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/ArrayList$ListItr", + parent_class: Some("java/util/ArrayList$Itr"), + interfaces: vec!["java/util/ListIterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/List;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasPrevious", "()Z", Self::has_previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previous", "()Ljava/lang/Object;", Self::previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextIndex", "()I", Self::next_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previousIndex", "()I", Self::previous_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(Ljava/lang/Object;)V", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)V", Self::add, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } - jvm.put_field(&mut this, "elements", "[Ljava/lang/Object;", elements).await?; - jvm.put_field(&mut this, "index", "I", 0).await?; + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, list: ClassInstanceRef, index: i32) -> Result<()> { + tracing::debug!("java.util.ArrayList$Itr::({this:?}, {list:?}, {index:?})"); - Ok(()) + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "list iterator index").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "list", "Ljava/util/List;", list).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await } async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { - tracing::debug!("java.util.ArrayList$Itr::hasNext({this:?})"); + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + Ok(cursor < size) + } - let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; - let index: i32 = jvm.get_field(&this, "index", "I").await?; - if index < 0 { - return Ok(false); + async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + if cursor >= size { + return Err(jvm.exception("java/util/NoSuchElementException", "ArrayList iterator exhausted").await); } - - Ok((index as usize) < jvm.array_length(&elements).await?) + let element = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (cursor,)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", cursor).await?; + Ok(element) } - async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { - tracing::debug!("java.util.ArrayList$Itr::next({this:?})"); + async fn has_previous(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? > 0) + } - let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; - let index: i32 = jvm.get_field(&this, "index", "I").await?; - if index < 0 || index as usize >= jvm.array_length(&elements).await? { + async fn previous(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor <= 0 { return Err(jvm.exception("java/util/NoSuchElementException", "ArrayList iterator exhausted").await); } + let index = cursor - 1; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let element = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (index,)).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", index).await?; + Ok(element) + } - let mut values = jvm.load_array(&elements, index as usize, 1).await?; - let Some(element) = values.pop() else { - return Err(jvm.exception("java/util/NoSuchElementException", "ArrayList iterator exhausted").await); - }; - jvm.put_field(&mut this, "index", "I", index + 1).await?; + async fn next_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "cursor", "I").await + } - Ok(element) + async fn previous_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? - 1) } - async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { - tracing::debug!("java.util.ArrayList$Itr::remove({this:?})"); + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&list, "remove", "(I)Ljava/lang/Object;", (last_returned,)).await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if last_returned < cursor { + jvm.put_field(&mut this, "cursor", "I", cursor - 1).await?; + } + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } + + async fn set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (last_returned, element)) + .await?; + Ok(()) + } - Err(jvm.exception("java/lang/UnsupportedOperationException", "Iterator.remove").await) + async fn add(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let _: () = jvm.invoke_virtual(&list, "add", "(ILjava/lang/Object;)V", (cursor, element)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await } } diff --git a/java_runtime/src/classes/java/util/arrays.rs b/java_runtime/src/classes/java/util/arrays.rs new file mode 100644 index 00000000..0dddd285 --- /dev/null +++ b/java_runtime/src/classes/java/util/arrays.rs @@ -0,0 +1,752 @@ +use alloc::vec; +use core::cmp::Ordering; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaValue, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +macro_rules! primitive_sort_methods { + ($whole:ident, $range:ident, $ty:ty, $compare:expr) => { + async fn $whole(jvm: &Jvm, _: &mut RuntimeContext, array: ClassInstanceRef>) -> Result<()> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + Self::sort_primitive(jvm, array, 0, length, $compare).await + } + + async fn $range(jvm: &Jvm, _: &mut RuntimeContext, array: ClassInstanceRef>, from_index: i32, to_index: i32) -> Result<()> { + let (from_index, to_index) = Self::checked_range(jvm, &array, from_index, to_index).await?; + Self::sort_primitive(jvm, array, from_index, to_index, $compare).await + } + }; +} + +macro_rules! primitive_binary_search_method { + ($name:ident, $ty:ty, $compare:expr) => { + async fn $name(jvm: &Jvm, _: &mut RuntimeContext, array: ClassInstanceRef>, key: $ty) -> Result { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + let values = jvm.load_array::<$ty>(&array, 0, length).await?; + Ok(Self::binary_search_primitive(&values, &key, $compare)) + } + }; +} + +macro_rules! primitive_equals_method { + ($name:ident, $ty:ty) => { + async fn $name(jvm: &Jvm, _: &mut RuntimeContext, first: ClassInstanceRef>, second: ClassInstanceRef>) -> Result { + if first.is_null() || second.is_null() { + return Ok(first.is_null() && second.is_null()); + } + let first_length = jvm.array_length(&first).await?; + if first_length != jvm.array_length(&second).await? { + return Ok(false); + } + Ok(jvm.load_array::<$ty>(&first, 0, first_length).await? == jvm.load_array::<$ty>(&second, 0, first_length).await?) + } + }; +} + +macro_rules! primitive_fill_methods { + ($whole:ident, $range:ident, $ty:ty) => { + async fn $whole(jvm: &Jvm, _: &mut RuntimeContext, mut array: ClassInstanceRef>, value: $ty) -> Result<()> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + if length != 0 { + jvm.store_array(&mut array, 0, vec![value; length]).await?; + } + Ok(()) + } + + async fn $range( + jvm: &Jvm, + _: &mut RuntimeContext, + mut array: ClassInstanceRef>, + from_index: i32, + to_index: i32, + value: $ty, + ) -> Result<()> { + let (from_index, to_index) = Self::checked_range(jvm, &array, from_index, to_index).await?; + if from_index != to_index { + jvm.store_array(&mut array, from_index, vec![value; to_index - from_index]).await?; + } + Ok(()) + } + }; +} + +// public final class java.util.Arrays +pub struct Arrays; + +impl Arrays { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Arrays", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PRIVATE), + JavaMethodProto::new("sort", "([B)V", Self::sort_byte, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([BII)V", + Self::sort_byte_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("sort", "([C)V", Self::sort_char, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([CII)V", + Self::sort_char_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("sort", "([S)V", Self::sort_short, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([SII)V", + Self::sort_short_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("sort", "([I)V", Self::sort_int, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([III)V", + Self::sort_int_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("sort", "([J)V", Self::sort_long, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([JII)V", + Self::sort_long_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("sort", "([F)V", Self::sort_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([FII)V", + Self::sort_float_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("sort", "([D)V", Self::sort_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "sort", + "([DII)V", + Self::sort_double_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "sort", + "([Ljava/lang/Object;)V", + Self::sort_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "sort", + "([Ljava/lang/Object;II)V", + Self::sort_object_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "sort", + "([Ljava/lang/Object;Ljava/util/Comparator;)V", + Self::sort_object_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "sort", + "([Ljava/lang/Object;IILjava/util/Comparator;)V", + Self::sort_object_range_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([BB)I", + Self::binary_search_byte, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([CC)I", + Self::binary_search_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([SS)I", + Self::binary_search_short, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([II)I", + Self::binary_search_int, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([JJ)I", + Self::binary_search_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([FF)I", + Self::binary_search_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([DD)I", + Self::binary_search_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;)I", + Self::binary_search_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + Self::binary_search_object_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([Z[Z)Z", + Self::equals_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([B[B)Z", + Self::equals_byte, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([C[C)Z", + Self::equals_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([S[S)Z", + Self::equals_short, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([I[I)Z", + Self::equals_int, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([J[J)Z", + Self::equals_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([F[F)Z", + Self::equals_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([D[D)Z", + Self::equals_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "equals", + "([Ljava/lang/Object;[Ljava/lang/Object;)Z", + Self::equals_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "fill", + "([ZZ)V", + Self::fill_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "fill", + "([ZIIZ)V", + Self::fill_boolean_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([BB)V", Self::fill_byte, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([BIIB)V", + Self::fill_byte_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([CC)V", Self::fill_char, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([CIIC)V", + Self::fill_char_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([SS)V", Self::fill_short, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([SIIS)V", + Self::fill_short_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([II)V", Self::fill_int, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([IIII)V", + Self::fill_int_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([JJ)V", Self::fill_long, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([JIIJ)V", + Self::fill_long_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([FF)V", Self::fill_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([FIIF)V", + Self::fill_float_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("fill", "([DD)V", Self::fill_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "fill", + "([DIID)V", + Self::fill_double_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "fill", + "([Ljava/lang/Object;Ljava/lang/Object;)V", + Self::fill_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + Self::fill_object_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "asList", + "([Ljava/lang/Object;)Ljava/util/List;", + Self::as_list, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn checked_range(jvm: &Jvm, array: &ClassInstanceRef>, from_index: i32, to_index: i32) -> Result<(usize, usize)> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + if from_index > to_index { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromIndex > toIndex").await); + } + let length = jvm.array_length(array).await?; + if from_index < 0 || to_index < 0 || to_index as usize > length { + return Err(jvm.exception("java/lang/ArrayIndexOutOfBoundsException", "array range").await); + } + Ok((from_index as usize, to_index as usize)) + } + + async fn sort_primitive(jvm: &Jvm, mut array: ClassInstanceRef>, from_index: usize, to_index: usize, mut compare: F) -> Result<()> + where + T: From + Into + Send, + F: FnMut(&T, &T) -> Ordering + Send, + { + let mut values = jvm.load_array::(&array, from_index, to_index - from_index).await?; + values.sort_by(|left, right| compare(left, right)); + if !values.is_empty() { + jvm.store_array(&mut array, from_index, values).await?; + } + Ok(()) + } + + fn float_order(left: &f32, right: &f32) -> Ordering { + if left < right { + Ordering::Less + } else if left > right { + Ordering::Greater + } else { + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() } as i32; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() } as i32; + left_bits.cmp(&right_bits) + } + } + + fn double_order(left: &f64, right: &f64) -> Ordering { + if left < right { + Ordering::Less + } else if left > right { + Ordering::Greater + } else { + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() } as i64; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() } as i64; + left_bits.cmp(&right_bits) + } + } + + primitive_sort_methods!(sort_byte, sort_byte_range, i8, Ord::cmp); + primitive_sort_methods!(sort_char, sort_char_range, JavaChar, Ord::cmp); + primitive_sort_methods!(sort_short, sort_short_range, i16, Ord::cmp); + primitive_sort_methods!(sort_int, sort_int_range, i32, Ord::cmp); + primitive_sort_methods!(sort_long, sort_long_range, i64, Ord::cmp); + primitive_sort_methods!(sort_float, sort_float_range, f32, Self::float_order); + primitive_sort_methods!(sort_double, sort_double_range, f64, Self::double_order); + + async fn compare_objects( + jvm: &Jvm, + comparator: &ClassInstanceRef, + left: &ClassInstanceRef, + right: &ClassInstanceRef, + ) -> Result { + if comparator.is_null() { + if left.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array element").await); + } + if !jvm.is_instance(left.as_ref(), "java/lang/Comparable") { + return Err(jvm.exception("java/lang/ClassCastException", &left.class_definition().name()).await); + } + jvm.invoke_virtual(left, "compareTo", "(Ljava/lang/Object;)I", (right.clone(),)).await + } else { + jvm.invoke_virtual( + comparator, + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + (left.clone(), right.clone()), + ) + .await + } + } + + async fn sort_objects( + jvm: &Jvm, + mut array: ClassInstanceRef>, + from_index: usize, + to_index: usize, + comparator: ClassInstanceRef, + ) -> Result<()> { + let mut source = jvm + .load_array::>(&array, from_index, to_index - from_index) + .await?; + let length = source.len(); + let mut target = source.clone(); + let mut width = 1usize; + + while width < length { + let mut start = 0usize; + while start < length { + let middle = core::cmp::min(start + width, length); + let end = core::cmp::min(start + width.saturating_mul(2), length); + let mut left = start; + let mut right = middle; + let mut output = start; + + while left < middle && right < end { + if Self::compare_objects(jvm, &comparator, &source[left], &source[right]).await? <= 0 { + target[output] = source[left].clone(); + left += 1; + } else { + target[output] = source[right].clone(); + right += 1; + } + output += 1; + } + while left < middle { + target[output] = source[left].clone(); + left += 1; + output += 1; + } + while right < end { + target[output] = source[right].clone(); + right += 1; + output += 1; + } + start = end; + } + core::mem::swap(&mut source, &mut target); + width = width.saturating_mul(2); + } + + if !source.is_empty() { + jvm.store_array(&mut array, from_index, source).await?; + } + Ok(()) + } + + async fn sort_object(jvm: &Jvm, _: &mut RuntimeContext, array: ClassInstanceRef>) -> Result<()> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + Self::sort_objects(jvm, array, 0, length, None.into()).await + } + + async fn sort_object_range( + jvm: &Jvm, + _: &mut RuntimeContext, + array: ClassInstanceRef>, + from_index: i32, + to_index: i32, + ) -> Result<()> { + let (from_index, to_index) = Self::checked_range(jvm, &array, from_index, to_index).await?; + Self::sort_objects(jvm, array, from_index, to_index, None.into()).await + } + + async fn sort_object_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + array: ClassInstanceRef>, + comparator: ClassInstanceRef, + ) -> Result<()> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + Self::sort_objects(jvm, array, 0, length, comparator).await + } + + async fn sort_object_range_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + array: ClassInstanceRef>, + from_index: i32, + to_index: i32, + comparator: ClassInstanceRef, + ) -> Result<()> { + let (from_index, to_index) = Self::checked_range(jvm, &array, from_index, to_index).await?; + Self::sort_objects(jvm, array, from_index, to_index, comparator).await + } + + fn binary_search_primitive(values: &[T], key: &T, mut compare: F) -> i32 + where + F: FnMut(&T, &T) -> Ordering, + { + let mut low = 0usize; + let mut high = values.len(); + while low < high { + let middle = low + (high - low) / 2; + match compare(&values[middle], key) { + Ordering::Less => low = middle + 1, + Ordering::Greater => high = middle, + Ordering::Equal => return middle as i32, + } + } + -(low as i32) - 1 + } + + primitive_binary_search_method!(binary_search_byte, i8, Ord::cmp); + primitive_binary_search_method!(binary_search_char, JavaChar, Ord::cmp); + primitive_binary_search_method!(binary_search_short, i16, Ord::cmp); + primitive_binary_search_method!(binary_search_int, i32, Ord::cmp); + primitive_binary_search_method!(binary_search_long, i64, Ord::cmp); + primitive_binary_search_method!(binary_search_float, f32, Self::float_order); + primitive_binary_search_method!(binary_search_double, f64, Self::double_order); + + async fn binary_search_objects( + jvm: &Jvm, + array: ClassInstanceRef>, + key: ClassInstanceRef, + comparator: ClassInstanceRef, + ) -> Result { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + let values = jvm.load_array::>(&array, 0, length).await?; + let mut low = 0usize; + let mut high = length; + while low < high { + let middle = low + (high - low) / 2; + let comparison = Self::compare_objects(jvm, &comparator, &values[middle], &key).await?; + if comparison < 0 { + low = middle + 1; + } else if comparison > 0 { + high = middle; + } else { + return Ok(middle as i32); + } + } + Ok(-(low as i32) - 1) + } + + async fn binary_search_object( + jvm: &Jvm, + _: &mut RuntimeContext, + array: ClassInstanceRef>, + key: ClassInstanceRef, + ) -> Result { + Self::binary_search_objects(jvm, array, key, None.into()).await + } + + async fn binary_search_object_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + array: ClassInstanceRef>, + key: ClassInstanceRef, + comparator: ClassInstanceRef, + ) -> Result { + Self::binary_search_objects(jvm, array, key, comparator).await + } + + primitive_equals_method!(equals_boolean, bool); + primitive_equals_method!(equals_byte, i8); + primitive_equals_method!(equals_char, JavaChar); + primitive_equals_method!(equals_short, i16); + primitive_equals_method!(equals_int, i32); + primitive_equals_method!(equals_long, i64); + + async fn equals_float( + jvm: &Jvm, + _: &mut RuntimeContext, + first: ClassInstanceRef>, + second: ClassInstanceRef>, + ) -> Result { + if first.is_null() || second.is_null() { + return Ok(first.is_null() && second.is_null()); + } + let length = jvm.array_length(&first).await?; + if length != jvm.array_length(&second).await? { + return Ok(false); + } + let first_values = jvm.load_array::(&first, 0, length).await?; + let second_values = jvm.load_array::(&second, 0, length).await?; + Ok(first_values.iter().zip(second_values).all(|(left, right)| { + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() }; + left_bits == right_bits + })) + } + + async fn equals_double( + jvm: &Jvm, + _: &mut RuntimeContext, + first: ClassInstanceRef>, + second: ClassInstanceRef>, + ) -> Result { + if first.is_null() || second.is_null() { + return Ok(first.is_null() && second.is_null()); + } + let length = jvm.array_length(&first).await?; + if length != jvm.array_length(&second).await? { + return Ok(false); + } + let first_values = jvm.load_array::(&first, 0, length).await?; + let second_values = jvm.load_array::(&second, 0, length).await?; + Ok(first_values.iter().zip(second_values).all(|(left, right)| { + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() }; + left_bits == right_bits + })) + } + + async fn equals_object( + jvm: &Jvm, + _: &mut RuntimeContext, + first: ClassInstanceRef>, + second: ClassInstanceRef>, + ) -> Result { + if first.is_null() || second.is_null() { + return Ok(first.is_null() && second.is_null()); + } + let length = jvm.array_length(&first).await?; + if length != jvm.array_length(&second).await? { + return Ok(false); + } + let first_values = jvm.load_array::>(&first, 0, length).await?; + let second_values = jvm.load_array::>(&second, 0, length).await?; + for (left, right) in first_values.into_iter().zip(second_values) { + if left.is_null() { + if !right.is_null() { + return Ok(false); + } + } else if !jvm.invoke_virtual::<_, bool>(&left, "equals", "(Ljava/lang/Object;)Z", (right,)).await? { + return Ok(false); + } + } + Ok(true) + } + + primitive_fill_methods!(fill_boolean, fill_boolean_range, bool); + primitive_fill_methods!(fill_byte, fill_byte_range, i8); + primitive_fill_methods!(fill_char, fill_char_range, JavaChar); + primitive_fill_methods!(fill_short, fill_short_range, i16); + primitive_fill_methods!(fill_int, fill_int_range, i32); + primitive_fill_methods!(fill_long, fill_long_range, i64); + primitive_fill_methods!(fill_float, fill_float_range, f32); + primitive_fill_methods!(fill_double, fill_double_range, f64); + + async fn fill_object(jvm: &Jvm, _: &mut RuntimeContext, array: ClassInstanceRef>, value: ClassInstanceRef) -> Result<()> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let length = jvm.array_length(&array).await?; + Self::fill_objects(jvm, array, 0, length, value).await + } + + async fn fill_object_range( + jvm: &Jvm, + _: &mut RuntimeContext, + array: ClassInstanceRef>, + from_index: i32, + to_index: i32, + value: ClassInstanceRef, + ) -> Result<()> { + let (from_index, to_index) = Self::checked_range(jvm, &array, from_index, to_index).await?; + Self::fill_objects(jvm, array, from_index, to_index, value).await + } + + async fn fill_objects( + jvm: &Jvm, + mut array: ClassInstanceRef>, + from_index: usize, + to_index: usize, + value: ClassInstanceRef, + ) -> Result<()> { + for index in from_index..to_index { + if !value.is_null() && !jvm.array_store_allowed(array.as_ref(), value.as_ref()) { + return Err(jvm.exception("java/lang/ArrayStoreException", &value.class_definition().name()).await); + } + jvm.store_array(&mut array, index, core::iter::once(value.clone())).await?; + } + Ok(()) + } + + async fn as_list(jvm: &Jvm, _: &mut RuntimeContext, array: ClassInstanceRef>) -> Result> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + Ok(jvm + .new_class("java/util/Arrays$ArrayList", "([Ljava/lang/Object;)V", (array,)) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/arrays_array_list.rs b/java_runtime/src/classes/java/util/arrays_array_list.rs new file mode 100644 index 00000000..cc4594c5 --- /dev/null +++ b/java_runtime/src/classes/java/util/arrays_array_list.rs @@ -0,0 +1,96 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, AsClassInstance, ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// private static class java.util.Arrays$ArrayList +pub struct ArraysArrayList; + +impl ArraysArrayList { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Arrays$ArrayList", + parent_class: Some("java/util/AbstractList"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new( + "a", + "[Ljava/lang/Object;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, array: ClassInstanceRef>) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractList", "", "()V", ()).await?; + jvm.put_field(&mut this, "a", "[Ljava/lang/Object;", array).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let array: ClassInstanceRef> = jvm.get_field(&this, "a", "[Ljava/lang/Object;").await?; + Ok(jvm.array_length(&array).await? as i32) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let array: ClassInstanceRef> = jvm.get_field(&this, "a", "[Ljava/lang/Object;").await?; + let length = jvm.array_length(&array).await?; + if index < 0 || index as usize >= length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "index").await); + } + Ok(jvm.load_array(&array, index as usize, 1).await?.remove(0)) + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + element: ClassInstanceRef, + ) -> Result> { + let mut array: ClassInstanceRef> = jvm.get_field(&this, "a", "[Ljava/lang/Object;").await?; + let length = jvm.array_length(&array).await?; + if index < 0 || index as usize >= length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "index").await); + } + if !element.is_null() && !jvm.array_store_allowed(array.as_class_instance(), element.as_class_instance()) { + return Err(jvm.exception("java/lang/ArrayStoreException", &element.class_definition().name()).await); + } + let previous = jvm.load_array(&array, index as usize, 1).await?.remove(0); + jvm.store_array(&mut array, index as usize, core::iter::once(element)).await?; + Ok(previous) + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "fixed-size list").await) + } + + async fn add_at(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "fixed-size list").await) + } + + async fn remove_at(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "fixed-size list").await) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "fixed-size list").await) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "fixed-size list").await) + } +} diff --git a/java_runtime/src/classes/java/util/collection.rs b/java_runtime/src/classes/java/util/collection.rs index 85b41982..30f8ac6b 100644 --- a/java_runtime/src/classes/java/util/collection.rs +++ b/java_runtime/src/classes/java/util/collection.rs @@ -1,7 +1,7 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -15,17 +15,54 @@ impl Collection { parent_class: None, interfaces: vec![], methods: vec![ - JavaMethodProto::new_abstract("size", "()I", Default::default()), - JavaMethodProto::new_abstract("isEmpty", "()Z", Default::default()), - JavaMethodProto::new_abstract("contains", "(Ljava/lang/Object;)Z", Default::default()), - JavaMethodProto::new_abstract("iterator", "()Ljava/util/Iterator;", Default::default()), - JavaMethodProto::new_abstract("toArray", "()[Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("add", "(Ljava/lang/Object;)Z", Default::default()), - JavaMethodProto::new_abstract("remove", "(Ljava/lang/Object;)Z", Default::default()), - JavaMethodProto::new_abstract("clear", "()V", Default::default()), + JavaMethodProto::new_abstract("size", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("isEmpty", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "contains", + "(Ljava/lang/Object;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "iterator", + "()Ljava/util/Iterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "toArray", + "()[Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("add", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("remove", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "containsAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "addAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "removeAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "retainAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("clear", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), ], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/util/collections.rs b/java_runtime/src/classes/java/util/collections.rs new file mode 100644 index 00000000..e7b543da --- /dev/null +++ b/java_runtime/src/classes/java/util/collections.rs @@ -0,0 +1,489 @@ +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}; + +// class java.util.Collections +pub struct Collections; + +impl Collections { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PRIVATE), + JavaMethodProto::new( + "sort", + "(Ljava/util/List;)V", + Self::sort, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "sort", + "(Ljava/util/List;Ljava/util/Comparator;)V", + Self::sort_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "(Ljava/util/List;Ljava/lang/Object;)I", + Self::binary_search, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "binarySearch", + "(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I", + Self::binary_search_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "reverse", + "(Ljava/util/List;)V", + Self::reverse, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "fill", + "(Ljava/util/List;Ljava/lang/Object;)V", + Self::fill, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "copy", + "(Ljava/util/List;Ljava/util/List;)V", + Self::copy, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "shuffle", + "(Ljava/util/List;)V", + Self::shuffle, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "shuffle", + "(Ljava/util/List;Ljava/util/Random;)V", + Self::shuffle_random, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "min", + "(Ljava/util/Collection;)Ljava/lang/Object;", + Self::min, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "min", + "(Ljava/util/Collection;Ljava/util/Comparator;)Ljava/lang/Object;", + Self::min_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "max", + "(Ljava/util/Collection;)Ljava/lang/Object;", + Self::max, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "max", + "(Ljava/util/Collection;Ljava/util/Comparator;)Ljava/lang/Object;", + Self::max_comparator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + Self::n_copies, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + Self::singleton, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "unmodifiableCollection", + "(Ljava/util/Collection;)Ljava/util/Collection;", + Self::unmodifiable_collection, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "unmodifiableList", + "(Ljava/util/List;)Ljava/util/List;", + Self::unmodifiable_list, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "unmodifiableSet", + "(Ljava/util/Set;)Ljava/util/Set;", + Self::unmodifiable_set, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "unmodifiableMap", + "(Ljava/util/Map;)Ljava/util/Map;", + Self::unmodifiable_map, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "unmodifiableSortedSet", + "(Ljava/util/SortedSet;)Ljava/util/SortedSet;", + Self::unmodifiable_sorted_set, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "unmodifiableSortedMap", + "(Ljava/util/SortedMap;)Ljava/util/SortedMap;", + Self::unmodifiable_sorted_map, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + ], + fields: vec![ + JavaFieldProto::new( + "EMPTY_LIST", + "Ljava/util/List;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "EMPTY_SET", + "Ljava/util/Set;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("r", "Ljava/util/Random;", FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + let empty_list = jvm.new_class("java/util/Collections$EmptyList", "()V", ()).await?; + jvm.put_static_field("java/util/Collections", "EMPTY_LIST", "Ljava/util/List;", empty_list) + .await?; + + let empty_set = jvm.new_class("java/util/Collections$EmptySet", "()V", ()).await?; + jvm.put_static_field("java/util/Collections", "EMPTY_SET", "Ljava/util/Set;", empty_set) + .await?; + + let random = jvm.new_class("java/util/Random", "()V", ()).await?; + jvm.put_static_field("java/util/Collections", "r", "Ljava/util/Random;", random).await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn compare( + jvm: &Jvm, + comparator: &ClassInstanceRef, + left: &ClassInstanceRef, + right: &ClassInstanceRef, + ) -> Result { + if comparator.is_null() { + if left.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection element").await); + } + if !jvm.is_instance(left.as_ref(), "java/lang/Comparable") { + return Err(jvm.exception("java/lang/ClassCastException", &left.class_definition().name()).await); + } + jvm.invoke_virtual(left, "compareTo", "(Ljava/lang/Object;)I", (right.clone(),)).await + } else { + jvm.invoke_virtual( + comparator, + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + (left.clone(), right.clone()), + ) + .await + } + } + + async fn write_list(jvm: &Jvm, list: &ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + let length = jvm.array_length(&elements).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + for element in jvm.load_array::>(&elements, 0, length).await? { + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let _: () = jvm.invoke_virtual(&iterator, "set", "(Ljava/lang/Object;)V", (element,)).await?; + } + Ok(()) + } + + async fn sort_values(jvm: &Jvm, list: ClassInstanceRef, comparator: ClassInstanceRef) -> Result<()> { + if list.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "list").await); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&list, "toArray", "()[Ljava/lang/Object;", ()).await?; + if comparator.is_null() { + let _: () = jvm + .invoke_static("java/util/Arrays", "sort", "([Ljava/lang/Object;)V", (elements.clone(),)) + .await?; + } else { + let _: () = jvm + .invoke_static( + "java/util/Arrays", + "sort", + "([Ljava/lang/Object;Ljava/util/Comparator;)V", + (elements.clone(), comparator), + ) + .await?; + } + + Self::write_list(jvm, &list, elements).await + } + + async fn sort(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef) -> Result<()> { + Self::sort_values(jvm, list, None.into()).await + } + + async fn sort_comparator(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef, comparator: ClassInstanceRef) -> Result<()> { + Self::sort_values(jvm, list, comparator).await + } + + async fn search(jvm: &Jvm, list: ClassInstanceRef, key: ClassInstanceRef, comparator: ClassInstanceRef) -> Result { + if list.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "list").await); + } + + let mut low = 0i32; + let mut high: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + while low < high { + let middle = low + (high - low) / 2; + let value: ClassInstanceRef = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (middle,)).await?; + let comparison = Self::compare(jvm, &comparator, &value, &key).await?; + if comparison < 0 { + low = middle + 1; + } else if comparison > 0 { + high = middle; + } else { + return Ok(middle); + } + } + Ok(-low - 1) + } + + async fn binary_search(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef, key: ClassInstanceRef) -> Result { + Self::search(jvm, list, key, None.into()).await + } + + async fn binary_search_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + list: ClassInstanceRef, + key: ClassInstanceRef, + comparator: ClassInstanceRef, + ) -> Result { + Self::search(jvm, list, key, comparator).await + } + + async fn reverse(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef) -> Result<()> { + if list.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "list").await); + } + + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + if size < 2 { + return Ok(()); + } + let forward: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + let backward: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (size,)).await?; + for _ in 0..size / 2 { + let left: ClassInstanceRef = jvm.invoke_virtual(&forward, "next", "()Ljava/lang/Object;", ()).await?; + let right: ClassInstanceRef = jvm.invoke_virtual(&backward, "previous", "()Ljava/lang/Object;", ()).await?; + let _: () = jvm.invoke_virtual(&forward, "set", "(Ljava/lang/Object;)V", (right,)).await?; + let _: () = jvm.invoke_virtual(&backward, "set", "(Ljava/lang/Object;)V", (left,)).await?; + } + Ok(()) + } + + async fn fill(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + if list.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "list").await); + } + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let _: () = jvm.invoke_virtual(&iterator, "set", "(Ljava/lang/Object;)V", (element.clone(),)).await?; + } + Ok(()) + } + + async fn copy(jvm: &Jvm, _: &mut RuntimeContext, destination: ClassInstanceRef, source: ClassInstanceRef) -> Result<()> { + if source.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "source").await); + } + let source_size: i32 = jvm.invoke_virtual(&source, "size", "()I", ()).await?; + if destination.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "destination").await); + } + let destination_size: i32 = jvm.invoke_virtual(&destination, "size", "()I", ()).await?; + if source_size > destination_size { + return Err(jvm + .exception("java/lang/IndexOutOfBoundsException", "source does not fit in destination") + .await); + } + + let elements: ClassInstanceRef> = jvm.invoke_virtual(&source, "toArray", "()[Ljava/lang/Object;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&destination, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + for element in jvm.load_array::>(&elements, 0, source_size as usize).await? { + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let _: () = jvm.invoke_virtual(&iterator, "set", "(Ljava/lang/Object;)V", (element,)).await?; + } + Ok(()) + } + + async fn shuffle_values(jvm: &Jvm, list: ClassInstanceRef, random: ClassInstanceRef) -> Result<()> { + if list.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "list").await); + } + let elements: ClassInstanceRef> = jvm.invoke_virtual(&list, "toArray", "()[Ljava/lang/Object;", ()).await?; + let length = jvm.array_length(&elements).await?; + if length < 2 { + return Ok(()); + } + if random.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "random").await); + } + let mut values = jvm.load_array::>(&elements, 0, length).await?; + for index in (1..length).rev() { + let swap_index: i32 = jvm.invoke_virtual(&random, "nextInt", "(I)I", ((index + 1) as i32,)).await?; + if swap_index < 0 || swap_index as usize > index { + return Err(jvm.exception("java/lang/ArrayIndexOutOfBoundsException", "random index").await); + } + values.swap(index, swap_index as usize); + } + + let mut shuffled: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", length).await?.into(); + if !values.is_empty() { + jvm.store_array(&mut shuffled, 0, values).await?; + } + Self::write_list(jvm, &list, shuffled).await + } + + async fn shuffle(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef) -> Result<()> { + let random: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "r", "Ljava/util/Random;").await?; + Self::shuffle_values(jvm, list, random).await + } + + async fn shuffle_random(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef, random: ClassInstanceRef) -> Result<()> { + Self::shuffle_values(jvm, list, random).await + } + + async fn extreme( + jvm: &Jvm, + collection: ClassInstanceRef, + comparator: ClassInstanceRef, + find_minimum: bool, + ) -> Result> { + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&collection, "iterator", "()Ljava/util/Iterator;", ()).await?; + if !jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + return Err(jvm.exception("java/util/NoSuchElementException", "empty collection").await); + } + let mut candidate: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let next: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let comparison = Self::compare(jvm, &comparator, &next, &candidate).await?; + if (find_minimum && comparison < 0) || (!find_minimum && comparison > 0) { + candidate = next; + } + } + Ok(candidate) + } + + async fn min(jvm: &Jvm, _: &mut RuntimeContext, collection: ClassInstanceRef) -> Result> { + Self::extreme(jvm, collection, None.into(), true).await + } + + async fn min_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + collection: ClassInstanceRef, + comparator: ClassInstanceRef, + ) -> Result> { + Self::extreme(jvm, collection, comparator, true).await + } + + async fn max(jvm: &Jvm, _: &mut RuntimeContext, collection: ClassInstanceRef) -> Result> { + Self::extreme(jvm, collection, None.into(), false).await + } + + async fn max_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + collection: ClassInstanceRef, + comparator: ClassInstanceRef, + ) -> Result> { + Self::extreme(jvm, collection, comparator, false).await + } + + async fn n_copies(jvm: &Jvm, _: &mut RuntimeContext, count: i32, element: ClassInstanceRef) -> Result> { + if count < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "List length = negative").await); + } + Ok(jvm + .new_class("java/util/Collections$CopiesList", "(ILjava/lang/Object;)V", (count, element)) + .await? + .into()) + } + + async fn singleton(jvm: &Jvm, _: &mut RuntimeContext, element: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$SingletonSet", "(Ljava/lang/Object;)V", (element,)) + .await? + .into()) + } + + async fn unmodifiable_collection(jvm: &Jvm, _: &mut RuntimeContext, collection: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$UnmodifiableCollection", "(Ljava/util/Collection;)V", (collection,)) + .await? + .into()) + } + + async fn unmodifiable_list(jvm: &Jvm, _: &mut RuntimeContext, list: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$UnmodifiableList", "(Ljava/util/List;)V", (list,)) + .await? + .into()) + } + + async fn unmodifiable_set(jvm: &Jvm, _: &mut RuntimeContext, set: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSet", "(Ljava/util/Set;)V", (set,)) + .await? + .into()) + } + + async fn unmodifiable_map(jvm: &Jvm, _: &mut RuntimeContext, map: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$UnmodifiableMap", "(Ljava/util/Map;)V", (map,)) + .await? + .into()) + } + + async fn unmodifiable_sorted_set(jvm: &Jvm, _: &mut RuntimeContext, set: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedSet", "(Ljava/util/SortedSet;)V", (set,)) + .await? + .into()) + } + + async fn unmodifiable_sorted_map(jvm: &Jvm, _: &mut RuntimeContext, map: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedMap", "(Ljava/util/SortedMap;)V", (map,)) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/collections_copies_list.rs b/java_runtime/src/classes/java/util/collections_copies_list.rs new file mode 100644 index 00000000..9ccda21d --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_copies_list.rs @@ -0,0 +1,202 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$CopiesList +pub struct CollectionsCopiesList; + +impl CollectionsCopiesList { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$CopiesList", + parent_class: Some("java/util/AbstractList"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(ILjava/lang/Object;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/Object;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/Object;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove_at, 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("n", "I", FieldAccessFlags::FINAL), + JavaFieldProto::new("element", "Ljava/lang/Object;", FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, count: i32, element: ClassInstanceRef) -> Result<()> { + if count < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "List length = negative").await); + } + let _: () = jvm.invoke_special(&this, "java/util/AbstractList", "", "()V", ()).await?; + jvm.put_field(&mut this, "n", "I", count).await?; + jvm.put_field(&mut this, "element", "Ljava/lang/Object;", element).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "n", "I").await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + if jvm.get_field::(&this, "n", "I").await? == 0 { + return Ok(false); + } + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + if target.is_null() { + return Ok(element.is_null()); + } + if element.is_null() { + return Ok(false); + } + jvm.invoke_virtual(&target, "equals", "(Ljava/lang/Object;)Z", (element,)).await + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + if jvm.get_field::(&this, "n", "I").await? == 0 { + return Ok(-1); + } + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let equal = if target.is_null() { + element.is_null() + } else if element.is_null() { + false + } else { + jvm.invoke_virtual::<_, bool>(&target, "equals", "(Ljava/lang/Object;)Z", (element,)) + .await? + }; + Ok(if equal { 0 } else { -1 }) + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + let count: i32 = jvm.get_field(&this, "n", "I").await?; + if count == 0 { + return Ok(-1); + } + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let equal = if target.is_null() { + element.is_null() + } else if element.is_null() { + false + } else { + jvm.invoke_virtual::<_, bool>(&target, "equals", "(Ljava/lang/Object;)Z", (element,)) + .await? + }; + Ok(if equal { count - 1 } else { -1 }) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let count: i32 = jvm.get_field(&this, "n", "I").await?; + if index < 0 || index >= count { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "index").await); + } + jvm.get_field(&this, "element", "Ljava/lang/Object;").await + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + if jvm.get_field::(&this, "n", "I").await? == 0 { + return Ok(()); + } + Err(jvm.exception("java/lang/UnsupportedOperationException", "copies list").await) + } + + async fn remove_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + let count: i32 = jvm.get_field(&this, "n", "I").await?; + if count == 0 { + return Ok(false); + } + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let equal = if target.is_null() { + element.is_null() + } else if element.is_null() { + false + } else { + jvm.invoke_virtual::<_, bool>(&target, "equals", "(Ljava/lang/Object;)Z", (element,)) + .await? + }; + if !equal { + return Ok(false); + } + Err(jvm.exception("java/lang/UnsupportedOperationException", "copies list").await) + } + + async fn add_at(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "copies list").await) + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + _: ClassInstanceRef, + _: i32, + _: ClassInstanceRef, + ) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "copies list").await) + } + + async fn remove_at(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "copies list").await) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Ok(false); + } + if this.identity() == other.identity() { + return Ok(true); + } + if !jvm.is_instance(other.as_ref(), "java/util/List") { + return Ok(false); + } + + let count: i32 = jvm.get_field(&this, "n", "I").await?; + if jvm.invoke_virtual::<_, i32>(&other, "size", "()I", ()).await? != count { + return Ok(false); + } + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&other, "iterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let current: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let equal = if element.is_null() { + current.is_null() + } else if current.is_null() { + false + } else { + jvm.invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await? + }; + if !equal { + return Ok(false); + } + } + Ok(true) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let count: i32 = jvm.get_field(&this, "n", "I").await?; + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let element_hash = if element.is_null() { + 0 + } else { + jvm.invoke_virtual(&element, "hashCode", "()I", ()).await? + }; + let mut hash = 1i32; + for _ in 0..count { + hash = hash.wrapping_mul(31).wrapping_add(element_hash); + } + Ok(hash) + } +} diff --git a/java_runtime/src/classes/java/util/collections_empty_list.rs b/java_runtime/src/classes/java/util/collections_empty_list.rs new file mode 100644 index 00000000..49e7760c --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_empty_list.rs @@ -0,0 +1,88 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$EmptyList +pub struct CollectionsEmptyList; + +impl CollectionsEmptyList { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$EmptyList", + parent_class: Some("java/util/AbstractList"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PRIVATE), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/util/AbstractList", "", "()V", ()).await + } + + async fn size(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } + + async fn contains(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result> { + Err(jvm.exception("java/lang/IndexOutOfBoundsException", "empty list").await) + } + + async fn clear(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } + + async fn remove_object(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn add_at(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "empty list").await) + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + _: ClassInstanceRef, + _: i32, + _: ClassInstanceRef, + ) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "empty list").await) + } + + async fn remove_at(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "empty list").await) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/List") { + return Ok(false); + } + jvm.invoke_virtual(&other, "isEmpty", "()Z", ()).await + } + + async fn hash_code(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(1) + } +} diff --git a/java_runtime/src/classes/java/util/collections_empty_set.rs b/java_runtime/src/classes/java/util/collections_empty_set.rs new file mode 100644 index 00000000..357a70ba --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_empty_set.rs @@ -0,0 +1,68 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$EmptySet +pub struct CollectionsEmptySet; + +impl CollectionsEmptySet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$EmptySet", + parent_class: Some("java/util/AbstractSet"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PRIVATE), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await + } + + async fn size(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } + + async fn contains(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result> { + let list: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_LIST", "Ljava/util/List;").await?; + jvm.invoke_virtual(&list, "iterator", "()Ljava/util/Iterator;", ()).await + } + + async fn clear(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } + + async fn remove(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Set") { + return Ok(false); + } + jvm.invoke_virtual(&other, "isEmpty", "()Z", ()).await + } + + async fn hash_code(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } +} diff --git a/java_runtime/src/classes/java/util/collections_singleton_set.rs b/java_runtime/src/classes/java/util/collections_singleton_set.rs new file mode 100644 index 00000000..7f131fe7 --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_singleton_set.rs @@ -0,0 +1,110 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$SingletonSet +pub struct CollectionsSingletonSet; + +impl CollectionsSingletonSet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$SingletonSet", + parent_class: Some("java/util/AbstractSet"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/Object;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, 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( + "element", + "Ljava/lang/Object;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + jvm.put_field(&mut this, "element", "Ljava/lang/Object;", element).await + } + + async fn size(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(1) + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + if target.is_null() { + return Ok(element.is_null()); + } + if element.is_null() { + return Ok(false); + } + jvm.invoke_virtual(&target, "equals", "(Ljava/lang/Object;)Z", (element,)).await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let list = jvm + .new_class("java/util/Collections$CopiesList", "(ILjava/lang/Object;)V", (1, element)) + .await?; + jvm.invoke_virtual(&list, "iterator", "()Ljava/util/Iterator;", ()).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let equal = if target.is_null() { + element.is_null() + } else if element.is_null() { + false + } else { + jvm.invoke_virtual::<_, bool>(&target, "equals", "(Ljava/lang/Object;)Z", (element,)) + .await? + }; + if !equal { + return Ok(false); + } + Err(jvm.exception("java/lang/UnsupportedOperationException", "singleton set").await) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Ok(false); + } + if this.identity() == other.identity() { + return Ok(true); + } + if !jvm.is_instance(other.as_ref(), "java/util/Set") || jvm.invoke_virtual::<_, i32>(&other, "size", "()I", ()).await? != 1 { + return Ok(false); + } + let iterator: ClassInstanceRef = jvm.invoke_virtual(&other, "iterator", "()Ljava/util/Iterator;", ()).await?; + let other_element: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + if other_element.is_null() { + return Ok(element.is_null()); + } + if element.is_null() { + return Ok(false); + } + jvm.invoke_virtual(&other_element, "equals", "(Ljava/lang/Object;)Z", (element,)).await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + if element.is_null() { + Ok(0) + } else { + jvm.invoke_virtual(&element, "hashCode", "()I", ()).await + } + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_collection.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_collection.rs new file mode 100644 index 00000000..7e480cdf --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_collection.rs @@ -0,0 +1,127 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableCollection +pub struct CollectionsUnmodifiableCollection; + +impl CollectionsUnmodifiableCollection { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableCollection", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Collection", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Collection;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toArray", "()[Ljava/lang/Object;", Self::to_array, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + Self::to_typed_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("containsAll", "(Ljava/util/Collection;)Z", Self::contains_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addAll", "(Ljava/util/Collection;)Z", Self::add_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("removeAll", "(Ljava/util/Collection;)Z", Self::remove_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("retainAll", "(Ljava/util/Collection;)Z", Self::retain_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("c", "Ljava/util/Collection;", FieldAccessFlags::FINAL)], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, collection: ClassInstanceRef) -> Result<()> { + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "c", "Ljava/util/Collection;", collection).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&collection, "size", "()I", ()).await + } + + async fn is_empty(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&collection, "isEmpty", "()Z", ()).await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&collection, "contains", "(Ljava/lang/Object;)Z", (element,)).await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&collection, "iterator", "()Ljava/util/Iterator;", ()).await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableCollection$1", "(Ljava/util/Iterator;)V", (iterator,)) + .await? + .into()) + } + + async fn to_array(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&collection, "toArray", "()[Ljava/lang/Object;", ()).await + } + + async fn to_typed_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + array: ClassInstanceRef>, + ) -> Result>> { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&collection, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (array,)) + .await + } + + async fn contains_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + let backing: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&backing, "containsAll", "(Ljava/util/Collection;)Z", (collection,)) + .await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let collection: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&collection, "toString", "()Ljava/lang/String;", ()).await + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable collection").await) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable collection").await) + } + + async fn add_all(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable collection").await) + } + + async fn remove_all(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable collection").await) + } + + async fn retain_all(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable collection").await) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable collection").await) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_collection_iterator.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_collection_iterator.rs new file mode 100644 index 00000000..6d6fc224 --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_collection_iterator.rs @@ -0,0 +1,54 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableCollection$1 +pub struct CollectionsUnmodifiableCollectionIterator; + +impl CollectionsUnmodifiableCollectionIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableCollection$1", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Iterator;)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new( + "i", + "Ljava/util/Iterator;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, iterator: ClassInstanceRef) -> Result<()> { + if iterator.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "iterator").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "i", "Ljava/util/Iterator;", iterator).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/Iterator;").await?; + jvm.invoke_virtual(&iterator, "hasNext", "()Z", ()).await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/Iterator;").await?; + jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable iterator").await) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_list.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_list.rs new file mode 100644 index 00000000..2c1d0e7f --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_list.rs @@ -0,0 +1,137 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableList +pub struct CollectionsUnmodifiableList; + +impl CollectionsUnmodifiableList { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableList", + parent_class: Some("java/util/Collections$UnmodifiableCollection"), + interfaces: vec!["java/util/List"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/List;)V", Self::init, Default::default()), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/Object;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/Object;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("subList", "(II)Ljava/util/List;", Self::sub_list, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addAll", "(ILjava/util/Collection;)Z", Self::add_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("list", "Ljava/util/List;", FieldAccessFlags::FINAL)], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, list: ClassInstanceRef) -> Result<()> { + let _: () = jvm + .invoke_special( + &this, + "java/util/Collections$UnmodifiableCollection", + "", + "(Ljava/util/Collection;)V", + (list.clone(),), + ) + .await?; + jvm.put_field(&mut this, "list", "Ljava/util/List;", list).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if !other.is_null() && this.identity() == other.identity() { + return Ok(true); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + jvm.invoke_virtual(&list, "equals", "(Ljava/lang/Object;)Z", (other,)).await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + jvm.invoke_virtual(&list, "hashCode", "()I", ()).await + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (index,)).await + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + jvm.invoke_virtual(&list, "indexOf", "(Ljava/lang/Object;)I", (element,)).await + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + jvm.invoke_virtual(&list, "lastIndexOf", "(Ljava/lang/Object;)I", (element,)).await + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableList$1", "(Ljava/util/ListIterator;)V", (iterator,)) + .await? + .into()) + } + + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (index,)).await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableList$1", "(Ljava/util/ListIterator;)V", (iterator,)) + .await? + .into()) + } + + async fn sub_list(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, from: i32, to: i32) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/List;").await?; + let sub_list: ClassInstanceRef = jvm.invoke_virtual(&list, "subList", "(II)Ljava/util/List;", (from, to)).await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableList", "(Ljava/util/List;)V", (sub_list,)) + .await? + .into()) + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + _: ClassInstanceRef, + _: i32, + _: ClassInstanceRef, + ) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable list").await) + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable list").await) + } + + async fn add_all(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable list").await) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable list").await) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_list_iterator.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_list_iterator.rs new file mode 100644 index 00000000..3549c84b --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_list_iterator.rs @@ -0,0 +1,94 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableList$1 +pub struct CollectionsUnmodifiableListIterator; + +impl CollectionsUnmodifiableListIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableList$1", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/ListIterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/ListIterator;)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hasPrevious", "()Z", Self::has_previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previous", "()Ljava/lang/Object;", Self::previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextIndex", "()I", Self::next_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previousIndex", "()I", Self::previous_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(Ljava/lang/Object;)V", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)V", Self::add, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new( + "i", + "Ljava/util/ListIterator;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, iterator: ClassInstanceRef) -> Result<()> { + if iterator.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "iterator").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "i", "Ljava/util/ListIterator;", iterator).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/ListIterator;").await?; + jvm.invoke_virtual(&iterator, "hasNext", "()Z", ()).await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/ListIterator;").await?; + jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await + } + + async fn has_previous(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/ListIterator;").await?; + jvm.invoke_virtual(&iterator, "hasPrevious", "()Z", ()).await + } + + async fn previous(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/ListIterator;").await?; + jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await + } + + async fn next_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/ListIterator;").await?; + jvm.invoke_virtual(&iterator, "nextIndex", "()I", ()).await + } + + async fn previous_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/ListIterator;").await?; + jvm.invoke_virtual(&iterator, "previousIndex", "()I", ()).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm + .exception("java/lang/UnsupportedOperationException", "unmodifiable list iterator") + .await) + } + + async fn set(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Err(jvm + .exception("java/lang/UnsupportedOperationException", "unmodifiable list iterator") + .await) + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Err(jvm + .exception("java/lang/UnsupportedOperationException", "unmodifiable list iterator") + .await) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_map.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_map.rs new file mode 100644 index 00000000..4819d712 --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_map.rs @@ -0,0 +1,181 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableMap +pub struct CollectionsUnmodifiableMap; + +impl CollectionsUnmodifiableMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableMap", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Map;)V", Self::init, 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("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("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), + 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), + ], + fields: vec![ + JavaFieldProto::new("m", "Ljava/util/Map;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + JavaFieldProto::new("keySet", "Ljava/util/Set;", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + JavaFieldProto::new("entrySet", "Ljava/util/Set;", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + JavaFieldProto::new( + "values", + "Ljava/util/Collection;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT, + ), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "m", "Ljava/util/Map;", map).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "size", "()I", ()).await + } + + async fn is_empty(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "isEmpty", "()Z", ()).await + } + + async fn contains_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "containsKey", "(Ljava/lang/Object;)Z", (key,)).await + } + + async fn contains_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "containsValue", "(Ljava/lang/Object;)Z", (value,)).await + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await + } + + async fn key_set(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cached: ClassInstanceRef = jvm.get_field(&this, "keySet", "Ljava/util/Set;").await?; + if !cached.is_null() { + return Ok(cached); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let wrapped: ClassInstanceRef = jvm + .new_class("java/util/Collections$UnmodifiableSet", "(Ljava/util/Set;)V", (keys,)) + .await? + .into(); + jvm.put_field(&mut this, "keySet", "Ljava/util/Set;", wrapped.clone()).await?; + Ok(wrapped) + } + + async fn values(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cached: ClassInstanceRef = jvm.get_field(&this, "values", "Ljava/util/Collection;").await?; + if !cached.is_null() { + return Ok(cached); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + let wrapped: ClassInstanceRef = jvm + .new_class("java/util/Collections$UnmodifiableCollection", "(Ljava/util/Collection;)V", (values,)) + .await? + .into(); + jvm.put_field(&mut this, "values", "Ljava/util/Collection;", wrapped.clone()).await?; + Ok(wrapped) + } + + async fn entry_set(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cached: ClassInstanceRef = jvm.get_field(&this, "entrySet", "Ljava/util/Set;").await?; + if !cached.is_null() { + return Ok(cached); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let wrapped: ClassInstanceRef = jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet", + "(Ljava/util/Set;)V", + (entries,), + ) + .await? + .into(); + jvm.put_field(&mut this, "entrySet", "Ljava/util/Set;", wrapped.clone()).await?; + Ok(wrapped) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if !other.is_null() && this.identity() == other.identity() { + return Ok(true); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "equals", "(Ljava/lang/Object;)Z", (other,)).await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "hashCode", "()I", ()).await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/Map;").await?; + jvm.invoke_virtual(&map, "toString", "()Ljava/lang/String;", ()).await + } + + async fn put( + jvm: &Jvm, + _: &mut RuntimeContext, + _: ClassInstanceRef, + _: ClassInstanceRef, + _: ClassInstanceRef, + ) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable map").await) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable map").await) + } + + async fn put_all(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable map").await) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable map").await) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry.rs new file mode 100644 index 00000000..2b3b4730 --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry.rs @@ -0,0 +1,104 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry +pub struct CollectionsUnmodifiableMapEntry; + +impl CollectionsUnmodifiableMapEntry { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map$Entry"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Map$Entry;)V", Self::init, 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("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new( + "e", + "Ljava/util/Map$Entry;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, entry: ClassInstanceRef) -> Result<()> { + if entry.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "entry").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "e", "Ljava/util/Map$Entry;", entry).await + } + + async fn get_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm.get_field(&this, "e", "Ljava/util/Map$Entry;").await?; + jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await + } + + async fn get_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm.get_field(&this, "e", "Ljava/util/Map$Entry;").await?; + jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await + } + + async fn set_value( + jvm: &Jvm, + _: &mut RuntimeContext, + _: ClassInstanceRef, + _: ClassInstanceRef, + ) -> Result> { + Err(jvm.exception("java/lang/UnsupportedOperationException", "unmodifiable map entry").await) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + if this.identity() == other.identity() { + return Ok(true); + } + let key: ClassInstanceRef = jvm.invoke_virtual(&this, "getKey", "()Ljava/lang/Object;", ()).await?; + let other_key: ClassInstanceRef = jvm.invoke_virtual(&other, "getKey", "()Ljava/lang/Object;", ()).await?; + let keys_equal = if key.is_null() { + other_key.is_null() + } else { + jvm.invoke_virtual::<_, bool>(&key, "equals", "(Ljava/lang/Object;)Z", (other_key,)) + .await? + }; + if !keys_equal { + return Ok(false); + } + let value: ClassInstanceRef = jvm.invoke_virtual(&this, "getValue", "()Ljava/lang/Object;", ()).await?; + let other_value: ClassInstanceRef = jvm.invoke_virtual(&other, "getValue", "()Ljava/lang/Object;", ()).await?; + if value.is_null() { + Ok(other_value.is_null()) + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (other_value,)).await + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let entry: ClassInstanceRef = jvm.get_field(&this, "e", "Ljava/util/Map$Entry;").await?; + jvm.invoke_virtual(&entry, "hashCode", "()I", ()).await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm.get_field(&this, "e", "Ljava/util/Map$Entry;").await?; + jvm.invoke_virtual(&entry, "toString", "()Ljava/lang/String;", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry_set.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry_set.rs new file mode 100644 index 00000000..7fb75b70 --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry_set.rs @@ -0,0 +1,171 @@ +use alloc::{vec, vec::Vec}; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{Array, AsClassInstance, ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet +pub struct CollectionsUnmodifiableMapEntrySet; + +impl CollectionsUnmodifiableMapEntrySet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet", + parent_class: Some("java/util/Collections$UnmodifiableSet"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Set;)V", Self::init, Default::default()), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toArray", "()[Ljava/lang/Object;", Self::to_array, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + Self::to_typed_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsAll", "(Ljava/util/Collection;)Z", Self::contains_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, set: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/util/Collections$UnmodifiableSet", "", "(Ljava/util/Set;)V", (set,)) + .await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&set, "iterator", "()Ljava/util/Iterator;", ()).await?; + Ok(jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1", + "(Ljava/util/Iterator;)V", + (iterator,), + ) + .await? + .into()) + } + + async fn to_array(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + let set: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + let entries: ClassInstanceRef> = jvm.invoke_virtual(&set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let length = jvm.array_length(&entries).await?; + let mut wrapped: Vec> = Vec::with_capacity(length); + for entry in jvm.load_array::>(&entries, 0, length).await? { + wrapped.push( + jvm.new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (entry,), + ) + .await? + .into(), + ); + } + let mut result: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", length).await?.into(); + if !wrapped.is_empty() { + jvm.store_array(&mut result, 0, wrapped).await?; + } + Ok(result) + } + + async fn to_typed_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + destination: ClassInstanceRef>, + ) -> Result>> { + if destination.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array").await); + } + let class_name = destination.class_definition().name(); + let component_descriptor = class_name.strip_prefix('[').unwrap(); + let set: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + let entries: ClassInstanceRef> = jvm.invoke_virtual(&set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let length = jvm.array_length(&entries).await?; + let mut wrapped: Vec> = Vec::with_capacity(length); + for entry in jvm.load_array::>(&entries, 0, length).await? { + wrapped.push( + jvm.new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (entry,), + ) + .await? + .into(), + ); + } + + let destination_length = jvm.array_length(&destination).await?; + let mut result = if destination_length < length { + ClassInstanceRef::from(jvm.instantiate_array(component_descriptor, length).await?) + } else { + destination + }; + for (index, entry) in wrapped.into_iter().enumerate() { + if !jvm.array_store_allowed(result.as_class_instance(), entry.as_class_instance()) { + return Err(jvm.exception("java/lang/ArrayStoreException", &entry.class_definition().name()).await); + } + jvm.store_array(&mut result, index, core::iter::once(entry)).await?; + } + if destination_length > length { + jvm.store_array(&mut result, length, core::iter::once(ClassInstanceRef::::from(None))) + .await?; + } + Ok(result) + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, candidate: ClassInstanceRef) -> Result { + if candidate.is_null() || !jvm.is_instance(candidate.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + let safe: ClassInstanceRef = jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (candidate,), + ) + .await? + .into(); + let set: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&set, "contains", "(Ljava/lang/Object;)Z", (safe,)).await + } + + async fn contains_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + let iterator: ClassInstanceRef = jvm.invoke_virtual(&collection, "iterator", "()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?; + if !jvm + .invoke_virtual::<_, bool>(&this, "contains", "(Ljava/lang/Object;)Z", (entry,)) + .await? + { + return Ok(false); + } + } + Ok(true) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if !other.is_null() && this.identity() == other.identity() { + return Ok(true); + } + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Set") { + return Ok(false); + } + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + let other_size: i32 = jvm.invoke_virtual(&other, "size", "()I", ()).await?; + if size != other_size { + return Ok(false); + } + jvm.invoke_virtual(&this, "containsAll", "(Ljava/util/Collection;)Z", (other,)).await + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry_set_iterator.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry_set_iterator.rs new file mode 100644 index 00000000..a7804c3f --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_map_entry_set_iterator.rs @@ -0,0 +1,64 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 +pub struct CollectionsUnmodifiableMapEntrySetIterator; + +impl CollectionsUnmodifiableMapEntrySetIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Iterator;)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new( + "i", + "Ljava/util/Iterator;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, iterator: ClassInstanceRef) -> Result<()> { + if iterator.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "iterator").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "i", "Ljava/util/Iterator;", iterator).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/Iterator;").await?; + jvm.invoke_virtual(&iterator, "hasNext", "()Z", ()).await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator: ClassInstanceRef = jvm.get_field(&this, "i", "Ljava/util/Iterator;").await?; + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + Ok(jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (entry,), + ) + .await? + .into()) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm + .exception("java/lang/UnsupportedOperationException", "unmodifiable entry iterator") + .await) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_set.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_set.rs new file mode 100644 index 00000000..33bb250e --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_set.rs @@ -0,0 +1,51 @@ +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}; + +// class java.util.Collections$UnmodifiableSet +pub struct CollectionsUnmodifiableSet; + +impl CollectionsUnmodifiableSet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableSet", + parent_class: Some("java/util/Collections$UnmodifiableCollection"), + interfaces: vec!["java/util/Set", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Set;)V", Self::init, Default::default()), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, set: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/Collections$UnmodifiableCollection", + "", + "(Ljava/util/Collection;)V", + (set,), + ) + .await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if !other.is_null() && this.identity() == other.identity() { + return Ok(true); + } + let set: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&set, "equals", "(Ljava/lang/Object;)Z", (other,)).await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let set: ClassInstanceRef = jvm.get_field(&this, "c", "Ljava/util/Collection;").await?; + jvm.invoke_virtual(&set, "hashCode", "()I", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_sorted_map.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_sorted_map.rs new file mode 100644 index 00000000..05362f57 --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_sorted_map.rs @@ -0,0 +1,127 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableSortedMap +pub struct CollectionsUnmodifiableSortedMap; + +impl CollectionsUnmodifiableSortedMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableSortedMap", + parent_class: Some("java/util/Collections$UnmodifiableMap"), + interfaces: vec!["java/util/SortedMap", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/SortedMap;)V", Self::init, Default::default()), + JavaMethodProto::new("comparator", "()Ljava/util/Comparator;", Self::comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("firstKey", "()Ljava/lang/Object;", Self::first_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastKey", "()Ljava/lang/Object;", Self::last_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::sub_map, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "headMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::head_map, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "tailMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::tail_map, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![JavaFieldProto::new( + "sm", + "Ljava/util/SortedMap;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let _: () = jvm + .invoke_special( + &this, + "java/util/Collections$UnmodifiableMap", + "", + "(Ljava/util/Map;)V", + (map.clone(),), + ) + .await?; + jvm.put_field(&mut this, "sm", "Ljava/util/SortedMap;", map).await + } + + async fn comparator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "sm", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "comparator", "()Ljava/util/Comparator;", ()).await + } + + async fn first_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "sm", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "firstKey", "()Ljava/lang/Object;", ()).await + } + + async fn last_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "sm", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "lastKey", "()Ljava/lang/Object;", ()).await + } + + async fn sub_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from: ClassInstanceRef, + to: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "sm", "Ljava/util/SortedMap;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&map, "subMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", (from, to)) + .await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedMap", "(Ljava/util/SortedMap;)V", (range,)) + .await? + .into()) + } + + async fn head_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + to: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "sm", "Ljava/util/SortedMap;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&map, "headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (to,)) + .await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedMap", "(Ljava/util/SortedMap;)V", (range,)) + .await? + .into()) + } + + async fn tail_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "sm", "Ljava/util/SortedMap;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&map, "tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (from,)) + .await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedMap", "(Ljava/util/SortedMap;)V", (range,)) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/collections_unmodifiable_sorted_set.rs b/java_runtime/src/classes/java/util/collections_unmodifiable_sorted_set.rs new file mode 100644 index 00000000..3cc3c06f --- /dev/null +++ b/java_runtime/src/classes/java/util/collections_unmodifiable_sorted_set.rs @@ -0,0 +1,127 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.Collections$UnmodifiableSortedSet +pub struct CollectionsUnmodifiableSortedSet; + +impl CollectionsUnmodifiableSortedSet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Collections$UnmodifiableSortedSet", + parent_class: Some("java/util/Collections$UnmodifiableSet"), + interfaces: vec!["java/util/SortedSet", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/SortedSet;)V", Self::init, Default::default()), + JavaMethodProto::new("comparator", "()Ljava/util/Comparator;", Self::comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("first", "()Ljava/lang/Object;", Self::first, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("last", "()Ljava/lang/Object;", Self::last, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + Self::sub_set, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "headSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + Self::head_set, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "tailSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + Self::tail_set, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![JavaFieldProto::new( + "ss", + "Ljava/util/SortedSet;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, set: ClassInstanceRef) -> Result<()> { + let _: () = jvm + .invoke_special( + &this, + "java/util/Collections$UnmodifiableSet", + "", + "(Ljava/util/Set;)V", + (set.clone(),), + ) + .await?; + jvm.put_field(&mut this, "ss", "Ljava/util/SortedSet;", set).await + } + + async fn comparator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "ss", "Ljava/util/SortedSet;").await?; + jvm.invoke_virtual(&set, "comparator", "()Ljava/util/Comparator;", ()).await + } + + async fn first(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "ss", "Ljava/util/SortedSet;").await?; + jvm.invoke_virtual(&set, "first", "()Ljava/lang/Object;", ()).await + } + + async fn last(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "ss", "Ljava/util/SortedSet;").await?; + jvm.invoke_virtual(&set, "last", "()Ljava/lang/Object;", ()).await + } + + async fn sub_set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from: ClassInstanceRef, + to: ClassInstanceRef, + ) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "ss", "Ljava/util/SortedSet;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&set, "subSet", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", (from, to)) + .await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedSet", "(Ljava/util/SortedSet;)V", (range,)) + .await? + .into()) + } + + async fn head_set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + to: ClassInstanceRef, + ) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "ss", "Ljava/util/SortedSet;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&set, "headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (to,)) + .await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedSet", "(Ljava/util/SortedSet;)V", (range,)) + .await? + .into()) + } + + async fn tail_set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from: ClassInstanceRef, + ) -> Result> { + let set: ClassInstanceRef = jvm.get_field(&this, "ss", "Ljava/util/SortedSet;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&set, "tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (from,)) + .await?; + Ok(jvm + .new_class("java/util/Collections$UnmodifiableSortedSet", "(Ljava/util/SortedSet;)V", (range,)) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/comparator.rs b/java_runtime/src/classes/java/util/comparator.rs new file mode 100644 index 00000000..b78c7ec8 --- /dev/null +++ b/java_runtime/src/classes/java/util/comparator.rs @@ -0,0 +1,29 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// interface java.util.Comparator +pub struct Comparator; + +impl Comparator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Comparator", + parent_class: None, + interfaces: vec![], + methods: vec![ + JavaMethodProto::new_abstract( + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("equals", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/util/concurrent_modification_exception.rs b/java_runtime/src/classes/java/util/concurrent_modification_exception.rs new file mode 100644 index 00000000..e17fd3d6 --- /dev/null +++ b/java_runtime/src/classes/java/util/concurrent_modification_exception.rs @@ -0,0 +1,39 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.util.ConcurrentModificationException +pub struct ConcurrentModificationException; + +impl ConcurrentModificationException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/ConcurrentModificationException", + parent_class: Some("java/lang/RuntimeException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.ConcurrentModificationException::({this:?})"); + + jvm.invoke_special(&this, "java/lang/RuntimeException", "", "()V", ()).await + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.ConcurrentModificationException::({this:?}, {message:?})"); + + jvm.invoke_special(&this, "java/lang/RuntimeException", "", "(Ljava/lang/String;)V", (message,)) + .await + } +} diff --git a/java_runtime/src/classes/java/util/date.rs b/java_runtime/src/classes/java/util/date.rs index fc9b033a..15fa1d4c 100644 --- a/java_runtime/src/classes/java/util/date.rs +++ b/java_runtime/src/classes/java/util/date.rs @@ -3,6 +3,7 @@ use alloc::{format, vec}; use chrono::{DateTime, Datelike, Timelike, Utc}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ @@ -20,16 +21,20 @@ impl Date { parent_class: Some("java/lang/Object"), interfaces: vec!["java/io/Serializable", "java/lang/Cloneable", "java/lang/Comparable"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(J)V", Self::init_with_time, Default::default()), - JavaMethodProto::new("getTime", "()J", Self::get_time, Default::default()), - JavaMethodProto::new("setTime", "(J)V", Self::set_time, Default::default()), - JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, Default::default()), - JavaMethodProto::new("hashCode", "()I", Self::hash_code, Default::default()), - JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(J)V", Self::init_with_time, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getTime", "()J", Self::get_time, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setTime", "(J)V", Self::set_time, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("before", "(Ljava/util/Date;)Z", Self::before, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("after", "(Ljava/util/Date;)Z", Self::after, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/util/Date;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to, 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: vec![JavaFieldProto::new("value", "J", Default::default())], - access_flags: Default::default(), + fields: vec![JavaFieldProto::new("value", "J", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, } } @@ -69,6 +74,54 @@ impl Date { Ok(()) } + async fn before(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, when: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Date::before({this:?}, {when:?})"); + + if when.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "when is null").await); + } + + let when: ClassInstanceRef = ClassInstanceRef::new(when.instance); + let time: i64 = jvm.get_field(&this, "value", "J").await?; + let when_time: i64 = jvm.get_field(&when, "value", "J").await?; + Ok(time < when_time) + } + + async fn after(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, when: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Date::after({this:?}, {when:?})"); + + if when.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "when is null").await); + } + + let when: ClassInstanceRef = ClassInstanceRef::new(when.instance); + let time: i64 = jvm.get_field(&this, "value", "J").await?; + let when_time: i64 = jvm.get_field(&when, "value", "J").await?; + Ok(time > when_time) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Date::compareTo({this:?}, {other:?})"); + + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "anotherDate is null").await); + } + if !jvm.is_instance(&**other, "java/util/Date") { + return Err(jvm.exception("java/lang/ClassCastException", &other.class_definition().name()).await); + } + + let other: ClassInstanceRef = ClassInstanceRef::new(other.instance); + let time: i64 = jvm.get_field(&this, "value", "J").await?; + let other_time: i64 = jvm.get_field(&other, "value", "J").await?; + Ok(if time < other_time { + -1 + } else if time == other_time { + 0 + } else { + 1 + }) + } + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { tracing::debug!("java.util.Date::equals({this:?}, {other:?})"); diff --git a/java_runtime/src/classes/java/util/hash_map.rs b/java_runtime/src/classes/java/util/hash_map.rs index ac0f925c..8038da37 100644 --- a/java_runtime/src/classes/java/util/hash_map.rs +++ b/java_runtime/src/classes/java/util/hash_map.rs @@ -1,6 +1,7 @@ use alloc::{format, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -22,6 +23,7 @@ impl HashMap { methods: vec![ JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + 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()), @@ -33,6 +35,7 @@ impl HashMap { Self::put, Default::default(), ), + 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()), @@ -44,7 +47,7 @@ impl HashMap { JavaFieldProto::new("size", "I", Default::default()), JavaFieldProto::new("threshold", "I", Default::default()), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } @@ -113,6 +116,20 @@ impl HashMap { Ok(()) } + async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.HashMap::({this:?}, {map:?})"); + + 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/HashMap", "", "(I)V", (capacity,)).await?; + let _: () = jvm.invoke_virtual(&this, "putAll", "(Ljava/util/Map;)V", (map,)).await?; + + Ok(()) + } + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.util.HashMap::size({this:?})"); @@ -179,10 +196,8 @@ impl HashMap { 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_len = jvm.array_length(&table).await? as i32; - let Some(bucket_index) = Self::bucket_index(key_hash, table_len) else { - return Err(jvm.exception("java/lang/RuntimeException", "HashMap table is empty").await); - }; + let table_len = jvm.array_length(&table).await?; + let bucket_index = Self::bucket_index(key_hash, table_len); let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; while !entry.is_null() { @@ -204,11 +219,7 @@ impl HashMap { let bucket_index = if size >= threshold { Self::rehash(jvm, &mut this).await?; table = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; - let table_len = jvm.array_length(&table).await? as i32; - let Some(bucket_index) = Self::bucket_index(key_hash, table_len) else { - return Err(jvm.exception("java/lang/RuntimeException", "HashMap table is empty").await); - }; - bucket_index + Self::bucket_index(key_hash, jvm.array_length(&table).await?) } else { bucket_index }; @@ -228,6 +239,13 @@ impl HashMap { Ok(None.into()) } + async fn put_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.HashMap::putAll({this:?}, {map:?})"); + + jvm.invoke_special(&this, "java/util/AbstractMap", "putAll", "(Ljava/util/Map;)V", (map,)) + .await + } + async fn remove( jvm: &Jvm, _: &mut RuntimeContext, @@ -238,10 +256,11 @@ impl HashMap { let key_hash = Self::object_hash_or_zero(jvm, &key).await?; let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; - let table_len = jvm.array_length(&table).await? as i32; - let Some(bucket_index) = Self::bucket_index(key_hash, table_len) else { + let table_len = jvm.array_length(&table).await?; + if table_len == 0 { return Ok(None.into()); - }; + } + let bucket_index = Self::bucket_index(key_hash, table_len); let mut previous: ClassInstanceRef = None.into(); let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; @@ -296,10 +315,11 @@ impl HashMap { ) -> Result> { let key_hash = Self::object_hash_or_zero(jvm, key).await?; let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; - let table_len = jvm.array_length(&table).await? as i32; - let Some(bucket_index) = Self::bucket_index(key_hash, table_len) else { + let table_len = jvm.array_length(&table).await?; + if table_len == 0 { return Ok(None.into()); - }; + } + let bucket_index = Self::bucket_index(key_hash, table_len); let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; while !entry.is_null() { @@ -335,7 +355,7 @@ impl HashMap { 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 = if old_capacity == 0 { 1 } else { old_capacity * 2 + 1 }; + let new_capacity = old_capacity * 2 + 1; let mut new_table: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", new_capacity).await?.into(); for bucket_index in 0..old_capacity { @@ -343,9 +363,7 @@ impl HashMap { while !entry.is_null() { let next: ClassInstanceRef = jvm.get_field(&entry, "next", "Ljava/util/HashMap$Entry;").await?; let entry_hash: i32 = jvm.get_field(&entry, "hash", "I").await?; - let Some(new_index) = Self::bucket_index(entry_hash, new_capacity as i32) else { - return Err(jvm.exception("java/lang/RuntimeException", "HashMap table is empty").await); - }; + let new_index = Self::bucket_index(entry_hash, new_capacity); let existing = Self::load_bucket(jvm, &new_table, new_index).await?; jvm.put_field(&mut entry, "next", "Ljava/util/HashMap$Entry;", existing).await?; @@ -363,9 +381,7 @@ impl HashMap { } async fn load_bucket(jvm: &Jvm, table: &ClassInstanceRef>, bucket_index: usize) -> Result> { - let mut entries = jvm.load_array(table, bucket_index, 1).await?; - - Ok(entries.pop().unwrap_or_else(|| None.into())) + Ok(jvm.load_array(table, bucket_index, 1).await?.remove(0)) } async fn object_equals(jvm: &Jvm, left: &ClassInstanceRef, right: &ClassInstanceRef) -> Result { @@ -401,19 +417,15 @@ impl HashMap { jvm.invoke_virtual(value, "hashCode", "()I", ()).await } - fn bucket_index(hash: i32, table_len: i32) -> Option { - if table_len <= 0 { - return None; - } - - Some(((hash & 0x7FFFFFFF) % table_len) as usize) + fn bucket_index(hash: i32, table_len: usize) -> usize { + (hash & 0x7FFFFFFF) as usize % table_len } async fn snapshot_entries(jvm: &Jvm, this: &ClassInstanceRef, kind: SnapshotKind) -> Result>> { let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; let table_len = jvm.array_length(&table).await?; let size: i32 = jvm.get_field(this, "size", "I").await?; - let mut elements: Vec> = Vec::with_capacity(size.max(0) as usize); + let mut elements: Vec> = Vec::with_capacity(size as usize); for bucket_index in 0..table_len { let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; 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 d07bbc63..7ca2d350 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry.rs @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -24,6 +25,8 @@ impl HashMapEntry { 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("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), ], fields: vec![ JavaFieldProto::new("hash", "I", Default::default()), @@ -81,4 +84,45 @@ impl HashMapEntry { Ok(old_value) } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + + let key: ClassInstanceRef = jvm.get_field(&this, "key", "Ljava/lang/Object;").await?; + let other_key: ClassInstanceRef = jvm.invoke_virtual(&other, "getKey", "()Ljava/lang/Object;", ()).await?; + let keys_equal = if key.is_null() { + other_key.is_null() + } else { + jvm.invoke_virtual(&key, "equals", "(Ljava/lang/Object;)Z", (other_key,)).await? + }; + if !keys_equal { + return Ok(false); + } + + let value: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + let other_value: ClassInstanceRef = jvm.invoke_virtual(&other, "getValue", "()Ljava/lang/Object;", ()).await?; + if value.is_null() { + Ok(other_value.is_null()) + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (other_value,)).await + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let key: ClassInstanceRef = jvm.get_field(&this, "key", "Ljava/lang/Object;").await?; + let value: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + let key_hash = if key.is_null() { + 0 + } else { + jvm.invoke_virtual(&key, "hashCode", "()I", ()).await? + }; + let value_hash = if value.is_null() { + 0 + } else { + jvm.invoke_virtual(&value, "hashCode", "()I", ()).await? + }; + Ok(key_hash ^ value_hash) + } } 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 a9e7bf62..0ecaf72f 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 @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -21,6 +22,7 @@ impl HashMapEntrySet { JavaMethodProto::new("size", "()I", Self::size, Default::default()), JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, Default::default()), ], @@ -63,16 +65,53 @@ impl HashMapEntrySet { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; let candidate_key: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getKey", "()Ljava/lang/Object;", ()).await?; + + let entry = HashMap::find_entry(jvm, &map, &candidate_key).await?; + if entry.is_null() { + return Ok(false); + } + + let entry_key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + if !Self::object_equals(jvm, &entry_key, &candidate_key).await? { + return Ok(false); + } + let candidate_value: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getValue", "()Ljava/lang/Object;", ()).await?; + let entry_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + Self::object_equals(jvm, &entry_value, &candidate_value).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, candidate: ClassInstanceRef) -> Result { + tracing::debug!("java.util.HashMap$EntrySet::remove({this:?}, {candidate:?})"); + + if candidate.is_null() || !jvm.is_instance(&**candidate, "java/util/Map$Entry") { + return Ok(false); + } + + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; + let candidate_key: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getKey", "()Ljava/lang/Object;", ()).await?; let entry = HashMap::find_entry(jvm, &map, &candidate_key).await?; if entry.is_null() { return Ok(false); } + let entry_key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + if !Self::object_equals(jvm, &entry_key, &candidate_key).await? { + return Ok(false); + } + + let candidate_value: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getValue", "()Ljava/lang/Object;", ()).await?; let entry_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + if !Self::object_equals(jvm, &entry_value, &candidate_value).await? { + return Ok(false); + } + + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (candidate_key,)) + .await?; - Self::object_equals(jvm, &candidate_value, &entry_value).await + Ok(true) } async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { 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 0b88e8ea..4dd2fed0 100644 --- a/java_runtime/src/classes/java/util/hash_map_values.rs +++ b/java_runtime/src/classes/java/util/hash_map_values.rs @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -21,6 +22,7 @@ impl HashMapValues { JavaMethodProto::new("size", "()I", Self::size, Default::default()), JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, Default::default()), ], @@ -62,6 +64,33 @@ impl HashMapValues { jvm.invoke_virtual(&map, "containsValue", "(Ljava/lang/Object;)Z", (value,)).await } + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + 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 entry_value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()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 { + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + return Ok(true); + } + } + + Ok(false) + } + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.HashMap$Values::clear({this:?})"); diff --git a/java_runtime/src/classes/java/util/hash_set.rs b/java_runtime/src/classes/java/util/hash_set.rs index d4adcec7..ffa5fc20 100644 --- a/java_runtime/src/classes/java/util/hash_set.rs +++ b/java_runtime/src/classes/java/util/hash_set.rs @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -21,6 +22,12 @@ impl HashSet { methods: vec![ JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + JavaMethodProto::new( + "", + "(Ljava/util/Collection;)V", + Self::init_from_collection, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, Default::default()), JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, Default::default()), JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), @@ -34,7 +41,7 @@ impl HashSet { JavaFieldProto::new("map", "Ljava/util/HashMap;", Default::default()), JavaFieldProto::new("present", "Ljava/lang/Object;", Default::default()), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } @@ -62,6 +69,25 @@ impl HashSet { Ok(()) } + async fn init_from_collection( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + collection: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.HashSet::({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + let size: i32 = jvm.invoke_virtual(&collection, "size", "()I", ()).await?; + let capacity = size.saturating_mul(2).max(DEFAULT_INITIAL_CAPACITY); + let _: () = jvm.invoke_special(&this, "java/util/HashSet", "", "(I)V", (capacity,)).await?; + let _: bool = jvm.invoke_virtual(&this, "addAll", "(Ljava/util/Collection;)Z", (collection,)).await?; + + Ok(()) + } + async fn add(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { tracing::debug!("java.util.HashSet::add({this:?}, {element:?})"); diff --git a/java_runtime/src/classes/java/util/hashtable.rs b/java_runtime/src/classes/java/util/hashtable.rs index 5510a942..d0ed3bca 100644 --- a/java_runtime/src/classes/java/util/hashtable.rs +++ b/java_runtime/src/classes/java/util/hashtable.rs @@ -1,8 +1,8 @@ use alloc::{string::String as RustString, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::MethodAccessFlags; -use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, @@ -26,6 +26,7 @@ impl Hashtable { methods: vec![ JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + 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("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), @@ -39,6 +40,12 @@ impl Hashtable { Self::put, Default::default(), ), + JavaMethodProto::new( + "putAll", + "(Ljava/util/Map;)V", + Self::put_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, Default::default()), JavaMethodProto::new("remove", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::remove, Default::default()), JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), @@ -46,14 +53,27 @@ impl Hashtable { JavaMethodProto::new("values", "()Ljava/util/Collection;", Self::values, Default::default()), JavaMethodProto::new("entrySet", "()Ljava/util/Set;", Self::entry_set, Default::default()), JavaMethodProto::new("rehash", "()V", Self::rehash, MethodAccessFlags::PROTECTED), + JavaMethodProto::new( + "equals", + "(Ljava/lang/Object;)Z", + Self::equals, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "hashCode", + "()I", + Self::hash_code, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), ], fields: vec![ JavaFieldProto::new("table", "[Ljava/util/Hashtable$Entry;", Default::default()), JavaFieldProto::new("count", "I", Default::default()), JavaFieldProto::new("threshold", "I", Default::default()), + JavaFieldProto::new("loadFactor", "F", Default::default()), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } @@ -79,6 +99,21 @@ impl Hashtable { jvm.put_field(&mut this, "count", "I", 0).await?; jvm.put_field(&mut this, "threshold", "I", (initial_capacity as f32 * DEFAULT_LOAD_FACTOR) as i32) .await?; + jvm.put_field(&mut this, "loadFactor", "F", DEFAULT_LOAD_FACTOR).await?; + + Ok(()) + } + + async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Hashtable::({this:?}, {map:?})"); + + 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/Hashtable", "", "(I)V", (capacity,)).await?; + let _: () = jvm.invoke_virtual(&this, "putAll", "(Ljava/util/Map;)V", (map,)).await?; Ok(()) } @@ -304,6 +339,41 @@ impl Hashtable { Ok(None.into()) } + async fn put_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Hashtable::putAll({this:?}, {map:?})"); + + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + if this.identity() == map.identity() { + return Ok(()); + } + + 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 validated = Vec::with_capacity(count); + 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?; + if key.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "Hashtable key is null").await); + } + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "Hashtable value is null").await); + } + validated.push((key, value)); + } + + for (key, value) in validated { + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + + Ok(()) + } + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.Hashtable::clear({this:?})"); @@ -415,6 +485,95 @@ impl Hashtable { Ok(()) } + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Hashtable::equals({this:?}, {other:?})"); + + if !other.is_null() && this.identity() == other.identity() { + return Ok(true); + } + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Map") { + return Ok(false); + } + + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let other_size: i32 = jvm.invoke_virtual(&other, "size", "()I", ()).await?; + if count != other_size { + return Ok(false); + } + + let comparison: Result = async { + let table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?; + let table_len = jvm.array_length(&table).await?; + for bucket_index in 0..table_len { + let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; + while !entry.is_null() { + let key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + let value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + let other_value: ClassInstanceRef = jvm + .invoke_virtual(&other, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await?; + if value.is_null() { + if !other_value.is_null() + || !jvm + .invoke_virtual::<_, bool>(&other, "containsKey", "(Ljava/lang/Object;)Z", (key,)) + .await? + { + return Ok(false); + } + } else if !jvm + .invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (other_value,)) + .await? + { + return Ok(false); + } + + entry = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?; + } + } + + Ok(true) + } + .await; + match comparison { + Err(JavaError::JavaException(exception)) + if jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException") + || jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException") => + { + Ok(false) + } + result => result, + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Hashtable::hashCode({this:?})"); + + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let load_factor: f32 = jvm.get_field(&this, "loadFactor", "F").await?; + if count == 0 || load_factor < 0.0 { + return Ok(0); + } + + jvm.put_field(&mut this, "loadFactor", "F", -load_factor).await?; + let result: Result = async { + let table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/Hashtable$Entry;").await?; + let table_len = jvm.array_length(&table).await?; + let mut hash = 0i32; + for bucket_index in 0..table_len { + let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; + while !entry.is_null() { + hash = hash.wrapping_add(jvm.invoke_virtual::<_, i32>(&entry, "hashCode", "()I", ()).await?); + entry = jvm.get_field(&entry, "next", "Ljava/util/Hashtable$Entry;").await?; + } + } + + Ok(hash) + } + .await; + jvm.put_field(&mut this, "loadFactor", "F", load_factor).await?; + result + } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.util.Hashtable::toString({this:?})"); @@ -455,9 +614,7 @@ impl Hashtable { table: &ClassInstanceRef>, bucket_index: usize, ) -> Result> { - let mut entries = jvm.load_array(table, bucket_index, 1).await?; - - Ok(entries.pop().unwrap_or_else(|| None.into())) + Ok(jvm.load_array(table, bucket_index, 1).await?.remove(0)) } async fn key_hash(jvm: &Jvm, key: &ClassInstanceRef) -> Result { @@ -484,7 +641,7 @@ impl Hashtable { let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/Hashtable$Entry;").await?; let table_len = jvm.array_length(&table).await?; let count: i32 = jvm.get_field(this, "count", "I").await?; - let mut elements: Vec> = Vec::with_capacity(count.max(0) as usize); + let mut elements: Vec> = Vec::with_capacity(count as usize); for bucket_index in 0..table_len { let mut entry = Self::load_bucket(jvm, &table, bucket_index).await?; diff --git a/java_runtime/src/classes/java/util/hashtable_entry.rs b/java_runtime/src/classes/java/util/hashtable_entry.rs index f6bd819b..6aa7be20 100644 --- a/java_runtime/src/classes/java/util/hashtable_entry.rs +++ b/java_runtime/src/classes/java/util/hashtable_entry.rs @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -24,6 +25,8 @@ impl HashtableEntry { 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("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), ], fields: vec![ JavaFieldProto::new("hash", "I", Default::default()), @@ -85,4 +88,46 @@ impl HashtableEntry { Ok(old_value) } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + + let key: ClassInstanceRef = jvm.get_field(&this, "key", "Ljava/lang/Object;").await?; + let other_key: ClassInstanceRef = jvm.invoke_virtual(&other, "getKey", "()Ljava/lang/Object;", ()).await?; + let keys_equal = if key.is_null() { + other_key.is_null() + } else { + jvm.invoke_virtual(&key, "equals", "(Ljava/lang/Object;)Z", (other_key,)).await? + }; + if !keys_equal { + return Ok(false); + } + + let value: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + let other_value: ClassInstanceRef = jvm.invoke_virtual(&other, "getValue", "()Ljava/lang/Object;", ()).await?; + if value.is_null() { + Ok(other_value.is_null()) + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (other_value,)).await + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let key: ClassInstanceRef = jvm.get_field(&this, "key", "Ljava/lang/Object;").await?; + let value: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + let key_hash = if key.is_null() { + 0 + } else { + jvm.invoke_virtual(&key, "hashCode", "()I", ()).await? + }; + let value_hash = if value.is_null() { + 0 + } else { + jvm.invoke_virtual(&value, "hashCode", "()I", ()).await? + }; + + Ok(key_hash ^ value_hash) + } } diff --git a/java_runtime/src/classes/java/util/hashtable_entry_set.rs b/java_runtime/src/classes/java/util/hashtable_entry_set.rs index 500b62de..68e5e570 100644 --- a/java_runtime/src/classes/java/util/hashtable_entry_set.rs +++ b/java_runtime/src/classes/java/util/hashtable_entry_set.rs @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -21,6 +22,7 @@ impl HashtableEntrySet { JavaMethodProto::new("size", "()I", Self::size, Default::default()), JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, Default::default()), ], @@ -75,7 +77,36 @@ impl HashtableEntrySet { let candidate_value: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getValue", "()Ljava/lang/Object;", ()).await?; let entry_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; - Self::object_equals(jvm, &candidate_value, &entry_value).await + Self::object_equals(jvm, &entry_value, &candidate_value).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, candidate: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Hashtable$EntrySet::remove({this:?}, {candidate:?})"); + + if candidate.is_null() || !jvm.is_instance(&**candidate, "java/util/Map$Entry") { + return Ok(false); + } + + let candidate_key: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getKey", "()Ljava/lang/Object;", ()).await?; + if candidate_key.is_null() { + return Ok(false); + } + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/Hashtable;").await?; + let entry = Hashtable::find_entry(jvm, &map, &candidate_key).await?; + if entry.is_null() { + return Ok(false); + } + let candidate_value: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getValue", "()Ljava/lang/Object;", ()).await?; + let entry_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + if !Self::object_equals(jvm, &entry_value, &candidate_value).await? { + return Ok(false); + } + + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (candidate_key,)) + .await?; + + Ok(true) } async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { diff --git a/java_runtime/src/classes/java/util/hashtable_values.rs b/java_runtime/src/classes/java/util/hashtable_values.rs index 3664dbec..4a39ed29 100644 --- a/java_runtime/src/classes/java/util/hashtable_values.rs +++ b/java_runtime/src/classes/java/util/hashtable_values.rs @@ -1,6 +1,7 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -21,6 +22,7 @@ impl HashtableValues { JavaMethodProto::new("size", "()I", Self::size, Default::default()), JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, Default::default()), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, Default::default()), ], @@ -62,6 +64,31 @@ impl HashtableValues { jvm.invoke_virtual(&map, "containsValue", "(Ljava/lang/Object;)Z", (value,)).await } + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Hashtable$Values::remove({this:?}, {value:?})"); + + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "Hashtable value is null").await); + } + + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/Hashtable;").await?; + let entries = Hashtable::entries_snapshot(jvm, &map).await?; + let count = jvm.array_length(&entries).await?; + for entry in jvm.load_array::>(&entries, 0, count).await? { + let entry_value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + let equal: bool = jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (entry_value,)).await?; + if equal { + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + return Ok(true); + } + } + + Ok(false) + } + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.Hashtable$Values::clear({this:?})"); diff --git a/java_runtime/src/classes/java/util/iterator.rs b/java_runtime/src/classes/java/util/iterator.rs index 7e48d7de..c25a57c3 100644 --- a/java_runtime/src/classes/java/util/iterator.rs +++ b/java_runtime/src/classes/java/util/iterator.rs @@ -1,7 +1,7 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -15,12 +15,12 @@ impl Iterator { parent_class: None, interfaces: vec![], methods: vec![ - JavaMethodProto::new_abstract("hasNext", "()Z", Default::default()), - JavaMethodProto::new_abstract("next", "()Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("remove", "()V", Default::default()), + JavaMethodProto::new_abstract("hasNext", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("next", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("remove", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), ], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/util/linked_list.rs b/java_runtime/src/classes/java/util/linked_list.rs new file mode 100644 index 00000000..08a825f3 --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_list.rs @@ -0,0 +1,342 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{lang::Object, util::LinkedListEntry}, +}; + +// class java.util.LinkedList +pub struct LinkedList; + +impl LinkedList { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedList", + parent_class: Some("java/util/AbstractList"), + interfaces: vec!["java/util/List", "java/lang/Cloneable", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/Collection;)V", Self::init_collection, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addFirst", "(Ljava/lang/Object;)V", Self::add_first, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("addLast", "(Ljava/lang/Object;)V", Self::add_last, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFirst", "()Ljava/lang/Object;", Self::get_first, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getLast", "()Ljava/lang/Object;", Self::get_last, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("removeFirst", "()Ljava/lang/Object;", Self::remove_first, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("removeLast", "()Ljava/lang/Object;", Self::remove_last, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/Object;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/Object;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![ + JavaFieldProto::new("header", "Ljava/util/LinkedList$Entry;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("size", "I", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractList", "", "()V", ()).await?; + let null_object: ClassInstanceRef = None.into(); + let null_entry: ClassInstanceRef = None.into(); + let mut header: ClassInstanceRef = jvm + .new_class( + "java/util/LinkedList$Entry", + "(Ljava/lang/Object;Ljava/util/LinkedList$Entry;Ljava/util/LinkedList$Entry;)V", + (null_object, null_entry.clone(), null_entry), + ) + .await? + .into(); + let header_next = header.clone(); + jvm.put_field(&mut header, "next", "Ljava/util/LinkedList$Entry;", header_next).await?; + let header_previous = header.clone(); + jvm.put_field(&mut header, "previous", "Ljava/util/LinkedList$Entry;", header_previous) + .await?; + jvm.put_field(&mut this, "header", "Ljava/util/LinkedList$Entry;", header).await?; + jvm.put_field(&mut this, "size", "I", 0).await + } + + async fn init_collection( + jvm: &Jvm, + context: &mut RuntimeContext, + this: ClassInstanceRef, + collection: ClassInstanceRef, + ) -> Result<()> { + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + Self::init(jvm, context, this.clone()).await?; + let _: bool = jvm.invoke_virtual(&this, "addAll", "(Ljava/util/Collection;)Z", (collection,)).await?; + Ok(()) + } + + async fn add_first(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + let first = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + Self::add_before(jvm, &this, element, first).await + } + + async fn add_last(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let header = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + Self::add_before(jvm, &this, element, header).await + } + + async fn get_first(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + if jvm.get_field::(&this, "size", "I").await? == 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "LinkedList is empty").await); + } + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + let first: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + jvm.get_field(&first, "element", "Ljava/lang/Object;").await + } + + async fn get_last(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + if jvm.get_field::(&this, "size", "I").await? == 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "LinkedList is empty").await); + } + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + let last: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + jvm.get_field(&last, "element", "Ljava/lang/Object;").await + } + + async fn remove_first(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + if jvm.get_field::(&this, "size", "I").await? == 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "LinkedList is empty").await); + } + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + let first = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + Self::remove_entry(jvm, &this, first).await + } + + async fn remove_last(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + if jvm.get_field::(&this, "size", "I").await? == 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "LinkedList is empty").await); + } + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + let last = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + Self::remove_entry(jvm, &this, last).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "size", "I").await + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let entry = Self::entry_at(jvm, &this, index).await?; + jvm.get_field(&entry, "element", "Ljava/lang/Object;").await + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + element: ClassInstanceRef, + ) -> Result> { + let mut entry = Self::entry_at(jvm, &this, index).await?; + let old = jvm.get_field(&entry, "element", "Ljava/lang/Object;").await?; + jvm.put_field(&mut entry, "element", "Ljava/lang/Object;", element).await?; + Ok(old) + } + + async fn add_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32, element: ClassInstanceRef) -> Result<()> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + if index < 0 || index > size { + return Err(jvm + .exception("java/lang/IndexOutOfBoundsException", &format!("Index: {index}, Size: {size}")) + .await); + } + let successor = if index == size { + jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await? + } else { + Self::entry_at(jvm, &this, index).await? + }; + Self::add_before(jvm, &this, element, successor).await + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let header = jvm.get_field(&this, "header", "Ljava/util/LinkedList$Entry;").await?; + Self::add_before(jvm, &this, element, header).await?; + Ok(true) + } + + async fn remove_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let entry = Self::entry_at(jvm, &this, index).await?; + Self::remove_entry(jvm, &this, entry).await + } + + async fn remove_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + for index in 0..size { + let entry = Self::entry_at(jvm, &this, index).await?; + let current: ClassInstanceRef = jvm.get_field(&entry, "element", "Ljava/lang/Object;").await?; + let equal = if element.is_null() { + current.is_null() + } else { + jvm.invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await? + }; + if equal { + let _: ClassInstanceRef = Self::remove_entry(jvm, &this, entry).await?; + return Ok(true); + } + } + Ok(false) + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + for index in 0..size { + let entry = Self::entry_at(jvm, &this, index).await?; + let current: ClassInstanceRef = jvm.get_field(&entry, "element", "Ljava/lang/Object;").await?; + if (element.is_null() && current.is_null()) + || (!element.is_null() + && jvm + .invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await?) + { + return Ok(index); + } + } + Ok(-1) + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + for index in (0..size).rev() { + let entry = Self::entry_at(jvm, &this, index).await?; + let current: ClassInstanceRef = jvm.get_field(&entry, "element", "Ljava/lang/Object;").await?; + if (element.is_null() && current.is_null()) + || (!element.is_null() + && jvm + .invoke_virtual::<_, bool>(&element, "equals", "(Ljava/lang/Object;)Z", (current,)) + .await?) + { + return Ok(index); + } + } + Ok(-1) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + while jvm.get_field::(&this, "size", "I").await? > 0 { + let _: ClassInstanceRef = jvm.invoke_virtual(&this, "removeFirst", "()Ljava/lang/Object;", ()).await?; + } + Ok(()) + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm + .new_class("java/util/LinkedList$ListItr", "(Ljava/util/LinkedList;I)V", (this, 0)) + .await?; + Ok(iterator.into()) + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm + .new_class("java/util/LinkedList$ListItr", "(Ljava/util/LinkedList;I)V", (this, 0)) + .await?; + Ok(iterator.into()) + } + + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let iterator = jvm + .new_class("java/util/LinkedList$ListItr", "(Ljava/util/LinkedList;I)V", (this, index)) + .await?; + Ok(iterator.into()) + } + + async fn entry_at(jvm: &Jvm, this: &ClassInstanceRef, index: i32) -> Result> { + let size: i32 = jvm.get_field(this, "size", "I").await?; + if index < 0 || index >= size { + return Err(jvm + .exception("java/lang/IndexOutOfBoundsException", &format!("Index: {index}, Size: {size}")) + .await); + } + let header: ClassInstanceRef = jvm.get_field(this, "header", "Ljava/util/LinkedList$Entry;").await?; + if index < size / 2 { + let mut entry: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + for _ in 0..index { + entry = jvm.get_field(&entry, "next", "Ljava/util/LinkedList$Entry;").await?; + } + Ok(entry) + } else { + let mut entry: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + for _ in (index + 1)..size { + entry = jvm.get_field(&entry, "previous", "Ljava/util/LinkedList$Entry;").await?; + } + Ok(entry) + } + } + + async fn add_before( + jvm: &Jvm, + this: &ClassInstanceRef, + element: ClassInstanceRef, + mut successor: ClassInstanceRef, + ) -> Result<()> { + let mut predecessor: ClassInstanceRef = jvm.get_field(&successor, "previous", "Ljava/util/LinkedList$Entry;").await?; + let entry: ClassInstanceRef = jvm + .new_class( + "java/util/LinkedList$Entry", + "(Ljava/lang/Object;Ljava/util/LinkedList$Entry;Ljava/util/LinkedList$Entry;)V", + (element, successor.clone(), predecessor.clone()), + ) + .await? + .into(); + jvm.put_field(&mut predecessor, "next", "Ljava/util/LinkedList$Entry;", entry.clone()) + .await?; + jvm.put_field(&mut successor, "previous", "Ljava/util/LinkedList$Entry;", entry.clone()) + .await?; + let size: i32 = jvm.get_field(this, "size", "I").await?; + let mut list = this.clone(); + jvm.put_field(&mut list, "size", "I", size + 1).await?; + Ok(()) + } + + async fn remove_entry( + jvm: &Jvm, + this: &ClassInstanceRef, + mut entry: ClassInstanceRef, + ) -> Result> { + let mut previous: ClassInstanceRef = jvm.get_field(&entry, "previous", "Ljava/util/LinkedList$Entry;").await?; + let mut next: ClassInstanceRef = jvm.get_field(&entry, "next", "Ljava/util/LinkedList$Entry;").await?; + jvm.put_field(&mut previous, "next", "Ljava/util/LinkedList$Entry;", next.clone()).await?; + jvm.put_field(&mut next, "previous", "Ljava/util/LinkedList$Entry;", previous).await?; + let element = jvm.get_field(&entry, "element", "Ljava/lang/Object;").await?; + let null_object: ClassInstanceRef = None.into(); + let null_entry: ClassInstanceRef = None.into(); + jvm.put_field(&mut entry, "element", "Ljava/lang/Object;", null_object).await?; + jvm.put_field(&mut entry, "next", "Ljava/util/LinkedList$Entry;", null_entry.clone()) + .await?; + jvm.put_field(&mut entry, "previous", "Ljava/util/LinkedList$Entry;", null_entry).await?; + let size: i32 = jvm.get_field(this, "size", "I").await?; + let mut list = this.clone(); + jvm.put_field(&mut list, "size", "I", size - 1).await?; + Ok(element) + } +} diff --git a/java_runtime/src/classes/java/util/linked_list_entry.rs b/java_runtime/src/classes/java/util/linked_list_entry.rs new file mode 100644 index 00000000..a45cebeb --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_list_entry.rs @@ -0,0 +1,45 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.LinkedList$Entry +pub struct LinkedListEntry; + +impl LinkedListEntry { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedList$Entry", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![JavaMethodProto::new( + "", + "(Ljava/lang/Object;Ljava/util/LinkedList$Entry;Ljava/util/LinkedList$Entry;)V", + Self::init, + Default::default(), + )], + fields: vec![ + JavaFieldProto::new("element", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("next", "Ljava/util/LinkedList$Entry;", Default::default()), + JavaFieldProto::new("previous", "Ljava/util/LinkedList$Entry;", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + element: ClassInstanceRef, + next: ClassInstanceRef, + previous: ClassInstanceRef, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "element", "Ljava/lang/Object;", element).await?; + jvm.put_field(&mut this, "next", "Ljava/util/LinkedList$Entry;", next).await?; + jvm.put_field(&mut this, "previous", "Ljava/util/LinkedList$Entry;", previous).await + } +} diff --git a/java_runtime/src/classes/java/util/linked_list_itr.rs b/java_runtime/src/classes/java/util/linked_list_itr.rs new file mode 100644 index 00000000..dbae76d8 --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_list_itr.rs @@ -0,0 +1,126 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.LinkedList$ListItr +pub struct LinkedListItr; + +impl LinkedListItr { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedList$ListItr", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/ListIterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedList;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hasPrevious", "()Z", Self::has_previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previous", "()Ljava/lang/Object;", Self::previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextIndex", "()I", Self::next_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previousIndex", "()I", Self::previous_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(Ljava/lang/Object;)V", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)V", Self::add, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("list", "Ljava/util/LinkedList;", Default::default()), + JavaFieldProto::new("cursor", "I", Default::default()), + JavaFieldProto::new("lastReturned", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, list: ClassInstanceRef, index: i32) -> Result<()> { + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "list iterator index").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "list", "Ljava/util/LinkedList;", list).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/LinkedList;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + Ok(cursor < jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?) + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/LinkedList;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor >= jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await? { + return Err(jvm.exception("java/util/NoSuchElementException", "LinkedList iterator exhausted").await); + } + let value = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (cursor,)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", cursor).await?; + Ok(value) + } + + async fn has_previous(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? > 0) + } + + async fn previous(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor <= 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "LinkedList iterator exhausted").await); + } + let index = cursor - 1; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/LinkedList;").await?; + let value = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (index,)).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", index).await?; + Ok(value) + } + + async fn next_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "cursor", "I").await + } + + async fn previous_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? - 1) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/LinkedList;").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&list, "remove", "(I)Ljava/lang/Object;", (last_returned,)).await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if last_returned < cursor { + jvm.put_field(&mut this, "cursor", "I", cursor - 1).await?; + } + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } + + async fn set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/LinkedList;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (last_returned, element)) + .await?; + Ok(()) + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/LinkedList;").await?; + let _: () = jvm.invoke_virtual(&list, "add", "(ILjava/lang/Object;)V", (cursor, element)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } +} diff --git a/java_runtime/src/classes/java/util/list.rs b/java_runtime/src/classes/java/util/list.rs index a350406a..672e607d 100644 --- a/java_runtime/src/classes/java/util/list.rs +++ b/java_runtime/src/classes/java/util/list.rs @@ -1,7 +1,7 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -15,14 +15,43 @@ impl List { parent_class: None, interfaces: vec!["java/util/Collection"], methods: vec![ - JavaMethodProto::new_abstract("get", "(I)Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("add", "(ILjava/lang/Object;)V", Default::default()), - JavaMethodProto::new_abstract("remove", "(I)Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("indexOf", "(Ljava/lang/Object;)I", Default::default()), + JavaMethodProto::new_abstract("get", "(I)Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "set", + "(ILjava/lang/Object;)Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("add", "(ILjava/lang/Object;)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "addAll", + "(ILjava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("remove", "(I)Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "indexOf", + "(Ljava/lang/Object;)I", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "lastIndexOf", + "(Ljava/lang/Object;)I", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "listIterator", + "()Ljava/util/ListIterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "listIterator", + "(I)Ljava/util/ListIterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("subList", "(II)Ljava/util/List;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), ], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/util/list_iterator.rs b/java_runtime/src/classes/java/util/list_iterator.rs new file mode 100644 index 00000000..9fce5731 --- /dev/null +++ b/java_runtime/src/classes/java/util/list_iterator.rs @@ -0,0 +1,36 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// interface java.util.ListIterator +pub struct ListIterator; + +impl ListIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/ListIterator", + parent_class: None, + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new_abstract("hasNext", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("next", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("hasPrevious", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "previous", + "()Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("nextIndex", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("previousIndex", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("remove", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("set", "(Ljava/lang/Object;)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("add", "(Ljava/lang/Object;)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/util/map.rs b/java_runtime/src/classes/java/util/map.rs index fd56351e..73a6e34f 100644 --- a/java_runtime/src/classes/java/util/map.rs +++ b/java_runtime/src/classes/java/util/map.rs @@ -1,7 +1,7 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -15,20 +15,45 @@ impl Map { parent_class: None, interfaces: vec![], methods: vec![ - JavaMethodProto::new_abstract("size", "()I", Default::default()), - JavaMethodProto::new_abstract("isEmpty", "()Z", Default::default()), - JavaMethodProto::new_abstract("containsKey", "(Ljava/lang/Object;)Z", Default::default()), - JavaMethodProto::new_abstract("containsValue", "(Ljava/lang/Object;)Z", Default::default()), - JavaMethodProto::new_abstract("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("remove", "(Ljava/lang/Object;)Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("clear", "()V", Default::default()), - JavaMethodProto::new_abstract("keySet", "()Ljava/util/Set;", Default::default()), - JavaMethodProto::new_abstract("values", "()Ljava/util/Collection;", Default::default()), - JavaMethodProto::new_abstract("entrySet", "()Ljava/util/Set;", Default::default()), + JavaMethodProto::new_abstract("size", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("isEmpty", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "containsKey", + "(Ljava/lang/Object;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "containsValue", + "(Ljava/lang/Object;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "get", + "(Ljava/lang/Object;)Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("putAll", "(Ljava/util/Map;)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "remove", + "(Ljava/lang/Object;)Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("clear", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("keySet", "()Ljava/util/Set;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "values", + "()Ljava/util/Collection;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("entrySet", "()Ljava/util/Set;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), ], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/util/map_entry.rs b/java_runtime/src/classes/java/util/map_entry.rs index 3e6eab7e..30a2e27b 100644 --- a/java_runtime/src/classes/java/util/map_entry.rs +++ b/java_runtime/src/classes/java/util/map_entry.rs @@ -1,7 +1,7 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -15,12 +15,22 @@ impl MapEntry { parent_class: None, interfaces: vec![], methods: vec![ - JavaMethodProto::new_abstract("getKey", "()Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("getValue", "()Ljava/lang/Object;", Default::default()), - JavaMethodProto::new_abstract("setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", Default::default()), + JavaMethodProto::new_abstract("getKey", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "getValue", + "()Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("equals", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("hashCode", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), ], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/util/properties.rs b/java_runtime/src/classes/java/util/properties.rs index 9a04fe06..9953cd0f 100644 --- a/java_runtime/src/classes/java/util/properties.rs +++ b/java_runtime/src/classes/java/util/properties.rs @@ -1,13 +1,19 @@ -use alloc::vec; +use alloc::{format, string::String as RustString, vec, vec::Vec}; -use java_class_proto::JavaMethodProto; -use jvm::{ClassInstanceRef, Jvm, Result}; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::lang::{Object, String}, + classes::java::{ + io::{InputStream, OutputStream}, + lang::{Object, String}, + }, }; +use super::Hashtable; + // class java.util.Properties pub struct Properties; @@ -18,31 +24,63 @@ impl Properties { parent_class: Some("java/util/Hashtable"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/Properties;)V", Self::init_with_defaults, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", Self::get_property, - Default::default(), + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + Self::get_property_with_default, + MethodAccessFlags::PUBLIC, ), JavaMethodProto::new( "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", Self::set_property, - Default::default(), + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "load", + "(Ljava/io/InputStream;)V", + Self::load, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "store", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + Self::store, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "propertyNames", + "()Ljava/util/Enumeration;", + Self::property_names, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), ], - fields: vec![], - access_flags: Default::default(), + fields: vec![JavaFieldProto::new("defaults", "Ljava/util/Properties;", FieldAccessFlags::PROTECTED)], + access_flags: ClassAccessFlags::PUBLIC, } } async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.Properties::({this:?})"); - let _: () = jvm.invoke_special(&this, "java/util/Hashtable", "", "()V", ()).await?; + let defaults: ClassInstanceRef = None.into(); + jvm.invoke_special(&this, "java/util/Properties", "", "(Ljava/util/Properties;)V", (defaults,)) + .await + } - Ok(()) + async fn init_with_defaults(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, defaults: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Properties::({this:?}, {defaults:?})"); + + let _: () = jvm.invoke_special(&this, "java/util/Hashtable", "", "()V", ()).await?; + jvm.put_field(&mut this, "defaults", "Ljava/util/Properties;", defaults).await } async fn get_property( @@ -53,11 +91,35 @@ impl Properties { ) -> Result> { tracing::debug!("java.util.Properties::getProperty({this:?}, {key:?})"); - let result = jvm.invoke_virtual(&this, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await?; + let result: ClassInstanceRef = jvm + .invoke_virtual(&this, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await?; + if !result.is_null() && jvm.is_instance(&**result, "java/lang/String") { + return Ok(ClassInstanceRef::new(result.instance)); + } + + let defaults: ClassInstanceRef = jvm.get_field(&this, "defaults", "Ljava/util/Properties;").await?; + if defaults.is_null() { + Ok(None.into()) + } else { + jvm.invoke_virtual(&defaults, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await + } + } - // TODO defaults + async fn get_property_with_default( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + key: ClassInstanceRef, + default_value: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.Properties::getProperty({this:?}, {key:?}, {default_value:?})"); - Ok(result) + let value: ClassInstanceRef = jvm + .invoke_virtual(&this, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if value.is_null() { Ok(default_value) } else { Ok(value) } } async fn set_property( @@ -69,10 +131,314 @@ impl Properties { ) -> Result> { tracing::debug!("java.util.Properties::setProperty({this:?}, {key:?}, {value:?})"); - let old = jvm - .invoke_virtual(&this, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + jvm.invoke_virtual(&this, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await + } + + async fn load(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, input: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Properties::load({this:?}, {input:?})"); + + if input.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "inStream").await); + } + + let mut logical_line = Vec::new(); + let mut skip_lf = false; + let mut skip_leading_whitespace = false; + loop { + let value: i32 = jvm.invoke_virtual(&input, "read", "()I", ()).await?; + if value < 0 { + if !logical_line.is_empty() { + let trailing_backslashes = logical_line.iter().rev().take_while(|character| **character == b'\\' as JavaChar).count(); + if trailing_backslashes % 2 == 1 { + logical_line.pop(); + } + Self::load_logical_line(jvm, &this, &logical_line).await?; + } + return Ok(()); + } + + let byte = value as u8; + if skip_lf { + skip_lf = false; + if byte == b'\n' { + continue; + } + } + + if byte == b'\n' || byte == b'\r' { + if byte == b'\r' { + skip_lf = true; + } + + let trailing_backslashes = logical_line.iter().rev().take_while(|character| **character == b'\\' as JavaChar).count(); + let first_non_whitespace = logical_line + .iter() + .position(|character| !matches!(*character, character if character == b' ' as JavaChar || character == b'\t' as JavaChar || character == 0x0c)); + let is_comment = first_non_whitespace + .is_some_and(|position| logical_line[position] == b'#' as JavaChar || logical_line[position] == b'!' as JavaChar); + if !is_comment && trailing_backslashes % 2 == 1 { + logical_line.pop(); + skip_leading_whitespace = true; + } else { + Self::load_logical_line(jvm, &this, &logical_line).await?; + logical_line.clear(); + skip_leading_whitespace = false; + } + continue; + } + + if skip_leading_whitespace && matches!(byte, b' ' | b'\t' | 0x0c) { + continue; + } + skip_leading_whitespace = false; + logical_line.push(byte as JavaChar); + } + } + + async fn load_logical_line(jvm: &Jvm, this: &ClassInstanceRef, logical_line: &[JavaChar]) -> Result<()> { + let mut key_start = 0; + while key_start < logical_line.len() + && matches!(logical_line[key_start], character if character == b' ' as JavaChar || character == b'\t' as JavaChar || character == 0x0c) + { + key_start += 1; + } + if key_start == logical_line.len() || logical_line[key_start] == b'#' as JavaChar || logical_line[key_start] == b'!' as JavaChar { + return Ok(()); + } + + let mut key_end = key_start; + let mut preceding_backslash = false; + while key_end < logical_line.len() { + let character = logical_line[key_end]; + if !preceding_backslash + && (character == b'=' as JavaChar + || character == b':' as JavaChar + || character == b' ' as JavaChar + || character == b'\t' as JavaChar + || character == 0x0c) + { + break; + } + if character == b'\\' as JavaChar { + preceding_backslash = !preceding_backslash; + } else { + preceding_backslash = false; + } + key_end += 1; + } + + let mut value_start = key_end; + while value_start < logical_line.len() + && matches!(logical_line[value_start], character if character == b' ' as JavaChar || character == b'\t' as JavaChar || character == 0x0c) + { + value_start += 1; + } + if value_start < logical_line.len() && (logical_line[value_start] == b'=' as JavaChar || logical_line[value_start] == b':' as JavaChar) { + value_start += 1; + } + while value_start < logical_line.len() + && matches!(logical_line[value_start], character if character == b' ' as JavaChar || character == b'\t' as JavaChar || character == 0x0c) + { + value_start += 1; + } + + let Some(key_chars) = Self::load_convert(&logical_line[key_start..key_end]) else { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Malformed \\uxxxx encoding").await); + }; + let Some(value_chars) = Self::load_convert(&logical_line[value_start..]) else { + return Err(jvm.exception("java/lang/IllegalArgumentException", "Malformed \\uxxxx encoding").await); + }; + + let mut key_value = jvm.instantiate_array("C", key_chars.len()).await?; + jvm.store_array(&mut key_value, 0, key_chars).await?; + let key = jvm.new_class("java/lang/String", "([C)V", (key_value,)).await?; + let mut property_value = jvm.instantiate_array("C", value_chars.len()).await?; + jvm.store_array(&mut property_value, 0, value_chars).await?; + let value = jvm.new_class("java/lang/String", "([C)V", (property_value,)).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(this, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) .await?; + Ok(()) + } + + async fn store( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + output: ClassInstanceRef, + comments: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.Properties::store({this:?}, {output:?}, {comments:?})"); + + if output.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "out").await); + } + + let mut text = RustString::new(); + if !comments.is_null() { + let comments_value: ClassInstanceRef> = jvm.get_field(&comments, "value", "[C").await?; + let comments: Vec = jvm.load_array(&comments_value, 0, jvm.array_length(&comments_value).await?).await?; + text.push('#'); + let mut index = 0; + while index < comments.len() { + let character = comments[index]; + if character == b'\r' as JavaChar || character == b'\n' as JavaChar { + if character == b'\r' as JavaChar && index + 1 < comments.len() && comments[index + 1] == b'\n' as JavaChar { + index += 1; + } + text.push('\n'); + if index + 1 < comments.len() { + text.push('#'); + } + } else if !(0x20..=0x7e).contains(&character) { + text.push_str(&format!("\\u{character:04X}")); + } else { + text.push(char::from_u32(character as u32).unwrap()); + } + index += 1; + } + text.push('\n'); + } + let date = jvm.new_class("java/util/Date", "()V", ()).await?; + let date: ClassInstanceRef = jvm.invoke_virtual(&date, "toString", "()Ljava/lang/String;", ()).await?; + text.push('#'); + text.push_str(&JavaLangString::to_rust_string(jvm, &date).await?); + text.push('\n'); + + let names: ClassInstanceRef = jvm.invoke_virtual(&this, "keys", "()Ljava/util/Enumeration;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&names, "hasMoreElements", "()Z", ()).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&names, "nextElement", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&this, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await?; + if !jvm.is_instance(&**key, "java/lang/String") || value.is_null() || !jvm.is_instance(&**value, "java/lang/String") { + return Err(jvm + .exception("java/lang/ClassCastException", "Properties key and value must be String") + .await); + } + + let key: ClassInstanceRef = ClassInstanceRef::new(key.instance); + let value: ClassInstanceRef = ClassInstanceRef::new(value.instance); + let key_value: ClassInstanceRef> = jvm.get_field(&key, "value", "[C").await?; + let value_value: ClassInstanceRef> = jvm.get_field(&value, "value", "[C").await?; + let key_chars: Vec = jvm.load_array(&key_value, 0, jvm.array_length(&key_value).await?).await?; + let value_chars: Vec = jvm.load_array(&value_value, 0, jvm.array_length(&value_value).await?).await?; + text.push_str(&Self::save_convert(&key_chars, true)); + text.push('='); + text.push_str(&Self::save_convert(&value_chars, false)); + text.push('\n'); + } + + let bytes = text.into_bytes(); + let mut byte_array = jvm.instantiate_array("B", bytes.len()).await?; + jvm.store_array(&mut byte_array, 0, bytes.into_iter().map(|byte| byte as i8)).await?; + let _: () = jvm.invoke_virtual(&output, "write", "([B)V", (byte_array,)).await?; + jvm.invoke_virtual(&output, "flush", "()V", ()).await + } + + async fn property_names(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.Properties::propertyNames({this:?})"); - Ok(old) + let table = Self::enumerate(jvm, &this).await?; + jvm.invoke_virtual(&table, "keys", "()Ljava/util/Enumeration;", ()).await + } + + async fn enumerate(jvm: &Jvm, this: &ClassInstanceRef) -> Result> { + let table: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let mut layers = Vec::new(); + let mut current = this.clone(); + while !current.is_null() { + layers.push(current.clone()); + current = jvm.get_field(¤t, "defaults", "Ljava/util/Properties;").await?; + } + + for layer in layers.into_iter().rev() { + let names: ClassInstanceRef = jvm.invoke_virtual(&layer, "keys", "()Ljava/util/Enumeration;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&names, "hasMoreElements", "()Z", ()).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&names, "nextElement", "()Ljava/lang/Object;", ()).await?; + if key.is_null() || !jvm.is_instance(&**key, "java/lang/String") { + return Err(jvm.exception("java/lang/ClassCastException", "Properties key must be String").await); + } + let value: ClassInstanceRef = jvm + .invoke_virtual(&layer, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&table, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + } + Ok(table) + } + + fn load_convert(input: &[JavaChar]) -> Option> { + let mut output = Vec::with_capacity(input.len()); + let mut index = 0; + while index < input.len() { + let character = input[index]; + index += 1; + if character != b'\\' as JavaChar { + output.push(character); + continue; + } + if index == input.len() { + output.push(b'\\' as JavaChar); + break; + } + + let escaped = input[index]; + index += 1; + match escaped { + character if character == b't' as JavaChar => output.push(b'\t' as JavaChar), + character if character == b'n' as JavaChar => output.push(b'\n' as JavaChar), + character if character == b'r' as JavaChar => output.push(b'\r' as JavaChar), + character if character == b'f' as JavaChar => output.push(0x0c), + character if character == b'u' as JavaChar => { + if index + 4 > input.len() { + return None; + } + let mut value = 0u16; + for digit in &input[index..index + 4] { + value = value.checked_mul(16)?; + value = value.checked_add(match *digit { + character if (b'0' as JavaChar..=b'9' as JavaChar).contains(&character) => character - b'0' as JavaChar, + character if (b'a' as JavaChar..=b'f' as JavaChar).contains(&character) => character - b'a' as JavaChar + 10, + character if (b'A' as JavaChar..=b'F' as JavaChar).contains(&character) => character - b'A' as JavaChar + 10, + _ => return None, + })?; + } + index += 4; + output.push(value); + } + _ => output.push(escaped), + } + } + Some(output) + } + + fn save_convert(input: &[JavaChar], escape_space: bool) -> RustString { + let mut output = RustString::new(); + for (index, character) in input.iter().copied().enumerate() { + match character { + character if character == b' ' as JavaChar && (index == 0 || escape_space) => output.push_str("\\ "), + character if character == b'\\' as JavaChar => output.push_str("\\\\"), + character if character == b'\t' as JavaChar => output.push_str("\\t"), + character if character == b'\n' as JavaChar => output.push_str("\\n"), + character if character == b'\r' as JavaChar => output.push_str("\\r"), + 0x0c => output.push_str("\\f"), + character + if character == b'=' as JavaChar + || character == b':' as JavaChar + || character == b'#' as JavaChar + || character == b'!' as JavaChar => + { + output.push('\\'); + output.push(char::from_u32(character as u32).unwrap()); + } + character if !(0x20..=0x7e).contains(&character) => output.push_str(&format!("\\u{character:04X}")), + character => output.push(char::from_u32(character as u32).unwrap()), + } + } + output } } diff --git a/java_runtime/src/classes/java/util/random.rs b/java_runtime/src/classes/java/util/random.rs index 06eaee53..698d77f8 100644 --- a/java_runtime/src/classes/java/util/random.rs +++ b/java_runtime/src/classes/java/util/random.rs @@ -1,8 +1,8 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::MethodAccessFlags; -use jvm::{ClassInstanceRef, Jvm, Result}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext}; @@ -14,20 +14,37 @@ impl Random { RuntimeClassProto { name: "java/util/Random", parent_class: Some("java/lang/Object"), - interfaces: vec![], + interfaces: vec!["java/io/Serializable"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(J)V", Self::init_with_seed, Default::default()), - JavaMethodProto::new("next", "(I)I", Self::next, MethodAccessFlags::PROTECTED), - JavaMethodProto::new("nextInt", "()I", Self::next_int, Default::default()), - JavaMethodProto::new("nextInt", "(I)I", Self::next_int_with_bound, Default::default()), - JavaMethodProto::new("nextLong", "()J", Self::next_long, Default::default()), - JavaMethodProto::new("nextFloat", "()F", Self::next_float, Default::default()), - JavaMethodProto::new("nextDouble", "()D", Self::next_double, Default::default()), - JavaMethodProto::new("setSeed", "(J)V", Self::set_seed, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(J)V", Self::init_with_seed, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "(I)I", Self::next, MethodAccessFlags::PROTECTED | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("nextBoolean", "()Z", Self::next_boolean, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextBytes", "([B)V", Self::next_bytes, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextInt", "()I", Self::next_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextInt", "(I)I", Self::next_int_with_bound, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextLong", "()J", Self::next_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextFloat", "()F", Self::next_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextDouble", "()D", Self::next_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "nextGaussian", + "()D", + Self::next_gaussian, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "setSeed", + "(J)V", + Self::set_seed, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), ], - fields: vec![JavaFieldProto::new("seed", "J", Default::default())], - access_flags: Default::default(), + fields: vec![ + JavaFieldProto::new("seed", "J", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("nextNextGaussian", "D", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("haveNextNextGaussian", "Z", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, } } @@ -63,6 +80,37 @@ impl Random { Ok(value) } + async fn next_boolean(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Random::nextBoolean({this:?})"); + + let value: i32 = jvm.invoke_virtual(&this, "next", "(I)I", (1,)).await?; + Ok(value != 0) + } + + async fn next_bytes(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, mut bytes: ClassInstanceRef>) -> Result<()> { + tracing::debug!("java.util.Random::nextBytes({this:?}, {bytes:?})"); + + if bytes.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "bytes is null").await); + } + + let length = jvm.array_length(&bytes).await?; + let mut index = 0; + while index < length { + let mut random: i32 = jvm.invoke_virtual(&this, "nextInt", "()I", ()).await?; + let chunk_length = core::cmp::min(length - index, 4); + let mut chunk = alloc::vec::Vec::with_capacity(chunk_length); + for _ in 0..chunk_length { + chunk.push(random as i8); + random >>= 8; + } + jvm.store_array(&mut bytes, index, chunk).await?; + index += chunk_length; + } + + Ok(()) + } + async fn next_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.util.Random::nextInt({this:?})"); jvm.invoke_virtual(&this, "next", "(I)I", (32,)).await @@ -112,12 +160,37 @@ impl Random { Ok(((high as i64) << 27 | low as i64) as f64 / (1u64 << 53) as f64) } + async fn next_gaussian(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Random::nextGaussian({this:?})"); + + let have_next_next_gaussian: bool = jvm.get_field(&this, "haveNextNextGaussian", "Z").await?; + if have_next_next_gaussian { + let next_next_gaussian: f64 = jvm.get_field(&this, "nextNextGaussian", "D").await?; + jvm.put_field(&mut this, "haveNextNextGaussian", "Z", false).await?; + return Ok(next_next_gaussian); + } + + let (first, second, radius_squared) = loop { + let first = 2.0 * jvm.invoke_virtual::<_, f64>(&this, "nextDouble", "()D", ()).await? - 1.0; + let second = 2.0 * jvm.invoke_virtual::<_, f64>(&this, "nextDouble", "()D", ()).await? - 1.0; + let radius_squared = first * first + second * second; + if radius_squared < 1.0 && radius_squared != 0.0 { + break (first, second, radius_squared); + } + }; + let multiplier = libm::sqrt(-2.0 * libm::log(radius_squared) / radius_squared); + jvm.put_field(&mut this, "nextNextGaussian", "D", second * multiplier).await?; + jvm.put_field(&mut this, "haveNextNextGaussian", "Z", true).await?; + Ok(first * multiplier) + } + async fn set_seed(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, seed: i64) -> Result<()> { tracing::debug!("java.util.Random::setSeed({this:?}, {seed:?})"); let seed = (seed ^ 0x5DEECE66D) & ((1 << 48) - 1); jvm.put_field(&mut this, "seed", "J", seed).await?; + jvm.put_field(&mut this, "haveNextNextGaussian", "Z", false).await?; Ok(()) } diff --git a/java_runtime/src/classes/java/util/set.rs b/java_runtime/src/classes/java/util/set.rs index a9ad4196..3796255c 100644 --- a/java_runtime/src/classes/java/util/set.rs +++ b/java_runtime/src/classes/java/util/set.rs @@ -1,6 +1,7 @@ use alloc::vec; -use java_constants::ClassAccessFlags; +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -13,9 +14,57 @@ impl Set { name: "java/util/Set", parent_class: None, interfaces: vec!["java/util/Collection"], - methods: vec![], + methods: vec![ + JavaMethodProto::new_abstract("size", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("isEmpty", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "contains", + "(Ljava/lang/Object;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "iterator", + "()Ljava/util/Iterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "toArray", + "()[Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("add", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("remove", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "containsAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "addAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "retainAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "removeAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("clear", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("equals", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("hashCode", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/util/simple_time_zone.rs b/java_runtime/src/classes/java/util/simple_time_zone.rs index a338962f..188913a2 100644 --- a/java_runtime/src/classes/java/util/simple_time_zone.rs +++ b/java_runtime/src/classes/java/util/simple_time_zone.rs @@ -1,9 +1,13 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{ClassInstanceRef, Jvm, Result}; -use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{lang::String, util::Date}, +}; // class java.util.SimpleTimeZone pub struct SimpleTimeZone; @@ -15,14 +19,16 @@ impl SimpleTimeZone { parent_class: Some("java/util/TimeZone"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, Default::default()), - JavaMethodProto::new("", "(ILjava/lang/String;)V", Self::init_with_raw_offset, Default::default()), - JavaMethodProto::new("getOffset", "(IIIIII)I", Self::get_offset, Default::default()), - JavaMethodProto::new("getRawOffset", "()I", Self::get_raw_offset, Default::default()), - JavaMethodProto::new("useDaylightTime", "()Z", Self::use_daylight_time, Default::default()), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(ILjava/lang/String;)V", Self::init_with_raw_offset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getOffset", "(IIIIII)I", Self::get_offset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getRawOffset", "()I", Self::get_raw_offset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setRawOffset", "(I)V", Self::set_raw_offset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("useDaylightTime", "()Z", Self::use_daylight_time, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("inDaylightTime", "(Ljava/util/Date;)Z", Self::in_daylight_time, MethodAccessFlags::PUBLIC), ], - fields: vec![JavaFieldProto::new("rawOffset", "I", Default::default())], - access_flags: Default::default(), + fields: vec![JavaFieldProto::new("rawOffset", "I", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, } } @@ -48,9 +54,7 @@ impl SimpleTimeZone { let _: () = jvm.invoke_special(&this, "java/util/TimeZone", "", "()V", ()).await?; jvm.put_field(&mut this, "rawOffset", "I", raw_offset).await?; - jvm.put_field(&mut this, "ID", "Ljava/lang/String;", id).await?; - - Ok(()) + jvm.put_field(&mut this, "ID", "Ljava/lang/String;", id).await } #[allow(clippy::too_many_arguments)] @@ -59,15 +63,22 @@ impl SimpleTimeZone { _: &mut RuntimeContext, this: ClassInstanceRef, era: i32, - _year: i32, + year: i32, month: i32, day: i32, day_of_week: i32, millis: i32, ) -> Result { + let days_in_month = match month { + 1 if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) => 29, + 1 => 28, + 3 | 5 | 8 | 10 => 30, + _ => 31, + }; if !(0..=1).contains(&era) || !(0..=11).contains(&month) - || !(1..=31).contains(&day) + || day < 1 + || day > days_in_month || !(1..=7).contains(&day_of_week) || !(0..86_400_000).contains(&millis) { @@ -81,7 +92,19 @@ impl SimpleTimeZone { jvm.get_field(&this, "rawOffset", "I").await } + async fn set_raw_offset(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, offset: i32) -> Result<()> { + tracing::debug!("java.util.SimpleTimeZone::setRawOffset({this:?}, {offset:?})"); + jvm.put_field(&mut this, "rawOffset", "I", offset).await + } + async fn use_daylight_time(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { Ok(false) } + + async fn in_daylight_time(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, date: ClassInstanceRef) -> Result { + if date.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "date").await); + } + Ok(false) + } } diff --git a/java_runtime/src/classes/java/util/sorted_map.rs b/java_runtime/src/classes/java/util/sorted_map.rs new file mode 100644 index 00000000..9eaef561 --- /dev/null +++ b/java_runtime/src/classes/java/util/sorted_map.rs @@ -0,0 +1,49 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// interface java.util.SortedMap +pub struct SortedMap; + +impl SortedMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/SortedMap", + parent_class: None, + interfaces: vec!["java/util/Map"], + methods: vec![ + JavaMethodProto::new_abstract( + "comparator", + "()Ljava/util/Comparator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "headMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "tailMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "firstKey", + "()Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("lastKey", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/util/sorted_set.rs b/java_runtime/src/classes/java/util/sorted_set.rs new file mode 100644 index 00000000..8e335365 --- /dev/null +++ b/java_runtime/src/classes/java/util/sorted_set.rs @@ -0,0 +1,45 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// interface java.util.SortedSet +pub struct SortedSet; + +impl SortedSet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/SortedSet", + parent_class: None, + interfaces: vec!["java/util/Set"], + methods: vec![ + JavaMethodProto::new_abstract( + "comparator", + "()Ljava/util/Comparator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "headSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "tailSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract("first", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("last", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/util/string_tokenizer.rs b/java_runtime/src/classes/java/util/string_tokenizer.rs new file mode 100644 index 00000000..b60ada80 --- /dev/null +++ b/java_runtime/src/classes/java/util/string_tokenizer.rs @@ -0,0 +1,225 @@ +use alloc::{vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// class java.util.StringTokenizer +pub struct StringTokenizer; + +impl StringTokenizer { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/StringTokenizer", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Enumeration"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::init_with_delimiters, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;Z)V", + Self::init_with_delimiters_and_return, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("hasMoreTokens", "()Z", Self::has_more_tokens, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextToken", "()Ljava/lang/String;", Self::next_token, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "nextToken", + "(Ljava/lang/String;)Ljava/lang/String;", + Self::next_token_with_delimiters, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("countTokens", "()I", Self::count_tokens, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hasMoreElements", "()Z", Self::has_more_elements, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextElement", "()Ljava/lang/Object;", Self::next_element, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("str", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("delimiters", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("currentPosition", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("maxPosition", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("returnDelimiters", "Z", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.StringTokenizer::({this:?}, {string:?})"); + + let delimiters = JavaLangString::from_rust_string(jvm, " \t\n\r\u{000c}").await?; + jvm.invoke_special( + &this, + "java/util/StringTokenizer", + "", + "(Ljava/lang/String;Ljava/lang/String;Z)V", + (string, delimiters, false), + ) + .await + } + + async fn init_with_delimiters( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + delimiters: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.StringTokenizer::({this:?}, {string:?}, {delimiters:?})"); + + jvm.invoke_special( + &this, + "java/util/StringTokenizer", + "", + "(Ljava/lang/String;Ljava/lang/String;Z)V", + (string, delimiters, false), + ) + .await + } + + async fn init_with_delimiters_and_return( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + string: ClassInstanceRef, + delimiters: ClassInstanceRef, + return_delimiters: bool, + ) -> Result<()> { + tracing::debug!("java.util.StringTokenizer::({this:?}, {string:?}, {delimiters:?}, {return_delimiters:?})"); + + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str").await); + } + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let value: ClassInstanceRef> = jvm.get_field(&string, "value", "[C").await?; + let max_position = jvm.array_length(&value).await? as i32; + jvm.put_field(&mut this, "str", "Ljava/lang/String;", string).await?; + jvm.put_field(&mut this, "delimiters", "Ljava/lang/String;", delimiters).await?; + jvm.put_field(&mut this, "currentPosition", "I", 0).await?; + jvm.put_field(&mut this, "maxPosition", "I", max_position).await?; + jvm.put_field(&mut this, "returnDelimiters", "Z", return_delimiters).await + } + + async fn has_more_tokens(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.StringTokenizer::hasMoreTokens({this:?})"); + + let string: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + let delimiters: ClassInstanceRef = jvm.get_field(&this, "delimiters", "Ljava/lang/String;").await?; + let position: i32 = jvm.get_field(&this, "currentPosition", "I").await?; + let max_position: i32 = jvm.get_field(&this, "maxPosition", "I").await?; + let return_delimiters: bool = jvm.get_field(&this, "returnDelimiters", "Z").await?; + Ok( + Self::token_bounds(jvm, &string, &delimiters, position as usize, max_position as usize, return_delimiters) + .await? + .is_some(), + ) + } + + async fn next_token(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.StringTokenizer::nextToken({this:?})"); + + let string: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + let delimiters: ClassInstanceRef = jvm.get_field(&this, "delimiters", "Ljava/lang/String;").await?; + let position: i32 = jvm.get_field(&this, "currentPosition", "I").await?; + let max_position: i32 = jvm.get_field(&this, "maxPosition", "I").await?; + let return_delimiters: bool = jvm.get_field(&this, "returnDelimiters", "Z").await?; + let Some((start, end)) = Self::token_bounds(jvm, &string, &delimiters, position as usize, max_position as usize, return_delimiters).await? + else { + return Err(jvm.exception("java/util/NoSuchElementException", "StringTokenizer exhausted").await); + }; + + let string_value: ClassInstanceRef> = jvm.get_field(&string, "value", "[C").await?; + let token_chars: Vec = jvm.load_array(&string_value, start, end - start).await?; + let mut token_value = jvm.instantiate_array("C", token_chars.len()).await?; + jvm.store_array(&mut token_value, 0, token_chars).await?; + let token = jvm.new_class("java/lang/String", "([C)V", (token_value,)).await?; + jvm.put_field(&mut this, "currentPosition", "I", end as i32).await?; + Ok(token.into()) + } + + async fn next_token_with_delimiters( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + delimiters: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.StringTokenizer::nextToken({this:?}, {delimiters:?})"); + + jvm.put_field(&mut this, "delimiters", "Ljava/lang/String;", delimiters).await?; + jvm.invoke_virtual(&this, "nextToken", "()Ljava/lang/String;", ()).await + } + + async fn count_tokens(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.StringTokenizer::countTokens({this:?})"); + + let string: ClassInstanceRef = jvm.get_field(&this, "str", "Ljava/lang/String;").await?; + let delimiters: ClassInstanceRef = jvm.get_field(&this, "delimiters", "Ljava/lang/String;").await?; + let mut position: i32 = jvm.get_field(&this, "currentPosition", "I").await?; + let max_position: i32 = jvm.get_field(&this, "maxPosition", "I").await?; + let return_delimiters: bool = jvm.get_field(&this, "returnDelimiters", "Z").await?; + let mut count = 0; + while let Some((_, end)) = Self::token_bounds(jvm, &string, &delimiters, position as usize, max_position as usize, return_delimiters).await? { + count += 1; + position = end as i32; + } + Ok(count) + } + + async fn has_more_elements(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.invoke_virtual(&this, "hasMoreTokens", "()Z", ()).await + } + + async fn next_element(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let token: ClassInstanceRef = jvm.invoke_virtual(&this, "nextToken", "()Ljava/lang/String;", ()).await?; + Ok(ClassInstanceRef::new(token.instance)) + } + + async fn token_bounds( + jvm: &Jvm, + string: &ClassInstanceRef, + delimiters: &ClassInstanceRef, + mut position: usize, + max_position: usize, + return_delimiters: bool, + ) -> Result> { + if delimiters.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "delimiters").await); + } + + let string_value: ClassInstanceRef> = jvm.get_field(string, "value", "[C").await?; + let delimiter_value: ClassInstanceRef> = jvm.get_field(delimiters, "value", "[C").await?; + let string_chars: Vec = jvm.load_array(&string_value, 0, jvm.array_length(&string_value).await?).await?; + let delimiter_chars: Vec = jvm.load_array(&delimiter_value, 0, jvm.array_length(&delimiter_value).await?).await?; + let max_position = max_position.min(string_chars.len()); + + if !return_delimiters { + while position < max_position && delimiter_chars.contains(&string_chars[position]) { + position += 1; + } + } + if position >= max_position { + return Ok(None); + } + + let start = position; + if return_delimiters && delimiter_chars.contains(&string_chars[position]) { + return Ok(Some((start, start + 1))); + } + while position < max_position && !delimiter_chars.contains(&string_chars[position]) { + position += 1; + } + Ok(Some((start, position))) + } +} diff --git a/java_runtime/src/classes/java/util/time_zone.rs b/java_runtime/src/classes/java/util/time_zone.rs index afcd053c..b1f95d7d 100644 --- a/java_runtime/src/classes/java/util/time_zone.rs +++ b/java_runtime/src/classes/java/util/time_zone.rs @@ -1,10 +1,13 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; -use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; // abstract class java.util.TimeZone pub struct TimeZone; @@ -14,29 +17,55 @@ impl TimeZone { RuntimeClassProto { name: "java/util/TimeZone", parent_class: Some("java/lang/Object"), - interfaces: vec![], + interfaces: vec!["java/io/Serializable", "java/lang/Cloneable"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), JavaMethodProto::new( "getTimeZone", "(Ljava/lang/String;)Ljava/util/TimeZone;", Self::get_time_zone, - MethodAccessFlags::STATIC, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getDefault", + "()Ljava/util/TimeZone;", + Self::get_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "setDefault", + "(Ljava/util/TimeZone;)V", + Self::set_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::SYNCHRONIZED, ), - JavaMethodProto::new("getDefault", "()Ljava/util/TimeZone;", Self::get_default, MethodAccessFlags::STATIC), JavaMethodProto::new( "getAvailableIDs", "()[Ljava/lang/String;", Self::get_available_ids, - MethodAccessFlags::STATIC, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("getID", "()Ljava/lang/String;", Self::get_id, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setID", "(Ljava/lang/String;)V", Self::set_id, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clone", "()Ljava/lang/Object;", Self::clone, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract("getOffset", "(IIIIII)I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("getRawOffset", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("setRawOffset", "(I)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("useDaylightTime", "()Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "inDaylightTime", + "(Ljava/util/Date;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + ], + fields: vec![ + JavaFieldProto::new("ID", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new( + "defaultTimeZone", + "Ljava/util/TimeZone;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC, ), - JavaMethodProto::new("getID", "()Ljava/lang/String;", Self::get_id, Default::default()), - JavaMethodProto::new_abstract("getOffset", "(IIIIII)I", Default::default()), - JavaMethodProto::new_abstract("getRawOffset", "()I", Default::default()), - JavaMethodProto::new_abstract("useDaylightTime", "()Z", Default::default()), ], - fields: vec![JavaFieldProto::new("ID", "Ljava/lang/String;", Default::default())], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } @@ -45,9 +74,7 @@ impl TimeZone { let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; let id = JavaLangString::from_rust_string(jvm, "GMT").await?; - jvm.put_field(&mut this, "ID", "Ljava/lang/String;", id).await?; - - Ok(()) + jvm.put_field(&mut this, "ID", "Ljava/lang/String;", id).await } async fn get_time_zone(jvm: &Jvm, _: &mut RuntimeContext, id: ClassInstanceRef) -> Result> { @@ -64,13 +91,42 @@ impl TimeZone { JavaLangString::from_rust_string(jvm, "GMT").await?.into() }; let result = jvm.new_class("java/util/SimpleTimeZone", "(ILjava/lang/String;)V", (0i32, id)).await?; - Ok(result.into()) } async fn get_default(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { - let id = JavaLangString::from_rust_string(jvm, "GMT").await?; - jvm.invoke_static("java/util/TimeZone", "getTimeZone", "(Ljava/lang/String;)Ljava/util/TimeZone;", (id,)) + tracing::debug!("java.util.TimeZone::getDefault()"); + + let mut default: ClassInstanceRef = jvm + .get_static_field("java/util/TimeZone", "defaultTimeZone", "Ljava/util/TimeZone;") + .await?; + if default.is_null() { + let id = JavaLangString::from_rust_string(jvm, "GMT").await?; + default = jvm + .new_class("java/util/SimpleTimeZone", "(ILjava/lang/String;)V", (0i32, id)) + .await? + .into(); + jvm.put_static_field("java/util/TimeZone", "defaultTimeZone", "Ljava/util/TimeZone;", default.clone()) + .await?; + } + + let cloned: ClassInstanceRef = jvm.invoke_virtual(&default, "clone", "()Ljava/lang/Object;", ()).await?; + Ok(ClassInstanceRef::new(cloned.instance)) + } + + async fn set_default(jvm: &Jvm, _: &mut RuntimeContext, timezone: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.TimeZone::setDefault({timezone:?})"); + + let stored: ClassInstanceRef = if timezone.is_null() { + let id = JavaLangString::from_rust_string(jvm, "GMT").await?; + jvm.new_class("java/util/SimpleTimeZone", "(ILjava/lang/String;)V", (0i32, id)) + .await? + .into() + } else { + let cloned: ClassInstanceRef = jvm.invoke_virtual(&timezone, "clone", "()Ljava/lang/Object;", ()).await?; + ClassInstanceRef::new(cloned.instance) + }; + jvm.put_static_field("java/util/TimeZone", "defaultTimeZone", "Ljava/util/TimeZone;", stored) .await } @@ -85,4 +141,17 @@ impl TimeZone { async fn get_id(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { jvm.get_field(&this, "ID", "Ljava/lang/String;").await } + + async fn set_id(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, id: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.TimeZone::setID({this:?}, {id:?})"); + + if id.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "ID").await); + } + jvm.put_field(&mut this, "ID", "Ljava/lang/String;", id).await + } + + async fn clone(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.invoke_special(&this, "java/lang/Object", "clone", "()Ljava/lang/Object;", ()).await + } } diff --git a/java_runtime/src/classes/java/util/timer.rs b/java_runtime/src/classes/java/util/timer.rs index 5323fbc2..cedf4013 100644 --- a/java_runtime/src/classes/java/util/timer.rs +++ b/java_runtime/src/classes/java/util/timer.rs @@ -1,11 +1,12 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::util::{TimerTask, Vector}, + classes::java::util::{Date, TimerTask, TimerTaskQueue, TimerThread}, }; // class java.util.Timer @@ -18,20 +19,40 @@ impl Timer { parent_class: Some("java/lang/Object"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("schedule", "(Ljava/util/TimerTask;JJ)V", Self::schedule, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("schedule", "(Ljava/util/TimerTask;J)V", Self::schedule_once, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "schedule", + "(Ljava/util/TimerTask;Ljava/util/Date;)V", + Self::schedule_date, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "schedule", + "(Ljava/util/TimerTask;JJ)V", + Self::schedule_repeated, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "schedule", + "(Ljava/util/TimerTask;Ljava/util/Date;J)V", + Self::schedule_date_repeated, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new( "scheduleAtFixedRate", "(Ljava/util/TimerTask;JJ)V", Self::schedule_at_fixed_rate, - Default::default(), + MethodAccessFlags::PUBLIC, ), + JavaMethodProto::new("cancel", "()V", Self::cancel, MethodAccessFlags::PUBLIC), ], - fields: vec![ - JavaFieldProto::new("tasks", "Ljava/util/Vector;", Default::default()), - JavaFieldProto::new("thread", "Ljava/lang/Thread;", Default::default()), - ], - access_flags: Default::default(), + fields: vec![JavaFieldProto::new( + "thread", + "Ljava/util/Timer$TimerThread;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL, + )], + access_flags: ClassAccessFlags::PUBLIC, } } @@ -39,37 +60,63 @@ impl Timer { tracing::debug!("java.util.Timer::({this:?})"); let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; - - let tasks = jvm.new_class("java/util/Vector", "()V", ()).await?; + let queue = jvm.new_class("java/util/Timer$TaskQueue", "()V", ()).await?; let timer_thread = jvm - .new_class("java/util/Timer$TimerThread", "(Ljava/util/Vector;)V", (tasks.clone(),)) + .new_class("java/util/Timer$TimerThread", "(Ljava/util/Timer$TaskQueue;)V", (queue,)) + .await?; + jvm.put_field(&mut this, "thread", "Ljava/util/Timer$TimerThread;", timer_thread.clone()) .await?; - - jvm.put_field(&mut this, "tasks", "Ljava/util/Vector;", tasks).await?; - jvm.put_field(&mut this, "thread", "Ljava/lang/Thread;", timer_thread.clone()).await?; - let _: () = jvm.invoke_virtual(&timer_thread, "start", "()V", ()).await?; - Ok(()) } - async fn schedule( + async fn schedule_once( jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, task: ClassInstanceRef, delay: i64, - period: i64, ) -> Result<()> { - tracing::debug!("java.util.Timer::schedule({this:?}, {task:?}, {delay:?}, {period:?})"); + tracing::debug!("java.util.Timer::schedule({this:?}, {task:?}, {delay:?})"); + + if task.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "task").await); + } + if delay < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "negative delay").await); + } + let Some(time) = (context.now() as i64).checked_add(delay) else { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + }; + if time < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + } + Self::sched(jvm, this, task, time, 0).await + } - let now: i64 = context.now() as i64; - let next_execution_time = now + delay; + async fn schedule_date( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + task: ClassInstanceRef, + date: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.Timer::schedule({this:?}, {task:?}, {date:?})"); - Self::do_schedule(jvm, this, task, next_execution_time, period).await + if task.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "task").await); + } + if date.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "time").await); + } + let time: i64 = jvm.invoke_virtual(&date, "getTime", "()J", ()).await?; + if time < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + } + Self::sched(jvm, this, task, time, 0).await } - async fn schedule_at_fixed_rate( + async fn schedule_repeated( jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, @@ -77,28 +124,169 @@ impl Timer { delay: i64, period: i64, ) -> Result<()> { - tracing::debug!("java.util.Timer::scheduleAtFixedRate({this:?}, {task:?}, {delay:?}, {period:?})"); - // FIXME: fixed rate is not different from normal rate + tracing::debug!("java.util.Timer::schedule({this:?}, {task:?}, {delay:?}, {period:?})"); - let now: i64 = context.now() as i64; - let next_execution_time = now + delay; + if task.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "task").await); + } + if delay < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "negative delay").await); + } + if period <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "non-positive period").await); + } + let Some(time) = (context.now() as i64).checked_add(delay) else { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + }; + if time < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + } + Self::sched(jvm, this, task, time, -period).await + } - Self::do_schedule(jvm, this, task, next_execution_time, period).await + async fn schedule_date_repeated( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + task: ClassInstanceRef, + date: ClassInstanceRef, + period: i64, + ) -> Result<()> { + tracing::debug!("java.util.Timer::schedule({this:?}, {task:?}, {date:?}, {period:?})"); + + if task.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "task").await); + } + if date.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "time").await); + } + if period <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "non-positive period").await); + } + let time: i64 = jvm.invoke_virtual(&date, "getTime", "()J", ()).await?; + if time < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + } + Self::sched(jvm, this, task, time, -period).await } - async fn do_schedule( + async fn schedule_at_fixed_rate( jvm: &Jvm, + context: &mut RuntimeContext, this: ClassInstanceRef, - mut task: ClassInstanceRef, - next_execution_time: i64, + task: ClassInstanceRef, + delay: i64, period: i64, ) -> Result<()> { - jvm.put_field(&mut task, "nextExecutionTime", "J", next_execution_time).await?; - jvm.put_field(&mut task, "period", "J", period).await?; + tracing::debug!("java.util.Timer::scheduleAtFixedRate({this:?}, {task:?}, {delay:?}, {period:?})"); - let tasks: ClassInstanceRef = jvm.get_field(&this, "tasks", "Ljava/util/Vector;").await?; - let _: bool = jvm.invoke_virtual(&tasks, "add", "(Ljava/lang/Object;)Z", (task,)).await?; + if task.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "task").await); + } + if delay < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "negative delay").await); + } + if period <= 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "non-positive period").await); + } + let Some(time) = (context.now() as i64).checked_add(delay) else { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + }; + if time < 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await); + } + Self::sched(jvm, this, task, time, period).await + } - Ok(()) + async fn sched(jvm: &Jvm, this: ClassInstanceRef, mut task: ClassInstanceRef, time: i64, period: i64) -> Result<()> { + let period = if !(-(i64::MAX >> 1)..=(i64::MAX >> 1)).contains(&period) { + period >> 1 + } else { + period + }; + if period != 0 { + let period_magnitude = if period < 0 { + let Some(period_magnitude) = period.checked_neg() else { + return Err(jvm.exception("java/lang/IllegalArgumentException", "period overflow").await); + }; + period_magnitude + } else { + period + }; + if time.checked_add(period_magnitude).is_none() { + return Err(jvm.exception("java/lang/IllegalArgumentException", "period overflow").await); + } + } + + let thread: ClassInstanceRef = jvm.get_field(&this, "thread", "Ljava/util/Timer$TimerThread;").await?; + let mut queue: ClassInstanceRef = jvm.get_field(&thread, "queue", "Ljava/util/Timer$TaskQueue;").await?; + let lock: ClassInstanceRef = jvm.get_field(&task, "lock", "Ljava/lang/Object;").await?; + + jvm.monitor_enter(&queue).await?; + let schedule_result = async { + if !jvm.get_field::(&thread, "newTasksMayBeScheduled", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "timer already cancelled").await); + } + + jvm.monitor_enter(&lock).await?; + let task_result = async { + if jvm.get_field::(&task, "state", "I").await? != TimerTask::VIRGIN { + return Err(jvm + .exception("java/lang/IllegalStateException", "task already scheduled or cancelled") + .await); + } + jvm.put_field(&mut task, "nextExecutionTime", "J", time).await?; + jvm.put_field(&mut task, "period", "J", period).await?; + jvm.put_field(&mut task, "state", "I", TimerTask::SCHEDULED).await?; + TimerTaskQueue::add(jvm, &mut queue, task.clone()).await + } + .await; + let task_exit_result = jvm.monitor_exit(&lock).await; + let new_first = match task_result { + Ok(new_first) => { + task_exit_result?; + new_first + } + Err(error) => { + task_exit_result?; + return Err(error); + } + }; + if new_first { + jvm.object_notify(&queue, 1).await?; + } + Ok(()) + } + .await; + let queue_exit_result = jvm.monitor_exit(&queue).await; + match schedule_result { + Ok(()) => queue_exit_result, + Err(error) => { + queue_exit_result?; + Err(error) + } + } + } + + async fn cancel(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Timer::cancel({this:?})"); + + let mut thread: ClassInstanceRef = jvm.get_field(&this, "thread", "Ljava/util/Timer$TimerThread;").await?; + let mut queue: ClassInstanceRef = jvm.get_field(&thread, "queue", "Ljava/util/Timer$TaskQueue;").await?; + jvm.monitor_enter(&queue).await?; + let cancel_result = async { + jvm.put_field(&mut thread, "newTasksMayBeScheduled", "Z", false).await?; + TimerTaskQueue::clear(jvm, &mut queue).await?; + jvm.object_notify(&queue, usize::MAX).await + } + .await; + let exit_result = jvm.monitor_exit(&queue).await; + match cancel_result { + Ok(()) => exit_result, + Err(error) => { + exit_result?; + Err(error) + } + } } } diff --git a/java_runtime/src/classes/java/util/timer_task.rs b/java_runtime/src/classes/java/util/timer_task.rs index 66c190fc..7ce55bea 100644 --- a/java_runtime/src/classes/java/util/timer_task.rs +++ b/java_runtime/src/classes/java/util/timer_task.rs @@ -1,37 +1,93 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{ClassInstanceRef, Jvm, Result}; -use crate::{RuntimeClassProto, RuntimeContext}; +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; // abstract class java.util.TimerTask pub struct TimerTask; impl TimerTask { + pub(crate) const VIRGIN: i32 = 0; + pub(crate) const SCHEDULED: i32 = 1; + pub(crate) const EXECUTED: i32 = 2; + pub(crate) const CANCELLED: i32 = 3; + pub fn as_proto() -> RuntimeClassProto { RuntimeClassProto { name: "java/util/TimerTask", parent_class: Some("java/lang/Object"), interfaces: vec!["java/lang/Runnable"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new_abstract("run", "()V", Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new_abstract("run", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new("cancel", "()Z", Self::cancel, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("scheduledExecutionTime", "()J", Self::scheduled_execution_time, MethodAccessFlags::PUBLIC), ], fields: vec![ + JavaFieldProto::new("lock", "Ljava/lang/Object;", FieldAccessFlags::FINAL), + JavaFieldProto::new("state", "I", Default::default()), JavaFieldProto::new("nextExecutionTime", "J", Default::default()), JavaFieldProto::new("period", "J", Default::default()), + JavaFieldProto::new("lastScheduledExecutionTime", "J", Default::default()), ], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } - async fn init(jvm: &Jvm, _context: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.TimerTask::({this:?})"); let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let lock = jvm.new_class("java/lang/Object", "()V", ()).await?; + jvm.put_field(&mut this, "lock", "Ljava/lang/Object;", lock).await?; + jvm.put_field(&mut this, "state", "I", Self::VIRGIN).await?; Ok(()) } + + async fn cancel(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.TimerTask::cancel({this:?})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + jvm.monitor_enter(&lock).await?; + let result = async { + let state: i32 = jvm.get_field(&this, "state", "I").await?; + jvm.put_field(&mut this, "state", "I", Self::CANCELLED).await?; + Ok(state == Self::SCHEDULED) + } + .await; + let exit_result = jvm.monitor_exit(&lock).await; + match result { + Ok(value) => { + exit_result?; + Ok(value) + } + Err(error) => { + exit_result?; + Err(error) + } + } + } + + async fn scheduled_execution_time(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.TimerTask::scheduledExecutionTime({this:?})"); + + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + jvm.monitor_enter(&lock).await?; + let result = jvm.get_field(&this, "lastScheduledExecutionTime", "J").await; + let exit_result = jvm.monitor_exit(&lock).await; + match result { + Ok(value) => { + exit_result?; + Ok(value) + } + Err(error) => { + exit_result?; + Err(error) + } + } + } } diff --git a/java_runtime/src/classes/java/util/timer_task_queue.rs b/java_runtime/src/classes/java/util/timer_task_queue.rs new file mode 100644 index 00000000..1eefbf41 --- /dev/null +++ b/java_runtime/src/classes/java/util/timer_task_queue.rs @@ -0,0 +1,144 @@ +use alloc::{vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::FieldAccessFlags; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::util::TimerTask}; + +// class java.util.Timer$TaskQueue +pub struct TimerTaskQueue; + +impl TimerTaskQueue { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Timer$TaskQueue", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![JavaMethodProto::new("", "()V", Self::init, Default::default())], + fields: vec![ + JavaFieldProto::new("queue", "[Ljava/util/TimerTask;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("size", "I", FieldAccessFlags::PRIVATE), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.Timer$TaskQueue::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let queue = jvm.instantiate_array("Ljava/util/TimerTask;", 128).await?; + jvm.put_field(&mut this, "queue", "[Ljava/util/TimerTask;", queue).await?; + Ok(()) + } + + pub(crate) async fn add(jvm: &Jvm, this: &mut ClassInstanceRef, task: ClassInstanceRef) -> Result { + let size: i32 = jvm.get_field(this, "size", "I").await?; + let mut queue: ClassInstanceRef> = jvm.get_field(this, "queue", "[Ljava/util/TimerTask;").await?; + if size as usize + 1 == jvm.array_length(&queue).await? { + let old_length = jvm.array_length(&queue).await?; + let mut expanded: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/TimerTask;", old_length * 2).await?.into(); + let tasks: Vec> = jvm.load_array(&queue, 0, size as usize + 1).await?; + jvm.store_array(&mut expanded, 0, tasks).await?; + queue = expanded; + jvm.put_field(this, "queue", "[Ljava/util/TimerTask;", queue.clone()).await?; + } + + let mut child = size as usize + 1; + while child > 1 { + let parent = child / 2; + let parent_task: ClassInstanceRef = jvm.load_array(&queue, parent, 1).await?.remove(0); + let parent_time: i64 = jvm.get_field(&parent_task, "nextExecutionTime", "J").await?; + let task_time: i64 = jvm.get_field(&task, "nextExecutionTime", "J").await?; + if parent_time <= task_time { + break; + } + jvm.store_array(&mut queue, child, core::iter::once(parent_task)).await?; + child = parent; + } + jvm.store_array(&mut queue, child, core::iter::once(task)).await?; + jvm.put_field(this, "size", "I", size + 1).await?; + Ok(child == 1) + } + + pub(crate) async fn remove_min(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { + let size: i32 = jvm.get_field(this, "size", "I").await?; + if size == 0 { + return Ok(()); + } + + let mut queue: ClassInstanceRef> = jvm.get_field(this, "queue", "[Ljava/util/TimerTask;").await?; + let last: ClassInstanceRef = jvm.load_array(&queue, size as usize, 1).await?.remove(0); + let null: ClassInstanceRef = None.into(); + jvm.store_array(&mut queue, size as usize, core::iter::once(null)).await?; + let new_size = size - 1; + jvm.put_field(this, "size", "I", new_size).await?; + if new_size == 0 { + return Ok(()); + } + + let last_time: i64 = jvm.get_field(&last, "nextExecutionTime", "J").await?; + let mut parent = 1usize; + while parent * 2 <= new_size as usize { + let mut child = parent * 2; + let mut child_task: ClassInstanceRef = jvm.load_array(&queue, child, 1).await?.remove(0); + let mut child_time: i64 = jvm.get_field(&child_task, "nextExecutionTime", "J").await?; + if child < new_size as usize { + let right: ClassInstanceRef = jvm.load_array(&queue, child + 1, 1).await?.remove(0); + let right_time: i64 = jvm.get_field(&right, "nextExecutionTime", "J").await?; + if right_time < child_time { + child += 1; + child_task = right; + child_time = right_time; + } + } + if last_time <= child_time { + break; + } + jvm.store_array(&mut queue, parent, core::iter::once(child_task)).await?; + parent = child; + } + jvm.store_array(&mut queue, parent, core::iter::once(last)).await + } + + pub(crate) async fn reschedule_min(jvm: &Jvm, this: &mut ClassInstanceRef, new_time: i64) -> Result<()> { + let mut queue: ClassInstanceRef> = jvm.get_field(this, "queue", "[Ljava/util/TimerTask;").await?; + let mut task: ClassInstanceRef = jvm.load_array(&queue, 1, 1).await?.remove(0); + jvm.put_field(&mut task, "nextExecutionTime", "J", new_time).await?; + + let size: i32 = jvm.get_field(this, "size", "I").await?; + let mut parent = 1usize; + while parent * 2 <= size as usize { + let mut child = parent * 2; + let mut child_task: ClassInstanceRef = jvm.load_array(&queue, child, 1).await?.remove(0); + let mut child_time: i64 = jvm.get_field(&child_task, "nextExecutionTime", "J").await?; + if child < size as usize { + let right: ClassInstanceRef = jvm.load_array(&queue, child + 1, 1).await?.remove(0); + let right_time: i64 = jvm.get_field(&right, "nextExecutionTime", "J").await?; + if right_time < child_time { + child += 1; + child_task = right; + child_time = right_time; + } + } + if new_time <= child_time { + break; + } + jvm.store_array(&mut queue, parent, core::iter::once(child_task)).await?; + parent = child; + } + jvm.store_array(&mut queue, parent, core::iter::once(task)).await + } + + pub(crate) async fn clear(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { + let size: i32 = jvm.get_field(this, "size", "I").await?; + if size > 0 { + let mut queue: ClassInstanceRef> = jvm.get_field(this, "queue", "[Ljava/util/TimerTask;").await?; + let nulls = core::iter::repeat_with(|| ClassInstanceRef::::from(None)).take(size as usize); + jvm.store_array(&mut queue, 1, nulls).await?; + jvm.put_field(this, "size", "I", 0i32).await?; + } + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/util/timer_thread.rs b/java_runtime/src/classes/java/util/timer_thread.rs index 5916bb49..28ad1489 100644 --- a/java_runtime/src/classes/java/util/timer_thread.rs +++ b/java_runtime/src/classes/java/util/timer_thread.rs @@ -1,10 +1,19 @@ -use alloc::{vec, vec::Vec}; -use core::time::Duration; +use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{ClassInstanceRef, Jvm, Result}; - -use crate::{RuntimeClassProto, RuntimeContext, classes::java::util::Vector}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::util::{TimerTask, TimerTaskQueue}, +}; + +enum WorkerAction { + Continue, + Run(ClassInstanceRef), + Stop, +} // class java.util.Timer$TimerThread pub struct TimerThread; @@ -16,68 +25,177 @@ impl TimerThread { parent_class: Some("java/lang/Thread"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "(Ljava/util/Vector;)V", Self::init, Default::default()), - JavaMethodProto::new("run", "()V", Self::run, Default::default()), + JavaMethodProto::new("", "(Ljava/util/Timer$TaskQueue;)V", Self::init, Default::default()), + JavaMethodProto::new("run", "()V", Self::run, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("queue", "Ljava/util/Timer$TaskQueue;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("newTasksMayBeScheduled", "Z", Default::default()), ], - fields: vec![JavaFieldProto::new("tasks", "Ljava/util/Vector;", Default::default())], access_flags: Default::default(), } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, tasks: ClassInstanceRef) -> Result<()> { + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, queue: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.Timer$TimerThread::({this:?})"); let _: () = jvm.invoke_special(&this, "java/lang/Thread", "", "()V", ()).await?; - - jvm.put_field(&mut this, "tasks", "Ljava/util/Vector;", tasks).await?; - + jvm.put_field(&mut this, "queue", "Ljava/util/Timer$TaskQueue;", queue).await?; + jvm.put_field(&mut this, "newTasksMayBeScheduled", "Z", true).await?; Ok(()) } async fn run(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.Timer$TimerThread::run({this:?})"); - let java_tasks = jvm.get_field(&this, "tasks", "Ljava/util/Vector;").await?; - - loop { - // TODO: we need to wait for new tasks to arrive - context.sleep(Duration::from_millis(16)).await; + let result = Self::main_loop(jvm, context, &this).await; + let mut thread = this; + let mut queue: ClassInstanceRef = jvm.get_field(&thread, "queue", "Ljava/util/Timer$TaskQueue;").await?; + jvm.monitor_enter(&queue).await?; + let cleanup_result = async { + jvm.put_field(&mut thread, "newTasksMayBeScheduled", "Z", false).await?; + TimerTaskQueue::clear(jvm, &mut queue).await?; + jvm.object_notify(&queue, usize::MAX).await + } + .await; + let exit_result = jvm.monitor_exit(&queue).await; - let tasks_size: i32 = jvm.invoke_virtual(&java_tasks, "size", "()I", ()).await?; - if tasks_size == 0 { - continue; + match result { + Ok(()) => { + cleanup_result?; + exit_result } - - // get all tasks. removing from tasks vector to avoid some concurrency issue - let mut tasks = Vec::with_capacity(tasks_size as _); - for _ in 0..tasks_size { - let task = jvm.invoke_virtual(&java_tasks, "remove", "(I)Ljava/lang/Object;", (0,)).await?; - tasks.push(task); + Err(error) => { + if let Err(cleanup_error) = cleanup_result { + exit_result?; + return Err(cleanup_error); + } + exit_result?; + Err(error) } + } + } - // execute tasks - let now = context.now() as i64; - let mut next_tasks = Vec::new(); - for mut task in tasks { - let next_execution_time: i64 = jvm.get_field(&task, "nextExecutionTime", "J").await?; + async fn main_loop(jvm: &Jvm, context: &mut RuntimeContext, this: &ClassInstanceRef) -> Result<()> { + let mut queue: ClassInstanceRef = jvm.get_field(this, "queue", "Ljava/util/Timer$TaskQueue;").await?; - if next_execution_time < now { - let _: () = jvm.invoke_virtual(&task, "run", "()V", ()).await?; + loop { + jvm.monitor_enter(&queue).await?; + let action_result = async { + loop { + let size: i32 = jvm.get_field(&queue, "size", "I").await?; + if size == 0 { + if !jvm.get_field::(this, "newTasksMayBeScheduled", "Z").await? { + return Ok(WorkerAction::Stop); + } + let wait_result: Result<()> = jvm.invoke_virtual(&queue, "wait", "()V", ()).await; + if let Err(error) = wait_result + && !matches!( + &error, + JavaError::JavaException(exception) + if exception.class_definition().name() == "java/lang/InterruptedException" + ) + { + return Err(error); + } + continue; + } - let period: i64 = jvm.get_field(&task, "period", "J").await?; - if period > 0 { - let next_execution_time = now + period; - jvm.put_field(&mut task, "nextExecutionTime", "J", next_execution_time).await?; - next_tasks.push(task); + let heap: ClassInstanceRef> = jvm.get_field(&queue, "queue", "[Ljava/util/TimerTask;").await?; + let mut task: ClassInstanceRef = jvm.load_array(&heap, 1, 1).await?.remove(0); + let lock: ClassInstanceRef = jvm.get_field(&task, "lock", "Ljava/lang/Object;").await?; + jvm.monitor_enter(&lock).await?; + let task_result = async { + let state: i32 = jvm.get_field(&task, "state", "I").await?; + if state == TimerTask::CANCELLED { + TimerTaskQueue::remove_min(jvm, &mut queue).await?; + return Ok(WorkerAction::Continue); + } + + let now = context.now() as i64; + let execution_time: i64 = jvm.get_field(&task, "nextExecutionTime", "J").await?; + if execution_time > now { + return Ok(WorkerAction::Continue); + } + + let period: i64 = jvm.get_field(&task, "period", "J").await?; + jvm.put_field(&mut task, "lastScheduledExecutionTime", "J", execution_time).await?; + if period == 0 { + TimerTaskQueue::remove_min(jvm, &mut queue).await?; + jvm.put_field(&mut task, "state", "I", TimerTask::EXECUTED).await?; + } else { + let next_execution_time = if period < 0 { + now.checked_sub(period) + } else { + execution_time.checked_add(period) + }; + if let Some(next_execution_time) = next_execution_time { + TimerTaskQueue::reschedule_min(jvm, &mut queue, next_execution_time).await?; + } else { + TimerTaskQueue::remove_min(jvm, &mut queue).await?; + jvm.put_field(&mut task, "state", "I", TimerTask::EXECUTED).await?; + } + } + Ok(WorkerAction::Run(task.clone())) + } + .await; + let task_exit_result = jvm.monitor_exit(&lock).await; + let action = match task_result { + Ok(action) => { + task_exit_result?; + action + } + Err(error) => { + task_exit_result?; + return Err(error); + } + }; + + if matches!(action, WorkerAction::Run(_)) { + return Ok(action); + } + let next_size: i32 = jvm.get_field(&queue, "size", "I").await?; + if next_size == 0 { + continue; + } + let next_heap: ClassInstanceRef> = jvm.get_field(&queue, "queue", "[Ljava/util/TimerTask;").await?; + let next_task: ClassInstanceRef = jvm.load_array(&next_heap, 1, 1).await?.remove(0); + let next_execution_time: i64 = jvm.get_field(&next_task, "nextExecutionTime", "J").await?; + let now = context.now() as i64; + if next_execution_time <= now { + continue; + } + let wait_result: Result<()> = jvm.invoke_virtual(&queue, "wait", "(J)V", (next_execution_time - now,)).await; + if let Err(error) = wait_result + && !matches!( + &error, + JavaError::JavaException(exception) + if exception.class_definition().name() == "java/lang/InterruptedException" + ) + { + return Err(error); } - } else { - next_tasks.push(task); } } + .await; + let queue_exit_result = jvm.monitor_exit(&queue).await; + let action = match action_result { + Ok(action) => { + queue_exit_result?; + action + } + Err(error) => { + queue_exit_result?; + return Err(error); + } + }; - // add pending tasks - for task in next_tasks { - let _: () = jvm.invoke_virtual(&java_tasks, "addElement", "(Ljava/lang/Object;)V", (task,)).await?; + match action { + WorkerAction::Continue => {} + WorkerAction::Run(task) => { + let _: () = jvm.invoke_virtual(&task, "run", "()V", ()).await?; + } + WorkerAction::Stop => return Ok(()), } } } diff --git a/java_runtime/src/classes/java/util/tree_map.rs b/java_runtime/src/classes/java/util/tree_map.rs new file mode 100644 index 00000000..9a708ff4 --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map.rs @@ -0,0 +1,903 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::TreeMapEntry; + +// public class java.util.TreeMap +pub struct TreeMap; + +impl TreeMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap", + parent_class: Some("java/util/AbstractMap"), + interfaces: vec!["java/util/SortedMap", "java/lang/Cloneable", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/Comparator;)V", Self::init_comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/Map;)V", Self::init_map, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/SortedMap;)V", Self::init_sorted_map, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, 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("putAll", "(Ljava/util/Map;)V", Self::put_all, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "remove", + "(Ljava/lang/Object;)Ljava/lang/Object;", + Self::remove, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("comparator", "()Ljava/util/Comparator;", Self::comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("firstKey", "()Ljava/lang/Object;", Self::first_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastKey", "()Ljava/lang/Object;", Self::last_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::sub_map, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "headMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::head_map, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "tailMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::tail_map, + 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("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( + "root", + "Ljava/util/TreeMap$Entry;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT, + ), + JavaFieldProto::new("size", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + JavaFieldProto::new("comparator", "Ljava/util/Comparator;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractMap", "", "()V", ()).await?; + Ok(()) + } + + async fn init_comparator( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + comparator: ClassInstanceRef, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractMap", "", "()V", ()).await?; + jvm.put_field(&mut this, "comparator", "Ljava/util/Comparator;", comparator).await + } + + async fn init_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + let _: () = jvm.invoke_special(&this, "java/util/TreeMap", "", "()V", ()).await?; + Self::copy_from_map(jvm, this, map).await + } + + async fn init_sorted_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + let comparator: ClassInstanceRef = jvm.invoke_virtual(&map, "comparator", "()Ljava/util/Comparator;", ()).await?; + let _: () = jvm + .invoke_special(&this, "java/util/TreeMap", "", "(Ljava/util/Comparator;)V", (comparator,)) + .await?; + Self::copy_from_map(jvm, this, map).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "size", "I").await + } + + async fn contains_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result { + Ok(!Self::find_entry(jvm, &this, &key).await?.is_null()) + } + + async fn contains_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let mut entry = Self::first_entry(jvm, &this).await?; + while !entry.is_null() { + let current: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + let equal = if value.is_null() { + current.is_null() + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (current,)).await? + }; + if equal { + return Ok(true); + } + entry = Self::successor(jvm, entry).await?; + } + Ok(false) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result> { + let entry = Self::find_entry(jvm, &this, &key).await?; + if entry.is_null() { + return Ok(None.into()); + } + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } + + async fn put( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/TreeMap$Entry;").await?; + if root.is_null() { + let _ = Self::compare(jvm, &this, &key, &key).await?; + let entry: ClassInstanceRef = jvm + .new_class( + "java/util/TreeMap$Entry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/TreeMap$Entry;)V", + (key, value, ClassInstanceRef::::from(None)), + ) + .await? + .into(); + jvm.put_field(&mut this, "root", "Ljava/util/TreeMap$Entry;", entry).await?; + jvm.put_field(&mut this, "size", "I", 1).await?; + return Ok(None.into()); + } + + let mut parent = root; + let comparison; + loop { + let stored_key: ClassInstanceRef = jvm.get_field(&parent, "key", "Ljava/lang/Object;").await?; + let current_comparison = Self::compare(jvm, &this, &key, &stored_key).await?; + if current_comparison == 0 { + let old_value: ClassInstanceRef = jvm.get_field(&parent, "value", "Ljava/lang/Object;").await?; + let mut parent = parent; + jvm.put_field(&mut parent, "value", "Ljava/lang/Object;", value).await?; + return Ok(old_value); + } + let child: ClassInstanceRef = if current_comparison < 0 { + jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await? + } else { + jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await? + }; + if child.is_null() { + comparison = current_comparison; + break; + } + parent = child; + } + + let mut entry: ClassInstanceRef = jvm + .new_class( + "java/util/TreeMap$Entry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/TreeMap$Entry;)V", + (key, value, parent.clone()), + ) + .await? + .into(); + jvm.put_field(&mut entry, "color", "Z", false).await?; + let mut parent = parent; + if comparison < 0 { + jvm.put_field(&mut parent, "left", "Ljava/util/TreeMap$Entry;", entry.clone()).await?; + } else { + jvm.put_field(&mut parent, "right", "Ljava/util/TreeMap$Entry;", entry.clone()).await?; + } + let size: i32 = jvm.get_field(&this, "size", "I").await?; + jvm.put_field(&mut this, "size", "I", size + 1).await?; + Self::fix_after_insertion(jvm, &mut this, entry).await?; + Ok(None.into()) + } + + async fn put_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + if this.identity() == map.identity() { + return Ok(()); + } + Self::copy_from_map(jvm, this, map).await + } + + async fn remove( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + key: ClassInstanceRef, + ) -> Result> { + let entry = Self::find_entry(jvm, &this, &key).await?; + if entry.is_null() { + return Ok(None.into()); + } + let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + Self::delete_entry(jvm, &mut this, entry).await?; + Ok(old_value) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field( + &mut this, + "root", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + jvm.put_field(&mut this, "size", "I", 0).await + } + + async fn comparator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "comparator", "Ljava/util/Comparator;").await + } + + async fn first_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = Self::first_entry(jvm, &this).await?; + if entry.is_null() { + return Err(jvm.exception("java/util/NoSuchElementException", "empty TreeMap").await); + } + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } + + async fn last_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = Self::last_entry(jvm, &this).await?; + if entry.is_null() { + return Err(jvm.exception("java/util/NoSuchElementException", "empty TreeMap").await); + } + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } + + async fn sub_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from_key: ClassInstanceRef, + to_key: ClassInstanceRef, + ) -> Result> { + if Self::compare(jvm, &this, &from_key, &to_key).await? > 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromKey > toKey").await); + } + Ok(jvm + .new_class( + "java/util/TreeMap$SubMap", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + (this, false, from_key, false, to_key), + ) + .await? + .into()) + } + + async fn head_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + to_key: ClassInstanceRef, + ) -> Result> { + let _ = Self::compare(jvm, &this, &to_key, &to_key).await?; + Ok(jvm + .new_class( + "java/util/TreeMap$SubMap", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + (this, true, ClassInstanceRef::::from(None), false, to_key), + ) + .await? + .into()) + } + + async fn tail_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from_key: ClassInstanceRef, + ) -> Result> { + let _ = Self::compare(jvm, &this, &from_key, &from_key).await?; + Ok(jvm + .new_class( + "java/util/TreeMap$SubMap", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + (this, false, from_key, true, ClassInstanceRef::::from(None)), + ) + .await? + .into()) + } + + async fn key_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/TreeMap$KeySet", "(Ljava/util/SortedMap;)V", (this,)) + .await? + .into()) + } + + async fn values(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/TreeMap$Values", "(Ljava/util/SortedMap;)V", (this,)) + .await? + .into()) + } + + async fn entry_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/TreeMap$EntrySet", "(Ljava/util/SortedMap;)V", (this,)) + .await? + .into()) + } + + async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let first = Self::first_entry(jvm, &this).await?; + Ok(jvm + .new_class( + "java/util/TreeMap$KeyIterator", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (this, first, ClassInstanceRef::::from(None), true), + ) + .await? + .into()) + } + + async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let first = Self::first_entry(jvm, &this).await?; + Ok(jvm + .new_class( + "java/util/TreeMap$ValueIterator", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (this, first, ClassInstanceRef::::from(None), true), + ) + .await? + .into()) + } + + async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let first = Self::first_entry(jvm, &this).await?; + Ok(jvm + .new_class( + "java/util/TreeMap$EntryIterator", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (this, first, ClassInstanceRef::::from(None), true), + ) + .await? + .into()) + } + + async fn copy_from_map(jvm: &Jvm, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let entry_set: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entry_set, "iterator", "()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 key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + Ok(()) + } + + pub(super) async fn compare( + jvm: &Jvm, + this: &ClassInstanceRef, + left: &ClassInstanceRef, + right: &ClassInstanceRef, + ) -> Result { + let comparator: ClassInstanceRef = jvm.get_field(this, "comparator", "Ljava/util/Comparator;").await?; + if !comparator.is_null() { + return jvm + .invoke_virtual( + &comparator, + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + (left.clone(), right.clone()), + ) + .await; + } + if left.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "null key").await); + } + if !jvm.is_instance(left.as_ref(), "java/lang/Comparable") { + return Err(jvm.exception("java/lang/ClassCastException", "key is not Comparable").await); + } + jvm.invoke_virtual(left, "compareTo", "(Ljava/lang/Object;)I", (right.clone(),)).await + } + + pub(super) async fn find_entry( + jvm: &Jvm, + this: &ClassInstanceRef, + key: &ClassInstanceRef, + ) -> Result> { + let comparator: ClassInstanceRef = jvm.get_field(this, "comparator", "Ljava/util/Comparator;").await?; + if comparator.is_null() { + if key.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "null key").await); + } + if !jvm.is_instance(key.as_ref(), "java/lang/Comparable") { + return Err(jvm.exception("java/lang/ClassCastException", "key is not Comparable").await); + } + } + + let mut entry: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + while !entry.is_null() { + let stored_key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + let comparison: i32 = if comparator.is_null() { + jvm.invoke_virtual(key, "compareTo", "(Ljava/lang/Object;)I", (stored_key,)).await? + } else { + jvm.invoke_virtual( + &comparator, + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + (key.clone(), stored_key), + ) + .await? + }; + if comparison == 0 { + return Ok(entry); + } + entry = if comparison < 0 { + jvm.get_field(&entry, "left", "Ljava/util/TreeMap$Entry;").await? + } else { + jvm.get_field(&entry, "right", "Ljava/util/TreeMap$Entry;").await? + }; + } + Ok(None.into()) + } + + pub(super) async fn first_entry(jvm: &Jvm, this: &ClassInstanceRef) -> Result> { + let mut entry: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + if entry.is_null() { + return Ok(entry); + } + loop { + let left: ClassInstanceRef = jvm.get_field(&entry, "left", "Ljava/util/TreeMap$Entry;").await?; + if left.is_null() { + return Ok(entry); + } + entry = left; + } + } + + pub(super) async fn last_entry(jvm: &Jvm, this: &ClassInstanceRef) -> Result> { + let mut entry: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + if entry.is_null() { + return Ok(entry); + } + loop { + let right: ClassInstanceRef = jvm.get_field(&entry, "right", "Ljava/util/TreeMap$Entry;").await?; + if right.is_null() { + return Ok(entry); + } + entry = right; + } + } + + pub(super) async fn ceiling_entry( + jvm: &Jvm, + this: &ClassInstanceRef, + key: &ClassInstanceRef, + ) -> Result> { + let mut entry: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + let mut candidate: ClassInstanceRef = None.into(); + while !entry.is_null() { + let stored_key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + let comparison = Self::compare(jvm, this, key, &stored_key).await?; + if comparison == 0 { + return Ok(entry); + } + if comparison < 0 { + candidate = entry.clone(); + entry = jvm.get_field(&entry, "left", "Ljava/util/TreeMap$Entry;").await?; + } else { + entry = jvm.get_field(&entry, "right", "Ljava/util/TreeMap$Entry;").await?; + } + } + Ok(candidate) + } + + pub(super) async fn lower_entry( + jvm: &Jvm, + this: &ClassInstanceRef, + key: &ClassInstanceRef, + ) -> Result> { + let mut entry: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + let mut candidate: ClassInstanceRef = None.into(); + while !entry.is_null() { + let stored_key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + if Self::compare(jvm, this, key, &stored_key).await? <= 0 { + entry = jvm.get_field(&entry, "left", "Ljava/util/TreeMap$Entry;").await?; + } else { + candidate = entry.clone(); + entry = jvm.get_field(&entry, "right", "Ljava/util/TreeMap$Entry;").await?; + } + } + Ok(candidate) + } + + pub(super) async fn successor(jvm: &Jvm, entry: ClassInstanceRef) -> Result> { + if entry.is_null() { + return Ok(None.into()); + } + let right: ClassInstanceRef = jvm.get_field(&entry, "right", "Ljava/util/TreeMap$Entry;").await?; + if !right.is_null() { + let mut candidate = right; + loop { + let left: ClassInstanceRef = jvm.get_field(&candidate, "left", "Ljava/util/TreeMap$Entry;").await?; + if left.is_null() { + return Ok(candidate); + } + candidate = left; + } + } + + let mut child = entry; + let mut parent: ClassInstanceRef = jvm.get_field(&child, "parent", "Ljava/util/TreeMap$Entry;").await?; + while !parent.is_null() { + let parent_right: ClassInstanceRef = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + if parent_right.is_null() || child.identity() != parent_right.identity() { + break; + } + child = parent; + parent = jvm.get_field(&child, "parent", "Ljava/util/TreeMap$Entry;").await?; + } + Ok(parent) + } + + async fn rotate_left(jvm: &Jvm, this: &mut ClassInstanceRef, mut pivot: ClassInstanceRef) -> Result<()> { + let mut right: ClassInstanceRef = jvm.get_field(&pivot, "right", "Ljava/util/TreeMap$Entry;").await?; + let right_left: ClassInstanceRef = jvm.get_field(&right, "left", "Ljava/util/TreeMap$Entry;").await?; + jvm.put_field(&mut pivot, "right", "Ljava/util/TreeMap$Entry;", right_left.clone()) + .await?; + if !right_left.is_null() { + let mut right_left = right_left; + jvm.put_field(&mut right_left, "parent", "Ljava/util/TreeMap$Entry;", pivot.clone()) + .await?; + } + let parent: ClassInstanceRef = jvm.get_field(&pivot, "parent", "Ljava/util/TreeMap$Entry;").await?; + jvm.put_field(&mut right, "parent", "Ljava/util/TreeMap$Entry;", parent.clone()).await?; + if parent.is_null() { + jvm.put_field(this, "root", "Ljava/util/TreeMap$Entry;", right.clone()).await?; + } else { + let parent_left: ClassInstanceRef = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + let mut parent = parent; + if !parent_left.is_null() && parent_left.identity() == pivot.identity() { + jvm.put_field(&mut parent, "left", "Ljava/util/TreeMap$Entry;", right.clone()).await?; + } else { + jvm.put_field(&mut parent, "right", "Ljava/util/TreeMap$Entry;", right.clone()).await?; + } + } + jvm.put_field(&mut right, "left", "Ljava/util/TreeMap$Entry;", pivot.clone()).await?; + jvm.put_field(&mut pivot, "parent", "Ljava/util/TreeMap$Entry;", right).await + } + + async fn rotate_right(jvm: &Jvm, this: &mut ClassInstanceRef, mut pivot: ClassInstanceRef) -> Result<()> { + let mut left: ClassInstanceRef = jvm.get_field(&pivot, "left", "Ljava/util/TreeMap$Entry;").await?; + let left_right: ClassInstanceRef = jvm.get_field(&left, "right", "Ljava/util/TreeMap$Entry;").await?; + jvm.put_field(&mut pivot, "left", "Ljava/util/TreeMap$Entry;", left_right.clone()).await?; + if !left_right.is_null() { + let mut left_right = left_right; + jvm.put_field(&mut left_right, "parent", "Ljava/util/TreeMap$Entry;", pivot.clone()) + .await?; + } + let parent: ClassInstanceRef = jvm.get_field(&pivot, "parent", "Ljava/util/TreeMap$Entry;").await?; + jvm.put_field(&mut left, "parent", "Ljava/util/TreeMap$Entry;", parent.clone()).await?; + if parent.is_null() { + jvm.put_field(this, "root", "Ljava/util/TreeMap$Entry;", left.clone()).await?; + } else { + let parent_right: ClassInstanceRef = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + let mut parent = parent; + if !parent_right.is_null() && parent_right.identity() == pivot.identity() { + jvm.put_field(&mut parent, "right", "Ljava/util/TreeMap$Entry;", left.clone()).await?; + } else { + jvm.put_field(&mut parent, "left", "Ljava/util/TreeMap$Entry;", left.clone()).await?; + } + } + jvm.put_field(&mut left, "right", "Ljava/util/TreeMap$Entry;", pivot.clone()).await?; + jvm.put_field(&mut pivot, "parent", "Ljava/util/TreeMap$Entry;", left).await + } + + async fn fix_after_insertion(jvm: &Jvm, this: &mut ClassInstanceRef, mut entry: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut entry, "color", "Z", false).await?; + loop { + let root: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + if entry.identity() == root.identity() { + break; + } + let mut parent: ClassInstanceRef = jvm.get_field(&entry, "parent", "Ljava/util/TreeMap$Entry;").await?; + if parent.is_null() || jvm.get_field::(&parent, "color", "Z").await? { + break; + } + let mut grand: ClassInstanceRef = jvm.get_field(&parent, "parent", "Ljava/util/TreeMap$Entry;").await?; + let grand_left: ClassInstanceRef = jvm.get_field(&grand, "left", "Ljava/util/TreeMap$Entry;").await?; + if !grand_left.is_null() && grand_left.identity() == parent.identity() { + let mut uncle: ClassInstanceRef = jvm.get_field(&grand, "right", "Ljava/util/TreeMap$Entry;").await?; + let uncle_is_red = !uncle.is_null() && !jvm.get_field::(&uncle, "color", "Z").await?; + if uncle_is_red { + jvm.put_field(&mut parent, "color", "Z", true).await?; + jvm.put_field(&mut uncle, "color", "Z", true).await?; + jvm.put_field(&mut grand, "color", "Z", false).await?; + entry = grand; + continue; + } + let parent_right: ClassInstanceRef = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + if !parent_right.is_null() && parent_right.identity() == entry.identity() { + entry = parent.clone(); + Self::rotate_left(jvm, this, entry.clone()).await?; + parent = jvm.get_field(&entry, "parent", "Ljava/util/TreeMap$Entry;").await?; + grand = jvm.get_field(&parent, "parent", "Ljava/util/TreeMap$Entry;").await?; + } + jvm.put_field(&mut parent, "color", "Z", true).await?; + jvm.put_field(&mut grand, "color", "Z", false).await?; + Self::rotate_right(jvm, this, grand).await?; + } else { + let mut uncle: ClassInstanceRef = jvm.get_field(&grand, "left", "Ljava/util/TreeMap$Entry;").await?; + let uncle_is_red = !uncle.is_null() && !jvm.get_field::(&uncle, "color", "Z").await?; + if uncle_is_red { + jvm.put_field(&mut parent, "color", "Z", true).await?; + jvm.put_field(&mut uncle, "color", "Z", true).await?; + jvm.put_field(&mut grand, "color", "Z", false).await?; + entry = grand; + continue; + } + let parent_left: ClassInstanceRef = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + if !parent_left.is_null() && parent_left.identity() == entry.identity() { + entry = parent.clone(); + Self::rotate_right(jvm, this, entry.clone()).await?; + parent = jvm.get_field(&entry, "parent", "Ljava/util/TreeMap$Entry;").await?; + grand = jvm.get_field(&parent, "parent", "Ljava/util/TreeMap$Entry;").await?; + } + jvm.put_field(&mut parent, "color", "Z", true).await?; + jvm.put_field(&mut grand, "color", "Z", false).await?; + Self::rotate_left(jvm, this, grand).await?; + } + } + let mut root: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + jvm.put_field(&mut root, "color", "Z", true).await?; + Ok(()) + } + + pub(super) async fn delete_entry(jvm: &Jvm, this: &mut ClassInstanceRef, entry: ClassInstanceRef) -> Result<()> { + let size: i32 = jvm.get_field(this, "size", "I").await?; + jvm.put_field(this, "size", "I", size - 1).await?; + + let mut target = entry; + let left: ClassInstanceRef = jvm.get_field(&target, "left", "Ljava/util/TreeMap$Entry;").await?; + let right: ClassInstanceRef = jvm.get_field(&target, "right", "Ljava/util/TreeMap$Entry;").await?; + if !left.is_null() && !right.is_null() { + let successor = Self::successor(jvm, target.clone()).await?; + let key: ClassInstanceRef = jvm.get_field(&successor, "key", "Ljava/lang/Object;").await?; + let value: ClassInstanceRef = jvm.get_field(&successor, "value", "Ljava/lang/Object;").await?; + jvm.put_field(&mut target, "key", "Ljava/lang/Object;", key).await?; + jvm.put_field(&mut target, "value", "Ljava/lang/Object;", value).await?; + target = successor; + } + + let target_left: ClassInstanceRef = jvm.get_field(&target, "left", "Ljava/util/TreeMap$Entry;").await?; + let target_right: ClassInstanceRef = jvm.get_field(&target, "right", "Ljava/util/TreeMap$Entry;").await?; + let mut replacement = if !target_left.is_null() { target_left } else { target_right }; + if !replacement.is_null() { + let parent: ClassInstanceRef = jvm.get_field(&target, "parent", "Ljava/util/TreeMap$Entry;").await?; + jvm.put_field(&mut replacement, "parent", "Ljava/util/TreeMap$Entry;", parent.clone()) + .await?; + if parent.is_null() { + jvm.put_field(this, "root", "Ljava/util/TreeMap$Entry;", replacement.clone()).await?; + } else { + let parent_left: ClassInstanceRef = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + let mut parent = parent; + if !parent_left.is_null() && parent_left.identity() == target.identity() { + jvm.put_field(&mut parent, "left", "Ljava/util/TreeMap$Entry;", replacement.clone()) + .await?; + } else { + jvm.put_field(&mut parent, "right", "Ljava/util/TreeMap$Entry;", replacement.clone()) + .await?; + } + } + jvm.put_field( + &mut target, + "left", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + jvm.put_field( + &mut target, + "right", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + jvm.put_field( + &mut target, + "parent", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + if jvm.get_field::(&target, "color", "Z").await? { + Self::fix_after_deletion(jvm, this, replacement).await?; + } + } else { + let parent: ClassInstanceRef = jvm.get_field(&target, "parent", "Ljava/util/TreeMap$Entry;").await?; + if parent.is_null() { + jvm.put_field(this, "root", "Ljava/util/TreeMap$Entry;", ClassInstanceRef::::from(None)) + .await?; + } else { + if jvm.get_field::(&target, "color", "Z").await? { + Self::fix_after_deletion(jvm, this, target.clone()).await?; + } + let parent: ClassInstanceRef = jvm.get_field(&target, "parent", "Ljava/util/TreeMap$Entry;").await?; + if !parent.is_null() { + let parent_left: ClassInstanceRef = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + let parent_right: ClassInstanceRef = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + let mut parent = parent; + if !parent_left.is_null() && parent_left.identity() == target.identity() { + jvm.put_field( + &mut parent, + "left", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + } else if !parent_right.is_null() && parent_right.identity() == target.identity() { + jvm.put_field( + &mut parent, + "right", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + } + jvm.put_field( + &mut target, + "parent", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await?; + } + } + } + Ok(()) + } + + async fn fix_after_deletion(jvm: &Jvm, this: &mut ClassInstanceRef, mut entry: ClassInstanceRef) -> Result<()> { + loop { + let root: ClassInstanceRef = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + if entry.identity() == root.identity() || !jvm.get_field::(&entry, "color", "Z").await? { + break; + } + let parent: ClassInstanceRef = jvm.get_field(&entry, "parent", "Ljava/util/TreeMap$Entry;").await?; + let parent_left: ClassInstanceRef = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + if !parent_left.is_null() && parent_left.identity() == entry.identity() { + let mut sibling: ClassInstanceRef = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + if !sibling.is_null() && !jvm.get_field::(&sibling, "color", "Z").await? { + jvm.put_field(&mut sibling, "color", "Z", true).await?; + let mut parent = parent.clone(); + jvm.put_field(&mut parent, "color", "Z", false).await?; + Self::rotate_left(jvm, this, parent.clone()).await?; + sibling = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + } + let sibling_left: ClassInstanceRef = if sibling.is_null() { + None.into() + } else { + jvm.get_field(&sibling, "left", "Ljava/util/TreeMap$Entry;").await? + }; + let sibling_right: ClassInstanceRef = if sibling.is_null() { + None.into() + } else { + jvm.get_field(&sibling, "right", "Ljava/util/TreeMap$Entry;").await? + }; + let left_black = sibling_left.is_null() || jvm.get_field::(&sibling_left, "color", "Z").await?; + let right_black = sibling_right.is_null() || jvm.get_field::(&sibling_right, "color", "Z").await?; + if left_black && right_black { + if !sibling.is_null() { + jvm.put_field(&mut sibling, "color", "Z", false).await?; + } + entry = parent; + } else { + if right_black { + if !sibling_left.is_null() { + let mut sibling_left = sibling_left; + jvm.put_field(&mut sibling_left, "color", "Z", true).await?; + } + if !sibling.is_null() { + jvm.put_field(&mut sibling, "color", "Z", false).await?; + Self::rotate_right(jvm, this, sibling.clone()).await?; + } + sibling = jvm.get_field(&parent, "right", "Ljava/util/TreeMap$Entry;").await?; + } + if !sibling.is_null() { + let parent_color: bool = jvm.get_field(&parent, "color", "Z").await?; + jvm.put_field(&mut sibling, "color", "Z", parent_color).await?; + } + let mut parent = parent; + jvm.put_field(&mut parent, "color", "Z", true).await?; + if !sibling.is_null() { + let mut sibling_right: ClassInstanceRef = jvm.get_field(&sibling, "right", "Ljava/util/TreeMap$Entry;").await?; + if !sibling_right.is_null() { + jvm.put_field(&mut sibling_right, "color", "Z", true).await?; + } + } + Self::rotate_left(jvm, this, parent).await?; + entry = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + } + } else { + let mut sibling: ClassInstanceRef = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + if !sibling.is_null() && !jvm.get_field::(&sibling, "color", "Z").await? { + jvm.put_field(&mut sibling, "color", "Z", true).await?; + let mut parent = parent.clone(); + jvm.put_field(&mut parent, "color", "Z", false).await?; + Self::rotate_right(jvm, this, parent.clone()).await?; + sibling = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + } + let sibling_left: ClassInstanceRef = if sibling.is_null() { + None.into() + } else { + jvm.get_field(&sibling, "left", "Ljava/util/TreeMap$Entry;").await? + }; + let sibling_right: ClassInstanceRef = if sibling.is_null() { + None.into() + } else { + jvm.get_field(&sibling, "right", "Ljava/util/TreeMap$Entry;").await? + }; + let left_black = sibling_left.is_null() || jvm.get_field::(&sibling_left, "color", "Z").await?; + let right_black = sibling_right.is_null() || jvm.get_field::(&sibling_right, "color", "Z").await?; + if left_black && right_black { + if !sibling.is_null() { + jvm.put_field(&mut sibling, "color", "Z", false).await?; + } + entry = parent; + } else { + if left_black { + if !sibling_right.is_null() { + let mut sibling_right = sibling_right; + jvm.put_field(&mut sibling_right, "color", "Z", true).await?; + } + if !sibling.is_null() { + jvm.put_field(&mut sibling, "color", "Z", false).await?; + Self::rotate_left(jvm, this, sibling.clone()).await?; + } + sibling = jvm.get_field(&parent, "left", "Ljava/util/TreeMap$Entry;").await?; + } + if !sibling.is_null() { + let parent_color: bool = jvm.get_field(&parent, "color", "Z").await?; + jvm.put_field(&mut sibling, "color", "Z", parent_color).await?; + } + let mut parent = parent; + jvm.put_field(&mut parent, "color", "Z", true).await?; + if !sibling.is_null() { + let mut sibling_left: ClassInstanceRef = jvm.get_field(&sibling, "left", "Ljava/util/TreeMap$Entry;").await?; + if !sibling_left.is_null() { + jvm.put_field(&mut sibling_left, "color", "Z", true).await?; + } + } + Self::rotate_right(jvm, this, parent).await?; + entry = jvm.get_field(this, "root", "Ljava/util/TreeMap$Entry;").await?; + } + } + } + if !entry.is_null() { + jvm.put_field(&mut entry, "color", "Z", true).await?; + } + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_entry.rs b/java_runtime/src/classes/java/util/tree_map_entry.rs new file mode 100644 index 00000000..fcf633dd --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_entry.rs @@ -0,0 +1,121 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// final class java.util.TreeMap$Entry +pub struct TreeMapEntry; + +impl TreeMapEntry { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$Entry", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map$Entry"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/TreeMap$Entry;)V", + Self::init, + 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), + ], + fields: vec![ + JavaFieldProto::new("key", "Ljava/lang/Object;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("value", "Ljava/lang/Object;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("left", "Ljava/util/TreeMap$Entry;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("right", "Ljava/util/TreeMap$Entry;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("parent", "Ljava/util/TreeMap$Entry;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("color", "Z", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::FINAL, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + parent: ClassInstanceRef, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "key", "Ljava/lang/Object;", key).await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + jvm.put_field(&mut this, "parent", "Ljava/util/TreeMap$Entry;", parent).await?; + jvm.put_field(&mut this, "color", "Z", true).await + } + + async fn get_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "key", "Ljava/lang/Object;").await + } + + async fn get_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "value", "Ljava/lang/Object;").await + } + + async fn set_value( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + let old_value = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + Ok(old_value) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + + let key: ClassInstanceRef = jvm.get_field(&this, "key", "Ljava/lang/Object;").await?; + let other_key: ClassInstanceRef = jvm.invoke_virtual(&other, "getKey", "()Ljava/lang/Object;", ()).await?; + let keys_equal = if key.is_null() { + other_key.is_null() + } else { + jvm.invoke_virtual(&key, "equals", "(Ljava/lang/Object;)Z", (other_key,)).await? + }; + if !keys_equal { + return Ok(false); + } + let value: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + let other_value: ClassInstanceRef = jvm.invoke_virtual(&other, "getValue", "()Ljava/lang/Object;", ()).await?; + if value.is_null() { + Ok(other_value.is_null()) + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (other_value,)).await + } + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let key: ClassInstanceRef = jvm.get_field(&this, "key", "Ljava/lang/Object;").await?; + let value: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + let key_hash = if key.is_null() { + 0 + } else { + jvm.invoke_virtual(&key, "hashCode", "()I", ()).await? + }; + let value_hash = if value.is_null() { + 0 + } else { + jvm.invoke_virtual(&value, "hashCode", "()I", ()).await? + }; + Ok(key_hash ^ value_hash) + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_entry_iterator.rs b/java_runtime/src/classes/java/util/tree_map_entry_iterator.rs new file mode 100644 index 00000000..74c3593b --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_entry_iterator.rs @@ -0,0 +1,57 @@ +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::{TreeMap, TreeMapEntry, TreeMapPrivateEntryIterator}; + +// class java.util.TreeMap$EntryIterator +pub struct TreeMapEntryIterator; + +impl TreeMapEntryIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$EntryIterator", + parent_class: Some("java/util/TreeMap$PrivateEntryIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)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, + next: ClassInstanceRef, + upper: ClassInstanceRef, + to_end: bool, + ) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/TreeMap$PrivateEntryIterator", + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (map, next, upper, to_end), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = TreeMapPrivateEntryIterator::next_entry(jvm, this).await?; + Ok(ClassInstanceRef::new(entry.instance)) + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_entry_set.rs b/java_runtime/src/classes/java/util/tree_map_entry_set.rs new file mode 100644 index 00000000..7f641e72 --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_entry_set.rs @@ -0,0 +1,102 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.TreeMap$EntrySet +pub struct TreeMapEntrySet; + +impl TreeMapEntrySet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$EntrySet", + parent_class: Some("java/util/AbstractSet"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/SortedMap;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("map", "Ljava/util/SortedMap;", FieldAccessFlags::PRIVATE)], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + jvm.put_field(&mut this, "map", "Ljava/util/SortedMap;", map).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "size", "()I", ()).await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, candidate: ClassInstanceRef) -> Result { + if candidate.is_null() || !jvm.is_instance(candidate.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getValue", "()Ljava/lang/Object;", ()).await?; + let key: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getKey", "()Ljava/lang/Object;", ()).await?; + if !jvm + .invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (key.clone(),)) + .await? + { + return Ok(false); + } + let stored: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await?; + if stored.is_null() { + Ok(value.is_null()) + } else { + jvm.invoke_virtual(&stored, "equals", "(Ljava/lang/Object;)Z", (value,)).await + } + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, candidate: ClassInstanceRef) -> Result { + if candidate.is_null() || !jvm.is_instance(candidate.as_ref(), "java/util/Map$Entry") { + return Ok(false); + } + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getValue", "()Ljava/lang/Object;", ()).await?; + let key: ClassInstanceRef = jvm.invoke_virtual(&candidate, "getKey", "()Ljava/lang/Object;", ()).await?; + if !jvm + .invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (key.clone(),)) + .await? + { + return Ok(false); + } + let stored: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await?; + let equal = if stored.is_null() { + value.is_null() + } else { + jvm.invoke_virtual::<_, bool>(&stored, "equals", "(Ljava/lang/Object;)Z", (value,)) + .await? + }; + if !equal { + return Ok(false); + } + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + Ok(true) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "clear", "()V", ()).await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_key_iterator.rs b/java_runtime/src/classes/java/util/tree_map_key_iterator.rs new file mode 100644 index 00000000..1cc6bfca --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_key_iterator.rs @@ -0,0 +1,57 @@ +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::{TreeMap, TreeMapEntry, TreeMapPrivateEntryIterator}; + +// class java.util.TreeMap$KeyIterator +pub struct TreeMapKeyIterator; + +impl TreeMapKeyIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$KeyIterator", + parent_class: Some("java/util/TreeMap$PrivateEntryIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)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, + next: ClassInstanceRef, + upper: ClassInstanceRef, + to_end: bool, + ) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/TreeMap$PrivateEntryIterator", + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (map, next, upper, to_end), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = TreeMapPrivateEntryIterator::next_entry(jvm, this).await?; + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_key_set.rs b/java_runtime/src/classes/java/util/tree_map_key_set.rs new file mode 100644 index 00000000..53ccb5c5 --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_key_set.rs @@ -0,0 +1,69 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.TreeMap$KeySet +pub struct TreeMapKeySet; + +impl TreeMapKeySet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$KeySet", + parent_class: Some("java/util/AbstractSet"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/SortedMap;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("map", "Ljava/util/SortedMap;", FieldAccessFlags::PRIVATE)], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + jvm.put_field(&mut this, "map", "Ljava/util/SortedMap;", map).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "size", "()I", ()).await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "containsKey", "(Ljava/lang/Object;)Z", (key,)).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + if !jvm + .invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (key.clone(),)) + .await? + { + return Ok(false); + } + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + Ok(true) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "clear", "()V", ()).await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "keyIterator", "()Ljava/util/Iterator;", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_private_entry_iterator.rs b/java_runtime/src/classes/java/util/tree_map_private_entry_iterator.rs new file mode 100644 index 00000000..d9cbd23d --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_private_entry_iterator.rs @@ -0,0 +1,113 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{TreeMap, TreeMapEntry}; + +// abstract class java.util.TreeMap$PrivateEntryIterator +pub struct TreeMapPrivateEntryIterator; + +impl TreeMapPrivateEntryIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$PrivateEntryIterator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + Self::init, + Default::default(), + ), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract("next", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("map", "Ljava/util/TreeMap;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("next", "Ljava/util/TreeMap$Entry;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("lastReturned", "Ljava/util/TreeMap$Entry;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("upper", "Ljava/lang/Object;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("toEnd", "Z", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::ABSTRACT, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + map: ClassInstanceRef, + next: ClassInstanceRef, + upper: ClassInstanceRef, + to_end: bool, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "map", "Ljava/util/TreeMap;", map).await?; + jvm.put_field(&mut this, "next", "Ljava/util/TreeMap$Entry;", next).await?; + jvm.put_field(&mut this, "upper", "Ljava/lang/Object;", upper).await?; + jvm.put_field(&mut this, "toEnd", "Z", to_end).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let next: ClassInstanceRef = jvm.get_field(&this, "next", "Ljava/util/TreeMap$Entry;").await?; + if next.is_null() { + return Ok(false); + } + if jvm.get_field::(&this, "toEnd", "Z").await? { + return Ok(true); + } + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/TreeMap;").await?; + let key: ClassInstanceRef = jvm.get_field(&next, "key", "Ljava/lang/Object;").await?; + let upper: ClassInstanceRef = jvm.get_field(&this, "upper", "Ljava/lang/Object;").await?; + Ok(TreeMap::compare(jvm, &map, &key, &upper).await? < 0) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: ClassInstanceRef = jvm.get_field(&this, "lastReturned", "Ljava/util/TreeMap$Entry;").await?; + if last_returned.is_null() { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let left: ClassInstanceRef = jvm.get_field(&last_returned, "left", "Ljava/util/TreeMap$Entry;").await?; + let right: ClassInstanceRef = jvm.get_field(&last_returned, "right", "Ljava/util/TreeMap$Entry;").await?; + if !left.is_null() && !right.is_null() { + jvm.put_field(&mut this, "next", "Ljava/util/TreeMap$Entry;", last_returned.clone()) + .await?; + } + let mut map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/TreeMap;").await?; + TreeMap::delete_entry(jvm, &mut map, last_returned).await?; + jvm.put_field( + &mut this, + "lastReturned", + "Ljava/util/TreeMap$Entry;", + ClassInstanceRef::::from(None), + ) + .await + } + + pub(super) async fn next_entry(jvm: &Jvm, mut this: ClassInstanceRef) -> Result> { + let next: ClassInstanceRef = jvm.get_field(&this, "next", "Ljava/util/TreeMap$Entry;").await?; + if next.is_null() { + return Err(jvm.exception("java/util/NoSuchElementException", "TreeMap iterator exhausted").await); + } + if !jvm.get_field::(&this, "toEnd", "Z").await? { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/TreeMap;").await?; + let key: ClassInstanceRef = jvm.get_field(&next, "key", "Ljava/lang/Object;").await?; + let upper: ClassInstanceRef = jvm.get_field(&this, "upper", "Ljava/lang/Object;").await?; + if TreeMap::compare(jvm, &map, &key, &upper).await? >= 0 { + return Err(jvm.exception("java/util/NoSuchElementException", "TreeMap iterator exhausted").await); + } + } + let successor = TreeMap::successor(jvm, next.clone()).await?; + jvm.put_field(&mut this, "next", "Ljava/util/TreeMap$Entry;", successor).await?; + jvm.put_field(&mut this, "lastReturned", "Ljava/util/TreeMap$Entry;", next.clone()) + .await?; + Ok(next) + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_sub_map.rs b/java_runtime/src/classes/java/util/tree_map_sub_map.rs new file mode 100644 index 00000000..82e0bd4a --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_sub_map.rs @@ -0,0 +1,412 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{TreeMap, TreeMapEntry}; + +// class java.util.TreeMap$SubMap +pub struct TreeMapSubMap; + +impl TreeMapSubMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$SubMap", + parent_class: Some("java/util/AbstractMap"), + interfaces: vec!["java/util/SortedMap"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + Self::init, + Default::default(), + ), + JavaMethodProto::new("size", "()I", Self::size, 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("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("comparator", "()Ljava/util/Comparator;", Self::comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("firstKey", "()Ljava/lang/Object;", Self::first_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastKey", "()Ljava/lang/Object;", Self::last_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::sub_map, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "headMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::head_map, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "tailMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + Self::tail_map, + 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("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("m", "Ljava/util/TreeMap;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("fromStart", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("fromKey", "Ljava/lang/Object;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("toEnd", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("toKey", "Ljava/lang/Object;", FieldAccessFlags::PRIVATE), + ], + access_flags: Default::default(), + } + } + + #[allow(clippy::too_many_arguments)] + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + map: ClassInstanceRef, + from_start: bool, + from_key: ClassInstanceRef, + to_end: bool, + to_key: ClassInstanceRef, + ) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + if !from_start { + let _ = TreeMap::compare(jvm, &map, &from_key, &from_key).await?; + } + if !to_end { + let _ = TreeMap::compare(jvm, &map, &to_key, &to_key).await?; + } + if !from_start && !to_end && TreeMap::compare(jvm, &map, &from_key, &to_key).await? > 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromKey > toKey").await); + } + let _: () = jvm.invoke_special(&this, "java/util/AbstractMap", "", "()V", ()).await?; + jvm.put_field(&mut this, "m", "Ljava/util/TreeMap;", map).await?; + jvm.put_field(&mut this, "fromStart", "Z", from_start).await?; + jvm.put_field(&mut this, "fromKey", "Ljava/lang/Object;", from_key).await?; + jvm.put_field(&mut this, "toEnd", "Z", to_end).await?; + jvm.put_field(&mut this, "toKey", "Ljava/lang/Object;", to_key).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&this, "entryIterator", "()Ljava/util/Iterator;", ()).await?; + let mut size = 0; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + size += 1; + } + Ok(size) + } + + async fn contains_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result { + if !Self::in_range(jvm, &this, &key, false).await? { + return Ok(false); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + Ok(!TreeMap::find_entry(jvm, &map, &key).await?.is_null()) + } + + async fn contains_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&this, "valueIterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let current: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let equal = if value.is_null() { + current.is_null() + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (current,)).await? + }; + if equal { + return Ok(true); + } + } + Ok(false) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result> { + if !Self::in_range(jvm, &this, &key, false).await? { + return Ok(None.into()); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + let entry = TreeMap::find_entry(jvm, &map, &key).await?; + if entry.is_null() { + return Ok(None.into()); + } + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } + + async fn put( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + if !Self::in_range(jvm, &this, &key, false).await? { + return Err(jvm.exception("java/lang/IllegalArgumentException", "key outside range").await); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + jvm.invoke_virtual(&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> { + if !Self::in_range(jvm, &this, &key, false).await? { + return Ok(None.into()); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + jvm.invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&this, "entryIterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + } + Ok(()) + } + + async fn comparator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + jvm.invoke_virtual(&map, "comparator", "()Ljava/util/Comparator;", ()).await + } + + async fn first_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = Self::first_entry(jvm, &this).await?; + if entry.is_null() { + return Err(jvm.exception("java/util/NoSuchElementException", "empty subMap").await); + } + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } + + async fn last_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = Self::last_entry(jvm, &this).await?; + if entry.is_null() { + return Err(jvm.exception("java/util/NoSuchElementException", "empty subMap").await); + } + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } + + async fn sub_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from_key: ClassInstanceRef, + to_key: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + if !Self::in_range(jvm, &this, &from_key, false).await? { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromKey outside range").await); + } + if !Self::in_range(jvm, &this, &to_key, true).await? { + return Err(jvm.exception("java/lang/IllegalArgumentException", "toKey outside range").await); + } + if TreeMap::compare(jvm, &map, &from_key, &to_key).await? > 0 { + return Err(jvm.exception("java/lang/IllegalArgumentException", "fromKey > toKey").await); + } + Ok(jvm + .new_class( + "java/util/TreeMap$SubMap", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + (map, false, from_key, false, to_key), + ) + .await? + .into()) + } + + async fn head_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + to_key: ClassInstanceRef, + ) -> Result> { + if !Self::in_range(jvm, &this, &to_key, true).await? { + return Err(jvm.exception("java/lang/IllegalArgumentException", "endpoint outside range").await); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + let from_start: bool = jvm.get_field(&this, "fromStart", "Z").await?; + let from_key: ClassInstanceRef = jvm.get_field(&this, "fromKey", "Ljava/lang/Object;").await?; + Ok(jvm + .new_class( + "java/util/TreeMap$SubMap", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + (map, from_start, from_key, false, to_key), + ) + .await? + .into()) + } + + async fn tail_map( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from_key: ClassInstanceRef, + ) -> Result> { + if !Self::in_range(jvm, &this, &from_key, false).await? { + return Err(jvm.exception("java/lang/IllegalArgumentException", "endpoint outside range").await); + } + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + let to_end: bool = jvm.get_field(&this, "toEnd", "Z").await?; + let to_key: ClassInstanceRef = jvm.get_field(&this, "toKey", "Ljava/lang/Object;").await?; + Ok(jvm + .new_class( + "java/util/TreeMap$SubMap", + "(Ljava/util/TreeMap;ZLjava/lang/Object;ZLjava/lang/Object;)V", + (map, false, from_key, to_end, to_key), + ) + .await? + .into()) + } + + async fn key_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/TreeMap$KeySet", "(Ljava/util/SortedMap;)V", (this,)) + .await? + .into()) + } + + async fn values(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/TreeMap$Values", "(Ljava/util/SortedMap;)V", (this,)) + .await? + .into()) + } + + async fn entry_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/TreeMap$EntrySet", "(Ljava/util/SortedMap;)V", (this,)) + .await? + .into()) + } + + async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + let first = Self::first_entry(jvm, &this).await?; + let to_end: bool = jvm.get_field(&this, "toEnd", "Z").await?; + let to_key: ClassInstanceRef = jvm.get_field(&this, "toKey", "Ljava/lang/Object;").await?; + Ok(jvm + .new_class( + "java/util/TreeMap$KeyIterator", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (map, first, to_key, to_end), + ) + .await? + .into()) + } + + async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + let first = Self::first_entry(jvm, &this).await?; + let to_end: bool = jvm.get_field(&this, "toEnd", "Z").await?; + let to_key: ClassInstanceRef = jvm.get_field(&this, "toKey", "Ljava/lang/Object;").await?; + Ok(jvm + .new_class( + "java/util/TreeMap$ValueIterator", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (map, first, to_key, to_end), + ) + .await? + .into()) + } + + async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/TreeMap;").await?; + let first = Self::first_entry(jvm, &this).await?; + let to_end: bool = jvm.get_field(&this, "toEnd", "Z").await?; + let to_key: ClassInstanceRef = jvm.get_field(&this, "toKey", "Ljava/lang/Object;").await?; + Ok(jvm + .new_class( + "java/util/TreeMap$EntryIterator", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (map, first, to_key, to_end), + ) + .await? + .into()) + } + + async fn in_range(jvm: &Jvm, this: &ClassInstanceRef, key: &ClassInstanceRef, allow_equal_upper: bool) -> Result { + let map: ClassInstanceRef = jvm.get_field(this, "m", "Ljava/util/TreeMap;").await?; + if !jvm.get_field::(this, "fromStart", "Z").await? { + let from_key: ClassInstanceRef = jvm.get_field(this, "fromKey", "Ljava/lang/Object;").await?; + if TreeMap::compare(jvm, &map, key, &from_key).await? < 0 { + return Ok(false); + } + } + if !jvm.get_field::(this, "toEnd", "Z").await? { + let to_key: ClassInstanceRef = jvm.get_field(this, "toKey", "Ljava/lang/Object;").await?; + let comparison = TreeMap::compare(jvm, &map, key, &to_key).await?; + if comparison > 0 || (!allow_equal_upper && comparison == 0) { + return Ok(false); + } + } + Ok(true) + } + + async fn first_entry(jvm: &Jvm, this: &ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(this, "m", "Ljava/util/TreeMap;").await?; + let entry = if jvm.get_field::(this, "fromStart", "Z").await? { + TreeMap::first_entry(jvm, &map).await? + } else { + let from_key: ClassInstanceRef = jvm.get_field(this, "fromKey", "Ljava/lang/Object;").await?; + TreeMap::ceiling_entry(jvm, &map, &from_key).await? + }; + if entry.is_null() || jvm.get_field::(this, "toEnd", "Z").await? { + return Ok(entry); + } + let key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + let to_key: ClassInstanceRef = jvm.get_field(this, "toKey", "Ljava/lang/Object;").await?; + if TreeMap::compare(jvm, &map, &key, &to_key).await? >= 0 { + Ok(None.into()) + } else { + Ok(entry) + } + } + + async fn last_entry(jvm: &Jvm, this: &ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(this, "m", "Ljava/util/TreeMap;").await?; + let entry = if jvm.get_field::(this, "toEnd", "Z").await? { + TreeMap::last_entry(jvm, &map).await? + } else { + let to_key: ClassInstanceRef = jvm.get_field(this, "toKey", "Ljava/lang/Object;").await?; + TreeMap::lower_entry(jvm, &map, &to_key).await? + }; + if entry.is_null() || jvm.get_field::(this, "fromStart", "Z").await? { + return Ok(entry); + } + let key: ClassInstanceRef = jvm.get_field(&entry, "key", "Ljava/lang/Object;").await?; + let from_key: ClassInstanceRef = jvm.get_field(this, "fromKey", "Ljava/lang/Object;").await?; + if TreeMap::compare(jvm, &map, &key, &from_key).await? < 0 { + Ok(None.into()) + } else { + Ok(entry) + } + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_value_iterator.rs b/java_runtime/src/classes/java/util/tree_map_value_iterator.rs new file mode 100644 index 00000000..eb322687 --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_value_iterator.rs @@ -0,0 +1,57 @@ +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::{TreeMap, TreeMapEntry, TreeMapPrivateEntryIterator}; + +// class java.util.TreeMap$ValueIterator +pub struct TreeMapValueIterator; + +impl TreeMapValueIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$ValueIterator", + parent_class: Some("java/util/TreeMap$PrivateEntryIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)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, + next: ClassInstanceRef, + upper: ClassInstanceRef, + to_end: bool, + ) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/TreeMap$PrivateEntryIterator", + "", + "(Ljava/util/TreeMap;Ljava/util/TreeMap$Entry;Ljava/lang/Object;Z)V", + (map, next, upper, to_end), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry = TreeMapPrivateEntryIterator::next_entry(jvm, this).await?; + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } +} diff --git a/java_runtime/src/classes/java/util/tree_map_values.rs b/java_runtime/src/classes/java/util/tree_map_values.rs new file mode 100644 index 00000000..7a4f72bc --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_map_values.rs @@ -0,0 +1,73 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// class java.util.TreeMap$Values +pub struct TreeMapValues; + +impl TreeMapValues { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeMap$Values", + parent_class: Some("java/util/AbstractCollection"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/SortedMap;)V", Self::init, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("map", "Ljava/util/SortedMap;", FieldAccessFlags::PRIVATE)], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractCollection", "", "()V", ()).await?; + jvm.put_field(&mut this, "map", "Ljava/util/SortedMap;", map).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "size", "()I", ()).await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "containsValue", "(Ljava/lang/Object;)Z", (value,)).await + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&map, "valueIterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let current: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let equal = if current.is_null() { + value.is_null() + } else { + jvm.invoke_virtual(¤t, "equals", "(Ljava/lang/Object;)Z", (value.clone(),)).await? + }; + if equal { + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + return Ok(true); + } + } + Ok(false) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "clear", "()V", ()).await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "valueIterator", "()Ljava/util/Iterator;", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/tree_set.rs b/java_runtime/src/classes/java/util/tree_set.rs new file mode 100644 index 00000000..1c36b437 --- /dev/null +++ b/java_runtime/src/classes/java/util/tree_set.rs @@ -0,0 +1,219 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +// public class java.util.TreeSet +pub struct TreeSet; + +impl TreeSet { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/TreeSet", + parent_class: Some("java/util/AbstractSet"), + interfaces: vec!["java/util/SortedSet", "java/lang/Cloneable", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/Comparator;)V", Self::init_comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/Collection;)V", Self::init_collection, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/SortedSet;)V", Self::init_sorted_set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/util/SortedMap;)V", Self::init_sorted_map, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("comparator", "()Ljava/util/Comparator;", Self::comparator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("first", "()Ljava/lang/Object;", Self::first, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("last", "()Ljava/lang/Object;", Self::last, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + Self::sub_set, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "headSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + Self::head_set, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "tailSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + Self::tail_set, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![ + JavaFieldProto::new("m", "Ljava/util/SortedMap;", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + JavaFieldProto::new( + "PRESENT", + "Ljava/lang/Object;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + let present: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + jvm.put_static_field("java/util/TreeSet", "PRESENT", "Ljava/lang/Object;", present).await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let mut this = this; + jvm.put_field(&mut this, "m", "Ljava/util/SortedMap;", map).await + } + + async fn init_comparator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, comparator: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + let map: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (comparator,)) + .await? + .into(); + let mut this = this; + jvm.put_field(&mut this, "m", "Ljava/util/SortedMap;", map).await + } + + async fn init_collection(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result<()> { + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + let _: () = jvm.invoke_special(&this, "java/util/TreeSet", "", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&this, "addAll", "(Ljava/util/Collection;)Z", (collection,)).await?; + Ok(()) + } + + async fn init_sorted_set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, set: ClassInstanceRef) -> Result<()> { + if set.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "set").await); + } + let comparator: ClassInstanceRef = jvm.invoke_virtual(&set, "comparator", "()Ljava/util/Comparator;", ()).await?; + let _: () = jvm + .invoke_special(&this, "java/util/TreeSet", "", "(Ljava/util/Comparator;)V", (comparator,)) + .await?; + let _: bool = jvm.invoke_virtual(&this, "addAll", "(Ljava/util/Collection;)Z", (set,)).await?; + Ok(()) + } + + async fn init_sorted_map(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + jvm.put_field(&mut this, "m", "Ljava/util/SortedMap;", map).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "size", "()I", ()).await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "containsKey", "(Ljava/lang/Object;)Z", (element,)).await + } + + async fn add(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + let present: ClassInstanceRef = jvm.get_static_field("java/util/TreeSet", "PRESENT", "Ljava/lang/Object;").await?; + let old: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (element, present), + ) + .await?; + Ok(old.is_null()) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + if !jvm + .invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await? + { + return Ok(false); + } + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (element,)) + .await?; + Ok(true) + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "clear", "()V", ()).await + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await + } + + async fn comparator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "comparator", "()Ljava/util/Comparator;", ()).await + } + + async fn first(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "firstKey", "()Ljava/lang/Object;", ()).await + } + + async fn last(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + jvm.invoke_virtual(&map, "lastKey", "()Ljava/lang/Object;", ()).await + } + + async fn sub_set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from: ClassInstanceRef, + to: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&map, "subMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", (from, to)) + .await?; + Ok(jvm.new_class("java/util/TreeSet", "(Ljava/util/SortedMap;)V", (range,)).await?.into()) + } + + async fn head_set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + to: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&map, "headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (to,)) + .await?; + Ok(jvm.new_class("java/util/TreeSet", "(Ljava/util/SortedMap;)V", (range,)).await?.into()) + } + + async fn tail_set( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + from: ClassInstanceRef, + ) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "m", "Ljava/util/SortedMap;").await?; + let range: ClassInstanceRef = jvm + .invoke_virtual(&map, "tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (from,)) + .await?; + Ok(jvm.new_class("java/util/TreeSet", "(Ljava/util/SortedMap;)V", (range,)).await?.into()) + } +} diff --git a/java_runtime/src/classes/java/util/vector.rs b/java_runtime/src/classes/java/util/vector.rs index 8e555164..9cacb6ab 100644 --- a/java_runtime/src/classes/java/util/vector.rs +++ b/java_runtime/src/classes/java/util/vector.rs @@ -6,7 +6,7 @@ use alloc::{ }; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::FieldAccessFlags; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ @@ -27,20 +27,51 @@ impl Vector { JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), JavaMethodProto::new("", "(II)V", Self::init_with_capacity_increment, Default::default()), + JavaMethodProto::new( + "", + "(Ljava/util/Collection;)V", + Self::init_from_collection, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("capacity", "()I", Self::capacity, Default::default()), JavaMethodProto::new("copyInto", "([Ljava/lang/Object;)V", Self::copy_into, Default::default()), JavaMethodProto::new("elements", "()Ljava/util/Enumeration;", Self::elements, Default::default()), JavaMethodProto::new("ensureCapacity", "(I)V", Self::ensure_capacity_api, Default::default()), - JavaMethodProto::new("add", "(Ljava/lang/Object;)Z", Self::add, Default::default()), - JavaMethodProto::new("add", "(ILjava/lang/Object;)V", Self::add_at, Default::default()), + JavaMethodProto::new( + "add", + "(Ljava/lang/Object;)Z", + Self::add, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "add", + "(ILjava/lang/Object;)V", + Self::add_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new("addElement", "(Ljava/lang/Object;)V", Self::add_element, Default::default()), JavaMethodProto::new("insertElementAt", "(Ljava/lang/Object;I)V", Self::insert_element_at, Default::default()), JavaMethodProto::new("elementAt", "(I)Ljava/lang/Object;", Self::element_at, Default::default()), - JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, Default::default()), - JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, Default::default()), - JavaMethodProto::new("size", "()I", Self::size, Default::default()), + JavaMethodProto::new( + "get", + "(I)Ljava/lang/Object;", + Self::get, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "set", + "(ILjava/lang/Object;)Ljava/lang/Object;", + Self::set, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), - JavaMethodProto::new("remove", "(I)Ljava/lang/Object;", Self::remove, Default::default()), + JavaMethodProto::new( + "remove", + "(I)Ljava/lang/Object;", + Self::remove, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new("remove", "(Ljava/lang/Object;)Z", Self::remove_object, Default::default()), JavaMethodProto::new("removeAllElements", "()V", Self::remove_all_elements, Default::default()), JavaMethodProto::new("removeElementAt", "(I)V", Self::remove_element_at, Default::default()), @@ -56,7 +87,60 @@ impl Vector { JavaMethodProto::new("removeElement", "(Ljava/lang/Object;)Z", Self::remove_element, Default::default()), JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), JavaMethodProto::new("toArray", "()[Ljava/lang/Object;", Self::to_array, Default::default()), - JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, Default::default()), + JavaMethodProto::new( + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + Self::to_typed_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "addAll", + "(Ljava/util/Collection;)Z", + Self::add_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "addAll", + "(ILjava/util/Collection;)Z", + Self::add_all_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "containsAll", + "(Ljava/util/Collection;)Z", + Self::contains_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "removeAll", + "(Ljava/util/Collection;)Z", + Self::remove_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "retainAll", + "(Ljava/util/Collection;)Z", + Self::retain_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "iterator", + "()Ljava/util/Iterator;", + Self::iterator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new("trimToSize", "()V", Self::trim_to_size, Default::default()), JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), ], @@ -65,7 +149,7 @@ impl Vector { JavaFieldProto::new("elementCount", "I", FieldAccessFlags::PROTECTED), JavaFieldProto::new("capacityIncrement", "I", FieldAccessFlags::PROTECTED), ], - access_flags: Default::default(), + access_flags: ClassAccessFlags::PUBLIC, } } @@ -110,6 +194,24 @@ impl Vector { Ok(()) } + async fn init_from_collection( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + collection: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.Vector::({this:?}, {collection:?})"); + + if collection.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "collection").await); + } + let size: i32 = jvm.invoke_virtual(&collection, "size", "()I", ()).await?; + let _: () = jvm.invoke_special(&this, "java/util/Vector", "", "(I)V", (size,)).await?; + let _: bool = jvm.invoke_virtual(&this, "addAll", "(Ljava/util/Collection;)Z", (collection,)).await?; + + Ok(()) + } + async fn capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { let element_data = jvm.get_field(&this, "elementData", "[Ljava/lang/Object;").await?; Ok(jvm.array_length(&element_data).await? as i32) @@ -548,12 +650,110 @@ impl Vector { Self::copy_to_array(jvm, &element_data, element_count).await } + async fn to_typed_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + destination: ClassInstanceRef>, + ) -> Result>> { + tracing::debug!("java.util.Vector::toArray({this:?}, {destination:?})"); + + jvm.invoke_special( + &this, + "java/util/AbstractCollection", + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (destination,), + ) + .await + } + + async fn add_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Vector::addAll({this:?}, {collection:?})"); + + jvm.invoke_special( + &this, + "java/util/AbstractCollection", + "addAll", + "(Ljava/util/Collection;)Z", + (collection,), + ) + .await + } + + async fn add_all_at( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + collection: ClassInstanceRef, + ) -> Result { + tracing::debug!("java.util.Vector::addAll({this:?}, {index:?}, {collection:?})"); + + jvm.invoke_special( + &this, + "java/util/AbstractList", + "addAll", + "(ILjava/util/Collection;)Z", + (index, collection), + ) + .await + } + + async fn contains_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Vector::containsAll({this:?}, {collection:?})"); + + jvm.invoke_special( + &this, + "java/util/AbstractCollection", + "containsAll", + "(Ljava/util/Collection;)Z", + (collection,), + ) + .await + } + + async fn remove_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Vector::removeAll({this:?}, {collection:?})"); + + jvm.invoke_special( + &this, + "java/util/AbstractCollection", + "removeAll", + "(Ljava/util/Collection;)Z", + (collection,), + ) + .await + } + + async fn retain_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, collection: ClassInstanceRef) -> Result { + tracing::debug!("java.util.Vector::retainAll({this:?}, {collection:?})"); + + jvm.invoke_special( + &this, + "java/util/AbstractCollection", + "retainAll", + "(Ljava/util/Collection;)Z", + (collection,), + ) + .await + } + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.util.Vector::iterator({this:?})"); - let snapshot: ClassInstanceRef> = jvm.invoke_virtual(&this, "toArray", "()[Ljava/lang/Object;", ()).await?; - let iterator = jvm.new_class("java/util/Vector$Itr", "([Ljava/lang/Object;)V", (snapshot,)).await?; + let iterator = jvm.new_class("java/util/Vector$Itr", "(Ljava/util/Vector;I)V", (this, 0)).await?; + + Ok(iterator.into()) + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let iterator = jvm.new_class("java/util/Vector$ListItr", "(Ljava/util/Vector;I)V", (this, 0)).await?; + Ok(iterator.into()) + } + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let iterator = jvm.new_class("java/util/Vector$ListItr", "(Ljava/util/Vector;I)V", (this, index)).await?; Ok(iterator.into()) } diff --git a/java_runtime/src/classes/java/util/vector_itr.rs b/java_runtime/src/classes/java/util/vector_itr.rs index 3401399d..5970e2da 100644 --- a/java_runtime/src/classes/java/util/vector_itr.rs +++ b/java_runtime/src/classes/java/util/vector_itr.rs @@ -1,7 +1,8 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{Array, ClassInstanceRef, Jvm, Result}; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; @@ -15,63 +16,124 @@ impl VectorItr { parent_class: Some("java/lang/Object"), interfaces: vec!["java/util/Iterator"], methods: vec![ - JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, Default::default()), - JavaMethodProto::new("hasNext", "()Z", Self::has_next, Default::default()), - JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, Default::default()), - JavaMethodProto::new("remove", "()V", Self::remove, Default::default()), + JavaMethodProto::new("", "(Ljava/util/Vector;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), ], fields: vec![ - JavaFieldProto::new("elements", "[Ljava/lang/Object;", Default::default()), - JavaFieldProto::new("index", "I", Default::default()), + JavaFieldProto::new("list", "Ljava/util/Vector;", Default::default()), + JavaFieldProto::new("cursor", "I", Default::default()), + JavaFieldProto::new("lastReturned", "I", Default::default()), ], access_flags: Default::default(), } } - async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { - tracing::debug!("java.util.Vector$Itr::({this:?}, {elements:?})"); + pub fn list_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Vector$ListItr", + parent_class: Some("java/util/Vector$Itr"), + interfaces: vec!["java/util/ListIterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/Vector;I)V", Self::init, Default::default()), + JavaMethodProto::new("hasPrevious", "()Z", Self::has_previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previous", "()Ljava/lang/Object;", Self::previous, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextIndex", "()I", Self::next_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("previousIndex", "()I", Self::previous_index, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(Ljava/lang/Object;)V", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("add", "(Ljava/lang/Object;)V", Self::add, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, list: ClassInstanceRef, index: i32) -> Result<()> { + let size: i32 = jvm.invoke_virtual(&list, "size", "()I", ()).await?; + if index < 0 || index > size { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "list iterator index").await); + } let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; - - jvm.put_field(&mut this, "elements", "[Ljava/lang/Object;", elements).await?; - jvm.put_field(&mut this, "index", "I", 0).await?; - - Ok(()) + jvm.put_field(&mut this, "list", "Ljava/util/Vector;", list).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await } async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { - tracing::debug!("java.util.Vector$Itr::hasNext({this:?})"); + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/Vector;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + Ok(cursor < jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?) + } - let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; - let index: i32 = jvm.get_field(&this, "index", "I").await?; - if index < 0 { - return Ok(false); + async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/Vector;").await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor >= jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await? { + return Err(jvm.exception("java/util/NoSuchElementException", "Vector iterator exhausted").await); } - - Ok((index as usize) < jvm.array_length(&elements).await?) + let element = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (cursor,)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", cursor).await?; + Ok(element) } - async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { - tracing::debug!("java.util.Vector$Itr::next({this:?})"); + async fn has_previous(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? > 0) + } - let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; - let index: i32 = jvm.get_field(&this, "index", "I").await?; - if index < 0 || index as usize >= jvm.array_length(&elements).await? { + async fn previous(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if cursor <= 0 { return Err(jvm.exception("java/util/NoSuchElementException", "Vector iterator exhausted").await); } + let index = cursor - 1; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/Vector;").await?; + let element = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (index,)).await?; + jvm.put_field(&mut this, "cursor", "I", index).await?; + jvm.put_field(&mut this, "lastReturned", "I", index).await?; + Ok(element) + } - let mut values = jvm.load_array(&elements, index as usize, 1).await?; - let Some(element) = values.pop() else { - return Err(jvm.exception("java/util/NoSuchElementException", "Vector iterator exhausted").await); - }; - jvm.put_field(&mut this, "index", "I", index + 1).await?; + async fn next_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "cursor", "I").await + } - Ok(element) + async fn previous_index(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "cursor", "I").await? - 1) } - async fn remove(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { - tracing::debug!("java.util.Vector$Itr::remove({this:?})"); + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/Vector;").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&list, "remove", "(I)Ljava/lang/Object;", (last_returned,)).await?; + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + if last_returned < cursor { + jvm.put_field(&mut this, "cursor", "I", cursor - 1).await?; + } + jvm.put_field(&mut this, "lastReturned", "I", -1).await + } + + async fn set(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let last_returned: i32 = jvm.get_field(&this, "lastReturned", "I").await?; + if last_returned < 0 { + return Err(jvm.exception("java/lang/IllegalStateException", "iterator state").await); + } + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/Vector;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (last_returned, element)) + .await?; + Ok(()) + } - Err(jvm.exception("java/lang/UnsupportedOperationException", "Iterator.remove").await) + async fn add(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef) -> Result<()> { + let cursor: i32 = jvm.get_field(&this, "cursor", "I").await?; + let list: ClassInstanceRef = jvm.get_field(&this, "list", "Ljava/util/Vector;").await?; + let _: () = jvm.invoke_virtual(&list, "add", "(ILjava/lang/Object;)V", (cursor, element)).await?; + jvm.put_field(&mut this, "cursor", "I", cursor + 1).await?; + jvm.put_field(&mut this, "lastReturned", "I", -1).await } } diff --git a/java_runtime/src/lib.rs b/java_runtime/src/lib.rs index f8bcfe3a..57f6bd3b 100644 --- a/java_runtime/src/lib.rs +++ b/java_runtime/src/lib.rs @@ -7,7 +7,7 @@ mod runtime; pub use self::{ loader::{get_bootstrap_class_loader, get_runtime_class_proto}, - runtime::{File, FileDescriptorId, FileSize, FileStat, FileType, IOError, IOResult, Runtime, SpawnCallback}, + runtime::{File, FileDescriptorId, FileOpenOptions, FileSize, FileStat, FileType, IOError, IOResult, Runtime, SpawnCallback}, }; pub type RuntimeContext = dyn runtime::Runtime; diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index 3b440ca9..a0bd938c 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -6,9 +6,14 @@ use crate::{RT_RUSTJAR, Runtime, RuntimeClassProto}; pub fn get_runtime_class_proto(name: &str) -> Option { let protos = [ + crate::classes::java::io::BufferedInputStream::as_proto(), + crate::classes::java::io::BufferedOutputStream::as_proto(), crate::classes::java::io::BufferedReader::as_proto(), + crate::classes::java::io::BufferedWriter::as_proto(), crate::classes::java::io::ByteArrayInputStream::as_proto(), crate::classes::java::io::ByteArrayOutputStream::as_proto(), + crate::classes::java::io::CharArrayReader::as_proto(), + crate::classes::java::io::CharArrayWriter::as_proto(), crate::classes::java::io::DataInput::as_proto(), crate::classes::java::io::DataInputStream::as_proto(), crate::classes::java::io::DataOutput::as_proto(), @@ -19,6 +24,8 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::io::FileInputStream::as_proto(), crate::classes::java::io::FileNotFoundException::as_proto(), crate::classes::java::io::FileOutputStream::as_proto(), + crate::classes::java::io::FileReader::as_proto(), + crate::classes::java::io::FileWriter::as_proto(), crate::classes::java::io::FilterInputStream::as_proto(), crate::classes::java::io::FilterOutputStream::as_proto(), crate::classes::java::io::InputStream::as_proto(), @@ -32,6 +39,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::io::RandomAccessFile::as_proto(), crate::classes::java::io::Reader::as_proto(), crate::classes::java::io::Serializable::as_proto(), + crate::classes::java::io::StringReader::as_proto(), crate::classes::java::io::StringWriter::as_proto(), crate::classes::java::io::UnsupportedEncodingException::as_proto(), crate::classes::java::io::UTFDataFormatException::as_proto(), @@ -43,6 +51,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::Boolean::as_proto(), crate::classes::java::lang::Class::as_proto(), crate::classes::java::lang::ClassCastException::as_proto(), + crate::classes::java::lang::ClassCircularityError::as_proto(), crate::classes::java::lang::ClassFormatError::as_proto(), crate::classes::java::lang::ClassLoader::as_proto(), crate::classes::java::lang::ClassNotFoundException::as_proto(), @@ -56,12 +65,15 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::Exception::as_proto(), crate::classes::java::lang::ExceptionInInitializerError::as_proto(), crate::classes::java::lang::Float::as_proto(), + crate::classes::java::lang::IllegalAccessError::as_proto(), crate::classes::java::lang::IllegalAccessException::as_proto(), crate::classes::java::lang::IllegalArgumentException::as_proto(), crate::classes::java::lang::IllegalMonitorStateException::as_proto(), + crate::classes::java::lang::IllegalStateException::as_proto(), crate::classes::java::lang::IllegalThreadStateException::as_proto(), crate::classes::java::lang::InstantiationError::as_proto(), crate::classes::java::lang::InstantiationException::as_proto(), + crate::classes::java::lang::InternalError::as_proto(), crate::classes::java::lang::IncompatibleClassChangeError::as_proto(), crate::classes::java::lang::IndexOutOfBoundsException::as_proto(), crate::classes::java::lang::Integer::as_proto(), @@ -72,7 +84,9 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::NegativeArraySizeException::as_proto(), crate::classes::java::lang::NoClassDefFoundError::as_proto(), crate::classes::java::lang::NoSuchFieldError::as_proto(), + crate::classes::java::lang::NoSuchFieldException::as_proto(), crate::classes::java::lang::NoSuchMethodError::as_proto(), + crate::classes::java::lang::NoSuchMethodException::as_proto(), crate::classes::java::lang::NullPointerException::as_proto(), crate::classes::java::lang::NumberFormatException::as_proto(), crate::classes::java::lang::Number::as_proto(), @@ -83,12 +97,15 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::RuntimeException::as_proto(), crate::classes::java::lang::SecurityException::as_proto(), crate::classes::java::lang::Short::as_proto(), + crate::classes::java::lang::StackOverflowError::as_proto(), crate::classes::java::lang::String::as_proto(), crate::classes::java::lang::StringBuffer::as_proto(), crate::classes::java::lang::StringIndexOutOfBoundsException::as_proto(), crate::classes::java::lang::System::as_proto(), crate::classes::java::lang::Thread::as_proto(), + crate::classes::java::lang::ThreadDeath::as_proto(), crate::classes::java::lang::Throwable::as_proto(), + crate::classes::java::lang::UnknownError::as_proto(), crate::classes::java::lang::UnsupportedOperationException::as_proto(), crate::classes::java::lang::UnsupportedClassVersionError::as_proto(), crate::classes::java::lang::UnsatisfiedLinkError::as_proto(), @@ -111,12 +128,36 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::text::SimpleDateFormat::as_proto(), crate::classes::java::util::AbstractCollection::as_proto(), crate::classes::java::util::AbstractList::as_proto(), + crate::classes::java::util::AbstractListItr::as_proto(), + crate::classes::java::util::AbstractListItr::list_proto(), + crate::classes::java::util::AbstractListSubList::as_proto(), crate::classes::java::util::AbstractMap::as_proto(), crate::classes::java::util::AbstractSet::as_proto(), crate::classes::java::util::ArrayList::as_proto(), crate::classes::java::util::ArrayListItr::as_proto(), + crate::classes::java::util::Arrays::as_proto(), + crate::classes::java::util::ArraysArrayList::as_proto(), + crate::classes::java::util::ArrayListItr::list_proto(), crate::classes::java::util::Calendar::as_proto(), crate::classes::java::util::Collection::as_proto(), + crate::classes::java::util::Collections::as_proto(), + crate::classes::java::util::CollectionsCopiesList::as_proto(), + crate::classes::java::util::CollectionsEmptyList::as_proto(), + crate::classes::java::util::CollectionsEmptySet::as_proto(), + crate::classes::java::util::CollectionsSingletonSet::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableCollection::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableCollectionIterator::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableList::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableListIterator::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableMap::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableMapEntry::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableMapEntrySet::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableMapEntrySetIterator::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableSet::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableSortedMap::as_proto(), + crate::classes::java::util::CollectionsUnmodifiableSortedSet::as_proto(), + crate::classes::java::util::Comparator::as_proto(), + crate::classes::java::util::ConcurrentModificationException::as_proto(), crate::classes::java::util::Date::as_proto(), crate::classes::java::util::Dictionary::as_proto(), crate::classes::java::util::EmptyStackException::as_proto(), @@ -140,6 +181,10 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::HashtableValues::as_proto(), crate::classes::java::util::Iterator::as_proto(), crate::classes::java::util::List::as_proto(), + crate::classes::java::util::ListIterator::as_proto(), + crate::classes::java::util::LinkedList::as_proto(), + crate::classes::java::util::LinkedListEntry::as_proto(), + crate::classes::java::util::LinkedListItr::as_proto(), crate::classes::java::util::Locale::as_proto(), crate::classes::java::util::Map::as_proto(), crate::classes::java::util::MapEntry::as_proto(), @@ -148,13 +193,29 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::Random::as_proto(), crate::classes::java::util::Set::as_proto(), crate::classes::java::util::SimpleTimeZone::as_proto(), + crate::classes::java::util::SortedMap::as_proto(), + crate::classes::java::util::SortedSet::as_proto(), crate::classes::java::util::Stack::as_proto(), + crate::classes::java::util::StringTokenizer::as_proto(), crate::classes::java::util::Timer::as_proto(), crate::classes::java::util::TimerTask::as_proto(), + crate::classes::java::util::TimerTaskQueue::as_proto(), crate::classes::java::util::TimerThread::as_proto(), crate::classes::java::util::TimeZone::as_proto(), + crate::classes::java::util::TreeMap::as_proto(), + crate::classes::java::util::TreeMapEntry::as_proto(), + crate::classes::java::util::TreeMapEntryIterator::as_proto(), + crate::classes::java::util::TreeMapEntrySet::as_proto(), + crate::classes::java::util::TreeMapKeyIterator::as_proto(), + crate::classes::java::util::TreeMapKeySet::as_proto(), + crate::classes::java::util::TreeMapPrivateEntryIterator::as_proto(), + crate::classes::java::util::TreeMapSubMap::as_proto(), + crate::classes::java::util::TreeMapValueIterator::as_proto(), + crate::classes::java::util::TreeMapValues::as_proto(), + crate::classes::java::util::TreeSet::as_proto(), crate::classes::java::util::Vector::as_proto(), 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::JarEntry::as_proto(), crate::classes::java::util::jar::JarFile::as_proto(), diff --git a/java_runtime/src/runtime.rs b/java_runtime/src/runtime.rs index d653d008..a6151039 100644 --- a/java_runtime/src/runtime.rs +++ b/java_runtime/src/runtime.rs @@ -7,7 +7,7 @@ use dyn_clone::{DynClone, clone_trait_object}; use jvm::{ClassDefinition, Jvm, Result as JvmResult}; -pub use io::{File, FileDescriptorId, FileSize, FileStat, FileType, IOError, IOResult}; +pub use io::{File, FileDescriptorId, FileOpenOptions, FileSize, FileStat, FileType, IOError, IOResult}; #[async_trait::async_trait] pub trait SpawnCallback: Sync + Send { @@ -28,7 +28,7 @@ pub trait Runtime: Sync + Send + DynClone { fn stdout(&self) -> IOResult; fn stderr(&self) -> IOResult; - async fn open(&self, path: &str, write: bool) -> IOResult; + async fn open(&self, path: &str, options: FileOpenOptions) -> IOResult; fn get_file(&self, fd: FileDescriptorId) -> IOResult>; fn close_file(&self, fd: FileDescriptorId); async fn unlink(&self, path: &str) -> IOResult<()>; diff --git a/java_runtime/src/runtime/io.rs b/java_runtime/src/runtime/io.rs index 9b460876..fde6fece 100644 --- a/java_runtime/src/runtime/io.rs +++ b/java_runtime/src/runtime/io.rs @@ -25,6 +25,15 @@ pub enum IOError { pub type IOResult = Result; pub type FileSize = u64; +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct FileOpenOptions { + pub read: bool, + pub write: bool, + pub append: bool, + pub truncate: bool, + pub create: bool, +} + #[derive(Eq, PartialEq)] pub enum FileType { File, diff --git a/java_runtime/tests/classes/java/io/mod.rs b/java_runtime/tests/classes/java/io/mod.rs index 6f45bf02..39416081 100644 --- a/java_runtime/tests/classes/java/io/mod.rs +++ b/java_runtime/tests/classes/java/io/mod.rs @@ -1,13 +1,18 @@ mod test_buffered_reader; +mod test_buffered_streams; mod test_byte_array_input_stream; mod test_byte_array_output_stream; +mod test_char_streams; mod test_data_input_stream; mod test_data_output_stream; mod test_file; mod test_file_input_stream; +mod test_file_reader_writer; mod test_input_stream_reader; +mod test_output_stream_close; mod test_output_stream_writer; mod test_print_stream; mod test_print_writer; mod test_random_access_file; +mod test_stream_locking; mod test_string_writer; diff --git a/java_runtime/tests/classes/java/io/test_buffered_reader.rs b/java_runtime/tests/classes/java/io/test_buffered_reader.rs index 57de0641..44705650 100644 --- a/java_runtime/tests/classes/java/io/test_buffered_reader.rs +++ b/java_runtime/tests/classes/java/io/test_buffered_reader.rs @@ -1,29 +1,471 @@ -use java_runtime::classes::java::lang::String; -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; +use core::time::Duration; -use test_utils::test_jvm; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{BufferedReader, Reader}, + lang::String, + }, +}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm}; + +struct ChunkedReader; + +impl ChunkedReader { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ChunkedReader", + parent_class: Some("java/io/Reader"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "([CI)V", Self::init, Default::default()), + JavaMethodProto::new("read", "([CII)I", Self::read, Default::default()), + JavaMethodProto::new("ready", "()Z", Self::ready, Default::default()), + JavaMethodProto::new("close", "()V", Self::close, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("data", "[C", Default::default()), + JavaFieldProto::new("position", "I", Default::default()), + JavaFieldProto::new("chunkSize", "I", Default::default()), + JavaFieldProto::new("visibleLength", "I", Default::default()), + JavaFieldProto::new("zeroReads", "I", Default::default()), + JavaFieldProto::new("closed", "Z", Default::default()), + JavaFieldProto::new("closeCount", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + data: ClassInstanceRef>, + chunk_size: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/io/Reader", "", "()V", ()).await?; + jvm.put_field(&mut this, "data", "[C", data).await?; + jvm.put_field(&mut this, "position", "I", 0).await?; + jvm.put_field(&mut this, "chunkSize", "I", chunk_size).await?; + let data: ClassInstanceRef> = jvm.get_field(&this, "data", "[C").await?; + let data_length = jvm.array_length(&data).await? as i32; + jvm.put_field(&mut this, "visibleLength", "I", data_length).await?; + jvm.put_field(&mut this, "zeroReads", "I", 0).await?; + jvm.put_field(&mut this, "closed", "Z", false).await?; + jvm.put_field(&mut this, "closeCount", "I", 0).await?; + Ok(()) + } + + async fn read( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + mut target: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result { + if jvm.get_field::(&this, "closed", "Z").await? { + return Err(jvm.exception("java/io/IOException", "reader is closed").await); + } + + let target_length = jvm.array_length(&target).await? as i32; + if offset < 0 || length < 0 || offset > target_length || length > target_length - offset { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid offset or length").await); + } + if length == 0 { + return Ok(0); + } + + let zero_reads: i32 = jvm.get_field(&this, "zeroReads", "I").await?; + if zero_reads > 0 { + jvm.put_field(&mut this, "zeroReads", "I", zero_reads - 1).await?; + return Ok(0); + } + + let data: ClassInstanceRef> = jvm.get_field(&this, "data", "[C").await?; + let position: i32 = jvm.get_field(&this, "position", "I").await?; + let visible_length: i32 = jvm.get_field(&this, "visibleLength", "I").await?; + let available = visible_length.min(jvm.array_length(&data).await? as i32) - position; + if available == 0 { + return Ok(-1); + } + + let chunk_size: i32 = jvm.get_field(&this, "chunkSize", "I").await?; + let count = length.min(available).min(chunk_size); + let values: Vec = jvm.load_array(&data, position as usize, count as usize).await?; + jvm.store_array(&mut target, offset as usize, values).await?; + jvm.put_field(&mut this, "position", "I", position + count).await?; + Ok(count) + } + + async fn ready(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + if jvm.get_field::(&this, "closed", "Z").await? { + return Err(jvm.exception("java/io/IOException", "reader is closed").await); + } + + let data: ClassInstanceRef> = jvm.get_field(&this, "data", "[C").await?; + let position: i32 = jvm.get_field(&this, "position", "I").await?; + let visible_length: i32 = jvm.get_field(&this, "visibleLength", "I").await?; + Ok(position < visible_length.min(jvm.array_length(&data).await? as i32)) + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let close_count: i32 = jvm.get_field(&this, "closeCount", "I").await?; + jvm.put_field(&mut this, "closeCount", "I", close_count + 1).await?; + jvm.put_field(&mut this, "closed", "Z", true).await?; + Ok(()) + } +} + +struct ReadRunner; + +impl ReadRunner { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "BufferedReaderReadRunner", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Runnable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/BufferedReader;)V", Self::init, Default::default()), + JavaMethodProto::new("run", "()V", Self::run, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("reader", "Ljava/io/BufferedReader;", Default::default()), + JavaFieldProto::new("started", "Z", Default::default()), + JavaFieldProto::new("done", "Z", Default::default()), + JavaFieldProto::new("value", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, reader: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "reader", "Ljava/io/BufferedReader;", reader).await?; + jvm.put_field(&mut this, "started", "Z", false).await?; + jvm.put_field(&mut this, "done", "Z", false).await?; + jvm.put_field(&mut this, "value", "I", -1).await?; + Ok(()) + } + + async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "started", "Z", true).await?; + let reader: ClassInstanceRef = jvm.get_field(&this, "reader", "Ljava/io/BufferedReader;").await?; + let value: i32 = jvm.invoke_virtual(&reader, "read", "()I", ()).await?; + jvm.put_field(&mut this, "value", "I", value).await?; + jvm.put_field(&mut this, "done", "Z", true).await?; + Ok(()) + } +} + +async fn buffered_reader( + value: &str, + chunk_size: i32, + buffer_size: i32, +) -> Result<(Jvm, ClassInstanceRef, ClassInstanceRef)> { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + ChunkedReader::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + ReadRunner::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + + let utf16: Vec = value.encode_utf16().collect(); + let mut data = jvm.instantiate_array("C", utf16.len()).await?; + jvm.store_array(&mut data, 0, utf16).await?; + let source = jvm.new_class("ChunkedReader", "([CI)V", (data, chunk_size)).await?; + let reader = jvm + .new_class("java/io/BufferedReader", "(Ljava/io/Reader;I)V", (source.clone(), buffer_size)) + .await?; + + Ok((jvm, source.into(), reader.into())) +} #[tokio::test] -async fn test_buffered_reader() -> Result<()> { - let jvm = test_jvm().await?; +async fn test_buffered_reader_constructors_and_read_contract() -> Result<()> { + let (jvm, _, reader) = buffered_reader("abc", 1, 2).await?; + + let mut chars = jvm.instantiate_array("C", 4).await?; + jvm.store_array(&mut chars, 0, ['?' as JavaChar; 4]).await?; - let mut buffer = jvm.instantiate_array("B", 11).await?; - jvm.array_raw_buffer_mut(&mut buffer).await?.write(0, b"Hello\nWorld")?; + let invalid_range: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (chars.clone(), -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid_range else { + panic!("negative offset must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); - let is = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (buffer,)).await?; - let isr = jvm.new_class("java/io/InputStreamReader", "(Ljava/io/InputStream;)V", (is,)).await?; - let reader = jvm.new_class("java/io/BufferedReader", "(Ljava/io/Reader;)V", (isr,)).await?; + let overflowing_range: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (chars.clone(), 3, 2)).await; + let Err(JavaError::JavaException(exception)) = overflowing_range else { + panic!("overflowing range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); - let line = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; - let line = JavaLangString::to_rust_string(&jvm, &line).await?; - assert_eq!(line, "Hello"); + let null_chars: ClassInstanceRef> = None.into(); + let null_result: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (null_chars, 0, 0)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("null destination must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); - let line = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; - let line = JavaLangString::to_rust_string(&jvm, &line).await?; - assert_eq!(line, "World"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'a' as i32); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 1, 2)).await?, 2); + assert_eq!( + jvm.load_array::(&chars, 0, 4).await?, + ['?' as JavaChar, 'b' as JavaChar, 'c' as JavaChar, '?' as JavaChar] + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, -1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 0, 0)).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 0, 1)).await?, -1); + + let empty = jvm.instantiate_array("C", 0).await?; + let input = jvm.new_class("ChunkedReader", "([CI)V", (empty, 1)).await?; + let default_reader = jvm.new_class("java/io/BufferedReader", "(Ljava/io/Reader;)V", (input.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&default_reader, "read", "()I", ()).await?, -1); + + for size in [0, -1] { + let invalid_size = jvm + .new_class("java/io/BufferedReader", "(Ljava/io/Reader;I)V", (input.clone(), size)) + .await; + let Err(JavaError::JavaException(exception)) = invalid_size else { + panic!("invalid buffer size must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + } + + let null_reader: ClassInstanceRef = None.into(); + let null_constructor = jvm + .new_class("java/io/BufferedReader", "(Ljava/io/Reader;)V", (null_reader.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = null_constructor else { + panic!("null reader must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_sized_constructor = jvm.new_class("java/io/BufferedReader", "(Ljava/io/Reader;I)V", (null_reader, 2)).await; + let Err(JavaError::JavaException(exception)) = null_sized_constructor else { + panic!("null reader with explicit size must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_read_line_endings_and_long_lines() -> Result<()> { + let long_line = "0123456789abcdef"; + let input = alloc::format!("first\nsecond\rthird\r\n\n{long_line}"); + let (jvm, _, reader) = buffered_reader(&input, 1, 3).await?; + + for expected in ["first", "second", "third", "", long_line] { + let line: ClassInstanceRef = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &line).await?, expected); + } let line: ClassInstanceRef = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; assert!(line.is_null()); Ok(()) } + +#[tokio::test] +async fn test_buffered_reader_mixed_reads_skip_and_ready_share_cursor() -> Result<()> { + let (jvm, source, reader) = buffered_reader("abc\ndef", 16, 8).await?; + + let negative_skip: Result = jvm.invoke_virtual(&reader, "skip", "(J)J", (-1i64,)).await; + let Err(JavaError::JavaException(exception)) = negative_skip else { + panic!("negative skip must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + assert!(jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'a' as i32); + assert!(!jvm.invoke_virtual::<_, bool>(&source, "ready", "()Z", ()).await?); + assert!(jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + + let chars = jvm.instantiate_array("C", 2).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 0, 2)).await?, 2); + assert_eq!(jvm.load_array::(&chars, 0, 2).await?, ['b' as JavaChar, 'c' as JavaChar]); + + let line: ClassInstanceRef = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &line).await?, ""); + assert_eq!(jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (2i64,)).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'f' as i32); + assert!(!jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (2i64,)).await?, 0); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_mark_reset_preservation_and_invalidation() -> Result<()> { + let (jvm, _, reader) = buffered_reader("abcdef", 2, 3).await?; + + assert!(jvm.invoke_virtual::<_, bool>(&reader, "markSupported", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'a' as i32); + let _: () = jvm.invoke_virtual(&reader, "mark", "(I)V", (4,)).await?; + + let negative_mark: Result<()> = jvm.invoke_virtual(&reader, "mark", "(I)V", (-1,)).await; + let Err(JavaError::JavaException(exception)) = negative_mark else { + panic!("negative read-ahead limit must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let chars = jvm.instantiate_array("C", 4).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 0, 4)).await?, 4); + assert_eq!( + jvm.load_array::(&chars, 0, 4).await?, + ['b' as JavaChar, 'c' as JavaChar, 'd' as JavaChar, 'e' as JavaChar] + ); + let _: () = jvm.invoke_virtual(&reader, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 0, 4)).await?, 4); + assert_eq!( + jvm.load_array::(&chars, 0, 4).await?, + ['b' as JavaChar, 'c' as JavaChar, 'd' as JavaChar, 'e' as JavaChar] + ); + + let (jvm, _, reader) = buffered_reader("abcd", 2, 2).await?; + let unset_reset: Result<()> = jvm.invoke_virtual(&reader, "reset", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = unset_reset else { + panic!("reset without mark must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + let _: () = jvm.invoke_virtual(&reader, "mark", "(I)V", (2,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'a' as i32); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'b' as i32); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'c' as i32); + let invalidated_reset: Result<()> = jvm.invoke_virtual(&reader, "reset", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = invalidated_reset else { + panic!("reset beyond read-ahead limit must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_mark_restores_pending_crlf_state() -> Result<()> { + let (jvm, _, reader) = buffered_reader("a\r\nb", 1, 2).await?; + + let line: ClassInstanceRef = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &line).await?, "a"); + let _: () = jvm.invoke_virtual(&reader, "mark", "(I)V", (2,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'b' as i32); + let _: () = jvm.invoke_virtual(&reader, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'b' as i32); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_ready_preserves_pending_lf_until_input_is_available() -> Result<()> { + let (jvm, mut source, reader) = buffered_reader("\r\nX", 3, 2).await?; + jvm.put_field(&mut source, "visibleLength", "I", 1).await?; + + let line: ClassInstanceRef = jvm.invoke_virtual(&reader, "readLine", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &line).await?, ""); + assert!(!jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + + jvm.put_field(&mut source, "visibleLength", "I", 3).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'X' as i32); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_fill_retries_temporary_zero_read() -> Result<()> { + let (jvm, mut source, reader) = buffered_reader("a", 1, 1).await?; + jvm.put_field(&mut source, "zeroReads", "I", 1).await?; + + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'a' as i32); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, -1); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_serializes_on_inherited_reader_lock() -> Result<()> { + let (jvm, source, reader) = buffered_reader("a", 1, 1).await?; + let runner = jvm + .new_class("BufferedReaderReadRunner", "(Ljava/io/BufferedReader;)V", (reader,)) + .await?; + let thread = jvm.new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (runner.clone(),)).await?; + + jvm.monitor_enter(&source).await?; + let _: () = jvm.invoke_virtual(&thread, "start", "()V", ()).await?; + + let mut started = false; + for _ in 0..100 { + started = jvm.get_field::(&runner, "started", "Z").await?; + if started { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + tokio::time::sleep(Duration::from_millis(10)).await; + let completed_while_lock_was_held = jvm.get_field::(&runner, "done", "Z").await?; + + jvm.monitor_exit(&source).await?; + let _: () = jvm.invoke_virtual(&thread, "join", "()V", ()).await?; + + assert!(started, "worker thread did not start"); + assert!(!completed_while_lock_was_held, "read did not synchronize on Reader.lock"); + assert_eq!(jvm.get_field::(&runner, "value", "I").await?, 'a' as i32); + + Ok(()) +} + +#[tokio::test] +async fn test_buffered_reader_close_is_idempotent_and_closes_all_operations() -> Result<()> { + let (jvm, source, reader) = buffered_reader("abc", 2, 2).await?; + + let _: () = jvm.invoke_virtual(&reader, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&reader, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&source, "closeCount", "I").await?, 1); + assert!(jvm.invoke_virtual::<_, bool>(&reader, "markSupported", "()Z", ()).await?); + + let chars = jvm.instantiate_array("C", 1).await?; + let operations: Vec<(&str, Result<()>)> = vec![ + ("read()", jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await.map(|_| ())), + ( + "read(char[],off,len)", + jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars, 0, 1)).await.map(|_| ()), + ), + ( + "readLine", + jvm.invoke_virtual::<_, ClassInstanceRef>(&reader, "readLine", "()Ljava/lang/String;", ()) + .await + .map(|_| ()), + ), + ("skip", jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (1i64,)).await.map(|_| ())), + ("ready", jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await.map(|_| ())), + ("mark", jvm.invoke_virtual(&reader, "mark", "(I)V", (1,)).await), + ("reset", jvm.invoke_virtual(&reader, "reset", "()V", ()).await), + ]; + + for (name, result) in operations { + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} must throw IOException after close"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException"), "{name} threw the wrong exception"); + } + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/io/test_buffered_streams.rs b/java_runtime/tests/classes/java/io/test_buffered_streams.rs new file mode 100644 index 00000000..3beff407 --- /dev/null +++ b/java_runtime/tests/classes/java/io/test_buffered_streams.rs @@ -0,0 +1,571 @@ +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::get_runtime_class_proto; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; + +use java_runtime::classes::java::io::{InputStream, OutputStream, Writer}; +use java_runtime::classes::java::lang::Object; +use test_utils::test_jvm; + +#[tokio::test] +async fn bio_01_bio_02_bio_03_buffered_input_stream_contract() -> Result<()> { + let jvm = test_jvm().await?; + + let null_input: ClassInstanceRef = None.into(); + let result = jvm + .new_class("java/io/BufferedInputStream", "(Ljava/io/InputStream;)V", (null_input,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null input must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let data = jvm.instantiate_array("B", 0).await?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (data,)).await?; + let result = jvm + .new_class("java/io/BufferedInputStream", "(Ljava/io/InputStream;I)V", (input, 0)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("zero buffer size must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let mut data = jvm.instantiate_array("B", 1).await?; + jvm.store_array(&mut data, 0, [7i8]).await?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (data,)).await?; + let default_stream = jvm.new_class("java/io/BufferedInputStream", "(Ljava/io/InputStream;)V", (input,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&default_stream, "read", "()I", ()).await?, 7); + + let mut data = jvm.instantiate_array("B", 6).await?; + jvm.store_array(&mut data, 0, [10i8, 20, 30, 40, 50, 60]).await?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (data,)).await?; + let stream = jvm + .new_class("java/io/BufferedInputStream", "(Ljava/io/InputStream;I)V", (input, 2)) + .await?; + + assert!(jvm.invoke_virtual::<_, bool>(&stream, "markSupported", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "available", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "()I", ()).await?, 10); + + let _: () = jvm.invoke_virtual(&stream, "mark", "(I)V", (4,)).await?; + let target = jvm.instantiate_array("B", 6).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "([BII)I", (target.clone(), 1, 3)).await?, 3); + assert_eq!(jvm.load_array::(&target, 0, 6).await?, [0, 20, 30, 40, 0, 0]); + let _: () = jvm.invoke_virtual(&stream, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "()I", ()).await?, 20); + assert_eq!(jvm.invoke_virtual::<_, i64>(&stream, "skip", "(J)J", (2i64,)).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "()I", ()).await?, 50); + assert_eq!(jvm.invoke_virtual::<_, i64>(&stream, "skip", "(J)J", (-1i64,)).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "()I", ()).await?, 60); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "()I", ()).await?, -1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "([BII)I", (target.clone(), 0, 0)).await?, 0); + + let invalid: Result = jvm.invoke_virtual(&stream, "read", "([BII)I", (target, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + + let mut data = jvm.instantiate_array("B", 5).await?; + jvm.store_array(&mut data, 0, [1i8, 2, 3, 4, 5]).await?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (data,)).await?; + let stream = jvm + .new_class("java/io/BufferedInputStream", "(Ljava/io/InputStream;I)V", (input, 2)) + .await?; + let null_bytes: ClassInstanceRef> = None.into(); + let invalid: Result = jvm.invoke_virtual(&stream, "read", "([BII)I", (null_bytes, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("open stream must reject a null array with NullPointerException before validating the range"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let _: () = jvm.invoke_virtual(&stream, "mark", "(I)V", (2,)).await?; + let bytes = jvm.instantiate_array("B", 3).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&stream, "read", "([BII)I", (bytes, 0, 3)).await?, 3); + let reset: Result<()> = jvm.invoke_virtual(&stream, "reset", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = reset else { + panic!("reading past mark limit must invalidate the mark"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + let closed: Result = jvm.invoke_virtual(&stream, "read", "()I", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("read after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let closed: Result = jvm.invoke_virtual(&stream, "available", "()I", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("available after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let empty = jvm.instantiate_array("B", 0).await?; + let closed: Result = jvm.invoke_virtual(&stream, "read", "([BII)I", (empty, 0, 0)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("zero-length read after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let null_bytes: ClassInstanceRef> = None.into(); + let closed: Result = jvm.invoke_virtual(&stream, "read", "([BII)I", (null_bytes, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed stream must reject a null array with IOException before validating arguments"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let bytes = jvm.instantiate_array("B", 1).await?; + let closed: Result = jvm.invoke_virtual(&stream, "read", "([BII)I", (bytes, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed stream must reject an invalid range with IOException before validating arguments"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let closed: Result = jvm.invoke_virtual(&stream, "skip", "(J)J", (0i64,)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("zero skip after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let position: i32 = jvm.get_field(&stream, "pos", "I").await?; + let _: () = jvm.invoke_virtual(&stream, "mark", "(I)V", (17,)).await?; + assert_eq!(jvm.get_field::(&stream, "marklimit", "I").await?, 17); + assert_eq!(jvm.get_field::(&stream, "markpos", "I").await?, position); + let closed: Result<()> = jvm.invoke_virtual(&stream, "reset", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("reset after a post-close mark must still throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + Ok(()) +} + +#[tokio::test] +async fn bio_04_bio_05_buffered_output_stream_contract() -> Result<()> { + let jvm = test_jvm().await?; + + let null_output: ClassInstanceRef = None.into(); + let result = jvm + .new_class("java/io/BufferedOutputStream", "(Ljava/io/OutputStream;)V", (null_output,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null output must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let result = jvm + .new_class("java/io/BufferedOutputStream", "(Ljava/io/OutputStream;I)V", (output, 0)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("zero buffer size must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let default_stream = jvm + .new_class("java/io/BufferedOutputStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&default_stream, "write", "(I)V", (8,)).await?; + let _: () = jvm.invoke_virtual(&default_stream, "flush", "()V", ()).await?; + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.load_array::(&bytes, 0, 1).await?, [8]); + + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let stream = jvm + .new_class("java/io/BufferedOutputStream", "(Ljava/io/OutputStream;I)V", (output.clone(), 3)) + .await?; + + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (1,)).await?; + let mut bytes = jvm.instantiate_array("B", 5).await?; + jvm.store_array(&mut bytes, 0, [10i8, 20, 30, 40, 50]).await?; + let _: () = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes.clone(), 1, 2)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&output, "size", "()I", ()).await?, 0); + + let _: () = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes.clone(), 2, 3)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&output, "size", "()I", ()).await?, + 6, + "a large write must flush buffered bytes before writing through" + ); + + let invalid: Result<()> = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes.clone(), -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + let _: () = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes, 5, 0)).await?; + + let _: () = jvm.invoke_virtual(&stream, "flush", "()V", ()).await?; + let actual: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.load_array::(&actual, 0, 6).await?, [1, 20, 30, 30, 40, 50]); + + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (99,)).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + let actual: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.load_array::(&actual, 0, 7).await?, [1, 20, 30, 30, 40, 50, 99]); + + let closed: Result<()> = jvm.invoke_virtual(&stream, "write", "(I)V", (100,)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("write after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let closed: Result<()> = jvm.invoke_virtual(&stream, "flush", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("flush after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + Ok(()) +} + +#[tokio::test] +async fn bio_06_bio_07_buffered_writer_contract() -> Result<()> { + let jvm = test_jvm().await?; + + let null_writer: ClassInstanceRef = None.into(); + let result = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (null_writer,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null writer must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let output = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let result = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;I)V", (output, 0)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("zero buffer size must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let key = JavaLangString::from_rust_string(&jvm, "line.separator").await?; + let separator = JavaLangString::from_rust_string(&jvm, "|").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key, separator), + ) + .await?; + + let default_output = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let default_writer = jvm + .new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (default_output.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&default_writer, "write", "(I)V", ('V' as i32,)).await?; + let _: () = jvm.invoke_virtual(&default_writer, "flush", "()V", ()).await?; + let actual: ClassInstanceRef = + jvm.invoke_virtual(&default_output, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "V"); + + let output = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let writer = jvm + .new_class("java/io/BufferedWriter", "(Ljava/io/Writer;I)V", (output.clone(), 4)) + .await?; + let lock: ClassInstanceRef = jvm.get_field(&writer, "lock", "Ljava/lang/Object;").await?; + assert_eq!( + lock.identity(), + output.identity(), + "BufferedWriter must use its backing writer as Writer.lock" + ); + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('A' as i32,)).await?; + let mut chars = jvm.instantiate_array("C", 5).await?; + jvm.store_array( + &mut chars, + 0, + ['0' as JavaChar, 'B' as JavaChar, 'C' as JavaChar, 'D' as JavaChar, '4' as JavaChar], + ) + .await?; + let _: () = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars.clone(), 1, 2)).await?; + let value: ClassInstanceRef = + jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, ""); + + let value = JavaLangString::from_rust_string(&jvm, "xyZ!").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;II)V", (value, 1, 3)).await?; + let _: () = jvm.invoke_virtual(&writer, "newLine", "()V", ()).await?; + + let invalid: Result<()> = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + + let _: () = jvm.invoke_virtual(&writer, "flush", "()V", ()).await?; + let actual: ClassInstanceRef = + jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "ABCyZ!|"); + + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('Q' as i32,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + let actual: ClassInstanceRef = + jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "ABCyZ!|Q"); + + let closed: Result<()> = jvm.invoke_virtual(&writer, "write", "(I)V", ('R' as i32,)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("write after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let closed: Result<()> = jvm.invoke_virtual(&writer, "flush", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("flush after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let null_chars: ClassInstanceRef> = None.into(); + let closed: Result<()> = jvm.invoke_virtual(&writer, "write", "([CII)V", (null_chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed writer must reject a null array with IOException before validating arguments"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let null_string: ClassInstanceRef = None.into(); + let closed: Result<()> = jvm + .invoke_virtual(&writer, "write", "(Ljava/lang/String;II)V", (null_string, -1, 1)) + .await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed writer must reject a null string with IOException before validating arguments"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + let output = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let writer = jvm + .new_class("java/io/BufferedWriter", "(Ljava/io/Writer;I)V", (output.clone(), 2)) + .await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('P' as i32,)).await?; + let mut chars = jvm.instantiate_array("C", 3).await?; + jvm.store_array(&mut chars, 0, ['A' as JavaChar, 'B' as JavaChar, 'C' as JavaChar]) + .await?; + let _: () = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars, 0, 3)).await?; + let actual: ClassInstanceRef = + jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_rust_string(&jvm, &actual).await?, + "PABC", + "a large write must flush pending chars before writing through" + ); + + Ok(()) +} + +#[test] +fn buffered_stream_descriptors_access_and_jdk_state_fields() { + for (class_name, parent, methods, synchronized_methods) in [ + ( + "java/io/BufferedInputStream", + "java/io/FilterInputStream", + &[ + ("", "(Ljava/io/InputStream;)V"), + ("", "(Ljava/io/InputStream;I)V"), + ("read", "()I"), + ("read", "([BII)I"), + ("skip", "(J)J"), + ("available", "()I"), + ("mark", "(I)V"), + ("reset", "()V"), + ("markSupported", "()Z"), + ("close", "()V"), + ][..], + &[ + ("read", "()I"), + ("read", "([BII)I"), + ("skip", "(J)J"), + ("available", "()I"), + ("mark", "(I)V"), + ("reset", "()V"), + ][..], + ), + ( + "java/io/BufferedOutputStream", + "java/io/FilterOutputStream", + &[ + ("", "(Ljava/io/OutputStream;)V"), + ("", "(Ljava/io/OutputStream;I)V"), + ("write", "(I)V"), + ("write", "([BII)V"), + ("flush", "()V"), + ][..], + &[("write", "(I)V"), ("write", "([BII)V"), ("flush", "()V")][..], + ), + ( + "java/io/BufferedWriter", + "java/io/Writer", + &[ + ("", "(Ljava/io/Writer;)V"), + ("", "(Ljava/io/Writer;I)V"), + ("write", "(I)V"), + ("write", "([CII)V"), + ("write", "(Ljava/lang/String;II)V"), + ("newLine", "()V"), + ("flush", "()V"), + ("close", "()V"), + ][..], + &[][..], + ), + ( + "java/io/StringReader", + "java/io/Reader", + &[ + ("", "(Ljava/lang/String;)V"), + ("read", "()I"), + ("read", "([CII)I"), + ("skip", "(J)J"), + ("ready", "()Z"), + ("markSupported", "()Z"), + ("mark", "(I)V"), + ("reset", "()V"), + ("close", "()V"), + ][..], + &[][..], + ), + ( + "java/io/CharArrayReader", + "java/io/Reader", + &[ + ("", "([C)V"), + ("", "([CII)V"), + ("read", "()I"), + ("read", "([CII)I"), + ("skip", "(J)J"), + ("ready", "()Z"), + ("markSupported", "()Z"), + ("mark", "(I)V"), + ("reset", "()V"), + ("close", "()V"), + ][..], + &[][..], + ), + ( + "java/io/CharArrayWriter", + "java/io/Writer", + &[ + ("", "()V"), + ("", "(I)V"), + ("write", "(I)V"), + ("write", "([CII)V"), + ("write", "(Ljava/lang/String;II)V"), + ("writeTo", "(Ljava/io/Writer;)V"), + ("reset", "()V"), + ("toCharArray", "()[C"), + ("size", "()I"), + ("toString", "()Ljava/lang/String;"), + ("flush", "()V"), + ("close", "()V"), + ][..], + &[][..], + ), + ( + "java/io/FileReader", + "java/io/InputStreamReader", + &[ + ("", "(Ljava/lang/String;)V"), + ("", "(Ljava/io/File;)V"), + ("", "(Ljava/io/FileDescriptor;)V"), + ][..], + &[][..], + ), + ( + "java/io/FileWriter", + "java/io/OutputStreamWriter", + &[ + ("", "(Ljava/lang/String;)V"), + ("", "(Ljava/lang/String;Z)V"), + ("", "(Ljava/io/File;)V"), + ("", "(Ljava/io/File;Z)V"), + ("", "(Ljava/io/FileDescriptor;)V"), + ][..], + &[][..], + ), + ] { + let proto = get_runtime_class_proto(class_name).unwrap_or_else(|| panic!("missing {class_name}")); + assert_eq!(proto.parent_class, Some(parent), "wrong parent for {class_name}"); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC), "{class_name} must be public"); + for (name, descriptor) in methods { + let method = proto + .methods + .iter() + .find(|method| method.name == *name && method.descriptor == *descriptor) + .unwrap_or_else(|| panic!("missing {class_name}.{name}{descriptor}")); + assert!( + method.access_flags.contains(MethodAccessFlags::PUBLIC), + "{class_name}.{name}{descriptor} must be public" + ); + assert_eq!( + method.access_flags.contains(MethodAccessFlags::SYNCHRONIZED), + synchronized_methods.contains(&(*name, *descriptor)), + "wrong synchronized flag for {class_name}.{name}{descriptor}" + ); + } + } + + let buffered_output = get_runtime_class_proto("java/io/BufferedOutputStream").expect("missing java/io/BufferedOutputStream"); + assert!( + !buffered_output + .methods + .iter() + .any(|method| method.name == "close" && method.descriptor == "()V"), + "BufferedOutputStream must inherit close() from FilterOutputStream" + ); + let filter_output = get_runtime_class_proto("java/io/FilterOutputStream").expect("missing java/io/FilterOutputStream"); + let close = filter_output + .methods + .iter() + .find(|method| method.name == "close" && method.descriptor == "()V") + .expect("missing java/io/FilterOutputStream.close()V"); + assert!(close.access_flags.contains(MethodAccessFlags::PUBLIC)); + assert!(!close.access_flags.contains(MethodAccessFlags::SYNCHRONIZED)); + + for (class_name, fields) in [ + ( + "java/io/BufferedInputStream", + &[ + ("buf", "[B", FieldAccessFlags::PROTECTED | FieldAccessFlags::VOLATILE), + ("count", "I", FieldAccessFlags::PROTECTED), + ("pos", "I", FieldAccessFlags::PROTECTED), + ("markpos", "I", FieldAccessFlags::PROTECTED), + ("marklimit", "I", FieldAccessFlags::PROTECTED), + ][..], + ), + ( + "java/io/BufferedOutputStream", + &[("buf", "[B", FieldAccessFlags::PROTECTED), ("count", "I", FieldAccessFlags::PROTECTED)][..], + ), + ( + "java/io/BufferedWriter", + &[ + ("out", "Ljava/io/Writer;", FieldAccessFlags::PRIVATE), + ("cb", "[C", FieldAccessFlags::PRIVATE), + ("nChars", "I", FieldAccessFlags::PRIVATE), + ("nextChar", "I", FieldAccessFlags::PRIVATE), + ("lineSeparator", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + ][..], + ), + ( + "java/io/StringReader", + &[ + ("str", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + ("length", "I", FieldAccessFlags::PRIVATE), + ("next", "I", FieldAccessFlags::PRIVATE), + ("mark", "I", FieldAccessFlags::PRIVATE), + ][..], + ), + ( + "java/io/CharArrayReader", + &[ + ("buf", "[C", FieldAccessFlags::PROTECTED), + ("pos", "I", FieldAccessFlags::PROTECTED), + ("markedPos", "I", FieldAccessFlags::PROTECTED), + ("count", "I", FieldAccessFlags::PROTECTED), + ][..], + ), + ( + "java/io/CharArrayWriter", + &[("buf", "[C", FieldAccessFlags::PROTECTED), ("count", "I", FieldAccessFlags::PROTECTED)][..], + ), + ] { + let proto = get_runtime_class_proto(class_name).unwrap_or_else(|| panic!("missing {class_name}")); + for (name, descriptor, access_flags) in fields { + let field = proto + .fields + .iter() + .find(|field| field.name == *name && field.descriptor == *descriptor) + .unwrap_or_else(|| panic!("missing {class_name}.{name}:{descriptor}")); + assert_eq!(field.access_flags, *access_flags, "wrong field access for {class_name}.{name}"); + } + } +} diff --git a/java_runtime/tests/classes/java/io/test_byte_array_output_stream.rs b/java_runtime/tests/classes/java/io/test_byte_array_output_stream.rs index b228d401..a8056ac6 100644 --- a/java_runtime/tests/classes/java/io/test_byte_array_output_stream.rs +++ b/java_runtime/tests/classes/java/io/test_byte_array_output_stream.rs @@ -1,11 +1,43 @@ -use alloc::vec; +use alloc::{vec, vec::Vec}; use bytemuck::cast_vec; -use jvm::Result; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::io::{ByteArrayOutputStream, OutputStream}; +use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; +#[tokio::test] +async fn baos_01_descriptors_fields_and_access_flags() -> Result<()> { + let proto = ByteArrayOutputStream::as_proto(); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC)); + + for (name, descriptor) in [ + ("writeTo", "(Ljava/io/OutputStream;)V"), + ("toString", "(Ljava/lang/String;)Ljava/lang/String;"), + ] { + let methods = proto + .methods + .iter() + .filter(|method| method.name == name && method.descriptor == descriptor) + .collect::>(); + assert_eq!(methods.len(), 1, "missing or duplicate {name}{descriptor}"); + assert!(methods[0].access_flags.contains(MethodAccessFlags::PUBLIC)); + assert!(methods[0].access_flags.contains(MethodAccessFlags::SYNCHRONIZED)); + } + + let buf = proto.fields.iter().find(|field| field.name == "buf").expect("buf field"); + assert_eq!(buf.descriptor, "[B"); + assert_eq!(buf.access_flags, FieldAccessFlags::PROTECTED); + let count = proto.fields.iter().find(|field| field.name == "count").expect("count field"); + assert_eq!(count.descriptor, "I"); + assert_eq!(count.access_flags, FieldAccessFlags::PROTECTED); + assert!(proto.fields.iter().all(|field| field.name != "pos")); + + Ok(()) +} + #[tokio::test] async fn test_byte_array_output_stream() -> Result<()> { let jvm = test_jvm().await?; @@ -26,3 +58,78 @@ async fn test_byte_array_output_stream() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn baos_01_write_to_and_named_encoding_use_only_logical_count_after_close() -> Result<()> { + let jvm = test_jvm().await?; + + let stream = jvm.new_class("java/io/ByteArrayOutputStream", "(I)V", (16,)).await?; + let mut bytes = jvm.instantiate_array("B", 2).await?; + jvm.store_array(&mut bytes, 0, [b'O' as i8, b'K' as i8]).await?; + let _: () = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes, 0, 2)).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + + let destination = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let destination_output: ClassInstanceRef = destination.clone().into(); + let _: () = jvm + .invoke_virtual(&stream, "writeTo", "(Ljava/io/OutputStream;)V", (destination_output,)) + .await?; + let copied: ClassInstanceRef> = jvm.invoke_virtual(&destination, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.array_length(&copied).await?, 2); + assert_eq!(jvm.load_array::(&copied, 0, 2).await?, [b'O' as i8, b'K' as i8]); + + let encoding = JavaLangString::from_rust_string(&jvm, "UTF-8").await?; + let text = jvm + .invoke_virtual(&stream, "toString", "(Ljava/lang/String;)Ljava/lang/String;", (encoding,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "OK"); + + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (b'!' as i32,)).await?; + let text = jvm.invoke_virtual(&stream, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "OK!"); + + Ok(()) +} + +#[tokio::test] +async fn baos_01_rejects_null_and_unknown_encoding() -> Result<()> { + let jvm = test_jvm().await?; + let stream = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + + let null_output: ClassInstanceRef = None.into(); + let result: Result<()> = jvm.invoke_virtual(&stream, "writeTo", "(Ljava/io/OutputStream;)V", (null_output,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null output must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_encoding: ClassInstanceRef = None.into(); + let result = jvm + .invoke_virtual::<_, ClassInstanceRef>( + &stream, + "toString", + "(Ljava/lang/String;)Ljava/lang/String;", + (null_encoding,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null encoding must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let unknown = JavaLangString::from_rust_string(&jvm, "not-an-encoding").await?; + let result = jvm + .invoke_virtual::<_, ClassInstanceRef>( + &stream, + "toString", + "(Ljava/lang/String;)Ljava/lang/String;", + (unknown,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("unknown encoding must throw UnsupportedEncodingException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/UnsupportedEncodingException")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/io/test_char_streams.rs b/java_runtime/tests/classes/java/io/test_char_streams.rs new file mode 100644 index 00000000..0d676ddd --- /dev/null +++ b/java_runtime/tests/classes/java/io/test_char_streams.rs @@ -0,0 +1,205 @@ +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; + +use java_runtime::classes::java::lang::String; +use test_utils::test_jvm; + +#[tokio::test] +async fn cr_01_cr_02_string_reader_contract() -> Result<()> { + let jvm = test_jvm().await?; + + let null_string: ClassInstanceRef = None.into(); + let result = jvm.new_class("java/io/StringReader", "(Ljava/lang/String;)V", (null_string,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null string must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let value = JavaLangString::from_rust_string(&jvm, "A한BC").await?; + let reader = jvm.new_class("java/io/StringReader", "(Ljava/lang/String;)V", (value,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + assert!(jvm.invoke_virtual::<_, bool>(&reader, "markSupported", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'A' as i32); + + let _: () = jvm.invoke_virtual(&reader, "mark", "(I)V", (10,)).await?; + let chars = jvm.instantiate_array("C", 5).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 1, 2)).await?, 2); + assert_eq!( + jvm.load_array::(&chars, 0, 5).await?, + [0, '한' as JavaChar, 'B' as JavaChar, 0, 0] + ); + let _: () = jvm.invoke_virtual(&reader, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, '한' as i32); + assert_eq!(jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (-2i64,)).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (10i64,)).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, -1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (chars.clone(), 0, 0)).await?, 0); + + let invalid: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + let invalid_mark: Result<()> = jvm.invoke_virtual(&reader, "mark", "(I)V", (-1,)).await; + let Err(JavaError::JavaException(exception)) = invalid_mark else { + panic!("negative read-ahead limit must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let _: () = jvm.invoke_virtual(&reader, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&reader, "close", "()V", ()).await?; + let closed: Result = jvm.invoke_virtual(&reader, "ready", "()Z", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("ready after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let closed: Result = jvm.invoke_virtual(&reader, "read", "()I", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("read after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let null_chars: ClassInstanceRef> = None.into(); + let closed: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (null_chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed StringReader must throw IOException before validating a null target"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let target = jvm.instantiate_array("C", 1).await?; + let closed: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (target, 2, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed StringReader must throw IOException before validating the target range"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + Ok(()) +} + +#[tokio::test] +async fn cr_03_cr_04_char_array_reader_contract() -> Result<()> { + let jvm = test_jvm().await?; + + let null_chars: ClassInstanceRef> = None.into(); + let result = jvm.new_class("java/io/CharArrayReader", "([C)V", (null_chars,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null array must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let chars = jvm.instantiate_array("C", 3).await?; + let result = jvm.new_class("java/io/CharArrayReader", "([CII)V", (chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid source range must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let mut chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut chars, 0, ['X' as JavaChar]).await?; + let default_reader = jvm.new_class("java/io/CharArrayReader", "([C)V", (chars,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&default_reader, "read", "()I", ()).await?, 'X' as i32); + + let mut chars = jvm.instantiate_array("C", 5).await?; + jvm.store_array( + &mut chars, + 0, + ['0' as JavaChar, 'A' as JavaChar, 'B' as JavaChar, 'C' as JavaChar, '4' as JavaChar], + ) + .await?; + let reader = jvm.new_class("java/io/CharArrayReader", "([CII)V", (chars, 1, 3)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + assert!(jvm.invoke_virtual::<_, bool>(&reader, "markSupported", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'A' as i32); + let _: () = jvm.invoke_virtual(&reader, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'A' as i32); + let _: () = jvm.invoke_virtual(&reader, "mark", "(I)V", (2,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (1i64,)).await?, 1); + let _: () = jvm.invoke_virtual(&reader, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'B' as i32); + + let target = jvm.instantiate_array("C", 2).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (target.clone(), 0, 1)).await?, 1); + assert_eq!(jvm.load_array::(&target, 0, 2).await?, ['C' as JavaChar, 0]); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, -1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "([CII)I", (target.clone(), 0, 0)).await?, 0); + assert!(!jvm.invoke_virtual::<_, bool>(&reader, "ready", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i64>(&reader, "skip", "(J)J", (-1i64,)).await?, 0); + + let invalid: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (target, 2, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid target range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + + let _: () = jvm.invoke_virtual(&reader, "close", "()V", ()).await?; + let closed: Result = jvm.invoke_virtual(&reader, "read", "()I", ()).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("read after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let null_chars: ClassInstanceRef> = None.into(); + let closed: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (null_chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed CharArrayReader must throw IOException before validating a null target"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let target = jvm.instantiate_array("C", 1).await?; + let closed: Result = jvm.invoke_virtual(&reader, "read", "([CII)I", (target, 2, 1)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("closed CharArrayReader must throw IOException before validating the target range"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + Ok(()) +} + +#[tokio::test] +async fn cw_01_cw_02_char_array_writer_contract() -> Result<()> { + let jvm = test_jvm().await?; + + let result = jvm.new_class("java/io/CharArrayWriter", "(I)V", (-1,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("negative size must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let writer = jvm.new_class("java/io/CharArrayWriter", "(I)V", (1,)).await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('A' as i32,)).await?; + let mut chars = jvm.instantiate_array("C", 4).await?; + jvm.store_array(&mut chars, 0, ['0' as JavaChar, 'B' as JavaChar, 'C' as JavaChar, '3' as JavaChar]) + .await?; + let _: () = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars.clone(), 1, 2)).await?; + let value = JavaLangString::from_rust_string(&jvm, "xDEy").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;II)V", (value, 1, 2)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&writer, "size", "()I", ()).await?, 5); + + let invalid: Result<()> = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars, -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + + let copy: ClassInstanceRef> = jvm.invoke_virtual(&writer, "toCharArray", "()[C", ()).await?; + assert_eq!( + jvm.load_array::(©, 0, 5).await?, + ['A' as JavaChar, 'B' as JavaChar, 'C' as JavaChar, 'D' as JavaChar, 'E' as JavaChar] + ); + let mut copy = copy; + jvm.store_array(&mut copy, 0, ['Z' as JavaChar]).await?; + + let string: ClassInstanceRef = jvm.invoke_virtual(&writer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &string).await?, "ABCDE"); + let output = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&writer, "writeTo", "(Ljava/io/Writer;)V", (output.clone(),)).await?; + let string: ClassInstanceRef = jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &string).await?, "ABCDE"); + + let _: () = jvm.invoke_virtual(&writer, "flush", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('F' as i32,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&writer, "size", "()I", ()).await?, 6); + let _: () = jvm.invoke_virtual(&writer, "reset", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&writer, "size", "()I", ()).await?, 0); + + let default_writer = jvm.new_class("java/io/CharArrayWriter", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&default_writer, "size", "()I", ()).await?, 0); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/io/test_file_reader_writer.rs b/java_runtime/tests/classes/java/io/test_file_reader_writer.rs new file mode 100644 index 00000000..a0e62348 --- /dev/null +++ b/java_runtime/tests/classes/java/io/test_file_reader_writer.rs @@ -0,0 +1,738 @@ +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec}; +use core::{ + sync::atomic::{AtomicU32, Ordering}, + time::Duration, +}; +use std::sync::Mutex; + +use java_runtime::{ + File as RuntimeFile, FileDescriptorId, FileOpenOptions, FileSize, FileStat, FileType, IOError, IOResult, RT_RUSTJAR, Runtime, SpawnCallback, + get_runtime_class_proto, +}; +use jvm::{ClassDefinition, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; +use test_utils::{TestRuntime, create_test_jvm, test_jvm_filesystem}; + +type MemoryFiles = Arc>>>>>; + +#[derive(Clone, Copy)] +struct MemoryFileConfig { + max_write_size: Option, + zero_on_write_call: Option, + error_on_write_call: Option, + seek_supported: bool, + set_len_supported: bool, + metadata_supported: bool, +} + +impl Default for MemoryFileConfig { + fn default() -> Self { + Self { + max_write_size: None, + zero_on_write_call: None, + error_on_write_call: None, + seek_supported: true, + set_len_supported: true, + metadata_supported: true, + } + } +} + +#[derive(Clone)] +struct MemoryFile { + data: Arc>>, + position: Arc>, + read: bool, + write: bool, + append: bool, + config: MemoryFileConfig, + write_calls: Arc, + seek_calls: Arc, + set_len_calls: Arc, + metadata_calls: Arc, +} + +#[async_trait::async_trait] +impl RuntimeFile for MemoryFile { + async fn read(&mut self, buffer: &mut [u8]) -> IOResult { + if !self.read { + return Err(IOError::Unsupported); + } + + let data = self.data.lock().unwrap(); + let mut position = self.position.lock().unwrap(); + let available = &data[(*position).min(data.len() as FileSize) as usize..]; + let length = available.len().min(buffer.len()); + buffer[..length].copy_from_slice(&available[..length]); + *position += length as FileSize; + Ok(length) + } + + async fn write(&mut self, buffer: &[u8]) -> IOResult { + if !self.write { + return Err(IOError::Unsupported); + } + + let write_call = self.write_calls.fetch_add(1, Ordering::SeqCst) + 1; + if self.config.error_on_write_call == Some(write_call) { + return Err(IOError::Io); + } + if self.config.zero_on_write_call == Some(write_call) { + return Ok(0); + } + + let length = self.config.max_write_size.map_or(buffer.len(), |limit| limit.min(buffer.len())); + let mut data = self.data.lock().unwrap(); + let mut position = self.position.lock().unwrap(); + let start = if self.append { data.len() } else { *position as usize }; + let end = start + length; + if data.len() < end { + data.resize(end, 0); + } + data[start..end].copy_from_slice(&buffer[..length]); + *position = end as FileSize; + Ok(length) + } + + async fn seek(&mut self, position: FileSize) -> IOResult<()> { + self.seek_calls.fetch_add(1, Ordering::SeqCst); + if !self.config.seek_supported { + return Err(IOError::Unsupported); + } + *self.position.lock().unwrap() = position; + Ok(()) + } + + async fn tell(&self) -> IOResult { + Ok(*self.position.lock().unwrap()) + } + + async fn set_len(&mut self, length: FileSize) -> IOResult<()> { + self.set_len_calls.fetch_add(1, Ordering::SeqCst); + if !self.write || !self.config.set_len_supported { + return Err(IOError::Unsupported); + } + self.data.lock().unwrap().resize(length as usize, 0); + let mut position = self.position.lock().unwrap(); + *position = (*position).min(length); + Ok(()) + } + + async fn metadata(&self) -> IOResult { + self.metadata_calls.fetch_add(1, Ordering::SeqCst); + if !self.config.metadata_supported { + return Err(IOError::Unsupported); + } + Ok(FileStat { + size: self.data.lock().unwrap().len() as FileSize, + r#type: FileType::File, + }) + } +} + +#[derive(Clone)] +struct MemoryRuntime { + classes: TestRuntime, + files: MemoryFiles, + handles: Arc>>>, + next_fd: Arc, + open_calls: Arc>>, + seek_calls: Arc, + set_len_calls: Arc, + metadata_calls: Arc, + file_config: MemoryFileConfig, +} + +impl MemoryRuntime { + fn new(files: BTreeMap>) -> Self { + Self { + classes: TestRuntime::new(BTreeMap::new()), + files: Arc::new(Mutex::new( + files.into_iter().map(|(path, data)| (path, Arc::new(Mutex::new(data)))).collect(), + )), + handles: Arc::new(Mutex::new(BTreeMap::new())), + next_fd: Arc::new(AtomicU32::new(1)), + open_calls: Arc::new(Mutex::new(Vec::new())), + seek_calls: Arc::new(AtomicU32::new(0)), + set_len_calls: Arc::new(AtomicU32::new(0)), + metadata_calls: Arc::new(AtomicU32::new(0)), + file_config: MemoryFileConfig::default(), + } + } +} + +#[async_trait::async_trait] +impl Runtime for MemoryRuntime { + async fn sleep(&self, duration: Duration) { + self.classes.sleep(duration).await; + } + + async fn r#yield(&self) { + self.classes.r#yield().await; + } + + fn spawn(&self, jvm: &Jvm, callback: Box) { + self.classes.spawn(jvm, callback); + } + + fn exit(&self, status: i32) { + self.classes.exit(status); + } + + fn now(&self) -> u64 { + self.classes.now() + } + + fn current_task_id(&self) -> u64 { + self.classes.current_task_id() + } + + fn stdin(&self) -> IOResult { + Err(IOError::NotFound) + } + + fn stdout(&self) -> IOResult { + Err(IOError::NotFound) + } + + fn stderr(&self) -> IOResult { + Err(IOError::NotFound) + } + + async fn open(&self, path: &str, options: FileOpenOptions) -> IOResult { + self.open_calls.lock().unwrap().push((path.into(), options)); + let write = options.write || options.append; + if !options.read && !write { + return Err(IOError::Unsupported); + } + if (options.create || options.truncate) && !write { + return Err(IOError::Unsupported); + } + + let data = { + let mut files = self.files.lock().unwrap(); + match files.get(path) { + Some(data) => data.clone(), + None if options.create => { + let data = Arc::new(Mutex::new(Vec::new())); + files.insert(path.into(), data.clone()); + data + } + None => return Err(IOError::NotFound), + } + }; + if options.truncate { + data.lock().unwrap().clear(); + } + let id = self.next_fd.fetch_add(1, Ordering::SeqCst); + self.handles.lock().unwrap().insert( + id, + Box::new(MemoryFile { + data, + position: Arc::new(Mutex::new(0)), + read: options.read, + write, + append: options.append, + config: self.file_config, + write_calls: Arc::new(AtomicU32::new(0)), + seek_calls: self.seek_calls.clone(), + set_len_calls: self.set_len_calls.clone(), + metadata_calls: self.metadata_calls.clone(), + }), + ); + Ok(FileDescriptorId::new(id)) + } + + fn get_file(&self, fd: FileDescriptorId) -> IOResult> { + self.handles.lock().unwrap().get(&fd.id()).cloned().ok_or(IOError::NotFound) + } + + fn close_file(&self, fd: FileDescriptorId) { + self.handles.lock().unwrap().remove(&fd.id()); + } + + async fn unlink(&self, path: &str) -> IOResult<()> { + self.files.lock().unwrap().remove(path).map(|_| ()).ok_or(IOError::NotFound) + } + + async fn metadata(&self, path: &str) -> IOResult { + let files = self.files.lock().unwrap(); + let data = files.get(path).ok_or(IOError::NotFound)?; + Ok(FileStat { + size: data.lock().unwrap().len() as FileSize, + r#type: FileType::File, + }) + } + + async fn find_rustjar_class(&self, _jvm: &Jvm, classpath: &str, class: &str) -> Result>> { + if classpath == RT_RUSTJAR + && let Some(proto) = get_runtime_class_proto(class) + { + return Ok(Some(Box::new(ClassDefinitionImpl::from_class_proto( + proto, + Box::new(self.clone()) as Box<_>, + )))); + } + Ok(None) + } + + async fn define_class(&self, jvm: &Jvm, data: &[u8]) -> Result> { + self.classes.define_class(jvm, data).await + } + + async fn define_array_class(&self, jvm: &Jvm, element_type_name: &str) -> Result> { + self.classes.define_array_class(jvm, element_type_name).await + } +} + +#[tokio::test] +async fn file_01_file_reader_constructor_contracts() -> Result<()> { + let filesystem = [("input.txt".into(), "A한B".as_bytes().to_vec())].into_iter().collect(); + let jvm = test_jvm_filesystem(filesystem).await?; + let path = JavaLangString::from_rust_string(&jvm, "input.txt").await?; + + let reader = jvm.new_class("java/io/FileReader", "(Ljava/lang/String;)V", (path.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'A' as i32); + + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path.clone(),)).await?; + let reader = jvm.new_class("java/io/FileReader", "(Ljava/io/File;)V", (file.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'A' as i32); + + let input = jvm.new_class("java/io/FileInputStream", "(Ljava/io/File;)V", (file,)).await?; + let descriptor: ClassInstanceRef = + jvm.get_field(&input, "fd", "Ljava/io/FileDescriptor;").await?; + let reader = jvm.new_class("java/io/FileReader", "(Ljava/io/FileDescriptor;)V", (descriptor,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&reader, "read", "()I", ()).await?, 'A' as i32); + + let null_path: ClassInstanceRef = None.into(); + let result = jvm.new_class("java/io/FileReader", "(Ljava/lang/String;)V", (null_path,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null path must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn file_02_file_03_file_writer_constructor_and_append_contracts() -> Result<()> { + let runtime = MemoryRuntime::new([("output.txt".into(), b"old data".to_vec())].into_iter().collect()); + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "output.txt").await?; + + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/lang/String;)V", (path.clone(),)).await?; + let value = JavaLangString::from_rust_string(&jvm, "new").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + assert_eq!(&*runtime.files.lock().unwrap()["output.txt"].lock().unwrap(), b"new"); + let value = JavaLangString::from_rust_string(&jvm, "!").await?; + let closed: Result<()> = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("write after close must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path.clone(),)).await?; + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/io/File;Z)V", (file.clone(), true)).await?; + let value = JavaLangString::from_rust_string(&jvm, "+file").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + assert_eq!(&*runtime.files.lock().unwrap()["output.txt"].lock().unwrap(), b"new+file"); + + let writer = jvm + .new_class("java/io/FileWriter", "(Ljava/lang/String;Z)V", (path.clone(), true)) + .await?; + let value = JavaLangString::from_rust_string(&jvm, "+path").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + assert_eq!(&*runtime.files.lock().unwrap()["output.txt"].lock().unwrap(), b"new+file+path"); + + let first = jvm + .new_class("java/io/FileWriter", "(Ljava/lang/String;Z)V", (path.clone(), true)) + .await?; + let second = jvm + .new_class("java/io/FileWriter", "(Ljava/lang/String;Z)V", (path.clone(), true)) + .await?; + for (writer, text) in [(&first, "+first"), (&second, "+second"), (&first, "+third"), (&second, "+fourth")] { + let value = JavaLangString::from_rust_string(&jvm, text).await?; + let _: () = jvm.invoke_virtual(writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + } + let _: () = jvm.invoke_virtual(&first, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&second, "close", "()V", ()).await?; + assert_eq!( + &*runtime.files.lock().unwrap()["output.txt"].lock().unwrap(), + b"new+file+path+first+second+third+fourth" + ); + + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/io/File;)V", (file.clone(),)).await?; + let value = JavaLangString::from_rust_string(&jvm, "reset").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + assert_eq!(&*runtime.files.lock().unwrap()["output.txt"].lock().unwrap(), b"reset"); + + let output = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?; + let descriptor: ClassInstanceRef = + jvm.get_field(&output, "fd", "Ljava/io/FileDescriptor;").await?; + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/io/FileDescriptor;)V", (descriptor,)).await?; + let value = JavaLangString::from_rust_string(&jvm, "F").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + assert_eq!(&*runtime.files.lock().unwrap()["output.txt"].lock().unwrap(), b"F"); + + let null_path: ClassInstanceRef = None.into(); + let result = jvm.new_class("java/io/FileWriter", "(Ljava/lang/String;)V", (null_path,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null path must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn file_open_options_and_handle_access_are_enforced() -> Result<()> { + let runtime = MemoryRuntime::new( + [ + ("input.txt".into(), b"input".to_vec()), + ("output.txt".into(), b"output".to_vec()), + ("random-read.txt".into(), b"read".to_vec()), + ("random-write.txt".into(), b"write".to_vec()), + ] + .into_iter() + .collect(), + ); + let jvm = create_test_jvm(runtime.clone()).await?; + + let input_path = JavaLangString::from_rust_string(&jvm, "input.txt").await?; + let input_file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (input_path,)).await?; + let _input = jvm.new_class("java/io/FileInputStream", "(Ljava/io/File;)V", (input_file,)).await?; + + let output_path = JavaLangString::from_rust_string(&jvm, "output.txt").await?; + let output_file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (output_path,)).await?; + let _output = jvm + .new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (output_file.clone(),)) + .await?; + let _append_output = jvm + .new_class("java/io/FileOutputStream", "(Ljava/io/File;Z)V", (output_file, true)) + .await?; + + let random_read_path = JavaLangString::from_rust_string(&jvm, "random-read.txt").await?; + let read_mode = JavaLangString::from_rust_string(&jvm, "r").await?; + let _random_read = jvm + .new_class( + "java/io/RandomAccessFile", + "(Ljava/lang/String;Ljava/lang/String;)V", + (random_read_path, read_mode), + ) + .await?; + + let random_write_path = JavaLangString::from_rust_string(&jvm, "random-write.txt").await?; + let write_mode = JavaLangString::from_rust_string(&jvm, "rw").await?; + let _random_write = jvm + .new_class( + "java/io/RandomAccessFile", + "(Ljava/lang/String;Ljava/lang/String;)V", + (random_write_path, write_mode), + ) + .await?; + + assert_eq!( + *runtime.open_calls.lock().unwrap(), + vec![ + ( + "input.txt".into(), + FileOpenOptions { + read: true, + ..Default::default() + } + ), + ( + "output.txt".into(), + FileOpenOptions { + write: true, + truncate: true, + create: true, + ..Default::default() + } + ), + ( + "output.txt".into(), + FileOpenOptions { + write: true, + append: true, + create: true, + ..Default::default() + } + ), + ( + "random-read.txt".into(), + FileOpenOptions { + read: true, + ..Default::default() + } + ), + ( + "random-write.txt".into(), + FileOpenOptions { + read: true, + write: true, + create: true, + ..Default::default() + } + ), + ] + ); + + assert!(matches!( + runtime + .open( + "missing.txt", + FileOpenOptions { + read: true, + ..Default::default() + } + ) + .await, + Err(IOError::NotFound) + )); + let created = runtime + .open( + "created.txt", + FileOpenOptions { + write: true, + create: true, + ..Default::default() + }, + ) + .await + .unwrap(); + assert!(runtime.files.lock().unwrap().contains_key("created.txt")); + + let read_only = runtime + .open( + "input.txt", + FileOpenOptions { + read: true, + ..Default::default() + }, + ) + .await + .unwrap(); + let mut read_only = runtime.get_file(read_only).unwrap(); + assert!(matches!(read_only.write(b"x").await, Err(IOError::Unsupported))); + assert!(matches!(read_only.set_len(0).await, Err(IOError::Unsupported))); + + let mut write_only = runtime.get_file(created).unwrap(); + let mut byte = [0]; + assert!(matches!(write_only.read(&mut byte).await, Err(IOError::Unsupported))); + assert_eq!(write_only.write(b"x").await.unwrap(), 1); + + let test_runtime = TestRuntime::new([("read-only.txt".into(), b"value".to_vec())].into_iter().collect()); + let descriptor = test_runtime + .open( + "read-only.txt", + FileOpenOptions { + read: true, + ..Default::default() + }, + ) + .await + .unwrap(); + let mut file = test_runtime.get_file(descriptor).unwrap(); + let mut bytes = [0; 5]; + assert_eq!(file.read(&mut bytes).await.unwrap(), 5); + assert_eq!(&bytes, b"value"); + for options in [ + FileOpenOptions::default(), + FileOpenOptions { + write: true, + ..Default::default() + }, + FileOpenOptions { + append: true, + ..Default::default() + }, + FileOpenOptions { + read: true, + truncate: true, + ..Default::default() + }, + FileOpenOptions { + read: true, + create: true, + ..Default::default() + }, + ] { + assert!(matches!(test_runtime.open("read-only.txt", options).await, Err(IOError::Unsupported))); + } + + Ok(()) +} + +#[tokio::test] +async fn file_writer_uses_only_opened_handle_write_semantics() -> Result<()> { + let mut runtime = MemoryRuntime::new([("truncate.txt".into(), b"old".to_vec())].into_iter().collect()); + runtime.file_config.seek_supported = false; + runtime.file_config.set_len_supported = false; + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "truncate.txt").await?; + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/lang/String;)V", (path,)).await?; + let value = JavaLangString::from_rust_string(&jvm, "new").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + + assert_eq!(&*runtime.files.lock().unwrap()["truncate.txt"].lock().unwrap(), b"new"); + assert_eq!(runtime.seek_calls.load(Ordering::SeqCst), 0); + assert_eq!(runtime.set_len_calls.load(Ordering::SeqCst), 0); + assert_eq!( + runtime.open_calls.lock().unwrap()[0].1, + FileOpenOptions { + write: true, + truncate: true, + create: true, + ..Default::default() + } + ); + + let mut runtime = MemoryRuntime::new([("append.txt".into(), b"start".to_vec())].into_iter().collect()); + runtime.file_config.seek_supported = false; + runtime.file_config.metadata_supported = false; + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "append.txt").await?; + let first = jvm + .new_class("java/io/FileWriter", "(Ljava/lang/String;Z)V", (path.clone(), true)) + .await?; + let second = jvm.new_class("java/io/FileWriter", "(Ljava/lang/String;Z)V", (path, true)).await?; + for (writer, text) in [(&first, "-a"), (&second, "-b"), (&first, "-c"), (&second, "-d")] { + let value = JavaLangString::from_rust_string(&jvm, text).await?; + let _: () = jvm.invoke_virtual(writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + } + assert_eq!(&*runtime.files.lock().unwrap()["append.txt"].lock().unwrap(), b"start-a-b-c-d"); + assert_eq!(runtime.seek_calls.load(Ordering::SeqCst), 0); + assert_eq!(runtime.metadata_calls.load(Ordering::SeqCst), 0); + + Ok(()) +} + +#[tokio::test] +async fn file_writers_retry_partial_runtime_writes_until_complete() -> Result<()> { + let mut runtime = MemoryRuntime::new([("fos.bin".into(), Vec::new())].into_iter().collect()); + runtime.file_config.max_write_size = Some(1); + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "fos.bin").await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?; + let output = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?; + let mut bytes = jvm.instantiate_array("B", 4).await?; + jvm.store_array(&mut bytes, 0, [b'F' as i8, b'O' as i8, b'S' as i8, b'!' as i8]).await?; + let _: () = jvm.invoke_virtual(&output, "write", "([BII)V", (bytes, 0, 4)).await?; + assert_eq!(&*runtime.files.lock().unwrap()["fos.bin"].lock().unwrap(), b"FOS!"); + + let mut runtime = MemoryRuntime::new([("raf.bin".into(), Vec::new())].into_iter().collect()); + runtime.file_config.max_write_size = Some(1); + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "raf.bin").await?; + let mode = JavaLangString::from_rust_string(&jvm, "rw").await?; + let file = jvm + .new_class("java/io/RandomAccessFile", "(Ljava/lang/String;Ljava/lang/String;)V", (path, mode)) + .await?; + let mut bytes = jvm.instantiate_array("B", 4).await?; + jvm.store_array(&mut bytes, 0, [b'R' as i8, b'A' as i8, b'F' as i8, b'!' as i8]).await?; + let _: () = jvm.invoke_virtual(&file, "write", "([BII)V", (bytes, 0, 4)).await?; + assert_eq!(&*runtime.files.lock().unwrap()["raf.bin"].lock().unwrap(), b"RAF!"); + + let mut runtime = MemoryRuntime::new([("writer.txt".into(), Vec::new())].into_iter().collect()); + runtime.file_config.max_write_size = Some(1); + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "writer.txt").await?; + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/lang/String;)V", (path,)).await?; + let value = JavaLangString::from_rust_string(&jvm, "A한B").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await?; + assert_eq!(&*runtime.files.lock().unwrap()["writer.txt"].lock().unwrap(), "A한B".as_bytes()); + + Ok(()) +} + +#[tokio::test] +async fn file_output_stream_reports_zero_and_late_write_failures_with_exact_prefix() -> Result<()> { + for (path, zero_on_write_call, error_on_write_call) in [("zero.bin", Some(2), None), ("error.bin", None, Some(2))] { + let mut runtime = MemoryRuntime::new([(path.into(), Vec::new())].into_iter().collect()); + runtime.file_config.max_write_size = Some(1); + runtime.file_config.zero_on_write_call = zero_on_write_call; + runtime.file_config.error_on_write_call = error_on_write_call; + let jvm = create_test_jvm(runtime.clone()).await?; + let path_string = JavaLangString::from_rust_string(&jvm, path).await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path_string,)).await?; + let output = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?; + let mut bytes = jvm.instantiate_array("B", 3).await?; + jvm.store_array(&mut bytes, 0, [b'a' as i8, b'b' as i8, b'c' as i8]).await?; + let result: Result<()> = jvm.invoke_virtual(&output, "write", "([BII)V", (bytes, 0, 3)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("incomplete runtime write must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(&*runtime.files.lock().unwrap()[path].lock().unwrap(), b"a"); + } + + let mut runtime = MemoryRuntime::new([("byte.bin".into(), Vec::new())].into_iter().collect()); + runtime.file_config.zero_on_write_call = Some(1); + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "byte.bin").await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?; + let output = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?; + let result: Result<()> = jvm.invoke_virtual(&output, "write", "(I)V", (65,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("zero-byte runtime write must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert!(runtime.files.lock().unwrap()["byte.bin"].lock().unwrap().is_empty()); + + Ok(()) +} + +#[tokio::test] +async fn random_access_file_reports_zero_and_late_write_failures_with_exact_prefix() -> Result<()> { + for (path, zero_on_write_call, error_on_write_call) in [("zero.bin", Some(2), None), ("error.bin", None, Some(2))] { + let mut runtime = MemoryRuntime::new([(path.into(), Vec::new())].into_iter().collect()); + runtime.file_config.max_write_size = Some(1); + runtime.file_config.zero_on_write_call = zero_on_write_call; + runtime.file_config.error_on_write_call = error_on_write_call; + let jvm = create_test_jvm(runtime.clone()).await?; + let path_string = JavaLangString::from_rust_string(&jvm, path).await?; + let mode = JavaLangString::from_rust_string(&jvm, "rw").await?; + let file = jvm + .new_class("java/io/RandomAccessFile", "(Ljava/lang/String;Ljava/lang/String;)V", (path_string, mode)) + .await?; + let mut bytes = jvm.instantiate_array("B", 3).await?; + jvm.store_array(&mut bytes, 0, [b'a' as i8, b'b' as i8, b'c' as i8]).await?; + let result: Result<()> = jvm.invoke_virtual(&file, "write", "([BII)V", (bytes, 0, 3)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("incomplete runtime write must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(&*runtime.files.lock().unwrap()[path].lock().unwrap(), b"a"); + } + + Ok(()) +} + +#[tokio::test] +async fn file_writer_reports_zero_and_late_write_failures_with_exact_prefix() -> Result<()> { + for (path, zero_on_write_call, error_on_write_call) in [("zero.txt", Some(2), None), ("error.txt", None, Some(2))] { + let mut runtime = MemoryRuntime::new([(path.into(), Vec::new())].into_iter().collect()); + runtime.file_config.max_write_size = Some(1); + runtime.file_config.zero_on_write_call = zero_on_write_call; + runtime.file_config.error_on_write_call = error_on_write_call; + let jvm = create_test_jvm(runtime.clone()).await?; + let path_string = JavaLangString::from_rust_string(&jvm, path).await?; + let writer = jvm.new_class("java/io/FileWriter", "(Ljava/lang/String;)V", (path_string,)).await?; + let value = JavaLangString::from_rust_string(&jvm, "abc").await?; + let result: Result<()> = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (value,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("incomplete runtime write must throw IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(&*runtime.files.lock().unwrap()[path].lock().unwrap(), b"a"); + } + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/io/test_output_stream_close.rs b/java_runtime/tests/classes/java/io/test_output_stream_close.rs new file mode 100644 index 00000000..06e38bd3 --- /dev/null +++ b/java_runtime/tests/classes/java/io/test_output_stream_close.rs @@ -0,0 +1,223 @@ +use alloc::{boxed::Box, collections::BTreeMap, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{RuntimeClassProto, RuntimeContext, classes::java::io::OutputStream}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result}; +use jvm_rust::ClassDefinitionImpl; +use test_utils::{TestRuntime, create_test_jvm}; + +struct CloseProbeOutputStream; + +impl CloseProbeOutputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CloseProbeOutputStream", + parent_class: Some("java/io/OutputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(II)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("flushMode", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("closeMode", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("bytesWritten", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("flushCalls", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("closeCalls", "I", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, flush_mode: i32, close_mode: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await?; + jvm.put_field(&mut this, "flushMode", "I", flush_mode).await?; + jvm.put_field(&mut this, "closeMode", "I", close_mode).await?; + jvm.put_field(&mut this, "bytesWritten", "I", 0).await?; + jvm.put_field(&mut this, "flushCalls", "I", 0).await?; + jvm.put_field(&mut this, "closeCalls", "I", 0).await + } + + async fn write(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, _: i32) -> Result<()> { + let count: i32 = jvm.get_field(&this, "bytesWritten", "I").await?; + jvm.put_field(&mut this, "bytesWritten", "I", count + 1).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let calls: i32 = jvm.get_field(&this, "flushCalls", "I").await?; + jvm.put_field(&mut this, "flushCalls", "I", calls + 1).await?; + match jvm.get_field::(&this, "flushMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "flush failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "flush failed").await), + _ => Ok(()), + } + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let calls: i32 = jvm.get_field(&this, "closeCalls", "I").await?; + jvm.put_field(&mut this, "closeCalls", "I", calls + 1).await?; + match jvm.get_field::(&this, "closeMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "close failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "close failed").await), + _ => Ok(()), + } + } +} + +struct FlushOverrideBufferedOutputStream; + +impl FlushOverrideBufferedOutputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "FlushOverrideBufferedOutputStream", + parent_class: Some("java/io/BufferedOutputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/OutputStream;II)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("flushMode", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("flushCalls", "I", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + out: ClassInstanceRef, + size: i32, + flush_mode: i32, + ) -> Result<()> { + let _: () = jvm + .invoke_special(&this, "java/io/BufferedOutputStream", "", "(Ljava/io/OutputStream;I)V", (out, size)) + .await?; + jvm.put_field(&mut this, "flushMode", "I", flush_mode).await?; + jvm.put_field(&mut this, "flushCalls", "I", 0).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let calls: i32 = jvm.get_field(&this, "flushCalls", "I").await?; + jvm.put_field(&mut this, "flushCalls", "I", calls + 1).await?; + match jvm.get_field::(&this, "flushMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "override flush failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "override flush failed").await), + _ => jvm.invoke_special(&this, "java/io/BufferedOutputStream", "flush", "()V", ()).await, + } + } +} + +async fn output_stream_close_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + CloseProbeOutputStream::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + FlushOverrideBufferedOutputStream::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + Ok(jvm) +} + +#[tokio::test] +async fn buffered_output_stream_inherited_close_dispatches_virtual_flush() -> Result<()> { + let jvm = output_stream_close_jvm().await?; + let backing: ClassInstanceRef = jvm.new_class("CloseProbeOutputStream", "(II)V", (0, 0)).await?.into(); + let output: ClassInstanceRef = backing.instance.clone().into(); + let stream: ClassInstanceRef = jvm + .new_class("FlushOverrideBufferedOutputStream", "(Ljava/io/OutputStream;II)V", (output, 4, 0)) + .await? + .into(); + let buffer: ClassInstanceRef> = jvm.get_field(&stream, "buf", "[B").await?; + + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (7,)).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + + assert_eq!(jvm.get_field::(&stream, "flushCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "bytesWritten", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "flushCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&stream, "count", "I").await?, 0); + let retained_buffer: ClassInstanceRef> = jvm.get_field(&stream, "buf", "[B").await?; + assert_eq!(retained_buffer.identity(), buffer.identity()); + let closed_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert!(closed_output.is_null()); + + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&stream, "flushCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + Ok(()) +} + +#[tokio::test] +async fn filter_output_stream_close_preserves_failure_sequence_and_state() -> Result<()> { + let jvm = output_stream_close_jvm().await?; + + let backing: ClassInstanceRef = jvm.new_class("CloseProbeOutputStream", "(II)V", (0, 0)).await?.into(); + let output: ClassInstanceRef = backing.instance.clone().into(); + let stream: ClassInstanceRef = jvm + .new_class("FlushOverrideBufferedOutputStream", "(Ljava/io/OutputStream;II)V", (output, 4, 1)) + .await? + .into(); + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (1,)).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&stream, "flushCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&stream, "count", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "bytesWritten", "I").await?, 0); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + let closed_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert!(closed_output.is_null()); + + let backing: ClassInstanceRef = jvm.new_class("CloseProbeOutputStream", "(II)V", (0, 0)).await?.into(); + let output: ClassInstanceRef = backing.instance.clone().into(); + let stream: ClassInstanceRef = jvm + .new_class("FlushOverrideBufferedOutputStream", "(Ljava/io/OutputStream;II)V", (output, 4, 2)) + .await? + .into(); + let result: Result<()> = jvm.invoke_virtual(&stream, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("unchecked flush failure must escape inherited close"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 0); + let closed_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert!(closed_output.is_null()); + + let backing: ClassInstanceRef = jvm.new_class("CloseProbeOutputStream", "(II)V", (0, 1)).await?.into(); + let output: ClassInstanceRef = backing.instance.clone().into(); + let stream: ClassInstanceRef = jvm + .new_class("FlushOverrideBufferedOutputStream", "(Ljava/io/OutputStream;II)V", (output, 4, 0)) + .await? + .into(); + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (1,)).await?; + let result: Result<()> = jvm.invoke_virtual(&stream, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("backing close failure must escape inherited close"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(jvm.get_field::(&stream, "flushCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&stream, "count", "I").await?, 0); + assert_eq!(jvm.get_field::(&backing, "bytesWritten", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "flushCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + let closed_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert!(closed_output.is_null()); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/io/test_print_stream.rs b/java_runtime/tests/classes/java/io/test_print_stream.rs index 163b20d3..b045f425 100644 --- a/java_runtime/tests/classes/java/io/test_print_stream.rs +++ b/java_runtime/tests/classes/java/io/test_print_stream.rs @@ -1,12 +1,828 @@ -use alloc::vec::Vec; +use alloc::{boxed::Box, collections::BTreeMap, format, vec, vec::Vec}; +use core::time::Duration; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use java_runtime::classes::java::{ - io::OutputStream, + io::{ByteArrayOutputStream, OutputStream, OutputStreamWriter, PrintStream}, lang::{Object, String}, }; -use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; +use java_runtime::{RuntimeClassProto, RuntimeContext}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; -use test_utils::test_jvm; +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; + +struct ProbeOutputStream; + +impl ProbeOutputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ProbeOutputStream", + parent_class: Some("java/io/OutputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(III)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("content", "Ljava/io/ByteArrayOutputStream;", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("writeMode", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("flushMode", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("closeMode", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("writeCount", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("flushCount", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("closeCount", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("blockFirstWrite", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("firstWriteEntered", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("releaseFirstWrite", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("blockFirstClose", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("firstCloseEntered", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("releaseFirstClose", "Z", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + write_mode: i32, + flush_mode: i32, + close_mode: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await?; + let content = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + jvm.put_field(&mut this, "content", "Ljava/io/ByteArrayOutputStream;", content).await?; + jvm.put_field(&mut this, "writeMode", "I", write_mode).await?; + jvm.put_field(&mut this, "flushMode", "I", flush_mode).await?; + jvm.put_field(&mut this, "closeMode", "I", close_mode).await?; + jvm.put_field(&mut this, "writeCount", "I", 0).await?; + jvm.put_field(&mut this, "flushCount", "I", 0).await?; + jvm.put_field(&mut this, "closeCount", "I", 0).await?; + jvm.put_field(&mut this, "blockFirstWrite", "Z", false).await?; + jvm.put_field(&mut this, "firstWriteEntered", "Z", false).await?; + jvm.put_field(&mut this, "releaseFirstWrite", "Z", false).await?; + jvm.put_field(&mut this, "blockFirstClose", "Z", false).await?; + jvm.put_field(&mut this, "firstCloseEntered", "Z", false).await?; + jvm.put_field(&mut this, "releaseFirstClose", "Z", false).await + } + + async fn write(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result<()> { + let count: i32 = jvm.get_field(&this, "writeCount", "I").await?; + jvm.put_field(&mut this, "writeCount", "I", count + 1).await?; + match jvm.get_field::(&this, "writeMode", "I").await? { + 1 => return Err(jvm.exception("java/io/IOException", "write failed").await), + 2 => return Err(jvm.exception("java/lang/IllegalStateException", "write failed").await), + 3 if count == 0 => return Err(jvm.exception("java/io/IOException", "first write failed").await), + 4 if count == 1 => return Err(jvm.exception("java/io/IOException", "second write failed").await), + _ => {} + } + if count == 0 && jvm.get_field::(&this, "blockFirstWrite", "Z").await? { + jvm.put_field(&mut this, "firstWriteEntered", "Z", true).await?; + for _ in 0..1000 { + if jvm.get_field::(&this, "releaseFirstWrite", "Z").await? { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + if !jvm.get_field::(&this, "releaseFirstWrite", "Z").await? { + return Err(jvm + .exception("java/lang/IllegalStateException", "timed out waiting for write release") + .await); + } + } + + let content: ClassInstanceRef = jvm.get_field(&this, "content", "Ljava/io/ByteArrayOutputStream;").await?; + jvm.invoke_virtual(&content, "write", "(I)V", (value,)).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let count: i32 = jvm.get_field(&this, "flushCount", "I").await?; + jvm.put_field(&mut this, "flushCount", "I", count + 1).await?; + match jvm.get_field::(&this, "flushMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "flush failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "flush failed").await), + 3 if count == 2 => Err(jvm.exception("java/io/IOException", "third flush failed").await), + _ => Ok(()), + } + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let count: i32 = jvm.get_field(&this, "closeCount", "I").await?; + jvm.put_field(&mut this, "closeCount", "I", count + 1).await?; + if count == 0 && jvm.get_field::(&this, "blockFirstClose", "Z").await? { + jvm.put_field(&mut this, "firstCloseEntered", "Z", true).await?; + for _ in 0..1000 { + if jvm.get_field::(&this, "releaseFirstClose", "Z").await? { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + if !jvm.get_field::(&this, "releaseFirstClose", "Z").await? { + return Err(jvm + .exception("java/lang/IllegalStateException", "timed out waiting for close release") + .await); + } + } + match jvm.get_field::(&this, "closeMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "close failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "close failed").await), + _ => Ok(()), + } + } +} + +struct OverridePrintStream; + +impl OverridePrintStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "OverridePrintStream", + parent_class: Some("java/io/PrintStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/OutputStream;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("print", "(I)V", Self::print_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("println", "()V", Self::println, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([BII)V", Self::write, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("printCount", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("printlnCount", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("writeCount", "I", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, out: ClassInstanceRef) -> Result<()> { + let _: () = jvm + .invoke_special(&this, "java/io/PrintStream", "", "(Ljava/io/OutputStream;)V", (out,)) + .await?; + jvm.put_field(&mut this, "printCount", "I", 0).await?; + jvm.put_field(&mut this, "printlnCount", "I", 0).await?; + jvm.put_field(&mut this, "writeCount", "I", 0).await + } + + async fn print_int(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result<()> { + let count: i32 = jvm.get_field(&this, "printCount", "I").await?; + jvm.put_field(&mut this, "printCount", "I", count + 1).await?; + let value = JavaLangString::from_rust_string(jvm, &format!("<{value}>")).await?; + jvm.invoke_special(&this, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", (value,)) + .await + } + + async fn println(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let count: i32 = jvm.get_field(&this, "printlnCount", "I").await?; + jvm.put_field(&mut this, "printlnCount", "I", count + 1).await + } + + async fn write( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + bytes: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + let count: i32 = jvm.get_field(&this, "writeCount", "I").await?; + jvm.put_field(&mut this, "writeCount", "I", count + 1).await?; + jvm.invoke_special(&this, "java/io/PrintStream", "write", "([BII)V", (bytes, offset, length)) + .await + } +} + +struct PrintStreamRunner; + +impl PrintStreamRunner { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "PrintStreamRunner", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Runnable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/PrintStream;I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("run", "()V", Self::run, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("stream", "Ljava/io/PrintStream;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("value", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("started", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("done", "Z", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + stream: ClassInstanceRef, + value: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "stream", "Ljava/io/PrintStream;", stream).await?; + jvm.put_field(&mut this, "value", "I", value).await?; + jvm.put_field(&mut this, "started", "Z", false).await?; + jvm.put_field(&mut this, "done", "Z", false).await + } + + async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "started", "Z", true).await?; + let stream: ClassInstanceRef = jvm.get_field(&this, "stream", "Ljava/io/PrintStream;").await?; + let value: i32 = jvm.get_field(&this, "value", "I").await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(I)V", (value,)).await?; + jvm.put_field(&mut this, "done", "Z", true).await + } +} + +struct PrintStreamCloseRunner; + +impl PrintStreamCloseRunner { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "PrintStreamCloseRunner", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Runnable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/PrintStream;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("run", "()V", Self::run, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("stream", "Ljava/io/PrintStream;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("started", "Z", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("done", "Z", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, stream: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "stream", "Ljava/io/PrintStream;", stream).await?; + jvm.put_field(&mut this, "started", "Z", false).await?; + jvm.put_field(&mut this, "done", "Z", false).await + } + + async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "started", "Z", true).await?; + let stream: ClassInstanceRef = jvm.get_field(&this, "stream", "Ljava/io/PrintStream;").await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + jvm.put_field(&mut this, "done", "Z", true).await + } +} + +async fn probe_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + ProbeOutputStream::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + OverridePrintStream::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + PrintStreamRunner::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + PrintStreamCloseRunner::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + Ok(jvm) +} + +async fn assert_monitor_released(jvm: &Jvm, stream: &ClassInstanceRef) -> Result<()> { + jvm.monitor_enter(stream).await?; + jvm.monitor_exit(stream).await?; + let result = jvm.monitor_exit(stream).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("PrintStream leaked a reentrant monitor acquisition"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalMonitorStateException")); + Ok(()) +} + +#[tokio::test] +async fn ps_01_constructor_state_descriptors_and_access_flags() -> Result<()> { + let proto = PrintStream::as_proto(); + assert_eq!(proto.parent_class, Some("java/io/FilterOutputStream")); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC)); + + for descriptor in ["(Ljava/io/OutputStream;)V", "(Ljava/io/OutputStream;Z)V"] { + let constructors = proto + .methods + .iter() + .filter(|method| method.name == "" && method.descriptor == descriptor) + .collect::>(); + assert_eq!(constructors.len(), 1, "missing or duplicate constructor {descriptor}"); + assert!(constructors[0].access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + let auto_flush = proto.fields.iter().find(|field| field.name == "autoFlush").expect("autoFlush field"); + assert_eq!(auto_flush.descriptor, "Z"); + assert_eq!(auto_flush.access_flags, FieldAccessFlags::PRIVATE); + let trouble = proto.fields.iter().find(|field| field.name == "trouble").expect("trouble field"); + assert_eq!(trouble.descriptor, "Z"); + assert_eq!(trouble.access_flags, FieldAccessFlags::PRIVATE); + let char_out = proto.fields.iter().find(|field| field.name == "charOut").expect("charOut field"); + assert_eq!(char_out.descriptor, "Ljava/io/OutputStreamWriter;"); + assert_eq!(char_out.access_flags, FieldAccessFlags::PRIVATE); + let closing = proto.fields.iter().find(|field| field.name == "closing").expect("closing field"); + assert_eq!(closing.descriptor, "Z"); + assert_eq!(closing.access_flags, FieldAccessFlags::PRIVATE); + + let jvm = probe_jvm().await?; + let output: ClassInstanceRef = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?.into(); + let default_stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + assert!(!jvm.get_field::(&default_stream, "autoFlush", "Z").await?); + assert!(!jvm.get_field::(&default_stream, "trouble", "Z").await?); + assert!(!jvm.get_field::(&default_stream, "closing", "Z").await?); + let char_out: ClassInstanceRef = jvm.get_field(&default_stream, "charOut", "Ljava/io/OutputStreamWriter;").await?; + assert!(!char_out.is_null()); + let configured_stream = jvm.new_class("java/io/PrintStream", "(Ljava/io/OutputStream;Z)V", (output, true)).await?; + assert!(jvm.get_field::(&configured_stream, "autoFlush", "Z").await?); + + for descriptor in ["(Ljava/io/OutputStream;)V", "(Ljava/io/OutputStream;Z)V"] { + let null_output: ClassInstanceRef = None.into(); + let result = if descriptor.ends_with(";Z)V") { + jvm.new_class("java/io/PrintStream", descriptor, (null_output, true)).await + } else { + jvm.new_class("java/io/PrintStream", descriptor, (null_output,)).await + }; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{descriptor} null output must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + + Ok(()) +} + +#[tokio::test] +async fn ps_02_auto_flush_matches_public_write_and_println_contracts() -> Result<()> { + let jvm = probe_jvm().await?; + let output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;Z)V", (output.clone(), true)) + .await?; + + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", ('x' as i32,)).await?; + assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 0); + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", ('\n' as i32,)).await?; + assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 1); + + let mut bytes = jvm.instantiate_array("B", 2).await?; + jvm.store_array(&mut bytes, 0, [b'a' as i8, b'b' as i8]).await?; + let _: () = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes, 0, 2)).await?; + assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 2); + + let text = JavaLangString::from_rust_string(&jvm, "s").await?; + let object: ClassInstanceRef = text.clone().into(); + let mut chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut chars, 0, ['c' as JavaChar]).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(Ljava/lang/Object;)V", (object,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(Ljava/lang/String;)V", (text,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(I)V", (1,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(J)V", (2i64,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(C)V", ('d' as JavaChar,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "([C)V", (chars,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(B)V", (3i8,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(S)V", (4i16,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(Z)V", (true,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(F)V", (1.5f32,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(D)V", (2.5f64,)).await?; + assert!(jvm.get_field::(&output, "flushCount", "I").await? >= 14); + + let output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + let mut bytes = jvm.instantiate_array("B", 1).await?; + jvm.store_array(&mut bytes, 0, [b'\n' as i8]).await?; + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", ('\n' as i32,)).await?; + let _: () = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes, 0, 1)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "()V", ()).await?; + assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 0); + assert!(!jvm.invoke_virtual::<_, bool>(&stream, "checkError", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 1); + + Ok(()) +} + +#[tokio::test] +async fn ps_02_uses_default_encoding_split_surrogate_state_and_line_separator() -> Result<()> { + let jvm = test_jvm().await?; + + let encoding_key = JavaLangString::from_rust_string(&jvm, "file.encoding").await?; + let euc_kr = JavaLangString::from_rust_string(&jvm, "EUC-KR").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (encoding_key, euc_kr), + ) + .await?; + let separator_key = JavaLangString::from_rust_string(&jvm, "line.separator").await?; + let separator = JavaLangString::from_rust_string(&jvm, "\r\n").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (separator_key, separator), + ) + .await?; + + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + let value = JavaLangString::from_rust_string(&jvm, "ê°€").await?; + let _: () = jvm.invoke_virtual(&stream, "print", "(Ljava/lang/String;)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "()V", ()).await?; + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, jvm.array_length(&bytes).await?).await?, + [0xb0u8 as i8, 0xa1u8 as i8, b'\r' as i8, b'\n' as i8] + ); + + let encoding_key = JavaLangString::from_rust_string(&jvm, "file.encoding").await?; + let utf8 = JavaLangString::from_rust_string(&jvm, "UTF-8").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (encoding_key, utf8), + ) + .await?; + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&stream, "print", "(C)V", (0xd83d as JavaChar,)).await?; + let pending: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.array_length(&pending).await?, 0); + let _: () = jvm.invoke_virtual(&stream, "print", "(C)V", (0xde00 as JavaChar,)).await?; + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, jvm.array_length(&bytes).await?).await?, + [0xf0u8 as i8, 0x9fu8 as i8, 0x98u8 as i8, 0x80u8 as i8] + ); + + Ok(()) +} + +#[tokio::test] +async fn ps_02_typed_println_is_atomic_and_preserves_virtual_dispatch() -> Result<()> { + let jvm = probe_jvm().await?; + + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let stream = jvm + .new_class("OverridePrintStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(I)V", (7,)).await?; + assert_eq!(jvm.get_field::(&stream, "printCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&stream, "printlnCount", "I").await?, 0); + assert!(jvm.get_field::(&stream, "writeCount", "I").await? >= 2); + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, jvm.array_length(&bytes).await?).await?, + b"<7>\n".iter().map(|value| *value as i8).collect::>() + ); + + let mut blocking_output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + jvm.put_field(&mut blocking_output, "blockFirstWrite", "Z", true).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (blocking_output.clone(),)) + .await? + .into(); + let first_runner = jvm + .new_class("PrintStreamRunner", "(Ljava/io/PrintStream;I)V", (stream.clone(), 1)) + .await?; + let second_runner = jvm + .new_class("PrintStreamRunner", "(Ljava/io/PrintStream;I)V", (stream.clone(), 2)) + .await?; + let first_thread = jvm + .new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (first_runner.clone(),)) + .await?; + let second_thread = jvm + .new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (second_runner.clone(),)) + .await?; + + let _: () = jvm.invoke_virtual(&first_thread, "start", "()V", ()).await?; + let mut first_write_entered = false; + for _ in 0..1000 { + first_write_entered = jvm.get_field::(&blocking_output, "firstWriteEntered", "Z").await?; + if first_write_entered { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + assert!(first_write_entered, "first println did not reach its backing write"); + + let _: () = jvm.invoke_virtual(&second_thread, "start", "()V", ()).await?; + let mut second_started = false; + for _ in 0..1000 { + second_started = jvm.get_field::(&second_runner, "started", "Z").await?; + if second_started { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + tokio::time::sleep(Duration::from_millis(10)).await; + assert!(second_started, "second println worker did not start"); + assert_eq!(jvm.get_field::(&blocking_output, "writeCount", "I").await?, 1); + assert!(!jvm.get_field::(&second_runner, "done", "Z").await?); + + jvm.put_field(&mut blocking_output, "releaseFirstWrite", "Z", true).await?; + let _: () = jvm.invoke_virtual(&first_thread, "join", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&second_thread, "join", "()V", ()).await?; + let content: ClassInstanceRef = jvm.get_field(&blocking_output, "content", "Ljava/io/ByteArrayOutputStream;").await?; + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&content, "toByteArray", "()[B", ()).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, jvm.array_length(&bytes).await?).await?, + b"1\n2\n".iter().map(|value| *value as i8).collect::>() + ); + + Ok(()) +} + +#[tokio::test] +async fn ps_02_write_and_close_are_serialized_on_the_stream_monitor() -> Result<()> { + let jvm = probe_jvm().await?; + let mut output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + jvm.put_field(&mut output, "blockFirstWrite", "Z", true).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await? + .into(); + let writer = jvm + .new_class("PrintStreamRunner", "(Ljava/io/PrintStream;I)V", (stream.clone(), 1)) + .await?; + let closer = jvm + .new_class("PrintStreamCloseRunner", "(Ljava/io/PrintStream;)V", (stream.clone(),)) + .await?; + let writer_thread = jvm.new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (writer,)).await?; + let close_thread = jvm.new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (closer.clone(),)).await?; + + let _: () = jvm.invoke_virtual(&writer_thread, "start", "()V", ()).await?; + let mut first_write_entered = false; + for _ in 0..1000 { + first_write_entered = jvm.get_field::(&output, "firstWriteEntered", "Z").await?; + if first_write_entered { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + assert!(first_write_entered); + + let _: () = jvm.invoke_virtual(&close_thread, "start", "()V", ()).await?; + let mut close_started = false; + for _ in 0..1000 { + close_started = jvm.get_field::(&closer, "started", "Z").await?; + if close_started { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + tokio::time::sleep(Duration::from_millis(10)).await; + assert!(close_started); + assert_eq!(jvm.get_field::(&output, "closeCount", "I").await?, 0); + assert!(!jvm.get_field::(&closer, "done", "Z").await?); + + jvm.put_field(&mut output, "releaseFirstWrite", "Z", true).await?; + let _: () = jvm.invoke_virtual(&writer_thread, "join", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&close_thread, "join", "()V", ()).await?; + assert_eq!(jvm.get_field::(&output, "closeCount", "I").await?, 1); + let content: ClassInstanceRef = jvm.get_field(&output, "content", "Ljava/io/ByteArrayOutputStream;").await?; + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&content, "toByteArray", "()[B", ()).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, jvm.array_length(&bytes).await?).await?, + b"1\n".iter().map(|value| *value as i8).collect::>() + ); + + Ok(()) +} + +#[tokio::test] +async fn ps_02_failure_phases_continue_like_jdk_and_nested_error_is_visible() -> Result<()> { + let jvm = probe_jvm().await?; + + let value_failure = jvm.new_class("ProbeOutputStream", "(III)V", (3, 0, 0)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (value_failure.clone(),)) + .await? + .into(); + let value = JavaLangString::from_rust_string(&jvm, "V").await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(Ljava/lang/String;)V", (value,)).await?; + assert_eq!(jvm.get_field::(&value_failure, "writeCount", "I").await?, 2); + let content: ClassInstanceRef = jvm.get_field(&value_failure, "content", "Ljava/io/ByteArrayOutputStream;").await?; + let bytes: ClassInstanceRef> = jvm.invoke_virtual(&content, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.load_array::(&bytes, 0, 1).await?, [b'\n' as i8]); + assert!(jvm.invoke_virtual::<_, bool>(&stream, "checkError", "()Z", ()).await?); + assert_monitor_released(&jvm, &stream).await?; + + let newline_failure = jvm.new_class("ProbeOutputStream", "(III)V", (4, 0, 0)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;Z)V", (newline_failure.clone(), true)) + .await? + .into(); + let value = JavaLangString::from_rust_string(&jvm, "V").await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(Ljava/lang/String;)V", (value,)).await?; + assert_eq!(jvm.get_field::(&newline_failure, "writeCount", "I").await?, 2); + assert_eq!(jvm.get_field::(&newline_failure, "flushCount", "I").await?, 2); + assert!(jvm.invoke_virtual::<_, bool>(&stream, "checkError", "()Z", ()).await?); + + let final_flush_failure = jvm.new_class("ProbeOutputStream", "(III)V", (0, 3, 0)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;Z)V", (final_flush_failure.clone(), true)) + .await? + .into(); + let value = JavaLangString::from_rust_string(&jvm, "V").await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(Ljava/lang/String;)V", (value,)).await?; + assert_eq!(jvm.get_field::(&final_flush_failure, "flushCount", "I").await?, 3); + assert!(jvm.get_field::(&stream, "trouble", "Z").await?); + + let nested_failure = jvm.new_class("ProbeOutputStream", "(III)V", (0, 1, 0)).await?; + let inner: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (nested_failure,)) + .await? + .into(); + let outer = jvm.new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (inner,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&outer, "checkError", "()Z", ()).await?); + + Ok(()) +} + +#[tokio::test] +async fn ps_02_suppresses_only_ioexception_and_closes_once() -> Result<()> { + let jvm = probe_jvm().await?; + + let io_failure = jvm.new_class("ProbeOutputStream", "(III)V", (1, 0, 0)).await?; + let stream = jvm.new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (io_failure,)).await?; + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (1,)).await?; + assert!(jvm.get_field::(&stream, "trouble", "Z").await?); + let stream: ClassInstanceRef = stream.into(); + assert_monitor_released(&jvm, &stream).await?; + + let runtime_failure = jvm.new_class("ProbeOutputStream", "(III)V", (2, 0, 0)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (runtime_failure,)) + .await? + .into(); + let result: Result<()> = jvm.invoke_virtual(&stream, "write", "(I)V", (1,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("non-IOException from write must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert!(!jvm.get_field::(&stream, "trouble", "Z").await?); + assert_monitor_released(&jvm, &stream).await?; + + let clean_output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + let stream = jvm.new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (clean_output,)).await?; + let null_bytes: ClassInstanceRef> = None.into(); + let result: Result<()> = jvm.invoke_virtual(&stream, "write", "([BII)V", (null_bytes, 0, 0)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null byte array must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let bytes = jvm.instantiate_array("B", 1).await?; + let result: Result<()> = jvm.invoke_virtual(&stream, "write", "([BII)V", (bytes, 1, 1)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid byte range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + assert!(!jvm.get_field::(&stream, "trouble", "Z").await?); + + let flush_failure = jvm.new_class("ProbeOutputStream", "(III)V", (0, 1, 0)).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (flush_failure,)) + .await?; + let _: () = jvm.invoke_virtual(&stream, "flush", "()V", ()).await?; + assert!(jvm.get_field::(&stream, "trouble", "Z").await?); + + let runtime_flush_failure = jvm.new_class("ProbeOutputStream", "(III)V", (0, 2, 0)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (runtime_flush_failure,)) + .await? + .into(); + let result: Result<()> = jvm.invoke_virtual(&stream, "flush", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("non-IOException from flush must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_monitor_released(&jvm, &stream).await?; + let result: Result = jvm.invoke_virtual(&stream, "checkError", "()Z", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("checkError must propagate a non-IOException from flush"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_monitor_released(&jvm, &stream).await?; + + let close_failure = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 1)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (close_failure.clone(),)) + .await? + .into(); + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&close_failure, "closeCount", "I").await?, 1); + assert!(jvm.get_field::(&stream, "trouble", "Z").await?); + assert!(jvm.get_field::(&stream, "closing", "Z").await?); + let closed_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert!(closed_output.is_null()); + let closed_writer: ClassInstanceRef = jvm.get_field(&stream, "charOut", "Ljava/io/OutputStreamWriter;").await?; + assert!(closed_writer.is_null()); + assert_monitor_released(&jvm, &stream).await?; + + let mut runtime_close_failure = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 2)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (runtime_close_failure.clone(),)) + .await? + .into(); + let result: Result<()> = jvm.invoke_virtual(&stream, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("non-IOException from close must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&runtime_close_failure, "closeCount", "I").await?, 1); + let stored_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert_eq!(stored_output.identity(), runtime_close_failure.identity()); + let stored_writer: ClassInstanceRef = jvm.get_field(&stream, "charOut", "Ljava/io/OutputStreamWriter;").await?; + assert!(!stored_writer.is_null()); + assert!(jvm.get_field::(&stream, "closing", "Z").await?); + assert!(!jvm.get_field::(&stream, "trouble", "Z").await?); + assert_monitor_released(&jvm, &stream).await?; + jvm.put_field(&mut runtime_close_failure, "closeMode", "I", 0).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&runtime_close_failure, "closeCount", "I").await?, 1); + let stored_output: ClassInstanceRef = jvm.get_field(&stream, "out", "Ljava/io/OutputStream;").await?; + assert_eq!(stored_output.identity(), runtime_close_failure.identity()); + assert_monitor_released(&jvm, &stream).await?; + + let mut encoder_close_failure = jvm.new_class("ProbeOutputStream", "(III)V", (2, 0, 0)).await?; + let stream: ClassInstanceRef = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (encoder_close_failure.clone(),)) + .await? + .into(); + let _: () = jvm.invoke_virtual(&stream, "print", "(C)V", (0xd83d as JavaChar,)).await?; + let result: Result<()> = jvm.invoke_virtual(&stream, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("non-IOException from encoder close must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&encoder_close_failure, "writeCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&encoder_close_failure, "closeCount", "I").await?, 0); + assert!(jvm.get_field::(&stream, "closing", "Z").await?); + assert!(!jvm.get_field::(&stream, "trouble", "Z").await?); + assert_monitor_released(&jvm, &stream).await?; + jvm.put_field(&mut encoder_close_failure, "writeMode", "I", 0).await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&encoder_close_failure, "writeCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&encoder_close_failure, "closeCount", "I").await?, 0); + assert_monitor_released(&jvm, &stream).await?; + + let closed_output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (closed_output.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; + assert!(!jvm.invoke_virtual::<_, bool>(&stream, "checkError", "()Z", ()).await?); + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", (1,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&stream, "checkError", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&closed_output, "writeCount", "I").await?, 0); + + Ok(()) +} #[tokio::test] async fn test_print_stream_cldc_api() -> Result<()> { diff --git a/java_runtime/tests/classes/java/io/test_print_writer.rs b/java_runtime/tests/classes/java/io/test_print_writer.rs index 188070c2..027a928d 100644 --- a/java_runtime/tests/classes/java/io/test_print_writer.rs +++ b/java_runtime/tests/classes/java/io/test_print_writer.rs @@ -1,25 +1,796 @@ -use java_runtime::classes::java::lang::String; -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use alloc::{boxed::Box, collections::BTreeMap, format, vec, vec::Vec}; +use core::time::Duration; -use test_utils::test_jvm; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{OutputStream, PrintWriter, Writer}, + lang::{Object, String, StringBuffer}, + }, +}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; + +struct ProbeWriter; + +impl ProbeWriter { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ProbeWriter", + parent_class: Some("java/io/Writer"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(III)V", Self::init, Default::default()), + JavaMethodProto::new("write", "([CII)V", Self::write, Default::default()), + JavaMethodProto::new("flush", "()V", Self::flush, Default::default()), + JavaMethodProto::new("close", "()V", Self::close, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("content", "Ljava/lang/StringBuffer;", Default::default()), + JavaFieldProto::new("writeMode", "I", Default::default()), + JavaFieldProto::new("flushMode", "I", Default::default()), + JavaFieldProto::new("closeMode", "I", Default::default()), + JavaFieldProto::new("writeCount", "I", Default::default()), + JavaFieldProto::new("flushCount", "I", Default::default()), + JavaFieldProto::new("closeCount", "I", Default::default()), + JavaFieldProto::new("blockFirstWrite", "Z", Default::default()), + JavaFieldProto::new("firstWriteEntered", "Z", Default::default()), + JavaFieldProto::new("releaseFirstWrite", "Z", Default::default()), + JavaFieldProto::new("blockFirstClose", "Z", Default::default()), + JavaFieldProto::new("firstCloseEntered", "Z", Default::default()), + JavaFieldProto::new("releaseFirstClose", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + write_mode: i32, + flush_mode: i32, + close_mode: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/io/Writer", "", "()V", ()).await?; + let content = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?; + jvm.put_field(&mut this, "content", "Ljava/lang/StringBuffer;", content).await?; + jvm.put_field(&mut this, "writeMode", "I", write_mode).await?; + jvm.put_field(&mut this, "flushMode", "I", flush_mode).await?; + jvm.put_field(&mut this, "closeMode", "I", close_mode).await?; + jvm.put_field(&mut this, "writeCount", "I", 0).await?; + jvm.put_field(&mut this, "flushCount", "I", 0).await?; + jvm.put_field(&mut this, "closeCount", "I", 0).await?; + jvm.put_field(&mut this, "blockFirstWrite", "Z", false).await?; + jvm.put_field(&mut this, "firstWriteEntered", "Z", false).await?; + jvm.put_field(&mut this, "releaseFirstWrite", "Z", false).await?; + jvm.put_field(&mut this, "blockFirstClose", "Z", false).await?; + jvm.put_field(&mut this, "firstCloseEntered", "Z", false).await?; + jvm.put_field(&mut this, "releaseFirstClose", "Z", false).await + } + + async fn write( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + chars: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result<()> { + let write_count: i32 = jvm.get_field(&this, "writeCount", "I").await?; + jvm.put_field(&mut this, "writeCount", "I", write_count + 1).await?; + match jvm.get_field::(&this, "writeMode", "I").await? { + 1 => return Err(jvm.exception("java/io/IOException", "write failed").await), + 2 => return Err(jvm.exception("java/lang/IllegalStateException", "write failed").await), + _ => {} + } + if write_count == 0 && jvm.get_field::(&this, "blockFirstWrite", "Z").await? { + jvm.put_field(&mut this, "firstWriteEntered", "Z", true).await?; + for _ in 0..1000 { + if jvm.get_field::(&this, "releaseFirstWrite", "Z").await? { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + if !jvm.get_field::(&this, "releaseFirstWrite", "Z").await? { + return Err(jvm + .exception("java/lang/IllegalStateException", "timed out waiting for write release") + .await); + } + } + + let content: ClassInstanceRef = jvm.get_field(&this, "content", "Ljava/lang/StringBuffer;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&content, "append", "([CII)Ljava/lang/StringBuffer;", (chars, offset, length)) + .await?; + Ok(()) + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let flush_count: i32 = jvm.get_field(&this, "flushCount", "I").await?; + jvm.put_field(&mut this, "flushCount", "I", flush_count + 1).await?; + match jvm.get_field::(&this, "flushMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "flush failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "flush failed").await), + _ => Ok(()), + } + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let close_count: i32 = jvm.get_field(&this, "closeCount", "I").await?; + jvm.put_field(&mut this, "closeCount", "I", close_count + 1).await?; + if close_count == 0 && jvm.get_field::(&this, "blockFirstClose", "Z").await? { + jvm.put_field(&mut this, "firstCloseEntered", "Z", true).await?; + for _ in 0..1000 { + if jvm.get_field::(&this, "releaseFirstClose", "Z").await? { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + if !jvm.get_field::(&this, "releaseFirstClose", "Z").await? { + return Err(jvm + .exception("java/lang/IllegalStateException", "timed out waiting for close release") + .await); + } + } + match jvm.get_field::(&this, "closeMode", "I").await? { + 1 => Err(jvm.exception("java/io/IOException", "close failed").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "close failed").await), + _ => Ok(()), + } + } +} + +struct OverridePrintWriter; + +impl OverridePrintWriter { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "OverridePrintWriter", + parent_class: Some("java/io/PrintWriter"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/Writer;)V", Self::init, Default::default()), + JavaMethodProto::new("print", "(I)V", Self::print_int, Default::default()), + JavaMethodProto::new("println", "()V", Self::println, Default::default()), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, writer: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/Writer;)V", (writer,)) + .await + } + + async fn print_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result<()> { + let value = JavaLangString::from_rust_string(jvm, &format!("<{value}>")).await?; + jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;)V", (value,)).await + } + + async fn println(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let value = JavaLangString::from_rust_string(jvm, "").await?; + jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;)V", (value,)).await + } +} + +struct PrintWriterRunner; + +impl PrintWriterRunner { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "PrintWriterRunner", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Runnable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/PrintWriter;I)V", Self::init, Default::default()), + JavaMethodProto::new("run", "()V", Self::run, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("writer", "Ljava/io/PrintWriter;", Default::default()), + JavaFieldProto::new("value", "I", Default::default()), + JavaFieldProto::new("started", "Z", Default::default()), + JavaFieldProto::new("done", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + writer: ClassInstanceRef, + value: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "writer", "Ljava/io/PrintWriter;", writer).await?; + jvm.put_field(&mut this, "value", "I", value).await?; + jvm.put_field(&mut this, "started", "Z", false).await?; + jvm.put_field(&mut this, "done", "Z", false).await + } + + async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "started", "Z", true).await?; + let writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/PrintWriter;").await?; + let value: i32 = jvm.get_field(&this, "value", "I").await?; + let _: () = jvm.invoke_virtual(&writer, "println", "(I)V", (value,)).await?; + jvm.put_field(&mut this, "done", "Z", true).await + } +} + +struct PrintWriterCloseRunner; + +impl PrintWriterCloseRunner { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "PrintWriterCloseRunner", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Runnable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/io/PrintWriter;)V", Self::init, Default::default()), + JavaMethodProto::new("run", "()V", Self::run, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("writer", "Ljava/io/PrintWriter;", Default::default()), + JavaFieldProto::new("started", "Z", Default::default()), + JavaFieldProto::new("done", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, writer: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "writer", "Ljava/io/PrintWriter;", writer).await?; + jvm.put_field(&mut this, "started", "Z", false).await?; + jvm.put_field(&mut this, "done", "Z", false).await + } + + async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "started", "Z", true).await?; + let writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/PrintWriter;").await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + jvm.put_field(&mut this, "done", "Z", true).await + } +} + +async fn probe_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + ProbeWriter::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + OverridePrintWriter::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + PrintWriterRunner::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + PrintWriterCloseRunner::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + Ok(jvm) +} + +async fn assert_monitor_released(jvm: &Jvm, writer: &ClassInstanceRef) -> Result<()> { + jvm.monitor_enter(writer).await?; + jvm.monitor_exit(writer).await?; + let result = jvm.monitor_exit(writer).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("PrintWriter leaked a reentrant Writer.lock acquisition"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalMonitorStateException")); + Ok(()) +} + +#[tokio::test] +async fn pw_01_constructors_fields_descriptors_and_access_flags() -> Result<()> { + let proto = PrintWriter::as_proto(); + assert_eq!(proto.parent_class, Some("java/io/Writer")); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC)); + + let expected_methods = [ + ("", "(Ljava/io/Writer;)V"), + ("", "(Ljava/io/Writer;Z)V"), + ("", "(Ljava/io/OutputStream;)V"), + ("", "(Ljava/io/OutputStream;Z)V"), + ("write", "(I)V"), + ("write", "([C)V"), + ("write", "([CII)V"), + ("write", "(Ljava/lang/String;)V"), + ("write", "(Ljava/lang/String;II)V"), + ("print", "(Z)V"), + ("print", "(C)V"), + ("print", "(I)V"), + ("print", "(J)V"), + ("print", "(F)V"), + ("print", "(D)V"), + ("print", "([C)V"), + ("print", "(Ljava/lang/String;)V"), + ("print", "(Ljava/lang/Object;)V"), + ("println", "()V"), + ("println", "(Z)V"), + ("println", "(C)V"), + ("println", "(I)V"), + ("println", "(J)V"), + ("println", "(F)V"), + ("println", "(D)V"), + ("println", "([C)V"), + ("println", "(Ljava/lang/String;)V"), + ("println", "(Ljava/lang/Object;)V"), + ("flush", "()V"), + ("close", "()V"), + ("checkError", "()Z"), + ]; + assert_eq!(proto.methods.len(), expected_methods.len()); + for (name, descriptor) in expected_methods { + let methods = proto + .methods + .iter() + .filter(|method| method.name == name && method.descriptor == descriptor) + .collect::>(); + assert_eq!(methods.len(), 1, "missing or duplicated {name}{descriptor}"); + assert!(methods[0].access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + assert_eq!(proto.fields.len(), 3); + let out = proto.fields.iter().find(|field| field.name == "out").expect("out field"); + assert_eq!(out.descriptor, "Ljava/io/Writer;"); + assert_eq!(out.access_flags, FieldAccessFlags::PROTECTED); + let auto_flush = proto.fields.iter().find(|field| field.name == "autoFlush").expect("autoFlush field"); + assert_eq!(auto_flush.descriptor, "Z"); + assert_eq!(auto_flush.access_flags, FieldAccessFlags::PRIVATE); + let trouble = proto.fields.iter().find(|field| field.name == "trouble").expect("trouble field"); + assert_eq!(trouble.descriptor, "Z"); + assert_eq!(trouble.access_flags, FieldAccessFlags::PRIVATE); + + let jvm = probe_jvm().await?; + let writer: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + let default_writer = jvm.new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (writer.clone(),)).await?; + assert!(!jvm.get_field::(&default_writer, "autoFlush", "Z").await?); + assert!(!jvm.get_field::(&default_writer, "trouble", "Z").await?); + let lock: ClassInstanceRef = jvm.get_field(&default_writer, "lock", "Ljava/lang/Object;").await?; + assert_eq!(lock.identity(), writer.identity()); + let stored_out: ClassInstanceRef = jvm.get_field(&default_writer, "out", "Ljava/io/Writer;").await?; + assert_eq!(stored_out.identity(), writer.identity()); + let configured_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;Z)V", (writer.clone(), true)) + .await?; + assert!(jvm.get_field::(&configured_writer, "autoFlush", "Z").await?); + + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let output_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/OutputStream;Z)V", (output.clone(), true)) + .await?; + assert!(jvm.get_field::(&output_writer, "autoFlush", "Z").await?); + let default_output_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await?; + assert!(!jvm.get_field::(&default_output_writer, "autoFlush", "Z").await?); + let output_text = JavaLangString::from_rust_string(&jvm, "os").await?; + let _: () = jvm + .invoke_virtual(&default_output_writer, "write", "(Ljava/lang/String;)V", (output_text,)) + .await?; + let _: () = jvm.invoke_virtual(&default_output_writer, "flush", "()V", ()).await?; + let output_bytes: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + assert_eq!(jvm.load_array::(&output_bytes, 0, 2).await?, [b'o' as i8, b's' as i8]); + + let null_writer: ClassInstanceRef = None.into(); + let null_writer_result = jvm.new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (null_writer,)).await; + let Err(JavaError::JavaException(exception)) = null_writer_result else { + panic!("null Writer must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_output: ClassInstanceRef = None.into(); + let null_output_result = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/OutputStream;Z)V", (null_output, false)) + .await; + let Err(JavaError::JavaException(exception)) = null_output_result else { + panic!("null OutputStream must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn pw_02_write_overloads_suppress_only_ioexception() -> Result<()> { + let jvm = probe_jvm().await?; + let string_writer = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (string_writer.clone(),)) + .await?; + + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('A' as i32,)).await?; + let mut chars = jvm.instantiate_array("C", 3).await?; + jvm.store_array(&mut chars, 0, ['B' as JavaChar, 'C' as JavaChar, 'D' as JavaChar]) + .await?; + let _: () = jvm.invoke_virtual(&writer, "write", "([C)V", (chars.clone(),)).await?; + let _: () = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars.clone(), 1, 1)).await?; + let text = JavaLangString::from_rust_string(&jvm, "EFG").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (text.clone(),)).await?; + let _: () = jvm + .invoke_virtual(&writer, "write", "(Ljava/lang/String;II)V", (text.clone(), 1, 1)) + .await?; + let result: ClassInstanceRef = jvm.invoke_virtual(&string_writer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "ABCDCEFGF"); + + let invalid_chars: Result<()> = jvm.invoke_virtual(&writer, "write", "([CII)V", (chars.clone(), -1, 1)).await; + let Err(JavaError::JavaException(exception)) = invalid_chars else { + panic!("invalid char range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + let invalid_string: Result<()> = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;II)V", (text, 2, 2)).await; + let Err(JavaError::JavaException(exception)) = invalid_string else { + panic!("invalid String range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + assert!(!jvm.get_field::(&writer, "trouble", "Z").await?); + + let null_chars: ClassInstanceRef> = None.into(); + let null_chars_result: Result<()> = jvm.invoke_virtual(&writer, "write", "([C)V", (null_chars,)).await; + let Err(JavaError::JavaException(exception)) = null_chars_result else { + panic!("null char array must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let null_string: ClassInstanceRef = None.into(); + let null_string_result: Result<()> = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (null_string,)).await; + let Err(JavaError::JavaException(exception)) = null_string_result else { + panic!("null String must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let failing_writer: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (1, 0, 0)).await?.into(); + let suppressing = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (failing_writer.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&suppressing, "write", "(I)V", ('x' as i32,)).await?; + assert!(jvm.get_field::(&suppressing, "trouble", "Z").await?); + assert_monitor_released(&jvm, &failing_writer).await?; + + let runtime_writer: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (2, 0, 0)).await?.into(); + let propagating = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (runtime_writer.clone(),)) + .await?; + let runtime_result: Result<()> = jvm.invoke_virtual(&propagating, "write", "(I)V", ('x' as i32,)).await; + let Err(JavaError::JavaException(exception)) = runtime_result else { + panic!("user RuntimeException must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert!(!jvm.get_field::(&propagating, "trouble", "Z").await?); + assert_monitor_released(&jvm, &runtime_writer).await?; + + Ok(()) +} #[tokio::test] -async fn test_print_writer() -> Result<()> { +async fn pw_03_print_overloads_follow_string_value_of_and_null_rules() -> Result<()> { let jvm = test_jvm().await?; + let string_writer = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (string_writer.clone(),)) + .await?; + + let _: () = jvm.invoke_virtual(&writer, "print", "(Z)V", (true,)).await?; + let _: () = jvm.invoke_virtual(&writer, "print", "(C)V", ('|' as JavaChar,)).await?; + let _: () = jvm.invoke_virtual(&writer, "print", "(I)V", (-2,)).await?; + let _: () = jvm.invoke_virtual(&writer, "print", "(J)V", (3i64,)).await?; + let _: () = jvm.invoke_virtual(&writer, "print", "(F)V", (1.5f32,)).await?; + let _: () = jvm.invoke_virtual(&writer, "print", "(D)V", (f64::INFINITY,)).await?; + let mut chars = jvm.instantiate_array("C", 2).await?; + jvm.store_array(&mut chars, 0, ['X' as JavaChar, 'Y' as JavaChar]).await?; + let _: () = jvm.invoke_virtual(&writer, "print", "([C)V", (chars,)).await?; + + let null_string: ClassInstanceRef = None.into(); + let _: () = jvm.invoke_virtual(&writer, "print", "(Ljava/lang/String;)V", (null_string,)).await?; + let object_string = JavaLangString::from_rust_string(&jvm, "obj").await?; + let object: ClassInstanceRef = object_string.into(); + let _: () = jvm.invoke_virtual(&writer, "print", "(Ljava/lang/Object;)V", (object,)).await?; + let null_object: ClassInstanceRef = None.into(); + let _: () = jvm.invoke_virtual(&writer, "print", "(Ljava/lang/Object;)V", (null_object,)).await?; + + let result: ClassInstanceRef = jvm.invoke_virtual(&string_writer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "true|-231.5InfinityXYnullobjnull"); + + Ok(()) +} + +#[tokio::test] +async fn pw_04_println_uses_line_separator_and_is_the_only_autoflush_path() -> Result<()> { + let jvm = probe_jvm().await?; + let key = JavaLangString::from_rust_string(&jvm, "line.separator").await?; + let separator = JavaLangString::from_rust_string(&jvm, "\r\n").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key, separator), + ) + .await?; + + let probe: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + let writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;Z)V", (probe.clone(), true)) + .await?; + let prefix = JavaLangString::from_rust_string(&jvm, "p").await?; + let _: () = jvm.invoke_virtual(&writer, "print", "(Ljava/lang/String;)V", (prefix,)).await?; + assert_eq!(jvm.get_field::(&probe, "flushCount", "I").await?, 0); + let newline = JavaLangString::from_rust_string(&jvm, "\n").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (newline,)).await?; + assert_eq!(jvm.get_field::(&probe, "flushCount", "I").await?, 0); + let _: () = jvm.invoke_virtual(&writer, "println", "(I)V", (7,)).await?; + assert_eq!(jvm.get_field::(&probe, "flushCount", "I").await?, 1); + let _: () = jvm.invoke_virtual(&writer, "println", "()V", ()).await?; + assert_eq!(jvm.get_field::(&probe, "flushCount", "I").await?, 2); + + let content: ClassInstanceRef = jvm.get_field(&probe, "content", "Ljava/lang/StringBuffer;").await?; + let content: ClassInstanceRef = jvm.invoke_virtual(&content, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &content).await?, "p\n7\r\n\r\n"); + + let no_flush_probe: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + let no_flush_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;Z)V", (no_flush_probe.clone(), false)) + .await?; + let _: () = jvm.invoke_virtual(&no_flush_writer, "println", "()V", ()).await?; + assert_eq!(jvm.get_field::(&no_flush_probe, "flushCount", "I").await?, 0); + + let newline_failure: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (1, 0, 0)).await?.into(); + let newline_failure_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;Z)V", (newline_failure.clone(), true)) + .await?; + let _: () = jvm.invoke_virtual(&newline_failure_writer, "println", "()V", ()).await?; + assert_eq!(jvm.get_field::(&newline_failure, "writeCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&newline_failure, "flushCount", "I").await?, 0); + assert!(jvm.get_field::(&newline_failure_writer, "trouble", "Z").await?); + assert_monitor_released(&jvm, &newline_failure).await?; + + let fallback_jvm = test_jvm().await?; + let fallback_output = fallback_jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let fallback_writer = fallback_jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (fallback_output.clone(),)) + .await?; + let _: () = fallback_jvm.invoke_virtual(&fallback_writer, "println", "()V", ()).await?; + let fallback: ClassInstanceRef = fallback_jvm + .invoke_virtual(&fallback_output, "toString", "()Ljava/lang/String;", ()) + .await?; + assert_eq!(JavaLangString::to_rust_string(&fallback_jvm, &fallback).await?, "\n"); + + Ok(()) +} + +#[tokio::test] +async fn pw_04_println_is_atomic_and_uses_java_virtual_dispatch() -> Result<()> { + let jvm = probe_jvm().await?; + + let dispatch_probe: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + let overriding_writer = jvm + .new_class("OverridePrintWriter", "(Ljava/io/Writer;)V", (dispatch_probe.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&overriding_writer, "println", "(I)V", (7,)).await?; + let dispatch_content: ClassInstanceRef = jvm.get_field(&dispatch_probe, "content", "Ljava/lang/StringBuffer;").await?; + let dispatch_content: ClassInstanceRef = jvm.invoke_virtual(&dispatch_content, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &dispatch_content).await?, "<7>"); + + let mut blocking_probe: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + jvm.put_field(&mut blocking_probe, "blockFirstWrite", "Z", true).await?; + let writer: ClassInstanceRef = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (blocking_probe.clone(),)) + .await? + .into(); + let first_runner = jvm + .new_class("PrintWriterRunner", "(Ljava/io/PrintWriter;I)V", (writer.clone(), 1)) + .await?; + let second_runner = jvm.new_class("PrintWriterRunner", "(Ljava/io/PrintWriter;I)V", (writer, 2)).await?; + let first_thread = jvm + .new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (first_runner.clone(),)) + .await?; + let second_thread = jvm + .new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (second_runner.clone(),)) + .await?; + + let _: () = jvm.invoke_virtual(&first_thread, "start", "()V", ()).await?; + let mut first_write_entered = false; + for _ in 0..100 { + first_write_entered = jvm.get_field::(&blocking_probe, "firstWriteEntered", "Z").await?; + if first_write_entered { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + assert!(first_write_entered, "first println did not reach its backing write"); + + let _: () = jvm.invoke_virtual(&second_thread, "start", "()V", ()).await?; + let mut second_started = false; + for _ in 0..100 { + second_started = jvm.get_field::(&second_runner, "started", "Z").await?; + if second_started { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + tokio::time::sleep(Duration::from_millis(10)).await; + assert!(second_started, "second println worker did not start"); + assert_eq!( + jvm.get_field::(&blocking_probe, "writeCount", "I").await?, + 1, + "second println entered the backing writer before the first line completed" + ); + assert!(!jvm.get_field::(&second_runner, "done", "Z").await?); + + jvm.put_field(&mut blocking_probe, "releaseFirstWrite", "Z", true).await?; + let _: () = jvm.invoke_virtual(&first_thread, "join", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&second_thread, "join", "()V", ()).await?; + assert!(jvm.get_field::(&first_runner, "done", "Z").await?); + assert!(jvm.get_field::(&second_runner, "done", "Z").await?); + + let content: ClassInstanceRef = jvm.get_field(&blocking_probe, "content", "Ljava/lang/StringBuffer;").await?; + let content: ClassInstanceRef = jvm.invoke_virtual(&content, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &content).await?, "1\n2\n"); + + let mut close_probe: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + jvm.put_field(&mut close_probe, "blockFirstClose", "Z", true).await?; + let close_writer: ClassInstanceRef = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (close_probe.clone(),)) + .await? + .into(); + let first_close_runner = jvm + .new_class("PrintWriterCloseRunner", "(Ljava/io/PrintWriter;)V", (close_writer.clone(),)) + .await?; + let second_close_runner = jvm + .new_class("PrintWriterCloseRunner", "(Ljava/io/PrintWriter;)V", (close_writer.clone(),)) + .await?; + let first_close_thread = jvm + .new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (first_close_runner.clone(),)) + .await?; + let second_close_thread = jvm + .new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (second_close_runner.clone(),)) + .await?; + + let _: () = jvm.invoke_virtual(&first_close_thread, "start", "()V", ()).await?; + let mut first_close_entered = false; + for _ in 0..100 { + first_close_entered = jvm.get_field::(&close_probe, "firstCloseEntered", "Z").await?; + if first_close_entered { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + assert!(first_close_entered, "first close did not reach the backing writer"); + + let _: () = jvm.invoke_virtual(&second_close_thread, "start", "()V", ()).await?; + let mut second_close_started = false; + for _ in 0..100 { + second_close_started = jvm.get_field::(&second_close_runner, "started", "Z").await?; + if second_close_started { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + tokio::time::sleep(Duration::from_millis(10)).await; + assert!(second_close_started, "second close worker did not start"); + assert_eq!(jvm.get_field::(&close_probe, "closeCount", "I").await?, 1); + assert!(!jvm.get_field::(&second_close_runner, "done", "Z").await?); + + jvm.put_field(&mut close_probe, "releaseFirstClose", "Z", true).await?; + let _: () = jvm.invoke_virtual(&first_close_thread, "join", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&second_close_thread, "join", "()V", ()).await?; + assert!(jvm.get_field::(&first_close_runner, "done", "Z").await?); + assert!(jvm.get_field::(&second_close_runner, "done", "Z").await?); + assert_eq!(jvm.get_field::(&close_probe, "closeCount", "I").await?, 1); + let closed_out: ClassInstanceRef = jvm.get_field(&close_writer, "out", "Ljava/io/Writer;").await?; + assert!(closed_out.is_null()); + + Ok(()) +} + +#[tokio::test] +async fn pw_05_flush_close_and_check_error_preserve_trouble_state() -> Result<()> { + let jvm = probe_jvm().await?; + + let mut flush_failure: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 1, 0)).await?.into(); + let flush_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (flush_failure.clone(),)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&flush_writer, "checkError", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&flush_failure, "flushCount", "I").await?, 1); + assert_monitor_released(&jvm, &flush_failure).await?; + jvm.put_field(&mut flush_failure, "flushMode", "I", 0).await?; + let _: () = jvm.invoke_virtual(&flush_writer, "flush", "()V", ()).await?; + assert!(jvm.get_field::(&flush_writer, "trouble", "Z").await?); + + let nested_failure: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 1, 0)).await?.into(); + let inner_writer: ClassInstanceRef = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (nested_failure,)) + .await? + .into(); + let outer_writer = jvm.new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (inner_writer,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&outer_writer, "checkError", "()Z", ()).await?); - let sw = jvm.new_class("java/io/StringWriter", "()V", ()).await?; - let pw = jvm.new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (sw.clone(),)).await?; + let mut close_failure: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 1)).await?.into(); + let close_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (close_failure.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&close_writer, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&close_failure, "closeCount", "I").await?, 1); + let open_after_failure: ClassInstanceRef = jvm.get_field(&close_writer, "out", "Ljava/io/Writer;").await?; + assert!(!open_after_failure.is_null()); + assert!(jvm.get_field::(&close_writer, "trouble", "Z").await?); + assert_monitor_released(&jvm, &close_failure).await?; - let hello = JavaLangString::from_rust_string(&jvm, "hello").await?; - let world = JavaLangString::from_rust_string(&jvm, "world").await?; + jvm.put_field(&mut close_failure, "closeMode", "I", 0).await?; + let _: () = jvm.invoke_virtual(&close_writer, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&close_writer, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&close_failure, "closeCount", "I").await?, 2); + assert!(jvm.get_field::(&close_writer, "trouble", "Z").await?); + let closed_out: ClassInstanceRef = jvm.get_field(&close_writer, "out", "Ljava/io/Writer;").await?; + assert!(closed_out.is_null()); - let _: () = jvm.invoke_virtual(&pw, "println", "(Ljava/lang/String;)V", (hello,)).await?; - let _: () = jvm.invoke_virtual(&pw, "println", "(Ljava/lang/String;)V", (world,)).await?; + let closed_probe: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 0)).await?.into(); + let closed_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (closed_probe.clone(),)) + .await?; + let _: () = jvm.invoke_virtual(&closed_writer, "close", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&closed_writer, "write", "(I)V", ('x' as i32,)).await?; + let _: () = jvm.invoke_virtual(&closed_writer, "flush", "()V", ()).await?; + assert!(jvm.invoke_virtual::<_, bool>(&closed_writer, "checkError", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&closed_probe, "writeCount", "I").await?, 0); + assert_eq!(jvm.get_field::(&closed_probe, "flushCount", "I").await?, 0); - let result: ClassInstanceRef = jvm.invoke_virtual(&sw, "toString", "()Ljava/lang/String;", ()).await?; - let result = JavaLangString::to_rust_string(&jvm, &result).await?; + let runtime_flush: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 2, 0)).await?.into(); + let runtime_flush_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (runtime_flush.clone(),)) + .await?; + let runtime_flush_result: Result<()> = jvm.invoke_virtual(&runtime_flush_writer, "flush", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = runtime_flush_result else { + panic!("flush RuntimeException must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_monitor_released(&jvm, &runtime_flush).await?; - assert_eq!(result, "hello\nworld\n"); + let mut runtime_close: ClassInstanceRef = jvm.new_class("ProbeWriter", "(III)V", (0, 0, 2)).await?.into(); + let runtime_close_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (runtime_close.clone(),)) + .await?; + let runtime_close_result: Result<()> = jvm.invoke_virtual(&runtime_close_writer, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = runtime_close_result else { + panic!("close RuntimeException must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + let open_after_runtime: ClassInstanceRef = jvm.get_field(&runtime_close_writer, "out", "Ljava/io/Writer;").await?; + assert!(!open_after_runtime.is_null()); + assert_monitor_released(&jvm, &runtime_close).await?; + jvm.put_field(&mut runtime_close, "closeMode", "I", 0).await?; + let _: () = jvm.invoke_virtual(&runtime_close_writer, "close", "()V", ()).await?; + let closed_after_retry: ClassInstanceRef = jvm.get_field(&runtime_close_writer, "out", "Ljava/io/Writer;").await?; + assert!(closed_after_retry.is_null()); Ok(()) } diff --git a/java_runtime/tests/classes/java/io/test_stream_locking.rs b/java_runtime/tests/classes/java/io/test_stream_locking.rs new file mode 100644 index 00000000..8b603cff --- /dev/null +++ b/java_runtime/tests/classes/java/io/test_stream_locking.rs @@ -0,0 +1,340 @@ +use alloc::{boxed::Box, collections::BTreeMap, vec}; +use core::time::Duration; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{BufferedWriter, Writer}, + lang::Object, + }, +}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; +use test_utils::{TestRuntime, create_test_jvm}; + +struct LockCheckingWriter; + +impl LockCheckingWriter { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "LockCheckingWriter", + parent_class: Some("java/io/Writer"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(IZ)V", Self::init, Default::default()), + JavaMethodProto::new("write", "([CII)V", Self::write, Default::default()), + JavaMethodProto::new("flush", "()V", Self::flush, Default::default()), + JavaMethodProto::new("close", "()V", Self::close, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("failWrites", "I", Default::default()), + JavaFieldProto::new("failClose", "Z", Default::default()), + JavaFieldProto::new("writeCalls", "I", Default::default()), + JavaFieldProto::new("flushCalls", "I", Default::default()), + JavaFieldProto::new("closeCalls", "I", Default::default()), + JavaFieldProto::new("written", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, fail_writes: i32, fail_close: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/io/Writer", "", "()V", ()).await?; + jvm.put_field(&mut this, "failWrites", "I", fail_writes).await?; + jvm.put_field(&mut this, "failClose", "Z", fail_close).await?; + jvm.put_field(&mut this, "writeCalls", "I", 0).await?; + jvm.put_field(&mut this, "flushCalls", "I", 0).await?; + jvm.put_field(&mut this, "closeCalls", "I", 0).await?; + jvm.put_field(&mut this, "written", "I", 0).await + } + + async fn write( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + _: ClassInstanceRef>, + _: i32, + length: i32, + ) -> Result<()> { + jvm.object_notify(&this, 1).await?; + let calls: i32 = jvm.get_field(&this, "writeCalls", "I").await?; + jvm.put_field(&mut this, "writeCalls", "I", calls + 1).await?; + let failures: i32 = jvm.get_field(&this, "failWrites", "I").await?; + if failures > 0 { + jvm.put_field(&mut this, "failWrites", "I", failures - 1).await?; + return Err(jvm.exception("java/io/IOException", "write failed").await); + } + let written: i32 = jvm.get_field(&this, "written", "I").await?; + jvm.put_field(&mut this, "written", "I", written + length).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.object_notify(&this, 1).await?; + let calls: i32 = jvm.get_field(&this, "flushCalls", "I").await?; + jvm.put_field(&mut this, "flushCalls", "I", calls + 1).await + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.object_notify(&this, 1).await?; + let calls: i32 = jvm.get_field(&this, "closeCalls", "I").await?; + jvm.put_field(&mut this, "closeCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "failClose", "Z").await? { + return Err(jvm.exception("java/io/IOException", "close failed").await); + } + Ok(()) + } +} + +struct StreamOperationRunner; + +impl StreamOperationRunner { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "StreamOperationRunner", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Runnable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/Object;I)V", Self::init, Default::default()), + JavaMethodProto::new("run", "()V", Self::run, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("target", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("operation", "I", Default::default()), + JavaFieldProto::new("started", "Z", Default::default()), + JavaFieldProto::new("done", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + target: ClassInstanceRef, + operation: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "target", "Ljava/lang/Object;", target).await?; + jvm.put_field(&mut this, "operation", "I", operation).await?; + jvm.put_field(&mut this, "started", "Z", false).await?; + jvm.put_field(&mut this, "done", "Z", false).await + } + + async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "started", "Z", true).await?; + let target: ClassInstanceRef = jvm.get_field(&this, "target", "Ljava/lang/Object;").await?; + match jvm.get_field::(&this, "operation", "I").await? { + 0 => { + let _: i32 = jvm.invoke_virtual(&target, "read", "()I", ()).await?; + } + 1 => { + let _: () = jvm.invoke_virtual(&target, "write", "(I)V", ('X' as i32,)).await?; + } + 2 => { + let _: () = jvm.invoke_virtual(&target, "close", "()V", ()).await?; + } + _ => { + let _: () = jvm.invoke_virtual(&target, "newLine", "()V", ()).await?; + } + } + jvm.put_field(&mut this, "done", "Z", true).await + } +} + +async fn stream_locking_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + LockCheckingWriter::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + StreamOperationRunner::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + Ok(jvm) +} + +async fn assert_monitor_released(jvm: &Jvm, lock: &ClassInstanceRef) -> Result<()> { + jvm.monitor_enter(lock).await?; + jvm.monitor_exit(lock).await?; + let result = jvm.monitor_exit(lock).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("stream leaked a reentrant inherited-lock acquisition"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalMonitorStateException")); + Ok(()) +} + +async fn assert_worker_waits_for_lock(jvm: &Jvm, target: ClassInstanceRef, lock: ClassInstanceRef, operation: i32) -> Result<()> { + let runner = jvm + .new_class("StreamOperationRunner", "(Ljava/lang/Object;I)V", (target, operation)) + .await?; + let thread = jvm.new_class("java/lang/Thread", "(Ljava/lang/Runnable;)V", (runner.clone(),)).await?; + + jvm.monitor_enter(&lock).await?; + let _: () = jvm.invoke_virtual(&thread, "start", "()V", ()).await?; + let mut started = false; + for _ in 0..100 { + started = jvm.get_field::(&runner, "started", "Z").await?; + if started { + break; + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + tokio::time::sleep(Duration::from_millis(10)).await; + let completed_while_locked = jvm.get_field::(&runner, "done", "Z").await?; + jvm.monitor_exit(&lock).await?; + let _: () = jvm.invoke_virtual(&thread, "join", "()V", ()).await?; + + assert!(started, "worker thread did not start"); + assert!(!completed_while_locked, "state operation did not synchronize on inherited lock"); + assert!(jvm.get_field::(&runner, "done", "Z").await?); + Ok(()) +} + +#[tokio::test] +async fn buffered_writer_preserves_state_and_closes_after_backing_failures() -> Result<()> { + let jvm = stream_locking_jvm().await?; + let mut backing: ClassInstanceRef = jvm.new_class("LockCheckingWriter", "(IZ)V", (1, false)).await?.into(); + let backing_writer: ClassInstanceRef = backing.instance.clone().into(); + let writer: ClassInstanceRef = jvm + .new_class("java/io/BufferedWriter", "(Ljava/io/Writer;I)V", (backing_writer, 4)) + .await? + .into(); + let lock: ClassInstanceRef = jvm.get_field(&writer, "lock", "Ljava/lang/Object;").await?; + assert_eq!(lock.identity(), backing.identity()); + + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('A' as i32,)).await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('B' as i32,)).await?; + let failed_flush: Result<()> = jvm.invoke_virtual(&writer, "flush", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = failed_flush else { + panic!("backing write failure must escape BufferedWriter.flush"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(jvm.get_field::(&writer, "nextChar", "I").await?, 2); + assert_eq!(jvm.get_field::(&backing, "written", "I").await?, 0); + assert_monitor_released(&jvm, &lock).await?; + + let _: () = jvm.invoke_virtual(&writer, "flush", "()V", ()).await?; + assert_eq!(jvm.get_field::(&writer, "nextChar", "I").await?, 0); + assert_eq!(jvm.get_field::(&backing, "written", "I").await?, 2); + + jvm.put_field(&mut backing, "failClose", "Z", true).await?; + let failed_close: Result<()> = jvm.invoke_virtual(&writer, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = failed_close else { + panic!("backing close failure must escape BufferedWriter.close"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + let closed: Result<()> = jvm.invoke_virtual(&writer, "write", "(I)V", ('C' as i32,)).await; + let Err(JavaError::JavaException(exception)) = closed else { + panic!("BufferedWriter must remain closed after a backing close failure"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_monitor_released(&jvm, &lock).await?; + + let backing: ClassInstanceRef = jvm.new_class("LockCheckingWriter", "(IZ)V", (1, false)).await?.into(); + let backing_writer: ClassInstanceRef = backing.instance.clone().into(); + let writer: ClassInstanceRef = jvm + .new_class("java/io/BufferedWriter", "(Ljava/io/Writer;I)V", (backing_writer, 4)) + .await? + .into(); + let _: () = jvm.invoke_virtual(&writer, "write", "(I)V", ('X' as i32,)).await?; + let failed_close: Result<()> = jvm.invoke_virtual(&writer, "close", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = failed_close else { + panic!("close must report a buffered backing write failure"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + assert_eq!(jvm.get_field::(&writer, "nextChar", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing, "closeCalls", "I").await?, 1); + let closed: Result<()> = jvm.invoke_virtual(&writer, "write", "(I)V", ('Y' as i32,)).await; + assert!(closed.is_err(), "BufferedWriter must close even when flushing during close fails"); + + Ok(()) +} + +#[tokio::test] +async fn character_streams_release_inherited_locks_after_errors() -> Result<()> { + let jvm = stream_locking_jvm().await?; + + let value = JavaLangString::from_rust_string(&jvm, "abc").await?; + let string_reader = jvm.new_class("java/io/StringReader", "(Ljava/lang/String;)V", (value,)).await?; + let target = jvm.instantiate_array("C", 1).await?; + let invalid: Result = jvm.invoke_virtual(&string_reader, "read", "([CII)I", (target, -1, 1)).await; + assert!(invalid.is_err()); + let lock: ClassInstanceRef = jvm.get_field(&string_reader, "lock", "Ljava/lang/Object;").await?; + assert_eq!(lock.identity(), string_reader.identity()); + assert_monitor_released(&jvm, &lock).await?; + + let chars = jvm.instantiate_array("C", 1).await?; + let char_reader = jvm.new_class("java/io/CharArrayReader", "([C)V", (chars,)).await?; + let target = jvm.instantiate_array("C", 1).await?; + let invalid: Result = jvm.invoke_virtual(&char_reader, "read", "([CII)I", (target, 2, 1)).await; + assert!(invalid.is_err()); + let lock: ClassInstanceRef = jvm.get_field(&char_reader, "lock", "Ljava/lang/Object;").await?; + assert_eq!(lock.identity(), char_reader.identity()); + assert_monitor_released(&jvm, &lock).await?; + + let char_writer = jvm.new_class("java/io/CharArrayWriter", "()V", ()).await?; + let null_chars: ClassInstanceRef> = None.into(); + let invalid: Result<()> = jvm.invoke_virtual(&char_writer, "write", "([CII)V", (null_chars, 0, 1)).await; + assert!(invalid.is_err()); + let lock: ClassInstanceRef = jvm.get_field(&char_writer, "lock", "Ljava/lang/Object;").await?; + assert_eq!(lock.identity(), char_writer.identity()); + assert_monitor_released(&jvm, &lock).await?; + + Ok(()) +} + +#[tokio::test] +async fn character_stream_operations_and_close_wait_for_inherited_locks() -> Result<()> { + let jvm = stream_locking_jvm().await?; + + let backing: ClassInstanceRef = jvm.new_class("LockCheckingWriter", "(IZ)V", (0, false)).await?.into(); + let writer: ClassInstanceRef = jvm + .new_class( + "java/io/BufferedWriter", + "(Ljava/io/Writer;)V", + (ClassInstanceRef::::from(backing.instance.clone()),), + ) + .await? + .into(); + let lock: ClassInstanceRef = jvm.get_field(&writer, "lock", "Ljava/lang/Object;").await?; + assert_worker_waits_for_lock(&jvm, writer.instance.clone().into(), lock.clone(), 3).await?; + assert_worker_waits_for_lock(&jvm, writer.instance.into(), lock, 2).await?; + + let value = JavaLangString::from_rust_string(&jvm, "abc").await?; + let string_reader: ClassInstanceRef = jvm.new_class("java/io/StringReader", "(Ljava/lang/String;)V", (value,)).await?.into(); + let lock: ClassInstanceRef = jvm.get_field(&string_reader, "lock", "Ljava/lang/Object;").await?; + assert_worker_waits_for_lock(&jvm, string_reader.instance.clone().into(), lock.clone(), 0).await?; + assert_worker_waits_for_lock(&jvm, string_reader.instance.clone().into(), lock, 2).await?; + let closed: Result = jvm.invoke_virtual(&string_reader, "read", "()I", ()).await; + assert!(closed.is_err()); + + let chars = jvm.instantiate_array("C", 1).await?; + let char_reader: ClassInstanceRef = jvm.new_class("java/io/CharArrayReader", "([C)V", (chars,)).await?.into(); + let lock: ClassInstanceRef = jvm.get_field(&char_reader, "lock", "Ljava/lang/Object;").await?; + assert_worker_waits_for_lock(&jvm, char_reader.instance.clone().into(), lock, 2).await?; + let closed: Result = jvm.invoke_virtual(&char_reader, "read", "()I", ()).await; + assert!(closed.is_err()); + + let char_writer: ClassInstanceRef = jvm.new_class("java/io/CharArrayWriter", "()V", ()).await?.into(); + let lock: ClassInstanceRef = jvm.get_field(&char_writer, "lock", "Ljava/lang/Object;").await?; + assert_worker_waits_for_lock(&jvm, char_writer.instance.clone().into(), lock, 1).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&char_writer, "size", "()I", ()).await?, 1); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_class.rs b/java_runtime/tests/classes/java/lang/test_class.rs index b288a8bf..c1094f10 100644 --- a/java_runtime/tests/classes/java/lang/test_class.rs +++ b/java_runtime/tests/classes/java/lang/test_class.rs @@ -1,3 +1,4 @@ +use java_constants::MethodAccessFlags; use java_runtime::{ Runtime, classes::java::{ @@ -13,6 +14,71 @@ use jvm::{ use test_utils::{TestRuntime, test_jvm}; +#[tokio::test] +async fn cls_01_to_04_descriptors_and_standard_class_metadata() -> Result<()> { + let proto = Class::as_proto(); + for (name, descriptor) in [ + ("getSuperclass", "()Ljava/lang/Class;"), + ("getClassLoader", "()Ljava/lang/ClassLoader;"), + ("getComponentType", "()Ljava/lang/Class;"), + ("getInterfaces", "()[Ljava/lang/Class;"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing java/lang/Class.{name}{descriptor}")); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + let jvm = test_jvm().await?; + let object = jvm.resolve_class("java/lang/Object").await?.java_class(); + let string = jvm.resolve_class("java/lang/String").await?.java_class(); + let comparable = jvm.resolve_class("java/lang/Comparable").await?.java_class(); + let serializable = jvm.resolve_class("java/io/Serializable").await?.java_class(); + let primitive = jvm.get_static_field("java/lang/Integer", "TYPE", "Ljava/lang/Class;").await?; + let primitive_array = jvm.resolve_class("[I").await?.java_class(); + let string_array = jvm.resolve_class("[Ljava/lang/String;").await?.java_class(); + let string_matrix = jvm.resolve_class("[[Ljava/lang/String;").await?.java_class(); + + let superclass: ClassInstanceRef = jvm.invoke_virtual(&string, "getSuperclass", "()Ljava/lang/Class;", ()).await?; + assert_eq!(superclass.identity(), object.identity()); + for class in [&object, &primitive] { + let superclass: ClassInstanceRef = jvm.invoke_virtual(class, "getSuperclass", "()Ljava/lang/Class;", ()).await?; + assert!(superclass.is_null()); + } + let array_superclass: ClassInstanceRef = jvm.invoke_virtual(&string_array, "getSuperclass", "()Ljava/lang/Class;", ()).await?; + assert_eq!(array_superclass.identity(), object.identity()); + + let primitive_component: ClassInstanceRef = jvm + .invoke_virtual(&primitive_array, "getComponentType", "()Ljava/lang/Class;", ()) + .await?; + assert_eq!(primitive_component.identity(), primitive.identity()); + let string_component: ClassInstanceRef = jvm.invoke_virtual(&string_array, "getComponentType", "()Ljava/lang/Class;", ()).await?; + assert_eq!(string_component.identity(), string.identity()); + let matrix_component: ClassInstanceRef = jvm.invoke_virtual(&string_matrix, "getComponentType", "()Ljava/lang/Class;", ()).await?; + assert_eq!(matrix_component.identity(), string_array.identity()); + let non_array_component: ClassInstanceRef = jvm.invoke_virtual(&string, "getComponentType", "()Ljava/lang/Class;", ()).await?; + assert!(non_array_component.is_null()); + + let string_interfaces: ClassInstanceRef> = jvm.invoke_virtual(&string, "getInterfaces", "()[Ljava/lang/Class;", ()).await?; + let string_interfaces: Vec> = jvm.load_array(&string_interfaces, 0, 2).await?; + assert_eq!(string_interfaces[0].identity(), serializable.identity()); + assert_eq!(string_interfaces[1].identity(), comparable.identity()); + + let array_interfaces: ClassInstanceRef> = jvm.invoke_virtual(&string_array, "getInterfaces", "()[Ljava/lang/Class;", ()).await?; + let array_interfaces: Vec> = jvm.load_array(&array_interfaces, 0, 2).await?; + assert_eq!(JavaLangClass::name(&jvm, &array_interfaces[0]).await?, "java/lang/Cloneable"); + assert_eq!(JavaLangClass::name(&jvm, &array_interfaces[1]).await?, "java/io/Serializable"); + + for class in [object, string, primitive_array, string_array, string_matrix] { + let loader: ClassInstanceRef = jvm.invoke_virtual(&class, "getClassLoader", "()Ljava/lang/ClassLoader;", ()).await?; + assert!(loader.is_null()); + } + + Ok(()) +} + #[tokio::test] async fn test_class() -> Result<()> { let jvm = test_jvm().await?; diff --git a/java_runtime/tests/classes/java/lang/test_cldc11_exceptions.rs b/java_runtime/tests/classes/java/lang/test_cldc11_exceptions.rs index 1628ea0f..f1d38224 100644 --- a/java_runtime/tests/classes/java/lang/test_cldc11_exceptions.rs +++ b/java_runtime/tests/classes/java/lang/test_cldc11_exceptions.rs @@ -1,4 +1,6 @@ -use jvm::Result; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::{classes::java::lang::String, get_runtime_class_proto}; +use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -42,3 +44,106 @@ async fn test_corrected_exception_names() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_runtime_api_exception_and_error_hierarchy() -> Result<()> { + let jvm = test_jvm().await?; + + for (class_name, parent_name, constructor_descriptors) in [ + ( + "java/lang/IllegalStateException", + "java/lang/RuntimeException", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/lang/StackOverflowError", + "java/lang/VirtualMachineError", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/lang/InternalError", + "java/lang/VirtualMachineError", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/lang/UnknownError", + "java/lang/VirtualMachineError", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/lang/IllegalAccessError", + "java/lang/IncompatibleClassChangeError", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ("java/lang/ThreadDeath", "java/lang/Error", &["()V"][..]), + ( + "java/lang/NoSuchFieldException", + "java/lang/Exception", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/lang/NoSuchMethodException", + "java/lang/Exception", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/lang/ClassCircularityError", + "java/lang/LinkageError", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ( + "java/util/ConcurrentModificationException", + "java/lang/RuntimeException", + &["()V", "(Ljava/lang/String;)V"][..], + ), + ] { + let proto = get_runtime_class_proto(class_name).unwrap_or_else(|| panic!("missing {class_name}")); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC, "{class_name}"); + assert_eq!(proto.parent_class, Some(parent_name), "{class_name}"); + assert!(proto.interfaces.is_empty(), "{class_name}"); + assert!(proto.fields.is_empty(), "{class_name}"); + assert_eq!(proto.methods.len(), constructor_descriptors.len(), "{class_name}"); + for descriptor in constructor_descriptors { + let constructor = proto + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == *descriptor) + .unwrap_or_else(|| panic!("missing {class_name}.{descriptor}")); + assert_eq!(constructor.access_flags, MethodAccessFlags::PUBLIC, "{class_name}.{descriptor}"); + } + + let class = jvm.resolve_class(class_name).await?; + assert_eq!(class.definition.super_class_name().as_deref(), Some(parent_name)); + assert!(jvm.is_inherited_from(&*class.definition, parent_name)); + + let instance = jvm.new_class(class_name, "()V", ()).await?; + assert!(jvm.is_instance(&*instance, parent_name)); + } + + Ok(()) +} + +#[tokio::test] +async fn test_runtime_api_exception_and_error_message_constructors() -> Result<()> { + let jvm = test_jvm().await?; + + for class_name in [ + "java/lang/IllegalStateException", + "java/lang/StackOverflowError", + "java/lang/InternalError", + "java/lang/UnknownError", + "java/lang/IllegalAccessError", + "java/lang/NoSuchFieldException", + "java/lang/NoSuchMethodException", + "java/lang/ClassCircularityError", + "java/util/ConcurrentModificationException", + ] { + let message = JavaLangString::from_rust_string(&jvm, class_name).await?; + let instance = jvm.new_class(class_name, "(Ljava/lang/String;)V", (message,)).await?; + let message: ClassInstanceRef = jvm.invoke_virtual(&instance, "getMessage", "()Ljava/lang/String;", ()).await?; + + assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, class_name); + } + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_math.rs b/java_runtime/tests/classes/java/lang/test_math.rs index e63bb265..0d63832c 100644 --- a/java_runtime/tests/classes/java/lang/test_math.rs +++ b/java_runtime/tests/classes/java/lang/test_math.rs @@ -1,35 +1,526 @@ -use jvm::Result; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::lang::Math; +use java_runtime::classes::java::util::Random; +use jvm::{ClassInstanceRef, Result}; use test_utils::test_jvm; #[tokio::test] -async fn test_abs() -> Result<()> { +async fn math_01_constants_descriptors_and_access_flags() -> Result<()> { + let proto = Math::as_proto(); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL)); + + for (name, descriptor) in [ + ("abs", "(I)I"), + ("abs", "(J)J"), + ("abs", "(F)F"), + ("abs", "(D)D"), + ("ceil", "(D)D"), + ("floor", "(D)D"), + ("sqrt", "(D)D"), + ("sin", "(D)D"), + ("cos", "(D)D"), + ("tan", "(D)D"), + ("toDegrees", "(D)D"), + ("toRadians", "(D)D"), + ("min", "(II)I"), + ("min", "(JJ)J"), + ("min", "(FF)F"), + ("min", "(DD)D"), + ("max", "(II)I"), + ("max", "(JJ)J"), + ("max", "(FF)F"), + ("max", "(DD)D"), + ("acos", "(D)D"), + ("asin", "(D)D"), + ("atan", "(D)D"), + ("atan2", "(DD)D"), + ("exp", "(D)D"), + ("log", "(D)D"), + ("pow", "(DD)D"), + ("rint", "(D)D"), + ("IEEEremainder", "(DD)D"), + ("round", "(F)I"), + ("round", "(D)J"), + ("random", "()D"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing java/lang/Math.{name}{descriptor}")); + if name == "random" { + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::SYNCHRONIZED, + "wrong access flags for java/lang/Math.random()D" + ); + } else { + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + "wrong access flags for java/lang/Math.{name}{descriptor}" + ); + } + } + + for name in ["E", "PI"] { + let field = proto + .fields + .iter() + .find(|field| field.name == name && field.descriptor == "D") + .unwrap_or_else(|| panic!("missing java/lang/Math.{name}:D")); + assert!( + field + .access_flags + .contains(FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL) + ); + } + + let jvm = test_jvm().await?; + assert_eq!( + jvm.get_static_field::("java/lang/Math", "E", "D").await?.to_bits(), + core::f64::consts::E.to_bits() + ); + assert_eq!( + jvm.get_static_field::("java/lang/Math", "PI", "D").await?.to_bits(), + core::f64::consts::PI.to_bits() + ); + + Ok(()) +} + +#[tokio::test] +async fn math_02_cldc_transcendentals_and_conversions() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "ceil", "(D)D", (1.25,)).await?, 2.0); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "ceil", "(D)D", (-1.25,)).await?, -1.0); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "ceil", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "ceil", "(D)D", (f64::NAN,)).await?.is_nan()); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "ceil", "(D)D", (f64::INFINITY,)).await?, + f64::INFINITY + ); + + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "floor", "(D)D", (1.75,)).await?, 1.0); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "floor", "(D)D", (-1.25,)).await?, -2.0); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "floor", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "sqrt", "(D)D", (4.0,)).await?, 2.0); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "sqrt", "(D)D", (-1.0,)).await?.is_nan()); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "sqrt", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "sqrt", "(D)D", (f64::INFINITY,)).await?, + f64::INFINITY + ); + + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "sin", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "cos", "(D)D", (0.0,)).await?, 1.0); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "tan", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "sin", "(D)D", (f64::INFINITY,)) + .await? + .is_nan() + ); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "cos", "(D)D", (f64::NAN,)).await?.is_nan()); + + let degrees: f64 = jvm.invoke_static("java/lang/Math", "toDegrees", "(D)D", (core::f64::consts::PI,)).await?; + assert!((degrees - 180.0).abs() <= f64::EPSILON); + let radians: f64 = jvm.invoke_static("java/lang/Math", "toRadians", "(D)D", (180.0,)).await?; + assert!((radians - core::f64::consts::PI).abs() <= f64::EPSILON); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "toDegrees", "(D)D", (-0.0,)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "toRadians", "(D)D", (f64::INFINITY,)) + .await?, + f64::INFINITY + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "toDegrees", "(D)D", (f64::NAN,)) + .await? + .is_nan() + ); + + Ok(()) +} + +#[tokio::test] +async fn math_03_floating_min_max_propagate_nan_and_order_signed_zero() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Math", "min", "(FF)F", (3.0f32, 4.0f32)).await?, + 3.0 + ); + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Math", "max", "(FF)F", (3.0f32, 4.0f32)).await?, + 4.0 + ); + assert!( + jvm.invoke_static::<_, f32>("java/lang/Math", "min", "(FF)F", (f32::NAN, 1.0f32)) + .await? + .is_nan() + ); + assert!( + jvm.invoke_static::<_, f32>("java/lang/Math", "max", "(FF)F", (1.0f32, f32::NAN)) + .await? + .is_nan() + ); + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Math", "min", "(FF)F", (0.0f32, -0.0f32)) + .await? + .to_bits(), + (-0.0f32).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Math", "max", "(FF)F", (-0.0f32, 0.0f32)) + .await? + .to_bits(), + 0.0f32.to_bits() + ); + + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "min", "(DD)D", (3.0, 4.0)).await?, 3.0); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "max", "(DD)D", (3.0, 4.0)).await?, 4.0); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "min", "(DD)D", (f64::NAN, 1.0)) + .await? + .is_nan() + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "max", "(DD)D", (1.0, f64::NAN)) + .await? + .is_nan() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "min", "(DD)D", (0.0, -0.0)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "max", "(DD)D", (-0.0, 0.0)) + .await? + .to_bits(), + 0.0f64.to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "min", "(DD)D", (f64::NEG_INFINITY, f64::INFINITY)) + .await?, + f64::NEG_INFINITY + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "max", "(DD)D", (f64::NEG_INFINITY, f64::INFINITY)) + .await?, + f64::INFINITY + ); + + Ok(()) +} + +#[tokio::test] +async fn math_04_j2se_unary_functions_and_rint() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "acos", "(D)D", (1.0,)).await?, 0.0); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "acos", "(D)D", (2.0,)).await?.is_nan()); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "asin", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "atan", "(D)D", (-0.0,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "exp", "(D)D", (f64::NEG_INFINITY,)).await?, + 0.0 + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "exp", "(D)D", (f64::INFINITY,)).await?, + f64::INFINITY + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "log", "(D)D", (0.0,)).await?, + f64::NEG_INFINITY + ); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "log", "(D)D", (-1.0,)).await?.is_nan()); + + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (0.5,)).await?.to_bits(), + 0.0f64.to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (-0.5,)).await?.to_bits(), + (-0.0f64).to_bits() + ); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (1.5,)).await?, 2.0); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (2.5,)).await?, 2.0); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (-1.5,)).await?, -2.0); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (f64::INFINITY,)).await?, + f64::INFINITY + ); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "rint", "(D)D", (f64::NAN,)).await?.is_nan()); + + Ok(()) +} + +#[tokio::test] +async fn math_05_binary_functions_and_ieee_remainder() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "atan2", "(DD)D", (0.0, -1.0)) + .await? + .to_bits(), + core::f64::consts::PI.to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "atan2", "(DD)D", (-0.0, -1.0)) + .await? + .to_bits(), + (-core::f64::consts::PI).to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "atan2", "(DD)D", (-0.0, 1.0)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (1.0, f64::NAN)) + .await? + .is_nan() + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (-1.0, f64::NAN)) + .await? + .is_nan() + ); + assert_eq!(jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (f64::NAN, 0.0)).await?, 1.0); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (f64::NAN, -0.0)).await?, + 1.0 + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (-1.0, f64::INFINITY)) + .await? + .is_nan() + ); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (-2.0, 0.5)).await?.is_nan()); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (-0.0, -3.0)).await?, + f64::NEG_INFINITY + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "pow", "(DD)D", (-0.0, 3.0)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "IEEEremainder", "(DD)D", (5.0, 2.0)) + .await?, + 1.0 + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "IEEEremainder", "(DD)D", (7.0, 2.0)) + .await?, + -1.0 + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "IEEEremainder", "(DD)D", (-0.0, 3.0)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "IEEEremainder", "(DD)D", (f64::INFINITY, 2.0)) + .await? + .is_nan() + ); + assert!( + jvm.invoke_static::<_, f64>("java/lang/Math", "IEEEremainder", "(DD)D", (2.0, 0.0)) + .await? + .is_nan() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "IEEEremainder", "(DD)D", (2.0, f64::INFINITY)) + .await?, + 2.0 + ); + + Ok(()) +} + +#[tokio::test] +async fn math_06_round_uses_java_saturation_and_half_toward_positive_infinity() -> Result<()> { let jvm = test_jvm().await?; - assert_eq!(42i32, jvm.invoke_static("java/lang/Math", "abs", "(I)I", (42,)).await?); - assert_eq!(42i32, jvm.invoke_static("java/lang/Math", "abs", "(I)I", (-42,)).await?); + assert_eq!(jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (1.4f32,)).await?, 1); + assert_eq!(jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (1.5f32,)).await?, 2); + assert_eq!(jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (-1.5f32,)).await?, -1); + assert_eq!(jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (-1.6f32,)).await?, -2); + assert_eq!(jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (f32::NAN,)).await?, 0); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (f32::INFINITY,)).await?, + i32::MAX + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (f32::NEG_INFINITY,)) + .await?, + i32::MIN + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (f32::MAX,)).await?, + i32::MAX + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (-f32::MAX,)).await?, + i32::MIN + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (f32::from_bits((i32::MAX as f32).to_bits() - 1),),) + .await?, + 2_147_483_520 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "round", "(F)I", (f32::from_bits((i32::MIN as f32).to_bits() - 1),),) + .await?, + -2_147_483_520 + ); + + assert_eq!(jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (1.4,)).await?, 1); + assert_eq!(jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (1.5,)).await?, 2); + assert_eq!(jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (-1.5,)).await?, -1); + assert_eq!(jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (-1.6,)).await?, -2); + assert_eq!(jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (f64::NAN,)).await?, 0); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (f64::INFINITY,)).await?, + i64::MAX + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (f64::NEG_INFINITY,)) + .await?, + i64::MIN + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (f64::MAX,)).await?, + i64::MAX + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (-f64::MAX,)).await?, + i64::MIN + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (f64::from_bits((i64::MAX as f64).to_bits() - 1),),) + .await?, + 9_223_372_036_854_774_784 + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "round", "(D)J", (f64::from_bits((i64::MIN as f64).to_bits() - 1),),) + .await?, + -9_223_372_036_854_774_784 + ); + + Ok(()) +} + +#[tokio::test] +async fn math_07_random_stays_in_unit_interval_and_advances_one_generator() -> Result<()> { + let jvm = test_jvm().await?; - assert_eq!(42i64, jvm.invoke_static("java/lang/Math", "abs", "(J)J", (42i64,)).await?); - assert_eq!(42i64, jvm.invoke_static("java/lang/Math", "abs", "(J)J", (-42i64,)).await?); + let uninitialized: ClassInstanceRef = jvm + .get_static_field("java/lang/Math", "randomNumberGenerator", "Ljava/util/Random;") + .await?; + assert!(uninitialized.is_null()); - assert_eq!(3.15f32, jvm.invoke_static("java/lang/Math", "abs", "(F)F", (3.15f32,)).await?); - assert_eq!(3.15f32, jvm.invoke_static("java/lang/Math", "abs", "(F)F", (-3.15f32,)).await?); + let first: f64 = jvm.invoke_static("java/lang/Math", "random", "()D", ()).await?; + assert!((0.0..1.0).contains(&first)); + let generator: ClassInstanceRef = jvm + .get_static_field("java/lang/Math", "randomNumberGenerator", "Ljava/util/Random;") + .await?; + assert!(!generator.is_null()); + let generator_identity = generator.identity(); - assert_eq!(2.818f64, jvm.invoke_static("java/lang/Math", "abs", "(D)D", (2.818f64,)).await?); - assert_eq!(2.818f64, jvm.invoke_static("java/lang/Math", "abs", "(D)D", (-2.818f64,)).await?); + let mut changed = false; + for _ in 0..256 { + let value: f64 = jvm.invoke_static("java/lang/Math", "random", "()D", ()).await?; + assert!((0.0..1.0).contains(&value)); + changed |= value != first; + } + assert!(changed); + let same_generator: ClassInstanceRef = jvm + .get_static_field("java/lang/Math", "randomNumberGenerator", "Ljava/util/Random;") + .await?; + assert_eq!(same_generator.identity(), generator_identity); Ok(()) } #[tokio::test] -async fn test_min_max() -> Result<()> { +async fn math_08_existing_abs_and_integer_min_max_cover_java_edges() -> Result<()> { let jvm = test_jvm().await?; - assert_eq!(10i32, jvm.invoke_static("java/lang/Math", "max", "(II)I", (10, 5)).await?); - assert_eq!(5i32, jvm.invoke_static("java/lang/Math", "min", "(II)I", (10, 5)).await?); + assert_eq!(jvm.invoke_static::<_, i32>("java/lang/Math", "abs", "(I)I", (i32::MIN,)).await?, i32::MIN); + assert_eq!(jvm.invoke_static::<_, i64>("java/lang/Math", "abs", "(J)J", (i64::MIN,)).await?, i64::MIN); + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Math", "abs", "(F)F", (-0.0f32,)).await?.to_bits(), + 0.0f32.to_bits() + ); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "abs", "(D)D", (-0.0,)).await?.to_bits(), + 0.0f64.to_bits() + ); + assert!(jvm.invoke_static::<_, f32>("java/lang/Math", "abs", "(F)F", (f32::NAN,)).await?.is_nan()); + assert!(jvm.invoke_static::<_, f64>("java/lang/Math", "abs", "(D)D", (f64::NAN,)).await?.is_nan()); + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Math", "abs", "(D)D", (f64::NEG_INFINITY,)).await?, + f64::INFINITY + ); - assert_eq!(20i64, jvm.invoke_static("java/lang/Math", "max", "(JJ)J", (20i64, 15i64)).await?); - assert_eq!(15i64, jvm.invoke_static("java/lang/Math", "min", "(JJ)J", (20i64, 15i64)).await?); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "min", "(II)I", (i32::MIN, i32::MAX)) + .await?, + i32::MIN + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Math", "max", "(II)I", (i32::MIN, i32::MAX)) + .await?, + i32::MAX + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "min", "(JJ)J", (i64::MIN, i64::MAX)) + .await?, + i64::MIN + ); + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Math", "max", "(JJ)J", (i64::MIN, i64::MAX)) + .await?, + i64::MAX + ); Ok(()) } diff --git a/java_runtime/tests/classes/java/lang/test_string.rs b/java_runtime/tests/classes/java/lang/test_string.rs index a87bb6ff..46740962 100644 --- a/java_runtime/tests/classes/java/lang/test_string.rs +++ b/java_runtime/tests/classes/java/lang/test_string.rs @@ -1,5 +1,6 @@ -use java_runtime::classes::java::lang::String as JavaString; -use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::lang::{Object, String as JavaString}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -486,3 +487,329 @@ async fn test_trim_uses_java_control_character_boundary() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_str_01_string_declares_jdk12_interfaces_and_access() -> Result<()> { + let jvm = test_jvm().await?; + let class = jvm.get_class("java/lang/String").expect("String must be loaded"); + let interfaces = class.definition.interface_names(); + + assert!(interfaces.iter().any(|name| name == "java/lang/Comparable")); + assert!(interfaces.iter().any(|name| name == "java/io/Serializable")); + assert!( + class + .definition + .access_flags() + .contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL) + ); + assert!( + class + .definition + .method("compareTo", "(Ljava/lang/String;)I", false) + .expect("typed compareTo") + .access_flags() + .contains(MethodAccessFlags::PUBLIC) + ); + assert!( + class + .definition + .method("compareTo", "(Ljava/lang/Object;)I", false) + .expect("raw compareTo") + .access_flags() + .contains(MethodAccessFlags::PUBLIC) + ); + + Ok(()) +} + +#[tokio::test] +async fn test_str_02_compare_to_uses_utf16_code_units_and_bridge_exceptions() -> Result<()> { + let jvm = test_jvm().await?; + + let mut supplementary_chars = jvm.instantiate_array("C", 2).await?; + jvm.store_array(&mut supplementary_chars, 0, [0xd83d as JavaChar, 0xde00 as JavaChar]) + .await?; + let supplementary = jvm.new_class("java/lang/String", "([C)V", (supplementary_chars,)).await?; + let mut private_use_chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut private_use_chars, 0, [0xe000 as JavaChar]).await?; + let private_use = jvm.new_class("java/lang/String", "([C)V", (private_use_chars,)).await?; + + assert_eq!( + jvm.invoke_virtual::<_, i32>(&supplementary, "compareTo", "(Ljava/lang/String;)I", (private_use.clone(),)) + .await?, + 0xd83d - 0xe000 + ); + + let mut first_unpaired_chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut first_unpaired_chars, 0, [0xd800 as JavaChar]).await?; + let first_unpaired = jvm.new_class("java/lang/String", "([C)V", (first_unpaired_chars,)).await?; + let mut second_unpaired_chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut second_unpaired_chars, 0, [0xd801 as JavaChar]).await?; + let second_unpaired = jvm.new_class("java/lang/String", "([C)V", (second_unpaired_chars,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&first_unpaired, "compareTo", "(Ljava/lang/String;)I", (second_unpaired.clone(),),) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&first_unpaired, "compareTo", "(Ljava/lang/Object;)I", (second_unpaired,)) + .await?, + -1 + ); + + let a = JavaLangString::from_rust_string(&jvm, "a").await?; + let ac = JavaLangString::from_rust_string(&jvm, "ac").await?; + let az = JavaLangString::from_rust_string(&jvm, "az").await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&ac, "compareTo", "(Ljava/lang/String;)I", (az,)).await?, -23); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&a, "compareTo", "(Ljava/lang/String;)I", (ac.clone(),)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&ac, "compareTo", "(Ljava/lang/Object;)I", (ac.clone(),)) + .await?, + 0 + ); + + let null: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&ac, "compareTo", "(Ljava/lang/String;)I", (null.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("String.compareTo(String) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let result: Result = jvm.invoke_virtual(&ac, "compareTo", "(Ljava/lang/Object;)I", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("String.compareTo(Object) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&ac, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("String.compareTo(Object) must reject non-String values"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + Ok(()) +} + +#[tokio::test] +async fn test_str_03_compare_to_ignore_case() -> Result<()> { + let jvm = test_jvm().await?; + let mixed = JavaLangString::from_rust_string(&jvm, "AbC").await?; + let lower = JavaLangString::from_rust_string(&jvm, "aBc").await?; + let later = JavaLangString::from_rust_string(&jvm, "abd").await?; + + assert_eq!( + jvm.invoke_virtual::<_, i32>(&mixed, "compareToIgnoreCase", "(Ljava/lang/String;)I", (lower,)) + .await?, + 0 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&mixed, "compareToIgnoreCase", "(Ljava/lang/String;)I", (later,)) + .await?, + -1 + ); + + let null: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&mixed, "compareToIgnoreCase", "(Ljava/lang/String;)I", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("compareToIgnoreCase must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_str_04_last_index_of_string_uses_utf16_indices() -> Result<()> { + let jvm = test_jvm().await?; + let string = JavaLangString::from_rust_string(&jvm, "a😀ba😀b").await?; + let emoji = JavaLangString::from_rust_string(&jvm, "😀").await?; + let empty = JavaLangString::from_rust_string(&jvm, "").await?; + + assert_eq!( + jvm.invoke_virtual::<_, i32>(&string, "lastIndexOf", "(Ljava/lang/String;)I", (emoji.clone(),)) + .await?, + 5 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&string, "lastIndexOf", "(Ljava/lang/String;I)I", (emoji.clone(), 4)) + .await?, + 1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&string, "lastIndexOf", "(Ljava/lang/String;I)I", (emoji, -1)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&string, "lastIndexOf", "(Ljava/lang/String;I)I", (empty.clone(), -1)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&string, "lastIndexOf", "(Ljava/lang/String;I)I", (empty.clone(), 8)) + .await?, + 8 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&string, "lastIndexOf", "(Ljava/lang/String;I)I", (empty, 9)) + .await?, + 8 + ); + + let null: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&string, "lastIndexOf", "(Ljava/lang/String;)I", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("lastIndexOf(String) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_str_05_copy_value_of_copies_and_checks_ranges() -> Result<()> { + let jvm = test_jvm().await?; + let mut chars = jvm.instantiate_array("C", 4).await?; + jvm.store_array(&mut chars, 0, ['a' as JavaChar, 'b' as JavaChar, 'c' as JavaChar, 'd' as JavaChar]) + .await?; + + let full: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "copyValueOf", "([C)Ljava/lang/String;", (chars.clone(),)) + .await?; + let partial: ClassInstanceRef = jvm + .invoke_static("java/lang/String", "copyValueOf", "([CII)Ljava/lang/String;", (chars.clone(), 1, 2)) + .await?; + jvm.store_array(&mut chars, 0, ['z' as JavaChar]).await?; + + assert_eq!(JavaLangString::to_rust_string(&jvm, &full).await?, "abcd"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &partial).await?, "bc"); + + let null: ClassInstanceRef> = None.into(); + let result: Result> = jvm + .invoke_static("java/lang/String", "copyValueOf", "([C)Ljava/lang/String;", (null,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("copyValueOf must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (offset, count) in [(-1, 1), (0, -1), (3, 2)] { + let result: Result> = jvm + .invoke_static( + "java/lang/String", + "copyValueOf", + "([CII)Ljava/lang/String;", + (chars.clone(), offset, count), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("copyValueOf must reject range ({offset}, {count})"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + } + + Ok(()) +} + +#[tokio::test] +async fn test_str_06_region_matches_without_ignore_case() -> Result<()> { + let jvm = test_jvm().await?; + let source = JavaLangString::from_rust_string(&jvm, "a😀bc").await?; + let same = JavaLangString::from_rust_string(&jvm, "x😀by").await?; + + assert!( + jvm.invoke_virtual::<_, bool>(&source, "regionMatches", "(ILjava/lang/String;II)Z", (1, same.clone(), 1, 3)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&source, "regionMatches", "(ILjava/lang/String;II)Z", (-1, same.clone(), 1, 1)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&source, "regionMatches", "(ILjava/lang/String;II)Z", (1, same.clone(), 1, 99)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&source, "regionMatches", "(ILjava/lang/String;II)Z", (1, same, 1, -1)) + .await? + ); + + let mut first_surrogate = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut first_surrogate, 0, [0xd800 as JavaChar]).await?; + let first_surrogate = jvm.new_class("java/lang/String", "([C)V", (first_surrogate,)).await?; + let mut second_surrogate = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut second_surrogate, 0, [0xd801 as JavaChar]).await?; + let second_surrogate = jvm.new_class("java/lang/String", "([C)V", (second_surrogate,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&first_surrogate, "regionMatches", "(ILjava/lang/String;II)Z", (0, second_surrogate, 0, 1),) + .await? + ); + + let null: ClassInstanceRef = None.into(); + let result: Result = jvm + .invoke_virtual(&source, "regionMatches", "(ILjava/lang/String;II)Z", (0, null, 0, 0)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("regionMatches must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_str_07_locale_case_overloads_and_float_formatting() -> Result<()> { + let jvm = test_jvm().await?; + let language = JavaLangString::from_rust_string(&jvm, "en").await?; + let locale = jvm.new_class("java/util/Locale", "(Ljava/lang/String;)V", (language,)).await?; + let mixed = JavaLangString::from_rust_string(&jvm, "AbC").await?; + + let lower: ClassInstanceRef = jvm + .invoke_virtual(&mixed, "toLowerCase", "(Ljava/util/Locale;)Ljava/lang/String;", (locale.clone(),)) + .await?; + let upper: ClassInstanceRef = jvm + .invoke_virtual(&mixed, "toUpperCase", "(Ljava/util/Locale;)Ljava/lang/String;", (locale,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &lower).await?, "abc"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &upper).await?, "ABC"); + + let null: ClassInstanceRef = None.into(); + let result: Result> = jvm + .invoke_virtual(&mixed, "toLowerCase", "(Ljava/util/Locale;)Ljava/lang/String;", (null,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("locale case conversion must reject null Locale"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null: ClassInstanceRef = None.into(); + let result: Result> = jvm + .invoke_virtual(&mixed, "toUpperCase", "(Ljava/util/Locale;)Ljava/lang/String;", (null,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("uppercase locale conversion must reject null Locale"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (descriptor, value, expected) in [ + ("(F)Ljava/lang/String;", 0.0f64, "0.0"), + ("(F)Ljava/lang/String;", -0.0f64, "-0.0"), + ("(F)Ljava/lang/String;", f32::INFINITY as f64, "Infinity"), + ("(D)Ljava/lang/String;", f64::NAN, "NaN"), + ("(D)Ljava/lang/String;", 1.0e20, "1.0E20"), + ] { + let text: ClassInstanceRef = if descriptor.starts_with("(F)") { + jvm.invoke_static("java/lang/String", "valueOf", descriptor, (value as f32,)).await? + } else { + jvm.invoke_static("java/lang/String", "valueOf", descriptor, (value,)).await? + }; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_string_buffer.rs b/java_runtime/tests/classes/java/lang/test_string_buffer.rs index 86ff677d..a7e7bc8f 100644 --- a/java_runtime/tests/classes/java/lang/test_string_buffer.rs +++ b/java_runtime/tests/classes/java/lang/test_string_buffer.rs @@ -1,5 +1,6 @@ +use java_constants::MethodAccessFlags; use java_runtime::classes::java::lang::StringBuffer; -use jvm::{ClassInstanceRef, JavaChar, Result, runtime::JavaLangString}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -44,3 +45,463 @@ async fn test_string_buffer() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_sb_01_constructors_capacity_and_exceptions() -> Result<()> { + let jvm = test_jvm().await?; + let empty = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&empty, "capacity", "()I", ()).await?, 16); + + let source = JavaLangString::from_rust_string(&jvm, "abc").await?; + let from_string = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (source,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&from_string, "capacity", "()I", ()).await?, 19); + assert_eq!(jvm.invoke_virtual::<_, i32>(&from_string, "length", "()I", ()).await?, 3); + + let result = jvm.new_class("java/lang/StringBuffer", "(I)V", (-1,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("negative StringBuffer capacity must throw"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NegativeArraySizeException")); + + let null = ClassInstanceRef::::new(None); + let result = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("StringBuffer(null) must throw"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_sb_02_capacity_and_ensure_capacity_growth() -> Result<()> { + let jvm = test_jvm().await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(I)V", (2,)).await?; + + let _: () = jvm.invoke_virtual(&buffer, "ensureCapacity", "(I)V", (3,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, 6); + let _: () = jvm.invoke_virtual(&buffer, "ensureCapacity", "(I)V", (20,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, 20); + let _: () = jvm.invoke_virtual(&buffer, "ensureCapacity", "(I)V", (-1,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, 20); + + Ok(()) +} + +#[tokio::test] +async fn test_sb_03_length_char_access_and_synchronized_flags() -> Result<()> { + let jvm = test_jvm().await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(I)V", (1,)).await?; + + let _: () = jvm.invoke_virtual(&buffer, "setLength", "(I)V", (3,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, 3); + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&buffer, "charAt", "(I)C", (1,)).await?, 0); + let _: () = jvm.invoke_virtual(&buffer, "setCharAt", "(IC)V", (1, 'x' as JavaChar)).await?; + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&buffer, "charAt", "(I)C", (1,)).await?, 'x' as JavaChar); + let _: () = jvm.invoke_virtual(&buffer, "setLength", "(I)V", (1,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, 1); + + for (name, descriptor) in [ + ("append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"), + ("append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;"), + ("append", "(Z)Ljava/lang/StringBuffer;"), + ("append", "(C)Ljava/lang/StringBuffer;"), + ("append", "(I)Ljava/lang/StringBuffer;"), + ("append", "(J)Ljava/lang/StringBuffer;"), + ("append", "(F)Ljava/lang/StringBuffer;"), + ("append", "(D)Ljava/lang/StringBuffer;"), + ("append", "([C)Ljava/lang/StringBuffer;"), + ("append", "([CII)Ljava/lang/StringBuffer;"), + ("insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;"), + ("insert", "(ILjava/lang/Object;)Ljava/lang/StringBuffer;"), + ("insert", "(IZ)Ljava/lang/StringBuffer;"), + ("insert", "(IC)Ljava/lang/StringBuffer;"), + ("insert", "(II)Ljava/lang/StringBuffer;"), + ("insert", "(IJ)Ljava/lang/StringBuffer;"), + ("insert", "(IF)Ljava/lang/StringBuffer;"), + ("insert", "(ID)Ljava/lang/StringBuffer;"), + ("insert", "(I[C)Ljava/lang/StringBuffer;"), + ("delete", "(II)Ljava/lang/StringBuffer;"), + ("deleteCharAt", "(I)Ljava/lang/StringBuffer;"), + ("replace", "(IILjava/lang/String;)Ljava/lang/StringBuffer;"), + ("substring", "(I)Ljava/lang/String;"), + ("substring", "(II)Ljava/lang/String;"), + ("capacity", "()I"), + ("ensureCapacity", "(I)V"), + ("length", "()I"), + ("setLength", "(I)V"), + ("charAt", "(I)C"), + ("setCharAt", "(IC)V"), + ("getChars", "(II[CI)V"), + ("reverse", "()Ljava/lang/StringBuffer;"), + ("toString", "()Ljava/lang/String;"), + ] { + let flags = jvm + .get_class("java/lang/StringBuffer") + .expect("StringBuffer must be loaded") + .definition + .method(name, descriptor, false) + .unwrap_or_else(|| panic!("missing {name}{descriptor}")) + .access_flags(); + assert!(flags.contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED)); + } + + for index in [-1, 1] { + let result: Result = jvm.invoke_virtual(&buffer, "charAt", "(I)C", (index,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("charAt must reject index {index}"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + + let result: Result<()> = jvm.invoke_virtual(&buffer, "setCharAt", "(IC)V", (index, 'z' as JavaChar)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("setCharAt must reject index {index}"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + } + + let before_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + let before_text = JavaLangString::to_rust_string(&jvm, &before_text).await?; + let before_length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let before_capacity: i32 = jvm.invoke_virtual(&buffer, "capacity", "()I", ()).await?; + let result: Result<()> = jvm.invoke_virtual(&buffer, "setLength", "(I)V", (-1,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("setLength must reject a negative length"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, before_capacity); + + Ok(()) +} + +#[tokio::test] +async fn test_sb_04_get_chars_checks_source_and_destination_ranges() -> Result<()> { + let jvm = test_jvm().await?; + let source = JavaLangString::from_rust_string(&jvm, "abcd").await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (source,)).await?; + let mut destination = jvm.instantiate_array("C", 5).await?; + jvm.store_array(&mut destination, 0, ['_' as JavaChar; 5]).await?; + + let _: () = jvm + .invoke_virtual(&buffer, "getChars", "(II[CI)V", (1, 3, destination.clone(), 2)) + .await?; + assert_eq!( + jvm.load_array::(&destination, 0, 5).await?, + ['_' as JavaChar, '_' as JavaChar, 'b' as JavaChar, 'c' as JavaChar, '_' as JavaChar] + ); + + for (start, end) in [(-1, 1), (2, 1), (0, 5)] { + let result: Result<()> = jvm + .invoke_virtual(&buffer, "getChars", "(II[CI)V", (start, end, destination.clone(), 0)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("getChars must reject source range ({start}, {end})"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + } + + for destination_offset in [-1, 4] { + let result: Result<()> = jvm + .invoke_virtual(&buffer, "getChars", "(II[CI)V", (0, 2, destination.clone(), destination_offset)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("getChars must reject destination offset {destination_offset}"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + } + + let null: ClassInstanceRef> = None.into(); + let result: Result<()> = jvm.invoke_virtual(&buffer, "getChars", "(II[CI)V", (0, 1, null, 0)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("getChars must reject a null destination"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_sb_05_append_float_double_and_char_array() -> Result<()> { + let jvm = test_jvm().await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?; + let mut chars = jvm.instantiate_array("C", 2).await?; + jvm.store_array(&mut chars, 0, ['x' as JavaChar, 'y' as JavaChar]).await?; + + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "append", "(F)Ljava/lang/StringBuffer;", (1.0f32,)).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "append", "(D)Ljava/lang/StringBuffer;", (-0.0f64,)).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "append", "([C)Ljava/lang/StringBuffer;", (chars,)).await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "1.0-0.0xy"); + + let null: ClassInstanceRef> = None.into(); + let result: Result> = jvm.invoke_virtual(&buffer, "append", "([C)Ljava/lang/StringBuffer;", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("append(char[]) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_string: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (null_string,)) + .await?; + let null_object: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", (null_object,)) + .await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "1.0-0.0xynullnull"); + + Ok(()) +} + +#[tokio::test] +async fn test_sb_06_insert_overloads_and_boundaries() -> Result<()> { + let jvm = test_jvm().await?; + let source = JavaLangString::from_rust_string(&jvm, "ab").await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (source,)).await?; + let text = JavaLangString::from_rust_string(&jvm, "S").await?; + let object = JavaLangString::from_rust_string(&jvm, "O").await?; + let mut chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut chars, 0, ['C' as JavaChar]).await?; + + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (0, text)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(ILjava/lang/Object;)Ljava/lang/StringBuffer;", (1, object)) + .await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "insert", "(IZ)Ljava/lang/StringBuffer;", (2, true)).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(IC)Ljava/lang/StringBuffer;", (6, '!' as JavaChar)) + .await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "insert", "(II)Ljava/lang/StringBuffer;", (7, 12)).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "insert", "(IJ)Ljava/lang/StringBuffer;", (9, 34i64)).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(IF)Ljava/lang/StringBuffer;", (11, 1.0f32)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(ID)Ljava/lang/StringBuffer;", (14, 2.0f64)) + .await?; + let length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(I[C)Ljava/lang/StringBuffer;", (length, chars)) + .await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "SOtrue!12341.02.0abC"); + + let null_string: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (0, null_string)) + .await?; + let text = jvm.invoke_virtual(&buffer, "substring", "(II)Ljava/lang/String;", (0, 4)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "null"); + + let null_object: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&buffer, "insert", "(ILjava/lang/Object;)Ljava/lang/StringBuffer;", (4, null_object)) + .await?; + let text = jvm.invoke_virtual(&buffer, "substring", "(II)Ljava/lang/String;", (0, 8)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "nullnull"); + + let before_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + let before_text = JavaLangString::to_rust_string(&jvm, &before_text).await?; + let before_length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let before_capacity: i32 = jvm.invoke_virtual(&buffer, "capacity", "()I", ()).await?; + for offset in [-1, 100] { + let result: Result> = jvm + .invoke_virtual(&buffer, "insert", "(IC)Ljava/lang/StringBuffer;", (offset, 'x' as JavaChar)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("insert must reject offset {offset}"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, before_capacity); + } + + let null_chars: ClassInstanceRef> = None.into(); + let result: Result> = jvm + .invoke_virtual(&buffer, "insert", "(I[C)Ljava/lang/StringBuffer;", (0, null_chars)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("insert(char[]) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for offset in [-1, before_length + 1] { + let null_chars: ClassInstanceRef> = None.into(); + let result: Result> = jvm + .invoke_virtual(&buffer, "insert", "(I[C)Ljava/lang/StringBuffer;", (offset, null_chars)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("insert(char[]) must validate invalid offset {offset} before null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, before_capacity); + } + + Ok(()) +} + +#[tokio::test] +async fn test_sb_07_reverse_uses_raw_utf16_code_units() -> Result<()> { + let jvm = test_jvm().await?; + let mut chars = jvm.instantiate_array("C", 3).await?; + jvm.store_array(&mut chars, 0, [0xd800, 'a' as JavaChar, 0xdc00]).await?; + let string = jvm.new_class("java/lang/String", "([C)V", (chars,)).await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (string,)).await?; + + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "reverse", "()Ljava/lang/StringBuffer;", ()).await?; + let reversed = jvm.instantiate_array("C", 3).await?; + let _: () = jvm.invoke_virtual(&buffer, "getChars", "(II[CI)V", (0, 3, reversed.clone(), 0)).await?; + assert_eq!(jvm.load_array::(&reversed, 0, 3).await?, [0xdc00, 'a' as JavaChar, 0xd800]); + + Ok(()) +} + +#[tokio::test] +async fn test_sb_08_delete_and_delete_char_at_ranges() -> Result<()> { + let jvm = test_jvm().await?; + let source = JavaLangString::from_rust_string(&jvm, "abcdef").await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (source,)).await?; + let initial_capacity: i32 = jvm.invoke_virtual(&buffer, "capacity", "()I", ()).await?; + + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "delete", "(II)Ljava/lang/StringBuffer;", (2, 100)).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&buffer, "deleteCharAt", "(I)Ljava/lang/StringBuffer;", (1,)).await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "a"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, initial_capacity); + + let before_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + let before_text = JavaLangString::to_rust_string(&jvm, &before_text).await?; + let before_length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + for (start, end) in [(-1, 1), (2, 1), (2, 2)] { + let result: Result> = + jvm.invoke_virtual(&buffer, "delete", "(II)Ljava/lang/StringBuffer;", (start, end)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("delete must reject ({start}, {end})"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, initial_capacity); + } + + for index in [-1, 1] { + let result: Result> = + jvm.invoke_virtual(&buffer, "deleteCharAt", "(I)Ljava/lang/StringBuffer;", (index,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("deleteCharAt must reject {index}"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, initial_capacity); + } + + Ok(()) +} + +#[tokio::test] +async fn test_sb_09_substring_uses_logical_count() -> Result<()> { + let jvm = test_jvm().await?; + let source = JavaLangString::from_rust_string(&jvm, "abcdef").await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (source,)).await?; + + let tail = jvm.invoke_virtual(&buffer, "substring", "(I)Ljava/lang/String;", (2,)).await?; + let middle = jvm.invoke_virtual(&buffer, "substring", "(II)Ljava/lang/String;", (1, 4)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &tail).await?, "cdef"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &middle).await?, "bcd"); + + for (start, end) in [(-1, 1), (3, 2), (0, 7)] { + let result: Result> = + jvm.invoke_virtual(&buffer, "substring", "(II)Ljava/lang/String;", (start, end)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("substring must reject ({start}, {end})"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + } + + Ok(()) +} + +#[tokio::test] +async fn test_sb_10_replace_clamps_end_and_checks_ranges() -> Result<()> { + let jvm = test_jvm().await?; + let source = JavaLangString::from_rust_string(&jvm, "abcdef").await?; + let buffer = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (source,)).await?; + let replacement = JavaLangString::from_rust_string(&jvm, "XY").await?; + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &buffer, + "replace", + "(IILjava/lang/String;)Ljava/lang/StringBuffer;", + (2, 100, replacement), + ) + .await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "abXY"); + + let before_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + let before_text = JavaLangString::to_rust_string(&jvm, &before_text).await?; + let before_length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let before_capacity: i32 = jvm.invoke_virtual(&buffer, "capacity", "()I", ()).await?; + let replacement = JavaLangString::from_rust_string(&jvm, "z").await?; + for (start, end) in [(-1, 1), (3, 2), (5, 5)] { + let result: Result> = jvm + .invoke_virtual( + &buffer, + "replace", + "(IILjava/lang/String;)Ljava/lang/StringBuffer;", + (start, end, replacement.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("replace must reject ({start}, {end})"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, before_capacity); + } + + let null: ClassInstanceRef = None.into(); + let result: Result> = jvm + .invoke_virtual(&buffer, "replace", "(IILjava/lang/String;)Ljava/lang/StringBuffer;", (0, 1, null)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("replace must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, before_capacity); + + let null: ClassInstanceRef = None.into(); + let result: Result> = jvm + .invoke_virtual(&buffer, "replace", "(IILjava/lang/String;)Ljava/lang/StringBuffer;", (-1, 1, null)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("replace must validate its range before dereferencing the replacement"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/StringIndexOutOfBoundsException")); + let after_text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &after_text).await?, before_text); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "length", "()I", ()).await?, before_length); + assert_eq!(jvm.invoke_virtual::<_, i32>(&buffer, "capacity", "()I", ()).await?, before_capacity); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_system.rs b/java_runtime/tests/classes/java/lang/test_system.rs index 78309ac4..02c71100 100644 --- a/java_runtime/tests/classes/java/lang/test_system.rs +++ b/java_runtime/tests/classes/java/lang/test_system.rs @@ -1,7 +1,15 @@ -use alloc::collections::BTreeMap; +use alloc::{collections::BTreeMap, vec}; -use java_runtime::Runtime; -use jvm::Result; +use java_constants::{FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + Runtime, + classes::java::{ + io::{InputStream, PrintStream}, + lang::{Object, Runtime as JavaRuntime, String, System}, + util::Properties, + }, +}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::{TestRuntime, create_test_jvm}; #[tokio::test] @@ -19,5 +27,121 @@ async fn test_system_time_yield_and_exit_runtime_contract() -> Result<()> { let _: () = jvm.invoke_static("java/lang/System", "exit", "(I)V", (i32::MIN,)).await?; assert_eq!(runtime.exit_status(), Some(i32::MIN)); + let runtime_instance: ClassInstanceRef = jvm.invoke_static("java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", ()).await?; + let _: () = jvm.invoke_virtual(&runtime_instance, "exit", "(I)V", (23,)).await?; + assert_eq!(runtime.exit_status(), Some(23)); + + Ok(()) +} + +#[tokio::test] +async fn sys_01_to_06_descriptors_streams_properties_and_identity_hash() -> Result<()> { + let system = System::as_proto(); + for (name, descriptor) in [ + ("identityHashCode", "(Ljava/lang/Object;)I"), + ("setIn", "(Ljava/io/InputStream;)V"), + ("setOut", "(Ljava/io/PrintStream;)V"), + ("setErr", "(Ljava/io/PrintStream;)V"), + ("getProperties", "()Ljava/util/Properties;"), + ("getProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), + ] { + let method = system + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing java/lang/System.{name}{descriptor}")); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC)); + } + let input = system + .fields + .iter() + .find(|field| field.name == "in" && field.descriptor == "Ljava/io/InputStream;") + .expect("missing java/lang/System.in"); + assert!( + input + .access_flags + .contains(FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL) + ); + + let runtime = TestRuntime::new_with_stdin(BTreeMap::new(), vec![0x41, 0x42]); + let jvm = create_test_jvm(runtime).await?; + + let stdin: ClassInstanceRef = jvm.get_static_field("java/lang/System", "in", "Ljava/io/InputStream;").await?; + assert!(!stdin.is_null()); + assert_eq!(jvm.invoke_virtual::<_, i32>(&stdin, "read", "()I", ()).await?, 0x41); + + let mut bytes = jvm.instantiate_array("B", 1).await?; + jvm.store_array(&mut bytes, 0, [7i8]).await?; + let replacement_in: ClassInstanceRef = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?.into(); + let _: () = jvm + .invoke_static("java/lang/System", "setIn", "(Ljava/io/InputStream;)V", (replacement_in.clone(),)) + .await?; + let stored_in: ClassInstanceRef = jvm.get_static_field("java/lang/System", "in", "Ljava/io/InputStream;").await?; + assert_eq!(stored_in.identity(), replacement_in.identity()); + + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let replacement_out: ClassInstanceRef = jvm.new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (output,)).await?.into(); + let _: () = jvm + .invoke_static("java/lang/System", "setOut", "(Ljava/io/PrintStream;)V", (replacement_out.clone(),)) + .await?; + let stored_out: ClassInstanceRef = jvm.get_static_field("java/lang/System", "out", "Ljava/io/PrintStream;").await?; + assert_eq!(stored_out.identity(), replacement_out.identity()); + + let _: () = jvm + .invoke_static("java/lang/System", "setErr", "(Ljava/io/PrintStream;)V", (None,)) + .await?; + let stored_err: ClassInstanceRef = jvm.get_static_field("java/lang/System", "err", "Ljava/io/PrintStream;").await?; + assert!(stored_err.is_null()); + + let properties: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperties", "()Ljava/util/Properties;", ()) + .await?; + let properties_again: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperties", "()Ljava/util/Properties;", ()) + .await?; + assert_eq!(properties.identity(), properties_again.identity()); + + let fallback = JavaLangString::from_rust_string(&jvm, "fallback").await?; + let missing = JavaLangString::from_rust_string(&jvm, "missing").await?; + let value: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (missing, fallback.clone()), + ) + .await?; + assert_eq!(value.identity(), fallback.identity()); + + let result: Result> = jvm + .invoke_static( + "java/lang/System", + "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (None, fallback), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("System.getProperty(null, default) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let object_hash: i32 = jvm.invoke_special(&object, "java/lang/Object", "hashCode", "()I", ()).await?; + let identity_hash: i32 = jvm + .invoke_static("java/lang/System", "identityHashCode", "(Ljava/lang/Object;)I", (object,)) + .await?; + assert_eq!(identity_hash, object_hash); + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/lang/System", + "identityHashCode", + "(Ljava/lang/Object;)I", + (ClassInstanceRef::::new(None),), + ) + .await?, + 0 + ); + Ok(()) } diff --git a/java_runtime/tests/classes/java/lang/test_thread.rs b/java_runtime/tests/classes/java/lang/test_thread.rs index 2c4d60d2..0ee62974 100644 --- a/java_runtime/tests/classes/java/lang/test_thread.rs +++ b/java_runtime/tests/classes/java/lang/test_thread.rs @@ -1,6 +1,7 @@ use alloc::{boxed::Box, collections::BTreeMap, vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::MethodAccessFlags; use java_runtime::{RuntimeClassProto, RuntimeContext}; use jvm::{ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; use jvm_rust::ClassDefinitionImpl; @@ -59,6 +60,10 @@ async fn test_thread() -> Result<()> { let ran: bool = jvm.get_field(&test_class, "ran", "Z").await?; assert!(ran); + assert!(!jvm.invoke_virtual::<_, bool>(&thread, "isAlive", "()Z", ()).await?); + let _: () = jvm.invoke_virtual(&thread, "setDaemon", "(Z)V", (true,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&thread, "isDaemon", "()Z", ()).await?); + Ok(()) } @@ -103,3 +108,93 @@ async fn test_thread_cldc_metadata_and_state() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_thread_jdk12_metadata_name_interrupt_and_daemon_state() -> Result<()> { + let runtime = TestRuntime::new_with_queued_spawns(BTreeMap::new()); + let jvm = create_test_jvm(runtime).await?; + let thread = jvm.new_class("java/lang/Thread", "()V", ()).await?; + + let class = jvm.get_class("java/lang/Thread").expect("Thread must be loaded"); + for (name, descriptor, is_static, expected) in [ + ( + "setName", + "(Ljava/lang/String;)V", + false, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + ("isInterrupted", "()Z", false, MethodAccessFlags::PUBLIC), + ("interrupted", "()Z", true, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + ( + "join", + "(J)V", + false, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL | MethodAccessFlags::SYNCHRONIZED, + ), + ("setDaemon", "(Z)V", false, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ("isDaemon", "()Z", false, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ] { + let flags = class + .definition + .method(name, descriptor, is_static) + .unwrap_or_else(|| panic!("missing Thread.{name}{descriptor}")) + .access_flags(); + assert_eq!(flags, expected, "wrong flags for Thread.{name}{descriptor}"); + } + + let original_name = jvm.invoke_virtual(&thread, "getName", "()Ljava/lang/String;", ()).await?; + let result: Result<()> = jvm + .invoke_virtual( + &thread, + "setName", + "(Ljava/lang/String;)V", + (ClassInstanceRef::::new(None),), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("setName(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let unchanged_name = jvm.invoke_virtual(&thread, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_rust_string(&jvm, &unchanged_name).await?, + JavaLangString::to_rust_string(&jvm, &original_name).await? + ); + + let renamed = JavaLangString::from_rust_string(&jvm, "renamed").await?; + let _: () = jvm.invoke_virtual(&thread, "setName", "(Ljava/lang/String;)V", (renamed,)).await?; + let actual_name = jvm.invoke_virtual(&thread, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual_name).await?, "renamed"); + + assert!(!jvm.invoke_virtual::<_, bool>(&thread, "isDaemon", "()Z", ()).await?); + let _: () = jvm.invoke_virtual(&thread, "setDaemon", "(Z)V", (true,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&thread, "isDaemon", "()Z", ()).await?); + + assert!(!jvm.invoke_virtual::<_, bool>(&thread, "isInterrupted", "()Z", ()).await?); + let _: () = jvm.invoke_virtual(&thread, "interrupt", "()V", ()).await?; + assert!(jvm.invoke_virtual::<_, bool>(&thread, "isInterrupted", "()Z", ()).await?); + assert!(jvm.invoke_virtual::<_, bool>(&thread, "isInterrupted", "()Z", ()).await?); + + let current = jvm.invoke_static("java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", ()).await?; + let _: () = jvm.invoke_virtual(¤t, "interrupt", "()V", ()).await?; + assert!(jvm.invoke_static::<_, bool>("java/lang/Thread", "interrupted", "()Z", ()).await?); + assert!(!jvm.invoke_static::<_, bool>("java/lang/Thread", "interrupted", "()Z", ()).await?); + assert!(!jvm.invoke_virtual::<_, bool>(¤t, "isInterrupted", "()Z", ()).await?); + assert!(!jvm.get_field::(¤t, "interrupted", "Z").await?); + + let result: Result<()> = jvm.invoke_virtual(&thread, "join", "(J)V", (-1i64,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("join(-1) must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + + let _: () = jvm.invoke_virtual(&thread, "start", "()V", ()).await?; + let result: Result<()> = jvm.invoke_virtual(&thread, "setDaemon", "(Z)V", (false,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("setDaemon after start must throw IllegalThreadStateException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalThreadStateException")); + assert!(jvm.invoke_virtual::<_, bool>(&thread, "isDaemon", "()Z", ()).await?); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_throwable.rs b/java_runtime/tests/classes/java/lang/test_throwable.rs index 2b0c7721..e3417649 100644 --- a/java_runtime/tests/classes/java/lang/test_throwable.rs +++ b/java_runtime/tests/classes/java/lang/test_throwable.rs @@ -20,6 +20,8 @@ async fn test_to_string() -> Result<()> { let message: ClassInstanceRef = jvm.invoke_virtual(&throwable, "getMessage", "()Ljava/lang/String;", ()).await?; assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, "test message"); + let localized: ClassInstanceRef = jvm.invoke_virtual(&throwable, "getLocalizedMessage", "()Ljava/lang/String;", ()).await?; + assert_eq!(localized.identity(), message.identity()); Ok(()) } diff --git a/java_runtime/tests/classes/java/util/mod.rs b/java_runtime/tests/classes/java/util/mod.rs index 99791d9e..fb974e99 100644 --- a/java_runtime/tests/classes/java/util/mod.rs +++ b/java_runtime/tests/classes/java/util/mod.rs @@ -1,16 +1,22 @@ mod jar; mod test_array_list; +mod test_arrays; +mod test_collection_contracts; +mod test_collections; mod test_date; mod test_gregorian_calendar; mod test_hash_map; mod test_hash_set; mod test_hashtable; +mod test_list_views; mod test_locale; mod test_properties; mod test_random; mod test_stack; +mod test_string_tokenizer; mod test_timer; mod test_timezone; +mod test_tree_map; mod test_url; mod test_vector; mod zip; diff --git a/java_runtime/tests/classes/java/util/test_array_list.rs b/java_runtime/tests/classes/java/util/test_array_list.rs index 919c5a78..d645201a 100644 --- a/java_runtime/tests/classes/java/util/test_array_list.rs +++ b/java_runtime/tests/classes/java/util/test_array_list.rs @@ -175,7 +175,7 @@ async fn test_array_list_assignability_and_interface_style_calls() -> Result<()> } #[tokio::test] -async fn test_array_list_itr_snapshot_exhaustion_and_remove() -> Result<()> { +async fn test_array_list_itr_is_live_and_remove_writes_through() -> Result<()> { let jvm = test_jvm().await?; let array_list = jvm.new_class("java/util/ArrayList", "()V", ()).await?; @@ -201,7 +201,13 @@ async fn test_array_list_itr_snapshot_exhaustion_and_remove() -> Result<()> { assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "second"); let has_next: bool = jvm.invoke_virtual(&iterator, "hasNext", "()Z", ()).await?; - assert!(!has_next); + assert!(has_next); + + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "third"); + + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&array_list, "size", "()I", ()).await?, 2); let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; let Err(JavaError::JavaException(exception)) = result else { @@ -213,7 +219,7 @@ async fn test_array_list_itr_snapshot_exhaustion_and_remove() -> Result<()> { 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")); let empty_array_list = jvm.new_class("java/util/ArrayList", "()V", ()).await?; let empty_iterator: ClassInstanceRef = jvm.invoke_virtual(&empty_array_list, "iterator", "()Ljava/util/Iterator;", ()).await?; diff --git a/java_runtime/tests/classes/java/util/test_arrays.rs b/java_runtime/tests/classes/java/util/test_arrays.rs new file mode 100644 index 00000000..c4975eb0 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_arrays.rs @@ -0,0 +1,1667 @@ +use alloc::{boxed::Box, collections::BTreeMap, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; + +struct ArraysSortValue; + +impl ArraysSortValue { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ArraysSortValue", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "(IIZ)V", Self::init, Default::default()), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("key", "I", Default::default()), + JavaFieldProto::new("id", "I", Default::default()), + JavaFieldProto::new("fail", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, key: i32, id: i32, fail: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "key", "I", key).await?; + jvm.put_field(&mut this, "id", "I", id).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(other.as_ref(), "ArraysSortValue") { + return Err(jvm.exception("java/lang/ClassCastException", "other").await); + } + if jvm.get_field::(&this, "fail", "Z").await? || jvm.get_field::(&other, "fail", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "comparison failure").await); + } + Ok(jvm + .get_field::(&this, "key", "I") + .await? + .cmp(&jvm.get_field::(&other, "key", "I").await?) as i32) + } +} + +struct ArraysComparator; + +impl ArraysComparator { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ArraysComparator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Comparator"], + methods: vec![ + JavaMethodProto::new("", "(ZZ)V", Self::init, Default::default()), + JavaMethodProto::new( + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + Self::compare, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![ + JavaFieldProto::new("reverse", "Z", Default::default()), + JavaFieldProto::new("fail", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, reverse: bool, fail: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "reverse", "Z", reverse).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await + } + + async fn compare( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + left: ClassInstanceRef, + right: ClassInstanceRef, + ) -> Result { + if jvm.get_field::(&this, "fail", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "comparison failure").await); + } + if left.is_null() || right.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let comparison = jvm + .get_field::(&left, "key", "I") + .await? + .cmp(&jvm.get_field::(&right, "key", "I").await?) as i32; + Ok(if jvm.get_field::(&this, "reverse", "Z").await? { + -comparison + } else { + comparison + }) + } +} + +struct ArraysEqualsProbe; + +impl ArraysEqualsProbe { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ArraysEqualsProbe", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Z)V", Self::init, Default::default()), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("result", "Z", Default::default())], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, result: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "result", "Z", result).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + jvm.get_field(&this, "result", "Z").await + } +} + +async fn arrays_object_test_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + for proto in [ArraysSortValue::as_proto(), ArraysComparator::as_proto(), ArraysEqualsProbe::as_proto()] { + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto(proto, Box::new(runtime.clone()) as Box<_>)), + None, + ) + .await?; + } + Ok(jvm) +} + +#[tokio::test] +async fn test_arr_00_exact_descriptors_access_and_registration() -> Result<()> { + let jvm = test_jvm().await?; + let class = jvm.resolve_class("java/util/Arrays").await?; + assert!( + class + .definition + .access_flags() + .contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL) + ); + assert!( + class + .definition + .method("", "()V", false) + .expect("Arrays private constructor") + .access_flags() + .contains(MethodAccessFlags::PRIVATE) + ); + + let descriptors = [ + ("sort", "([B)V"), + ("sort", "([BII)V"), + ("sort", "([C)V"), + ("sort", "([CII)V"), + ("sort", "([S)V"), + ("sort", "([SII)V"), + ("sort", "([I)V"), + ("sort", "([III)V"), + ("sort", "([J)V"), + ("sort", "([JII)V"), + ("sort", "([F)V"), + ("sort", "([FII)V"), + ("sort", "([D)V"), + ("sort", "([DII)V"), + ("sort", "([Ljava/lang/Object;)V"), + ("sort", "([Ljava/lang/Object;II)V"), + ("sort", "([Ljava/lang/Object;Ljava/util/Comparator;)V"), + ("sort", "([Ljava/lang/Object;IILjava/util/Comparator;)V"), + ("binarySearch", "([BB)I"), + ("binarySearch", "([CC)I"), + ("binarySearch", "([SS)I"), + ("binarySearch", "([II)I"), + ("binarySearch", "([JJ)I"), + ("binarySearch", "([FF)I"), + ("binarySearch", "([DD)I"), + ("binarySearch", "([Ljava/lang/Object;Ljava/lang/Object;)I"), + ("binarySearch", "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I"), + ("equals", "([Z[Z)Z"), + ("equals", "([B[B)Z"), + ("equals", "([C[C)Z"), + ("equals", "([S[S)Z"), + ("equals", "([I[I)Z"), + ("equals", "([J[J)Z"), + ("equals", "([F[F)Z"), + ("equals", "([D[D)Z"), + ("equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z"), + ("fill", "([ZZ)V"), + ("fill", "([ZIIZ)V"), + ("fill", "([BB)V"), + ("fill", "([BIIB)V"), + ("fill", "([CC)V"), + ("fill", "([CIIC)V"), + ("fill", "([SS)V"), + ("fill", "([SIIS)V"), + ("fill", "([II)V"), + ("fill", "([IIII)V"), + ("fill", "([JJ)V"), + ("fill", "([JIIJ)V"), + ("fill", "([FF)V"), + ("fill", "([FIIF)V"), + ("fill", "([DD)V"), + ("fill", "([DIID)V"), + ("fill", "([Ljava/lang/Object;Ljava/lang/Object;)V"), + ("fill", "([Ljava/lang/Object;IILjava/lang/Object;)V"), + ("asList", "([Ljava/lang/Object;)Ljava/util/List;"), + ]; + for (name, descriptor) in descriptors { + let method = class + .definition + .method(name, descriptor, true) + .unwrap_or_else(|| panic!("missing Arrays.{name}{descriptor}")); + assert!( + method.access_flags().contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + "wrong access for Arrays.{name}{descriptor}" + ); + } + assert!(class.definition.method("sort", "([Z)V", true).is_none()); + assert!(class.definition.method("sort", "([ZII)V", true).is_none()); + assert!(class.definition.method("binarySearch", "([ZZ)I", true).is_none()); + + let array_list = jvm.resolve_class("java/util/Arrays$ArrayList").await?; + assert_eq!(array_list.definition.super_class_name().as_deref(), Some("java/util/AbstractList")); + assert!(array_list.definition.interface_names().iter().any(|name| name == "java/io/Serializable")); + assert!(!array_list.definition.access_flags().contains(ClassAccessFlags::PUBLIC)); + let field = array_list + .definition + .field("a", "[Ljava/lang/Object;", false) + .expect("Arrays$ArrayList.a"); + assert!(field.access_flags().contains(FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_01_all_primitive_sort_overloads_and_ranges() -> Result<()> { + let jvm = test_jvm().await?; + + let mut bytes: ClassInstanceRef> = jvm.instantiate_array("B", 3).await?.into(); + jvm.store_array(&mut bytes, 0, [3i8, -1, 2]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([B)V", (bytes.clone(),)).await?; + assert_eq!(jvm.load_array::(&bytes, 0, 3).await?, [-1, 2, 3]); + + let mut chars: ClassInstanceRef> = jvm.instantiate_array("C", 4).await?.into(); + jvm.store_array(&mut chars, 0, [9 as JavaChar, 4 as JavaChar, 2 as JavaChar, 8 as JavaChar]) + .await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([CII)V", (chars.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&chars, 0, 4).await?, [9, 2, 4, 8]); + + let mut shorts: ClassInstanceRef> = jvm.instantiate_array("S", 3).await?.into(); + jvm.store_array(&mut shorts, 0, [2i16, -4, 1]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([S)V", (shorts.clone(),)).await?; + assert_eq!(jvm.load_array::(&shorts, 0, 3).await?, [-4, 1, 2]); + + let mut ints: ClassInstanceRef> = jvm.instantiate_array("I", 5).await?.into(); + jvm.store_array(&mut ints, 0, [9, 4, 3, 2, 8]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([III)V", (ints.clone(), 1, 4)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 5).await?, [9, 2, 3, 4, 8]); + + let mut longs: ClassInstanceRef> = jvm.instantiate_array("J", 3).await?.into(); + jvm.store_array(&mut longs, 0, [i64::MAX, i64::MIN, 0]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([J)V", (longs.clone(),)).await?; + assert_eq!(jvm.load_array::(&longs, 0, 3).await?, [i64::MIN, 0, i64::MAX]); + + let mut floats: ClassInstanceRef> = jvm.instantiate_array("F", 3).await?.into(); + jvm.store_array(&mut floats, 0, [2.0f32, -1.0f32, 1.0f32]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([F)V", (floats.clone(),)).await?; + assert_eq!(jvm.load_array::(&floats, 0, 3).await?, [-1.0, 1.0, 2.0]); + + let mut doubles: ClassInstanceRef> = jvm.instantiate_array("D", 4).await?.into(); + jvm.store_array(&mut doubles, 0, [9.0, 2.0, 1.0, 8.0]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([DII)V", (doubles.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&doubles, 0, 4).await?, [9.0, 1.0, 2.0, 8.0]); + + let empty: ClassInstanceRef> = jvm.instantiate_array("I", 0).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([I)V", (empty,)).await?; + let single: ClassInstanceRef> = jvm.instantiate_array("I", 1).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([III)V", (single.clone(), 0, 1)) + .await?; + assert_eq!(jvm.load_array::(&single, 0, 1).await?, [0]); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_01_range_exception_order() -> Result<()> { + let jvm = test_jvm().await?; + let array: ClassInstanceRef> = jvm.instantiate_array("I", 2).await?.into(); + + let result: Result<()> = jvm.invoke_static("java/util/Arrays", "sort", "([III)V", (array.clone(), 4, 3)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fromIndex > toIndex must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + + for (from, to) in [(-1, 1), (0, 3)] { + let result: Result<()> = jvm.invoke_static("java/util/Arrays", "sort", "([III)V", (array.clone(), from, to)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("out-of-bounds range must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayIndexOutOfBoundsException")); + } + + Ok(()) +} + +#[tokio::test] +async fn test_arr_01_float_double_total_order() -> Result<()> { + let jvm = test_jvm().await?; + let nan_a = f32::from_bits(0x7fc0_0001); + let nan_b = f32::from_bits(0xffc0_1234); + let mut floats: ClassInstanceRef> = jvm.instantiate_array("F", 9).await?.into(); + jvm.store_array( + &mut floats, + 0, + [nan_a, 0.0, f32::INFINITY, -0.0, -1.0, nan_b, f32::NEG_INFINITY, 1.0, 4.0], + ) + .await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([F)V", (floats.clone(),)).await?; + let sorted = jvm.load_array::(&floats, 0, 9).await?; + assert_eq!(sorted[..7], [f32::NEG_INFINITY, -1.0, -0.0, 0.0, 1.0, 4.0, f32::INFINITY]); + assert_eq!(sorted[2].to_bits(), (-0.0f32).to_bits()); + assert_eq!(sorted[3].to_bits(), 0.0f32.to_bits()); + assert!(sorted[7].is_nan() && sorted[8].is_nan()); + + let nan_a = f64::from_bits(0x7ff8_0000_0000_0001); + let nan_b = f64::from_bits(0xfff8_0000_0000_1234); + let mut doubles: ClassInstanceRef> = jvm.instantiate_array("D", 7).await?.into(); + jvm.store_array(&mut doubles, 0, [nan_a, 0.0, f64::INFINITY, -0.0, nan_b, f64::NEG_INFINITY, 1.0]) + .await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([D)V", (doubles.clone(),)) + .await?; + let sorted = jvm.load_array::(&doubles, 0, 7).await?; + assert_eq!(sorted[..5], [f64::NEG_INFINITY, -0.0, 0.0, 1.0, f64::INFINITY]); + assert_eq!(sorted[1].to_bits(), (-0.0f64).to_bits()); + assert_eq!(sorted[2].to_bits(), 0.0f64.to_bits()); + assert!(sorted[5].is_nan() && sorted[6].is_nan()); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_02_object_sort_is_stable_and_supports_all_modes() -> Result<()> { + let jvm = arrays_object_test_jvm().await?; + let values = [ + jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 0, false)).await?, + jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 1, false)).await?, + jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 2, false)).await?, + jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 3, false)).await?, + ]; + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 4).await?.into(); + jvm.store_array(&mut array, 0, values).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([Ljava/lang/Object;)V", (array.clone(),)) + .await?; + let sorted = jvm.load_array::>(&array, 0, 4).await?; + let mut ids = vec![]; + for value in sorted { + ids.push(jvm.get_field::(&value, "id", "I").await?); + } + assert_eq!(ids, [1, 3, 0, 2]); + + let outside_left = jvm.new_class("ArraysSortValue", "(IIZ)V", (99, 10, false)).await?; + let low = jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 11, false)).await?; + let high = jvm.new_class("ArraysSortValue", "(IIZ)V", (3, 12, false)).await?; + let outside_right = jvm.new_class("ArraysSortValue", "(IIZ)V", (-99, 13, false)).await?; + let mut range: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 4).await?.into(); + jvm.store_array(&mut range, 0, [outside_left.clone(), low, high, outside_right.clone()]) + .await?; + let reverse = jvm.new_class("ArraysComparator", "(ZZ)V", (true, false)).await?; + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "sort", + "([Ljava/lang/Object;IILjava/util/Comparator;)V", + (range.clone(), 1, 3, reverse), + ) + .await?; + let sorted = jvm.load_array::>(&range, 0, 4).await?; + assert_eq!(sorted[0].identity(), outside_left.identity()); + assert_eq!(jvm.get_field::(&sorted[1], "key", "I").await?, 3); + assert_eq!(jvm.get_field::(&sorted[2], "key", "I").await?, 1); + assert_eq!(sorted[3].identity(), outside_right.identity()); + + let null_comparator: ClassInstanceRef = None.into(); + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "sort", + "([Ljava/lang/Object;Ljava/util/Comparator;)V", + (range.clone(), null_comparator), + ) + .await?; + let sorted = jvm.load_array::>(&range, 0, 4).await?; + let keys = [ + jvm.get_field::(&sorted[0], "key", "I").await?, + jvm.get_field::(&sorted[1], "key", "I").await?, + jvm.get_field::(&sorted[2], "key", "I").await?, + jvm.get_field::(&sorted[3], "key", "I").await?, + ]; + assert_eq!(keys, [-99, 1, 3, 99]); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_02_comparison_exceptions_do_not_partially_write() -> Result<()> { + let jvm = arrays_object_test_jvm().await?; + let first = jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 1, false)).await?; + let failing = jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 2, true)).await?; + let last = jvm.new_class("ArraysSortValue", "(IIZ)V", (0, 3, false)).await?; + let mut natural: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + jvm.store_array(&mut natural, 0, [first.clone(), failing.clone(), last.clone()]).await?; + let result: Result<()> = jvm + .invoke_static("java/util/Arrays", "sort", "([Ljava/lang/Object;)V", (natural.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("natural comparison failure must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + let after = jvm.load_array::>(&natural, 0, 3).await?; + assert_eq!( + after.iter().map(|value| value.identity()).collect::>(), + [first.identity(), failing.identity(), last.identity()] + ); + + let mut compared: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + jvm.store_array(&mut compared, 0, [last.clone(), first.clone(), failing.clone()]).await?; + let comparator = jvm.new_class("ArraysComparator", "(ZZ)V", (false, true)).await?; + let result: Result<()> = jvm + .invoke_static( + "java/util/Arrays", + "sort", + "([Ljava/lang/Object;Ljava/util/Comparator;)V", + (compared.clone(), comparator), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("comparator failure must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + let after = jvm.load_array::>(&compared, 0, 3).await?; + assert_eq!( + after.iter().map(|value| value.identity()).collect::>(), + [last.identity(), first.identity(), failing.identity()] + ); + + let first_object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let second_object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let mut non_comparable: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut non_comparable, 0, [first_object.clone(), second_object.clone()]) + .await?; + let result: Result<()> = jvm + .invoke_static("java/util/Arrays", "sort", "([Ljava/lang/Object;)V", (non_comparable.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("natural sort must reject non-Comparable values"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException")); + let after = jvm.load_array::>(&non_comparable, 0, 2).await?; + assert_eq!(after[0].identity(), first_object.identity()); + assert_eq!(after[1].identity(), second_object.identity()); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_03_04_binary_search_insertion_points_and_object_modes() -> Result<()> { + let jvm = arrays_object_test_jvm().await?; + + let mut bytes: ClassInstanceRef> = jvm.instantiate_array("B", 3).await?.into(); + jvm.store_array(&mut bytes, 0, [-2i8, 0, 5]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([BB)I", (bytes, 5i8)) + .await?, + 2 + ); + let mut chars: ClassInstanceRef> = jvm.instantiate_array("C", 2).await?.into(); + jvm.store_array(&mut chars, 0, [1 as JavaChar, 3 as JavaChar]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([CC)I", (chars, 2 as JavaChar)) + .await?, + -2 + ); + let mut shorts: ClassInstanceRef> = jvm.instantiate_array("S", 2).await?.into(); + jvm.store_array(&mut shorts, 0, [-1i16, 7]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([SS)I", (shorts, -1i16)) + .await?, + 0 + ); + let mut ints: ClassInstanceRef> = jvm.instantiate_array("I", 3).await?.into(); + jvm.store_array(&mut ints, 0, [1, 3, 5]).await?; + for (key, expected) in [(0, -1), (1, 0), (2, -2), (5, 2), (6, -4)] { + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([II)I", (ints.clone(), key)) + .await?, + expected + ); + } + let mut longs: ClassInstanceRef> = jvm.instantiate_array("J", 2).await?.into(); + jvm.store_array(&mut longs, 0, [i64::MIN, i64::MAX]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([JJ)I", (longs, i64::MAX)) + .await?, + 1 + ); + let mut floats: ClassInstanceRef> = jvm.instantiate_array("F", 3).await?.into(); + jvm.store_array(&mut floats, 0, [-0.0, 0.0, f32::NAN]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([FF)I", (floats.clone(), -0.0f32)) + .await?, + 0 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([FF)I", (floats, f32::from_bits(0x7fc0_1234))) + .await?, + 2 + ); + let mut doubles: ClassInstanceRef> = jvm.instantiate_array("D", 2).await?.into(); + jvm.store_array(&mut doubles, 0, [0.0, f64::INFINITY]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([DD)I", (doubles, -0.0f64)) + .await?, + -1 + ); + + let low = jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 1, false)).await?; + let high = jvm.new_class("ArraysSortValue", "(IIZ)V", (3, 2, false)).await?; + let key = jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 3, false)).await?; + let mut objects: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut objects, 0, [low, high]).await?; + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;)I", + (objects.clone(), key.clone()), + ) + .await?, + -2 + ); + + let high = jvm.new_class("ArraysSortValue", "(IIZ)V", (3, 4, false)).await?; + let low = jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 5, false)).await?; + let mut descending: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut descending, 0, [high, low]).await?; + let reverse = jvm.new_class("ArraysComparator", "(ZZ)V", (true, false)).await?; + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + (descending, key, reverse), + ) + .await?, + -2 + ); + + let failing_key = jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 6, true)).await?; + let result: Result = jvm + .invoke_static( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;)I", + (objects, failing_key), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("natural binarySearch comparison failure must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + + let first_object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let second_object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let mut non_comparable: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut non_comparable, 0, core::iter::once(first_object)).await?; + let result: Result = jvm + .invoke_static( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;)I", + (non_comparable, second_object), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("natural binarySearch must reject non-Comparable values"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException")); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_05_equals_null_float_bits_and_query_direction() -> Result<()> { + let jvm = arrays_object_test_jvm().await?; + let null_ints: ClassInstanceRef> = None.into(); + let values: ClassInstanceRef> = jvm.instantiate_array("I", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([I[I)Z", (null_ints.clone(), null_ints.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([I[I)Z", (null_ints, values)) + .await? + ); + + let mut first_float: ClassInstanceRef> = jvm.instantiate_array("F", 1).await?.into(); + let mut second_float: ClassInstanceRef> = jvm.instantiate_array("F", 1).await?.into(); + jvm.store_array(&mut first_float, 0, [f32::from_bits(0x7fc0_0001)]).await?; + jvm.store_array(&mut second_float, 0, [f32::from_bits(0xffc0_1234)]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([F[F)Z", (first_float.clone(), second_float.clone()),) + .await? + ); + jvm.store_array(&mut first_float, 0, [-0.0f32]).await?; + jvm.store_array(&mut second_float, 0, [0.0f32]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([F[F)Z", (first_float, second_float)) + .await? + ); + + let mut first_double: ClassInstanceRef> = jvm.instantiate_array("D", 1).await?.into(); + let mut second_double: ClassInstanceRef> = jvm.instantiate_array("D", 1).await?.into(); + jvm.store_array(&mut first_double, 0, [f64::from_bits(0x7ff8_0000_0000_0001)]).await?; + jvm.store_array(&mut second_double, 0, [f64::from_bits(0xfff8_0000_0000_1234)]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([D[D)Z", (first_double.clone(), second_double.clone()),) + .await? + ); + jvm.store_array(&mut first_double, 0, [-0.0f64]).await?; + jvm.store_array(&mut second_double, 0, [0.0f64]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([D[D)Z", (first_double, second_double)) + .await? + ); + + let query: ClassInstanceRef = jvm.new_class("ArraysEqualsProbe", "(Z)V", (true,)).await?.into(); + let stored: ClassInstanceRef = jvm.new_class("ArraysEqualsProbe", "(Z)V", (false,)).await?.into(); + let mut first: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + let mut second: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + let null: ClassInstanceRef = None.into(); + jvm.store_array(&mut first, 0, [query, null.clone()]).await?; + jvm.store_array(&mut second, 0, [stored, null]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", (first, second),) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_06_07_fill_ranges_and_object_store_failure() -> Result<()> { + let jvm = test_jvm().await?; + let mut ints: ClassInstanceRef> = jvm.instantiate_array("I", 5).await?.into(); + jvm.store_array(&mut ints, 0, [1, 2, 3, 4, 5]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), 1, 4, 9)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 5).await?, [1, 9, 9, 9, 5]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([II)V", (ints.clone(), -2)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 5).await?, [-2; 5]); + + let result: Result<()> = jvm.invoke_static("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), 8, 7, 0)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fill fromIndex > toIndex must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + let result: Result<()> = jvm.invoke_static("java/util/Arrays", "fill", "([IIII)V", (ints, -1, 2, 0)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fill bounds must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayIndexOutOfBoundsException")); + + let left = JavaLangString::from_rust_string(&jvm, "left").await?; + let right = JavaLangString::from_rust_string(&jvm, "right").await?; + let mut strings: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 3).await?.into(); + jvm.store_array(&mut strings, 0, [left.clone(), left.clone(), right.clone()]).await?; + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (strings.clone(), 1, 3, right.clone()), + ) + .await?; + let after = jvm.load_array::>(&strings, 0, 3).await?; + assert_eq!(after[0].identity(), left.identity()); + assert_eq!(after[1].identity(), right.identity()); + assert_eq!(after[2].identity(), right.identity()); + + let incompatible = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result<()> = jvm + .invoke_static( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (strings.clone(), 1, 3, incompatible), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("incompatible object fill must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + let after_failure = jvm.load_array::>(&strings, 0, 3).await?; + assert_eq!( + after_failure.iter().map(|value| value.identity()).collect::>(), + after.iter().map(|value| value.identity()).collect::>() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_08_as_list_is_live_fixed_size_and_uses_abstract_list_contracts() -> Result<()> { + let jvm = test_jvm().await?; + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let second = JavaLangString::from_rust_string(&jvm, "second").await?; + let third = JavaLangString::from_rust_string(&jvm, "third").await?; + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 2).await?.into(); + jvm.store_array(&mut array, 0, [first.clone(), second.clone()]).await?; + let list: ClassInstanceRef = jvm + .invoke_static("java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", (array.clone(),)) + .await?; + assert!(jvm.is_instance(list.as_ref(), "java/util/List")); + assert!(jvm.is_instance(list.as_ref(), "java/io/Serializable")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 2); + + jvm.store_array(&mut array, 0, core::iter::once(third.clone())).await?; + let from_list: ClassInstanceRef = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (0,)).await?; + assert_eq!(from_list.identity(), third.identity()); + let previous: ClassInstanceRef = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (1, first.clone())) + .await?; + assert_eq!(previous.identity(), second.identity()); + let from_array = jvm.load_array::>(&array, 1, 1).await?.remove(0); + assert_eq!(from_array.identity(), first.identity()); + let incompatible = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result> = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (1, incompatible)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("array-backed list set must preserve the component type"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + assert_eq!( + jvm.load_array::>(&array, 1, 1).await?.remove(0).identity(), + first.identity() + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&list, "indexOf", "(Ljava/lang/Object;)I", (first.clone(),)) + .await?, + 1 + ); + assert!( + jvm.invoke_virtual::<_, bool>(&list, "contains", "(Ljava/lang/Object;)Z", (third.clone(),)) + .await? + ); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let _: () = jvm.invoke_virtual(&iterator, "set", "(Ljava/lang/Object;)V", (second.clone(),)).await?; + assert_eq!( + jvm.load_array::>(&array, 0, 1).await?.remove(0).identity(), + second.identity() + ); + + let result: Result<()> = jvm.invoke_virtual(&iterator, "add", "(Ljava/lang/Object;)V", (third.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("list iterator add must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let result: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("list iterator remove must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let result: Result = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (third.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fixed list add must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let result: Result = jvm.invoke_virtual(&list, "remove", "(Ljava/lang/Object;)Z", (first,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fixed list remove must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let result: Result<()> = jvm.invoke_virtual(&list, "clear", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fixed list clear must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let typed: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 0).await?.into(); + let copied: ClassInstanceRef> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (typed,)) + .await?; + assert_eq!(copied.class_definition().name(), "[Ljava/lang/String;"); + assert_eq!(jvm.array_length(&copied).await?, 2); + + let null_array: ClassInstanceRef> = None.into(); + let result: Result> = jvm + .invoke_static("java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", (null_array,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Arrays.asList(null) must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_01_every_primitive_sort_whole_and_range_overload_executes() -> Result<()> { + let jvm = test_jvm().await?; + + let mut bytes: ClassInstanceRef> = jvm.instantiate_array("B", 4).await?.into(); + jvm.store_array(&mut bytes, 0, [4i8, 3, 2, 1]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([B)V", (bytes.clone(),)).await?; + assert_eq!(jvm.load_array::(&bytes, 0, 4).await?, [1, 2, 3, 4]); + jvm.store_array(&mut bytes, 0, [9i8, 4, 2, 8]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([BII)V", (bytes.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&bytes, 0, 4).await?, [9, 2, 4, 8]); + + let mut chars: ClassInstanceRef> = jvm.instantiate_array("C", 4).await?.into(); + jvm.store_array(&mut chars, 0, [4 as JavaChar, 3 as JavaChar, 2 as JavaChar, 1 as JavaChar]) + .await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([C)V", (chars.clone(),)).await?; + assert_eq!(jvm.load_array::(&chars, 0, 4).await?, [1, 2, 3, 4]); + jvm.store_array(&mut chars, 0, [9 as JavaChar, 4 as JavaChar, 2 as JavaChar, 8 as JavaChar]) + .await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([CII)V", (chars.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&chars, 0, 4).await?, [9, 2, 4, 8]); + + let mut shorts: ClassInstanceRef> = jvm.instantiate_array("S", 4).await?.into(); + jvm.store_array(&mut shorts, 0, [4i16, 3, 2, 1]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([S)V", (shorts.clone(),)).await?; + assert_eq!(jvm.load_array::(&shorts, 0, 4).await?, [1, 2, 3, 4]); + jvm.store_array(&mut shorts, 0, [9i16, 4, 2, 8]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([SII)V", (shorts.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&shorts, 0, 4).await?, [9, 2, 4, 8]); + + let mut ints: ClassInstanceRef> = jvm.instantiate_array("I", 4).await?.into(); + jvm.store_array(&mut ints, 0, [4, 3, 2, 1]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([I)V", (ints.clone(),)).await?; + assert_eq!(jvm.load_array::(&ints, 0, 4).await?, [1, 2, 3, 4]); + jvm.store_array(&mut ints, 0, [9, 4, 2, 8]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([III)V", (ints.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 4).await?, [9, 2, 4, 8]); + + let mut longs: ClassInstanceRef> = jvm.instantiate_array("J", 4).await?.into(); + jvm.store_array(&mut longs, 0, [4i64, 3, 2, 1]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([J)V", (longs.clone(),)).await?; + assert_eq!(jvm.load_array::(&longs, 0, 4).await?, [1, 2, 3, 4]); + jvm.store_array(&mut longs, 0, [9i64, 4, 2, 8]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([JII)V", (longs.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&longs, 0, 4).await?, [9, 2, 4, 8]); + + let mut floats: ClassInstanceRef> = jvm.instantiate_array("F", 4).await?.into(); + jvm.store_array(&mut floats, 0, [4.0f32, 3.0, 2.0, 1.0]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([F)V", (floats.clone(),)).await?; + assert_eq!(jvm.load_array::(&floats, 0, 4).await?, [1.0, 2.0, 3.0, 4.0]); + jvm.store_array(&mut floats, 0, [9.0f32, 4.0, 2.0, 8.0]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([FII)V", (floats.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&floats, 0, 4).await?, [9.0, 2.0, 4.0, 8.0]); + + let mut doubles: ClassInstanceRef> = jvm.instantiate_array("D", 4).await?.into(); + jvm.store_array(&mut doubles, 0, [4.0f64, 3.0, 2.0, 1.0]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([D)V", (doubles.clone(),)) + .await?; + assert_eq!(jvm.load_array::(&doubles, 0, 4).await?, [1.0, 2.0, 3.0, 4.0]); + jvm.store_array(&mut doubles, 0, [9.0f64, 4.0, 2.0, 8.0]).await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "sort", "([DII)V", (doubles.clone(), 1, 3)) + .await?; + assert_eq!(jvm.load_array::(&doubles, 0, 4).await?, [9.0, 2.0, 4.0, 8.0]); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_03_04_binary_search_duplicates_null_comparator_and_exception_priority() -> Result<()> { + let jvm = arrays_object_test_jvm().await?; + + let mut duplicates: ClassInstanceRef> = jvm.instantiate_array("I", 5).await?.into(); + jvm.store_array(&mut duplicates, 0, [1, 2, 2, 2, 3]).await?; + let hit = jvm + .invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([II)I", (duplicates.clone(), 2)) + .await?; + assert!((1..=3).contains(&hit)); + assert_eq!(jvm.load_array::(&duplicates, hit as usize, 1).await?, [2]); + for (key, expected) in [(0, -1), (4, -6)] { + assert_eq!( + jvm.invoke_static::<_, i32>("java/util/Arrays", "binarySearch", "([II)I", (duplicates.clone(), key)) + .await?, + expected + ); + } + + let first = jvm.new_class("ArraysSortValue", "(IIZ)V", (1, 1, false)).await?; + let duplicate_a = jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 2, false)).await?; + let duplicate_b = jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 3, false)).await?; + let last = jvm.new_class("ArraysSortValue", "(IIZ)V", (3, 4, false)).await?; + let key = jvm.new_class("ArraysSortValue", "(IIZ)V", (2, 5, false)).await?; + let mut objects: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 4).await?.into(); + jvm.store_array(&mut objects, 0, [first, duplicate_a, duplicate_b, last]).await?; + let natural_hit = jvm + .invoke_static::<_, i32>( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;)I", + (objects.clone(), key.clone()), + ) + .await?; + assert!((1..=2).contains(&natural_hit)); + + let null_comparator: ClassInstanceRef = None.into(); + let null_comparator_hit = jvm + .invoke_static::<_, i32>( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + (objects.clone(), key.clone(), null_comparator), + ) + .await?; + assert!((1..=2).contains(&null_comparator_hit)); + + let comparator = jvm.new_class("ArraysComparator", "(ZZ)V", (false, false)).await?; + let comparator_hit = jvm + .invoke_static::<_, i32>( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + (objects.clone(), key.clone(), comparator), + ) + .await?; + assert!((1..=2).contains(&comparator_hit)); + + let failing_comparator = jvm.new_class("ArraysComparator", "(ZZ)V", (false, true)).await?; + let result: Result = jvm + .invoke_static( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + (objects.clone(), key.clone(), failing_comparator), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("comparator binarySearch failure must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + + let null_objects: ClassInstanceRef> = None.into(); + let failing_comparator = jvm.new_class("ArraysComparator", "(ZZ)V", (false, true)).await?; + let result: Result = jvm + .invoke_static( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + (null_objects, key.clone(), failing_comparator), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null array must fail before comparator invocation"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + let empty: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 0).await?.into(); + let failing_comparator = jvm.new_class("ArraysComparator", "(ZZ)V", (false, true)).await?; + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/util/Arrays", + "binarySearch", + "([Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Comparator;)I", + (empty, key, failing_comparator), + ) + .await?, + -1 + ); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_05_every_equals_overload_values_lengths_and_nulls() -> Result<()> { + let jvm = test_jvm().await?; + + let mut booleans_a: ClassInstanceRef> = jvm.instantiate_array("Z", 2).await?.into(); + let mut booleans_b: ClassInstanceRef> = jvm.instantiate_array("Z", 2).await?.into(); + jvm.store_array(&mut booleans_a, 0, [true, false]).await?; + jvm.store_array(&mut booleans_b, 0, [true, false]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([Z[Z)Z", (booleans_a.clone(), booleans_b.clone())) + .await? + ); + jvm.store_array(&mut booleans_b, 1, [true]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([Z[Z)Z", (booleans_a, booleans_b)) + .await? + ); + + let mut bytes_a: ClassInstanceRef> = jvm.instantiate_array("B", 2).await?.into(); + let mut bytes_b: ClassInstanceRef> = jvm.instantiate_array("B", 2).await?.into(); + jvm.store_array(&mut bytes_a, 0, [i8::MIN, i8::MAX]).await?; + jvm.store_array(&mut bytes_b, 0, [i8::MIN, i8::MAX]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([B[B)Z", (bytes_a.clone(), bytes_b.clone())) + .await? + ); + jvm.store_array(&mut bytes_b, 1, [0i8]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([B[B)Z", (bytes_a, bytes_b)) + .await? + ); + + let mut chars_a: ClassInstanceRef> = jvm.instantiate_array("C", 2).await?.into(); + let mut chars_b: ClassInstanceRef> = jvm.instantiate_array("C", 2).await?.into(); + jvm.store_array(&mut chars_a, 0, [0 as JavaChar, u16::MAX]).await?; + jvm.store_array(&mut chars_b, 0, [0 as JavaChar, u16::MAX]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([C[C)Z", (chars_a.clone(), chars_b.clone())) + .await? + ); + jvm.store_array(&mut chars_b, 1, [1 as JavaChar]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([C[C)Z", (chars_a, chars_b)) + .await? + ); + + let mut shorts_a: ClassInstanceRef> = jvm.instantiate_array("S", 2).await?.into(); + let mut shorts_b: ClassInstanceRef> = jvm.instantiate_array("S", 2).await?.into(); + jvm.store_array(&mut shorts_a, 0, [i16::MIN, i16::MAX]).await?; + jvm.store_array(&mut shorts_b, 0, [i16::MIN, i16::MAX]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([S[S)Z", (shorts_a.clone(), shorts_b.clone())) + .await? + ); + jvm.store_array(&mut shorts_b, 1, [0i16]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([S[S)Z", (shorts_a, shorts_b)) + .await? + ); + + let mut ints_a: ClassInstanceRef> = jvm.instantiate_array("I", 2).await?.into(); + let mut ints_b: ClassInstanceRef> = jvm.instantiate_array("I", 2).await?.into(); + jvm.store_array(&mut ints_a, 0, [i32::MIN, i32::MAX]).await?; + jvm.store_array(&mut ints_b, 0, [i32::MIN, i32::MAX]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([I[I)Z", (ints_a.clone(), ints_b.clone())) + .await? + ); + let shorter: ClassInstanceRef> = jvm.instantiate_array("I", 1).await?.into(); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([I[I)Z", (ints_a, shorter)) + .await? + ); + + let mut longs_a: ClassInstanceRef> = jvm.instantiate_array("J", 2).await?.into(); + let mut longs_b: ClassInstanceRef> = jvm.instantiate_array("J", 2).await?.into(); + jvm.store_array(&mut longs_a, 0, [i64::MIN, i64::MAX]).await?; + jvm.store_array(&mut longs_b, 0, [i64::MIN, i64::MAX]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([J[J)Z", (longs_a.clone(), longs_b.clone())) + .await? + ); + jvm.store_array(&mut longs_b, 1, [0i64]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([J[J)Z", (longs_a, longs_b)) + .await? + ); + + let mut floats_a: ClassInstanceRef> = jvm.instantiate_array("F", 2).await?.into(); + let mut floats_b: ClassInstanceRef> = jvm.instantiate_array("F", 2).await?.into(); + jvm.store_array(&mut floats_a, 0, [f32::from_bits(0x7fc0_0001), -0.0]).await?; + jvm.store_array(&mut floats_b, 0, [f32::from_bits(0xffc0_1234), -0.0]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([F[F)Z", (floats_a.clone(), floats_b.clone())) + .await? + ); + jvm.store_array(&mut floats_b, 1, [0.0f32]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([F[F)Z", (floats_a, floats_b)) + .await? + ); + + let mut doubles_a: ClassInstanceRef> = jvm.instantiate_array("D", 2).await?.into(); + let mut doubles_b: ClassInstanceRef> = jvm.instantiate_array("D", 2).await?.into(); + jvm.store_array(&mut doubles_a, 0, [f64::from_bits(0x7ff8_0000_0000_0001), -0.0]).await?; + jvm.store_array(&mut doubles_b, 0, [f64::from_bits(0xfff8_0000_0000_1234), -0.0]).await?; + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([D[D)Z", (doubles_a.clone(), doubles_b.clone())) + .await? + ); + jvm.store_array(&mut doubles_b, 1, [0.0f64]).await?; + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([D[D)Z", (doubles_a, doubles_b)) + .await? + ); + + let first: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "first").await?.into(); + let equal: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "first").await?.into(); + let different: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "different").await?.into(); + let null: ClassInstanceRef = None.into(); + let mut objects_a: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + let mut objects_b: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut objects_a, 0, [first, null.clone()]).await?; + jvm.store_array(&mut objects_b, 0, [equal, null.clone()]).await?; + assert!( + jvm.invoke_static::<_, bool>( + "java/util/Arrays", + "equals", + "([Ljava/lang/Object;[Ljava/lang/Object;)Z", + (objects_a.clone(), objects_b.clone()), + ) + .await? + ); + jvm.store_array(&mut objects_b, 0, [different, null]).await?; + assert!( + !jvm.invoke_static::<_, bool>( + "java/util/Arrays", + "equals", + "([Ljava/lang/Object;[Ljava/lang/Object;)Z", + (objects_a, objects_b), + ) + .await? + ); + + let null_booleans: ClassInstanceRef> = None.into(); + let empty_booleans: ClassInstanceRef> = jvm.instantiate_array("Z", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([Z[Z)Z", (null_booleans.clone(), null_booleans),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>( + "java/util/Arrays", + "equals", + "([Z[Z)Z", + (ClassInstanceRef::>::from(None), empty_booleans), + ) + .await? + ); + + let null_bytes: ClassInstanceRef> = None.into(); + let empty_bytes: ClassInstanceRef> = jvm.instantiate_array("B", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([B[B)Z", (null_bytes.clone(), null_bytes.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([B[B)Z", (null_bytes, empty_bytes)) + .await? + ); + + let null_chars: ClassInstanceRef> = None.into(); + let empty_chars: ClassInstanceRef> = jvm.instantiate_array("C", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([C[C)Z", (null_chars.clone(), null_chars.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([C[C)Z", (null_chars, empty_chars)) + .await? + ); + + let null_shorts: ClassInstanceRef> = None.into(); + let empty_shorts: ClassInstanceRef> = jvm.instantiate_array("S", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([S[S)Z", (null_shorts.clone(), null_shorts.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([S[S)Z", (null_shorts, empty_shorts),) + .await? + ); + + let null_ints: ClassInstanceRef> = None.into(); + let empty_ints: ClassInstanceRef> = jvm.instantiate_array("I", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([I[I)Z", (null_ints.clone(), null_ints.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([I[I)Z", (null_ints, empty_ints)) + .await? + ); + + let null_longs: ClassInstanceRef> = None.into(); + let empty_longs: ClassInstanceRef> = jvm.instantiate_array("J", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([J[J)Z", (null_longs.clone(), null_longs.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([J[J)Z", (null_longs, empty_longs)) + .await? + ); + + let null_floats: ClassInstanceRef> = None.into(); + let empty_floats: ClassInstanceRef> = jvm.instantiate_array("F", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([F[F)Z", (null_floats.clone(), null_floats.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([F[F)Z", (null_floats, empty_floats),) + .await? + ); + + let null_doubles: ClassInstanceRef> = None.into(); + let empty_doubles: ClassInstanceRef> = jvm.instantiate_array("D", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([D[D)Z", (null_doubles.clone(), null_doubles.clone()),) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/util/Arrays", "equals", "([D[D)Z", (null_doubles, empty_doubles),) + .await? + ); + + let null_objects: ClassInstanceRef> = None.into(); + let empty_objects: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 0).await?.into(); + assert!( + jvm.invoke_static::<_, bool>( + "java/util/Arrays", + "equals", + "([Ljava/lang/Object;[Ljava/lang/Object;)Z", + (null_objects.clone(), null_objects), + ) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>( + "java/util/Arrays", + "equals", + "([Ljava/lang/Object;[Ljava/lang/Object;)Z", + (ClassInstanceRef::>::from(None), empty_objects), + ) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_06_07_every_fill_overload_ranges_and_exception_priority() -> Result<()> { + let jvm = test_jvm().await?; + + let booleans: ClassInstanceRef> = jvm.instantiate_array("Z", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([ZZ)V", (booleans.clone(), true)) + .await?; + assert_eq!(jvm.load_array::(&booleans, 0, 3).await?, [true, true, true]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([ZIIZ)V", (booleans.clone(), 1, 2, false)) + .await?; + assert_eq!(jvm.load_array::(&booleans, 0, 3).await?, [true, false, true]); + + let bytes: ClassInstanceRef> = jvm.instantiate_array("B", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([BB)V", (bytes.clone(), 7i8)) + .await?; + assert_eq!(jvm.load_array::(&bytes, 0, 3).await?, [7, 7, 7]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([BIIB)V", (bytes.clone(), 1, 2, -1i8)) + .await?; + assert_eq!(jvm.load_array::(&bytes, 0, 3).await?, [7, -1, 7]); + + let chars: ClassInstanceRef> = jvm.instantiate_array("C", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([CC)V", (chars.clone(), 7 as JavaChar)) + .await?; + assert_eq!(jvm.load_array::(&chars, 0, 3).await?, [7, 7, 7]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([CIIC)V", (chars.clone(), 1, 2, 1 as JavaChar)) + .await?; + assert_eq!(jvm.load_array::(&chars, 0, 3).await?, [7, 1, 7]); + + let shorts: ClassInstanceRef> = jvm.instantiate_array("S", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([SS)V", (shorts.clone(), 7i16)) + .await?; + assert_eq!(jvm.load_array::(&shorts, 0, 3).await?, [7, 7, 7]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([SIIS)V", (shorts.clone(), 1, 2, -1i16)) + .await?; + assert_eq!(jvm.load_array::(&shorts, 0, 3).await?, [7, -1, 7]); + + let ints: ClassInstanceRef> = jvm.instantiate_array("I", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([II)V", (ints.clone(), 7)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 3).await?, [7, 7, 7]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), 1, 2, -1)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 3).await?, [7, -1, 7]); + + let longs: ClassInstanceRef> = jvm.instantiate_array("J", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([JJ)V", (longs.clone(), 7i64)) + .await?; + assert_eq!(jvm.load_array::(&longs, 0, 3).await?, [7, 7, 7]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([JIIJ)V", (longs.clone(), 1, 2, -1i64)) + .await?; + assert_eq!(jvm.load_array::(&longs, 0, 3).await?, [7, -1, 7]); + + let floats: ClassInstanceRef> = jvm.instantiate_array("F", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([FF)V", (floats.clone(), 7.0f32)) + .await?; + assert_eq!(jvm.load_array::(&floats, 0, 3).await?, [7.0, 7.0, 7.0]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([FIIF)V", (floats.clone(), 1, 2, -0.0f32)) + .await?; + let float_values = jvm.load_array::(&floats, 0, 3).await?; + assert_eq!(float_values[0], 7.0); + assert_eq!(float_values[1].to_bits(), (-0.0f32).to_bits()); + assert_eq!(float_values[2], 7.0); + + let doubles: ClassInstanceRef> = jvm.instantiate_array("D", 3).await?.into(); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([DD)V", (doubles.clone(), 7.0f64)) + .await?; + assert_eq!(jvm.load_array::(&doubles, 0, 3).await?, [7.0, 7.0, 7.0]); + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([DIID)V", (doubles.clone(), 1, 2, -0.0f64)) + .await?; + let double_values = jvm.load_array::(&doubles, 0, 3).await?; + assert_eq!(double_values[0], 7.0); + assert_eq!(double_values[1].to_bits(), (-0.0f64).to_bits()); + assert_eq!(double_values[2], 7.0); + + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let second = JavaLangString::from_rust_string(&jvm, "second").await?; + let objects: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;Ljava/lang/Object;)V", + (objects.clone(), first.clone()), + ) + .await?; + let values = jvm.load_array::>(&objects, 0, 3).await?; + assert!(values.iter().all(|value| value.identity() == first.identity())); + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (objects.clone(), 1, 2, second.clone()), + ) + .await?; + let values = jvm.load_array::>(&objects, 0, 3).await?; + assert_eq!(values[0].identity(), first.identity()); + assert_eq!(values[1].identity(), second.identity()); + assert_eq!(values[2].identity(), first.identity()); + + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), 0, 0, 99)) + .await?; + jvm.invoke_static::<_, ()>("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), 3, 3, 99)) + .await?; + assert_eq!(jvm.load_array::(&ints, 0, 3).await?, [7, -1, 7]); + + let result: Result<()> = jvm.invoke_static("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), 5, 4, 99)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fromIndex > toIndex must win over bounds"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + for (from, to) in [(-1, 0), (0, 4)] { + let result: Result<()> = jvm + .invoke_static("java/util/Arrays", "fill", "([IIII)V", (ints.clone(), from, to, 99)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("fill range outside the array must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayIndexOutOfBoundsException")); + } + let null_ints: ClassInstanceRef> = None.into(); + let result: Result<()> = jvm.invoke_static("java/util/Arrays", "fill", "([IIII)V", (null_ints, 5, 4, 99)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null array must fail before range validation"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_07_multidimensional_fill_store_compatibility() -> Result<()> { + let jvm = test_jvm().await?; + let first_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/String;", 1).await?.into(); + let second_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/String;", 2).await?.into(); + let incompatible_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + let matrix: ClassInstanceRef> = jvm.instantiate_array("[Ljava/lang/String;", 3).await?.into(); + + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;Ljava/lang/Object;)V", + (matrix.clone(), first_row.clone()), + ) + .await?; + let values = jvm.load_array::>(&matrix, 0, 3).await?; + assert!(values.iter().all(|value| value.identity() == first_row.identity())); + + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (matrix.clone(), 1, 3, second_row.clone()), + ) + .await?; + let before_failure = jvm.load_array::>(&matrix, 0, 3).await?; + assert_eq!(before_failure[0].identity(), first_row.identity()); + assert_eq!(before_failure[1].identity(), second_row.identity()); + assert_eq!(before_failure[2].identity(), second_row.identity()); + + let result: Result<()> = jvm + .invoke_static( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (matrix.clone(), 1, 3, incompatible_row), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("String[][] must reject an Object[] fill value"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + let after_failure = jvm.load_array::>(&matrix, 0, 3).await?; + assert_eq!( + after_failure.iter().map(|value| value.identity()).collect::>(), + before_failure.iter().map(|value| value.identity()).collect::>() + ); + + let incompatible_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (matrix.clone(), 1, 1, incompatible_row.clone()), + ) + .await?; + let result: Result<()> = jvm + .invoke_static( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (matrix.clone(), 5, 4, incompatible_row.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid range must fail before store compatibility"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + let result: Result<()> = jvm + .invoke_static( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (matrix.clone(), 0, 4, incompatible_row), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("array bounds must fail before store compatibility"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayIndexOutOfBoundsException")); + + let null: ClassInstanceRef = None.into(); + jvm.invoke_static::<_, ()>( + "java/util/Arrays", + "fill", + "([Ljava/lang/Object;IILjava/lang/Object;)V", + (matrix.clone(), 2, 3, null), + ) + .await?; + assert!(jvm.load_array::>(&matrix, 2, 1).await?.remove(0).is_null()); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_08_array_list_bulk_mutation_noop_and_unsupported_paths() -> Result<()> { + let jvm = test_jvm().await?; + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let second = JavaLangString::from_rust_string(&jvm, "second").await?; + let absent = JavaLangString::from_rust_string(&jvm, "absent").await?; + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut array, 0, [first.clone(), second.clone()]).await?; + let list: ClassInstanceRef = jvm + .invoke_static("java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", (array.clone(),)) + .await?; + + let empty = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "addAll", "(Ljava/util/Collection;)Z", (empty.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "addAll", "(ILjava/util/Collection;)Z", (1, empty.clone())) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "removeAll", "(Ljava/util/Collection;)Z", (empty.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "retainAll", "(Ljava/util/Collection;)Z", (list.clone(),)) + .await? + ); + + let absent_collection = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&absent_collection, "add", "(Ljava/lang/Object;)Z", (absent,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "removeAll", "(Ljava/util/Collection;)Z", (absent_collection,),) + .await? + ); + + let all = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&all, "add", "(Ljava/lang/Object;)Z", (first.clone(),)).await?; + let _: bool = jvm.invoke_virtual(&all, "add", "(Ljava/lang/Object;)Z", (second.clone(),)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "retainAll", "(Ljava/util/Collection;)Z", (all,)) + .await? + ); + + let non_empty = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&non_empty, "add", "(Ljava/lang/Object;)Z", (first.clone(),)).await?; + for result in [ + jvm.invoke_virtual::<_, bool>(&list, "addAll", "(Ljava/util/Collection;)Z", (non_empty.clone(),)) + .await, + jvm.invoke_virtual::<_, bool>(&list, "addAll", "(ILjava/util/Collection;)Z", (1, non_empty.clone())) + .await, + jvm.invoke_virtual::<_, bool>(&list, "removeAll", "(Ljava/util/Collection;)Z", (non_empty.clone(),)) + .await, + jvm.invoke_virtual::<_, bool>(&list, "retainAll", "(Ljava/util/Collection;)Z", (empty,)) + .await, + ] { + let Err(JavaError::JavaException(exception)) = result else { + panic!("bulk operation requiring a size change must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let values = jvm.load_array::>(&array, 0, 2).await?; + assert_eq!(values[0].identity(), first.identity()); + assert_eq!(values[1].identity(), second.identity()); + } + + let null_collection: ClassInstanceRef = None.into(); + let result: Result = jvm + .invoke_virtual(&list, "removeAll", "(Ljava/util/Collection;)Z", (null_collection,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("bulk operation must reject a null collection"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_arr_08_array_list_typed_to_array_reuse_grow_termination_and_sequential_ase() -> Result<()> { + let jvm = test_jvm().await?; + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let second = JavaLangString::from_rust_string(&jvm, "second").await?; + let sentinel = JavaLangString::from_rust_string(&jvm, "sentinel").await?; + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut array, 0, [first.clone(), second.clone()]).await?; + let list: ClassInstanceRef = jvm + .invoke_static("java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", (array,)) + .await?; + + let mut oversized: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 4).await?.into(); + jvm.store_array( + &mut oversized, + 0, + [sentinel.clone(), sentinel.clone(), sentinel.clone(), sentinel.clone()], + ) + .await?; + let oversized_identity = oversized.identity(); + let reused: ClassInstanceRef> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (oversized,)) + .await?; + assert_eq!(reused.identity(), oversized_identity); + let reused_values = jvm.load_array::>(&reused, 0, 4).await?; + assert_eq!(reused_values[0].identity(), first.identity()); + assert_eq!(reused_values[1].identity(), second.identity()); + assert!(reused_values[2].is_null()); + assert_eq!(reused_values[3].identity(), sentinel.identity()); + + let mut exact: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 2).await?.into(); + jvm.store_array(&mut exact, 0, [sentinel.clone(), sentinel.clone()]).await?; + let exact_identity = exact.identity(); + let exact_result: ClassInstanceRef> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (exact,)) + .await?; + assert_eq!(exact_result.identity(), exact_identity); + let exact_values = jvm.load_array::>(&exact_result, 0, 2).await?; + assert_eq!(exact_values[0].identity(), first.identity()); + assert_eq!(exact_values[1].identity(), second.identity()); + + let mut small: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 1).await?.into(); + jvm.store_array(&mut small, 0, core::iter::once(sentinel.clone())).await?; + let small_identity = small.identity(); + let grown: ClassInstanceRef> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (small.clone(),)) + .await?; + assert_ne!(grown.identity(), small_identity); + assert_eq!(grown.class_definition().name(), "[Ljava/lang/String;"); + assert_eq!(jvm.array_length(&grown).await?, 2); + assert_eq!( + jvm.load_array::>(&small, 0, 1).await?.remove(0).identity(), + sentinel.identity() + ); + + let null_destination: ClassInstanceRef> = None.into(); + let result: Result>> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (null_destination,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed toArray must reject null"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + let compatible_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/String;", 1).await?.into(); + let incompatible_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + let trailing_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/String;", 2).await?.into(); + let sentinel_row: ClassInstanceRef = jvm.instantiate_array("Ljava/lang/String;", 3).await?.into(); + let mut rows: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + jvm.store_array(&mut rows, 0, [compatible_row.clone(), incompatible_row, trailing_row]) + .await?; + let row_list: ClassInstanceRef = jvm + .invoke_static("java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", (rows,)) + .await?; + let mut destination: ClassInstanceRef> = jvm.instantiate_array("[Ljava/lang/String;", 4).await?.into(); + jvm.store_array( + &mut destination, + 0, + [sentinel_row.clone(), sentinel_row.clone(), sentinel_row.clone(), sentinel_row.clone()], + ) + .await?; + let result: Result>> = jvm + .invoke_virtual(&row_list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed multidimensional toArray must reject an Object[] row"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + let partial = jvm.load_array::>(&destination, 0, 4).await?; + assert_eq!(partial[0].identity(), compatible_row.identity()); + assert_eq!(partial[1].identity(), sentinel_row.identity()); + assert_eq!(partial[2].identity(), sentinel_row.identity()); + assert_eq!(partial[3].identity(), sentinel_row.identity()); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_collection_contracts.rs b/java_runtime/tests/classes/java/util/test_collection_contracts.rs new file mode 100644 index 00000000..8e0c6c8d --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_collection_contracts.rs @@ -0,0 +1,1906 @@ +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 SnapshotCollection; + +impl SnapshotCollection { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "SnapshotCollection", + parent_class: Some("java/util/AbstractCollection"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("elements", "[Ljava/lang/Object;", Default::default())], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractCollection", "", "()V", ()).await?; + jvm.put_field(&mut this, "elements", "[Ljava/lang/Object;", elements).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + Ok(jvm.array_length(&elements).await? as i32) + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + Ok(jvm.new_class("SnapshotIterator", "([Ljava/lang/Object;)V", (elements,)).await?.into()) + } +} + +struct SnapshotIterator; + +impl SnapshotIterator { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "SnapshotIterator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("elements", "[Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("index", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "elements", "[Ljava/lang/Object;", elements).await?; + jvm.put_field(&mut this, "index", "I", 0).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + let index: i32 = jvm.get_field(&this, "index", "I").await?; + Ok(index < jvm.array_length(&elements).await? as i32) + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + let index: i32 = jvm.get_field(&this, "index", "I").await?; + if index >= jvm.array_length(&elements).await? as i32 { + return Err(jvm.exception("java/util/NoSuchElementException", "snapshot iterator exhausted").await); + } + let value = jvm.load_array::>(&elements, index as usize, 1).await?.remove(0); + jvm.put_field(&mut this, "index", "I", index + 1).await?; + Ok(value) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Err(jvm + .exception("java/lang/UnsupportedOperationException", "immutable test collection") + .await) + } +} + +struct DirectionalStoredKey; + +impl DirectionalStoredKey { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "DirectionalStoredKey", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn hash_code(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } + + async fn equals(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } +} + +struct DirectionalQueryKey; + +impl DirectionalQueryKey { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "DirectionalQueryKey", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn hash_code(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, other: ClassInstanceRef) -> Result { + Ok(!other.is_null() && jvm.is_instance(&**other, "DirectionalStoredKey")) + } +} + +struct ConfigurableEqualsValue; + +impl ConfigurableEqualsValue { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ConfigurableEqualsValue", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Z)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("result", "Z", Default::default()), + JavaFieldProto::new("equalsCalls", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, result: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "result", "Z", result).await?; + jvm.put_field(&mut this, "equalsCalls", "I", 0).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + let calls: i32 = jvm.get_field(&this, "equalsCalls", "I").await?; + jvm.put_field(&mut this, "equalsCalls", "I", calls + 1).await?; + jvm.get_field(&this, "result", "Z").await + } +} + +struct StatefulMapEntry; + +impl StatefulMapEntry { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "StatefulMapEntry", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map$Entry"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + Self::init, + MethodAccessFlags::PUBLIC, + ), + 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, + ), + ], + fields: vec![ + JavaFieldProto::new("firstKey", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("laterKey", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("value", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("mode", "I", Default::default()), + JavaFieldProto::new("keyCalls", "I", Default::default()), + JavaFieldProto::new("valueCalls", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + first_key: ClassInstanceRef, + later_key: ClassInstanceRef, + value: ClassInstanceRef, + mode: i32, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "firstKey", "Ljava/lang/Object;", first_key).await?; + jvm.put_field(&mut this, "laterKey", "Ljava/lang/Object;", later_key).await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + jvm.put_field(&mut this, "mode", "I", mode).await?; + jvm.put_field(&mut this, "keyCalls", "I", 0).await?; + jvm.put_field(&mut this, "valueCalls", "I", 0).await + } + + async fn get_key(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "keyCalls", "I").await?; + jvm.put_field(&mut this, "keyCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "mode", "I").await? == 1 { + return Err(jvm.exception("java/lang/IllegalStateException", "getKey failure").await); + } + if calls == 0 { + jvm.get_field(&this, "firstKey", "Ljava/lang/Object;").await + } else { + jvm.get_field(&this, "laterKey", "Ljava/lang/Object;").await + } + } + + async fn get_value(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "valueCalls", "I").await?; + jvm.put_field(&mut this, "valueCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "mode", "I").await? == 2 { + return Err(jvm.exception("java/lang/IllegalStateException", "getValue failure").await); + } + jvm.get_field(&this, "value", "Ljava/lang/Object;").await + } + + async fn set_value( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + let previous: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + Ok(previous) + } +} + +struct ThrowingMap; + +impl ThrowingMap { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ThrowingMap", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map"], + methods: vec![ + JavaMethodProto::new("", "(II)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("size", "I", Default::default()), + JavaFieldProto::new("mode", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, size: i32, mode: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "size", "I", size).await?; + jvm.put_field(&mut this, "mode", "I", mode).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "size", "I").await + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: ClassInstanceRef) -> Result> { + if jvm.get_field::(&this, "mode", "I").await? == 0 { + Err(jvm.exception("java/lang/NullPointerException", "test map get failure").await) + } else { + Err(jvm.exception("java/lang/ClassCastException", "test map get failure").await) + } + } +} + +async fn collection_contract_fixture_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + for proto in [ + SnapshotCollection::as_proto(), + SnapshotIterator::as_proto(), + DirectionalStoredKey::as_proto(), + DirectionalQueryKey::as_proto(), + ConfigurableEqualsValue::as_proto(), + StatefulMapEntry::as_proto(), + ThrowingMap::as_proto(), + ] { + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto(proto, Box::new(runtime.clone()) as Box<_>)), + None, + ) + .await?; + } + + Ok(jvm) +} + +#[tokio::test] +async fn col_01_to_06_interfaces_have_exact_descriptors_and_flags() -> Result<()> { + for (class_name, interfaces, methods) in [ + ( + "java/util/Collection", + Vec::new(), + vec![ + ("containsAll", "(Ljava/util/Collection;)Z"), + ("addAll", "(Ljava/util/Collection;)Z"), + ("removeAll", "(Ljava/util/Collection;)Z"), + ("retainAll", "(Ljava/util/Collection;)Z"), + ("toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;"), + ], + ), + ( + "java/util/List", + vec!["java/util/Collection"], + vec![ + ("addAll", "(ILjava/util/Collection;)Z"), + ("indexOf", "(Ljava/lang/Object;)I"), + ("lastIndexOf", "(Ljava/lang/Object;)I"), + ("listIterator", "()Ljava/util/ListIterator;"), + ("listIterator", "(I)Ljava/util/ListIterator;"), + ("subList", "(II)Ljava/util/List;"), + ], + ), + ( + "java/util/Set", + vec!["java/util/Collection"], + vec![ + ("size", "()I"), + ("isEmpty", "()Z"), + ("contains", "(Ljava/lang/Object;)Z"), + ("iterator", "()Ljava/util/Iterator;"), + ("toArray", "()[Ljava/lang/Object;"), + ("toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;"), + ("add", "(Ljava/lang/Object;)Z"), + ("remove", "(Ljava/lang/Object;)Z"), + ("containsAll", "(Ljava/util/Collection;)Z"), + ("addAll", "(Ljava/util/Collection;)Z"), + ("retainAll", "(Ljava/util/Collection;)Z"), + ("removeAll", "(Ljava/util/Collection;)Z"), + ("clear", "()V"), + ("equals", "(Ljava/lang/Object;)Z"), + ("hashCode", "()I"), + ], + ), + ("java/util/Map", Vec::new(), vec![("putAll", "(Ljava/util/Map;)V")]), + ( + "java/util/Comparator", + Vec::new(), + vec![ + ("compare", "(Ljava/lang/Object;Ljava/lang/Object;)I"), + ("equals", "(Ljava/lang/Object;)Z"), + ], + ), + ( + "java/util/ListIterator", + vec!["java/util/Iterator"], + vec![ + ("hasNext", "()Z"), + ("next", "()Ljava/lang/Object;"), + ("hasPrevious", "()Z"), + ("previous", "()Ljava/lang/Object;"), + ("nextIndex", "()I"), + ("previousIndex", "()I"), + ("remove", "()V"), + ("set", "(Ljava/lang/Object;)V"), + ("add", "(Ljava/lang/Object;)V"), + ], + ), + ] { + let proto = get_runtime_class_proto(class_name).unwrap_or_else(|| panic!("missing {class_name}")); + assert_eq!( + proto.access_flags, + ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT + ); + assert_eq!(proto.interfaces, interfaces); + for (name, descriptor) in methods { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing {class_name}.{name}{descriptor}")); + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + "{class_name}.{name}{descriptor}" + ); + } + } + let set = get_runtime_class_proto("java/util/Set").unwrap(); + assert_eq!(set.parent_class, None); + assert!(set.fields.is_empty()); + assert_eq!(set.methods.len(), 15, "java/util/Set must redeclare the complete J2SE 1.2 contract"); + + let jvm = test_jvm().await?; + let list_iterator = jvm.resolve_class("java/util/ListIterator").await?; + assert!( + list_iterator + .definition + .interface_names() + .iter() + .any(|interface| interface == "java/util/Iterator") + ); + + for class_name in [ + "java/util/ArrayList", + "java/util/Vector", + "java/util/HashSet", + "java/util/HashMap", + "java/util/Hashtable", + ] { + assert_eq!( + get_runtime_class_proto(class_name).unwrap().access_flags, + ClassAccessFlags::PUBLIC, + "{class_name}" + ); + } + for (class_name, name, descriptor, flags) in [ + ("java/util/ArrayList", "", "(Ljava/util/Collection;)V", MethodAccessFlags::PUBLIC), + ("java/util/Vector", "", "(Ljava/util/Collection;)V", MethodAccessFlags::PUBLIC), + ( + "java/util/Vector", + "addAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "addAll", + "(ILjava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "containsAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "removeAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "retainAll", + "(Ljava/util/Collection;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "listIterator", + "()Ljava/util/ListIterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Vector", + "listIterator", + "(I)Ljava/util/ListIterator;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ("java/util/HashSet", "", "(Ljava/util/Collection;)V", MethodAccessFlags::PUBLIC), + ("java/util/HashMap", "", "(Ljava/util/Map;)V", MethodAccessFlags::PUBLIC), + ("java/util/HashMap", "putAll", "(Ljava/util/Map;)V", MethodAccessFlags::PUBLIC), + ("java/util/Hashtable", "", "(Ljava/util/Map;)V", MethodAccessFlags::PUBLIC), + ( + "java/util/Hashtable", + "putAll", + "(Ljava/util/Map;)V", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Hashtable", + "equals", + "(Ljava/lang/Object;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ( + "java/util/Hashtable", + "hashCode", + "()I", + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + ] { + let proto = get_runtime_class_proto(class_name).unwrap(); + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap(); + assert_eq!(method.access_flags, flags, "{class_name}.{name}{descriptor}"); + } + let entry = get_runtime_class_proto("java/util/Hashtable$Entry").unwrap(); + for (name, descriptor) in [("equals", "(Ljava/lang/Object;)Z"), ("hashCode", "()I")] { + let method = entry + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing java/util/Hashtable$Entry.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + + Ok(()) +} + +#[tokio::test] +async fn col_01_bulk_operations_handle_null_self_and_mutation_results() -> Result<()> { + let jvm = test_jvm().await?; + let list = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let second = JavaLangString::from_rust_string(&jvm, "second").await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (first.clone(),)).await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (second.clone(),)).await?; + + let contains_all: bool = jvm + .invoke_virtual(&list, "containsAll", "(Ljava/util/Collection;)Z", (list.clone(),)) + .await?; + assert!(contains_all); + let retained: bool = jvm + .invoke_virtual(&list, "retainAll", "(Ljava/util/Collection;)Z", (list.clone(),)) + .await?; + assert!(!retained); + + let added: bool = jvm.invoke_virtual(&list, "addAll", "(Ljava/util/Collection;)Z", (list.clone(),)).await?; + assert!(added); + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 4); + + let filter = jvm.new_class("java/util/HashSet", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&filter, "add", "(Ljava/lang/Object;)Z", (first.clone(),)).await?; + let removed: bool = jvm + .invoke_virtual(&list, "removeAll", "(Ljava/util/Collection;)Z", (filter.clone(),)) + .await?; + assert!(removed); + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 2); + let removed_again: bool = jvm.invoke_virtual(&list, "removeAll", "(Ljava/util/Collection;)Z", (filter,)).await?; + assert!(!removed_again); + + let removed_self: bool = jvm + .invoke_virtual(&list, "removeAll", "(Ljava/util/Collection;)Z", (list.clone(),)) + .await?; + assert!(removed_self); + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 0); + + let null_collection: ClassInstanceRef = None.into(); + for (name, descriptor) in [ + ("containsAll", "(Ljava/util/Collection;)Z"), + ("addAll", "(Ljava/util/Collection;)Z"), + ("removeAll", "(Ljava/util/Collection;)Z"), + ("retainAll", "(Ljava/util/Collection;)Z"), + ] { + let result: Result = jvm.invoke_virtual(&list, name, descriptor, (null_collection.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + + Ok(()) +} + +#[tokio::test] +async fn col_02_07_09_list_defaults_and_collection_copy_constructors() -> Result<()> { + let jvm = test_jvm().await?; + let source = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + for value in ["a", "b", "a"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&source, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + + let index: i32 = jvm + .invoke_virtual( + &source, + "lastIndexOf", + "(Ljava/lang/Object;)I", + (JavaLangString::from_rust_string(&jvm, "a").await?,), + ) + .await?; + assert_eq!(index, 2); + + let inserted = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm + .invoke_virtual( + &inserted, + "add", + "(Ljava/lang/Object;)Z", + (JavaLangString::from_rust_string(&jvm, "x").await?,), + ) + .await?; + let changed: bool = jvm.invoke_virtual(&source, "addAll", "(ILjava/util/Collection;)Z", (1, inserted)).await?; + assert!(changed); + let value: ClassInstanceRef = jvm.invoke_virtual(&source, "get", "(I)Ljava/lang/Object;", (1,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "x"); + + for (class_name, descriptor) in [ + ("java/util/ArrayList", "(Ljava/util/Collection;)V"), + ("java/util/Vector", "(Ljava/util/Collection;)V"), + ("java/util/HashSet", "(Ljava/util/Collection;)V"), + ] { + let copy = jvm.new_class(class_name, descriptor, (source.clone(),)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(©, "size", "()I", ()).await?, + if class_name.ends_with("HashSet") { 3 } else { 4 } + ); + assert!( + jvm.invoke_virtual::<_, bool>( + ©, + "contains", + "(Ljava/lang/Object;)Z", + (JavaLangString::from_rust_string(&jvm, "x").await?,) + ) + .await? + ); + } + + let null_collection: ClassInstanceRef = None.into(); + for class_name in ["java/util/ArrayList", "java/util/Vector", "java/util/HashSet"] { + let result = jvm.new_class(class_name, "(Ljava/util/Collection;)V", (null_collection.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class_name} copy constructor must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + + Ok(()) +} + +#[tokio::test] +async fn col_10_11_map_copy_and_put_all_validate_before_mutation() -> Result<()> { + let jvm = test_jvm().await?; + let source = jvm.new_class("java/util/HashMap", "()V", ()).await?; + for (key, value) in [("one", "1"), ("two", "2")] { + let _: ClassInstanceRef = jvm + .invoke_virtual( + &source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + ( + JavaLangString::from_rust_string(&jvm, key).await?, + JavaLangString::from_rust_string(&jvm, value).await?, + ), + ) + .await?; + } + + let hash_map = jvm.new_class("java/util/HashMap", "(Ljava/util/Map;)V", (source.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&hash_map, "size", "()I", ()).await?, 2); + let _: () = jvm.invoke_virtual(&hash_map, "putAll", "(Ljava/util/Map;)V", (hash_map.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&hash_map, "size", "()I", ()).await?, 2); + + let hashtable = jvm.new_class("java/util/Hashtable", "(Ljava/util/Map;)V", (source,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&hashtable, "size", "()I", ()).await?, 2); + let _: () = jvm + .invoke_virtual(&hashtable, "putAll", "(Ljava/util/Map;)V", (hashtable.clone(),)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&hashtable, "size", "()I", ()).await?, 2); + + let invalid = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &invalid, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + ( + JavaLangString::from_rust_string(&jvm, "valid").await?, + JavaLangString::from_rust_string(&jvm, "value").await?, + ), + ) + .await?; + let null_value: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &invalid, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (JavaLangString::from_rust_string(&jvm, "invalid").await?, null_value), + ) + .await?; + + let target = jvm.new_class("java/util/Hashtable", "()V", ()).await?; + let original_key = JavaLangString::from_rust_string(&jvm, "original").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &target, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (original_key.clone(), JavaLangString::from_rust_string(&jvm, "kept").await?), + ) + .await?; + let result: Result<()> = jvm.invoke_virtual(&target, "putAll", "(Ljava/util/Map;)V", (invalid.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Hashtable.putAll must reject null values"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&target, "size", "()I", ()).await?, 1); + let original: ClassInstanceRef = jvm + .invoke_virtual(&target, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (original_key,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &original).await?, "kept"); + + let result = jvm.new_class("java/util/Hashtable", "(Ljava/util/Map;)V", (invalid,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Hashtable copy constructor must reject null values"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn col_12_typed_to_array_reuses_grows_terminates_and_preserves_on_ase() -> Result<()> { + let jvm = test_jvm().await?; + let list = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + for value in ["first", "second"] { + let _: bool = jvm + .invoke_virtual( + &list, + "add", + "(Ljava/lang/Object;)Z", + (JavaLangString::from_rust_string(&jvm, value).await?,), + ) + .await?; + } + + let sentinel = JavaLangString::from_rust_string(&jvm, "sentinel").await?; + let mut oversized: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 4).await?.into(); + jvm.store_array( + &mut oversized, + 0, + [sentinel.clone(), sentinel.clone(), sentinel.clone(), sentinel.clone()], + ) + .await?; + let oversized_identity = oversized.identity(); + let reused: ClassInstanceRef> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (oversized,)) + .await?; + assert_eq!(reused.identity(), oversized_identity); + assert_eq!(reused.class_definition().name(), "[Ljava/lang/String;"); + let values: Vec> = jvm.load_array(&reused, 0, 4).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &values[0]).await?, "first"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &values[1]).await?, "second"); + assert!(values[2].is_null()); + assert_eq!(JavaLangString::to_rust_string(&jvm, &values[3]).await?, "sentinel"); + + let small: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 1).await?.into(); + let small_identity = small.identity(); + let grown: ClassInstanceRef> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (small,)) + .await?; + assert_ne!(grown.identity(), small_identity); + assert_eq!(grown.class_definition().name(), "[Ljava/lang/String;"); + assert_eq!(jvm.array_length(&grown).await?, 2); + + for map_class in ["java/util/HashMap", "java/util/Hashtable"] { + let map = jvm.new_class(map_class, "()V", ()).await?; + 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.clone(), value.clone()), + ) + .await?; + + for (view_method, view_descriptor, component_descriptor, expected_class) in [ + ("keySet", "()Ljava/util/Set;", "Ljava/lang/String;", "[Ljava/lang/String;"), + ("values", "()Ljava/util/Collection;", "Ljava/lang/String;", "[Ljava/lang/String;"), + ("entrySet", "()Ljava/util/Set;", "Ljava/util/Map$Entry;", "[Ljava/util/Map$Entry;"), + ] { + let view: ClassInstanceRef = jvm.invoke_virtual(&map, view_method, view_descriptor, ()).await?; + let destination: ClassInstanceRef> = jvm.instantiate_array(component_descriptor, 0).await?.into(); + let typed: ClassInstanceRef> = jvm + .invoke_virtual(&view, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination,)) + .await?; + assert_eq!(typed.class_definition().name(), expected_class); + assert_eq!(jvm.array_length(&typed).await?, 1); + } + + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + let removal = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&removal, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + let changed: bool = jvm.invoke_virtual(&values, "removeAll", "(Ljava/util/Collection;)Z", (removal,)).await?; + assert!(changed); + assert_eq!(jvm.invoke_virtual::<_, i32>(&map, "size", "()I", ()).await?, 0); + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, JavaLangString::from_rust_string(&jvm, "again").await?), + ) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let changed: bool = jvm + .invoke_virtual(&entries, "removeAll", "(Ljava/util/Collection;)Z", (entries.clone(),)) + .await?; + assert!(changed); + assert_eq!(jvm.invoke_virtual::<_, i32>(&map, "size", "()I", ()).await?, 0); + } + + let incompatible = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm + .invoke_virtual( + &incompatible, + "add", + "(Ljava/lang/Object;)Z", + (JavaLangString::from_rust_string(&jvm, "compatible").await?,), + ) + .await?; + let _: bool = jvm + .invoke_virtual( + &incompatible, + "add", + "(Ljava/lang/Object;)Z", + (jvm.new_class("java/lang/Object", "()V", ()).await?,), + ) + .await?; + let before_first = JavaLangString::from_rust_string(&jvm, "before-first").await?; + let before_second = JavaLangString::from_rust_string(&jvm, "before-second").await?; + let mut destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 2).await?.into(); + jvm.store_array(&mut destination, 0, [before_first, before_second]).await?; + let result: Result>> = jvm + .invoke_virtual( + &incompatible, + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (destination.clone(),), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed toArray must reject incompatible elements"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ArrayStoreException")); + let partially_written: Vec> = jvm.load_array(&destination, 0, 2).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &partially_written[0]).await?, "compatible"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &partially_written[1]).await?, "before-second"); + + let null_array: ClassInstanceRef> = None.into(); + let result: Result>> = jvm + .invoke_virtual(&list, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (null_array,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed toArray must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn col_07_08_copy_constructors_preserve_custom_snapshot_order_duplicates_and_nulls() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + let beta: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "beta").await?.into(); + let alpha: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "alpha").await?.into(); + let null: ClassInstanceRef = None.into(); + let mut elements: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 5).await?.into(); + jvm.store_array(&mut elements, 0, [beta.clone(), null.clone(), alpha.clone(), beta.clone(), null]) + .await?; + let source = jvm.new_class("SnapshotCollection", "([Ljava/lang/Object;)V", (elements,)).await?; + + for class_name in ["java/util/ArrayList", "java/util/Vector"] { + let copy = jvm.new_class(class_name, "(Ljava/util/Collection;)V", (source.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(©, "size", "()I", ()).await?, 5); + for (index, expected) in [Some("beta"), None, Some("alpha"), Some("beta"), None].into_iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(©, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + match expected { + Some(expected) => assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, expected), + None => assert!(value.is_null()), + } + } + } + + let set = jvm.new_class("java/util/HashSet", "(Ljava/util/Collection;)V", (source,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&set, "size", "()I", ()).await?, 3); + assert!(jvm.invoke_virtual::<_, bool>(&set, "contains", "(Ljava/lang/Object;)Z", (beta,)).await?); + assert!(jvm.invoke_virtual::<_, bool>(&set, "contains", "(Ljava/lang/Object;)Z", (alpha,)).await?); + let null: ClassInstanceRef = None.into(); + assert!(jvm.invoke_virtual::<_, bool>(&set, "contains", "(Ljava/lang/Object;)Z", (null,)).await?); + + Ok(()) +} + +#[tokio::test] +async fn col_10_11_put_all_preserves_equals_direction_and_hashtable_prevalidates_both_null_forms() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + let stored_key: ClassInstanceRef = jvm.new_class("DirectionalStoredKey", "()V", ()).await?.into(); + let query_key: ClassInstanceRef = jvm.new_class("DirectionalQueryKey", "()V", ()).await?.into(); + let target = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let source = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let old_value = JavaLangString::from_rust_string(&jvm, "old").await?; + let new_value = JavaLangString::from_rust_string(&jvm, "new").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &target, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (stored_key.clone(), old_value), + ) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (query_key.clone(), new_value.clone()), + ) + .await?; + + let _: () = jvm.invoke_virtual(&target, "putAll", "(Ljava/util/Map;)V", (source.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&target, "size", "()I", ()).await?, 1); + let replaced: ClassInstanceRef = jvm + .invoke_virtual(&target, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (query_key.clone(),)) + .await?; + assert_eq!(replaced.identity(), new_value.identity()); + let target_entries: ClassInstanceRef = jvm.invoke_virtual(&target, "entrySet", "()Ljava/util/Set;", ()).await?; + let target_entries: ClassInstanceRef> = jvm.invoke_virtual(&target_entries, "toArray", "()[Ljava/lang/Object;", ()).await?; + let target_entry: ClassInstanceRef = jvm.load_array(&target_entries, 0, 1).await?.pop().unwrap(); + let retained_key: ClassInstanceRef = jvm.invoke_virtual(&target_entry, "getKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(retained_key.identity(), stored_key.identity()); + + let copy = jvm.new_class("java/util/HashMap", "(Ljava/util/Map;)V", (source,)).await?; + let copy_entries: ClassInstanceRef = jvm.invoke_virtual(©, "entrySet", "()Ljava/util/Set;", ()).await?; + let copy_entries: ClassInstanceRef> = jvm.invoke_virtual(©_entries, "toArray", "()[Ljava/lang/Object;", ()).await?; + let copy_entry: ClassInstanceRef = jvm.load_array(©_entries, 0, 1).await?.pop().unwrap(); + let copied_key: ClassInstanceRef = jvm.invoke_virtual(©_entry, "getKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(copied_key.identity(), query_key.identity()); + + let hashtable = jvm.new_class("java/util/Hashtable", "()V", ()).await?; + let original_key = JavaLangString::from_rust_string(&jvm, "original-key").await?; + let original_value = JavaLangString::from_rust_string(&jvm, "original-value").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &hashtable, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (original_key.clone(), original_value.clone()), + ) + .await?; + + let null_key_source = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let null_key: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_key_source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (null_key, JavaLangString::from_rust_string(&jvm, "invalid-null-key").await?), + ) + .await?; + let valid_after_null_key: ClassInstanceRef = jvm.new_class("DirectionalStoredKey", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_key_source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + ( + valid_after_null_key.clone(), + JavaLangString::from_rust_string(&jvm, "valid-before-null-key").await?, + ), + ) + .await?; + let result: Result<()> = jvm.invoke_virtual(&hashtable, "putAll", "(Ljava/util/Map;)V", (null_key_source,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Hashtable.putAll must reject a null key"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&hashtable, "size", "()I", ()).await?, 1); + assert!( + !jvm.invoke_virtual::<_, bool>(&hashtable, "containsKey", "(Ljava/lang/Object;)Z", (valid_after_null_key,)) + .await? + ); + + let null_value_source = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let invalid_value_key: ClassInstanceRef = jvm.new_class("DirectionalQueryKey", "()V", ()).await?.into(); + let null_value: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_value_source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (invalid_value_key, null_value), + ) + .await?; + let valid_after_null_value: ClassInstanceRef = jvm.new_class("DirectionalStoredKey", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_value_source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + ( + valid_after_null_value.clone(), + JavaLangString::from_rust_string(&jvm, "valid-before-null-value").await?, + ), + ) + .await?; + let result: Result<()> = jvm.invoke_virtual(&hashtable, "putAll", "(Ljava/util/Map;)V", (null_value_source,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Hashtable.putAll must reject a null value"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&hashtable, "size", "()I", ()).await?, 1); + assert!( + !jvm.invoke_virtual::<_, bool>(&hashtable, "containsKey", "(Ljava/lang/Object;)Z", (valid_after_null_value,)) + .await? + ); + let preserved: ClassInstanceRef = jvm + .invoke_virtual(&hashtable, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (original_key,)) + .await?; + assert_eq!(preserved.identity(), original_value.identity()); + + let null_map: ClassInstanceRef = None.into(); + for map in [&target, &hashtable] { + let result: Result<()> = jvm.invoke_virtual(map, "putAll", "(Ljava/util/Map;)V", (null_map.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("putAll(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + let target_size: i32 = jvm.invoke_virtual(&target, "size", "()I", ()).await?; + let _: () = jvm.invoke_virtual(&target, "putAll", "(Ljava/util/Map;)V", (target.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&target, "size", "()I", ()).await?, target_size); + let hashtable_size: i32 = jvm.invoke_virtual(&hashtable, "size", "()I", ()).await?; + let _: () = jvm + .invoke_virtual(&hashtable, "putAll", "(Ljava/util/Map;)V", (hashtable.clone(),)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&hashtable, "size", "()I", ()).await?, hashtable_size); + + Ok(()) +} + +#[tokio::test] +async fn col_12_typed_to_array_preserves_multidimensional_components_and_jdk_ase_timing() -> Result<()> { + let jvm = test_jvm().await?; + let strings = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let string_row_1: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 1).await?.into(); + let string_row_2: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 2).await?.into(); + for row in [string_row_1.clone(), string_row_2.clone()] { + let _: bool = jvm.invoke_virtual(&strings, "add", "(Ljava/lang/Object;)Z", (row,)).await?; + } + + let small: ClassInstanceRef> = jvm.instantiate_array("[Ljava/lang/String;", 0).await?.into(); + let grown: ClassInstanceRef> = jvm + .invoke_virtual(&strings, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (small,)) + .await?; + assert_eq!(grown.class_definition().name(), "[[Ljava/lang/String;"); + let grown_rows: Vec> = jvm.load_array(&grown, 0, 2).await?; + assert_eq!(grown_rows[0].identity(), string_row_1.identity()); + assert_eq!(grown_rows[1].identity(), string_row_2.identity()); + + let object_matrix: ClassInstanceRef> = jvm.instantiate_array("[Ljava/lang/Object;", 0).await?.into(); + let covariant: ClassInstanceRef> = jvm + .invoke_virtual(&strings, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (object_matrix,)) + .await?; + assert_eq!(covariant.class_definition().name(), "[[Ljava/lang/Object;"); + + let ints = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let int_row_1: ClassInstanceRef> = jvm.instantiate_array("I", 1).await?.into(); + let int_row_2: ClassInstanceRef> = jvm.instantiate_array("I", 2).await?.into(); + for row in [int_row_1.clone(), int_row_2.clone()] { + let _: bool = jvm.invoke_virtual(&ints, "add", "(Ljava/lang/Object;)Z", (row,)).await?; + } + let sentinel_int_row: ClassInstanceRef> = jvm.instantiate_array("I", 3).await?.into(); + let mut int_matrix: ClassInstanceRef> = jvm.instantiate_array("[I", 3).await?.into(); + jvm.store_array( + &mut int_matrix, + 0, + [sentinel_int_row.clone(), sentinel_int_row.clone(), sentinel_int_row.clone()], + ) + .await?; + let int_matrix_identity = int_matrix.identity(); + let reused: ClassInstanceRef> = jvm + .invoke_virtual(&ints, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (int_matrix,)) + .await?; + assert_eq!(reused.identity(), int_matrix_identity); + assert_eq!(reused.class_definition().name(), "[[I"); + let reused_rows: Vec> = jvm.load_array(&reused, 0, 3).await?; + assert_eq!(reused_rows[0].identity(), int_row_1.identity()); + assert_eq!(reused_rows[1].identity(), int_row_2.identity()); + assert!(reused_rows[2].is_null()); + + let incompatible = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let object_row: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + let _: bool = jvm + .invoke_virtual(&incompatible, "add", "(Ljava/lang/Object;)Z", (string_row_1.clone(),)) + .await?; + let _: bool = jvm.invoke_virtual(&incompatible, "add", "(Ljava/lang/Object;)Z", (object_row,)).await?; + let sentinel_string_row: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 4).await?.into(); + let mut destination: ClassInstanceRef> = jvm.instantiate_array("[Ljava/lang/String;", 3).await?.into(); + jvm.store_array( + &mut destination, + 0, + [sentinel_string_row.clone(), sentinel_string_row.clone(), sentinel_string_row.clone()], + ) + .await?; + let result: Result>> = jvm + .invoke_virtual( + &incompatible, + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (destination.clone(),), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("String[][] must reject an Object[] element"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ArrayStoreException")); + let partial: Vec> = jvm.load_array(&destination, 0, 3).await?; + assert_eq!(partial[0].identity(), string_row_1.identity()); + assert_eq!(partial[1].identity(), sentinel_string_row.identity()); + assert_eq!(partial[2].identity(), sentinel_string_row.identity()); + + Ok(()) +} + +#[tokio::test] +async fn col_01_bulk_unsupported_mutations_respect_empty_and_pre_mutation_state() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let second = JavaLangString::from_rust_string(&jvm, "second").await?; + let mut elements: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut elements, 0, [first.clone(), second.clone()]).await?; + let immutable = jvm.new_class("SnapshotCollection", "([Ljava/lang/Object;)V", (elements,)).await?; + let empty_elements: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 0).await?.into(); + let empty = jvm.new_class("SnapshotCollection", "([Ljava/lang/Object;)V", (empty_elements,)).await?; + + for (name, descriptor) in [("addAll", "(Ljava/util/Collection;)Z"), ("removeAll", "(Ljava/util/Collection;)Z")] { + let changed: bool = jvm.invoke_virtual(&immutable, name, descriptor, (empty.clone(),)).await?; + assert!(!changed); + } + let retained: bool = jvm + .invoke_virtual(&immutable, "retainAll", "(Ljava/util/Collection;)Z", (immutable.clone(),)) + .await?; + assert!(!retained); + + for (name, argument) in [ + ("addAll", immutable.clone()), + ("removeAll", immutable.clone()), + ("retainAll", empty.clone()), + ] { + let result: Result = jvm.invoke_virtual(&immutable, name, "(Ljava/util/Collection;)Z", (argument,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} must reject an unsupported mutation"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/UnsupportedOperationException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&immutable, "size", "()I", ()).await?, 2); + assert!( + jvm.invoke_virtual::<_, bool>(&immutable, "contains", "(Ljava/lang/Object;)Z", (first.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&immutable, "contains", "(Ljava/lang/Object;)Z", (second.clone(),)) + .await? + ); + } + + let map = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let map_key_1 = JavaLangString::from_rust_string(&jvm, "map-key-1").await?; + let map_key_2 = JavaLangString::from_rust_string(&jvm, "map-key-2").await?; + let map_value = JavaLangString::from_rust_string(&jvm, "map-value").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (map_key_1.clone(), map_value.clone()), + ) + .await?; + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let snapshot_iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let empty_add: bool = jvm.invoke_virtual(&keys, "addAll", "(Ljava/util/Collection;)Z", (empty.clone(),)).await?; + assert!(!empty_add); + let result: Result = jvm + .invoke_virtual(&keys, "addAll", "(Ljava/util/Collection;)Z", (immutable.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("keySet.addAll(nonEmpty) must throw UnsupportedOperationException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/UnsupportedOperationException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&map, "size", "()I", ()).await?, 1); + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (map_key_2.clone(), map_value), + ) + .await?; + let target = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + let _: bool = jvm.invoke_virtual(&target, "addAll", "(Ljava/util/Collection;)Z", (keys,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&target, "size", "()I", ()).await?, 2); + let snapshot_first: ClassInstanceRef = jvm.invoke_virtual(&snapshot_iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(snapshot_first.identity(), map_key_1.identity()); + assert!(!jvm.invoke_virtual::<_, bool>(&snapshot_iterator, "hasNext", "()Z", ()).await?); + assert!( + jvm.invoke_virtual::<_, bool>(&target, "contains", "(Ljava/lang/Object;)Z", (map_key_1,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&target, "contains", "(Ljava/lang/Object;)Z", (map_key_2,)) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn coll_08_entry_set_contains_and_remove_snapshot_candidates_and_use_stored_value_equals() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + let null: ClassInstanceRef = None.into(); + + let asymmetric_key_map = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let stored_key: ClassInstanceRef = jvm.new_class("DirectionalStoredKey", "()V", ()).await?.into(); + let query_key: ClassInstanceRef = jvm.new_class("DirectionalQueryKey", "()V", ()).await?.into(); + let stored_key_value = JavaLangString::from_rust_string(&jvm, "stored-key-value").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &asymmetric_key_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (stored_key.clone(), stored_key_value.clone()), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&asymmetric_key_map, "containsKey", "(Ljava/lang/Object;)Z", (query_key.clone(),),) + .await?, + "the query key must find the stored key before EntrySet applies stored-key equality" + ); + let asymmetric_entries: ClassInstanceRef = jvm.invoke_virtual(&asymmetric_key_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let asymmetric_contains: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (query_key.clone(), query_key.clone(), null.clone(), 2), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&asymmetric_entries, "contains", "(Ljava/lang/Object;)Z", (asymmetric_contains.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&asymmetric_contains, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&asymmetric_contains, "valueCalls", "I").await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&asymmetric_key_map, "size", "()I", ()).await?, 1); + + let asymmetric_remove: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (query_key.clone(), query_key, null.clone(), 2), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&asymmetric_entries, "remove", "(Ljava/lang/Object;)Z", (asymmetric_remove.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&asymmetric_remove, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&asymmetric_remove, "valueCalls", "I").await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&asymmetric_key_map, "size", "()I", ()).await?, 1); + let preserved_iterator: ClassInstanceRef = jvm.invoke_virtual(&asymmetric_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let preserved_entry: ClassInstanceRef = jvm.invoke_virtual(&preserved_iterator, "next", "()Ljava/lang/Object;", ()).await?; + let preserved_key: ClassInstanceRef = jvm.invoke_virtual(&preserved_entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let preserved_value: ClassInstanceRef = jvm.invoke_virtual(&preserved_entry, "getValue", "()Ljava/lang/Object;", ()).await?; + assert_eq!(preserved_key.identity(), stored_key.identity()); + assert_eq!(preserved_value.identity(), stored_key_value.identity()); + assert!(!jvm.invoke_virtual::<_, bool>(&preserved_iterator, "hasNext", "()Z", ()).await?); + + let null_key_map = jvm.new_class("java/util/HashMap", "()V", ()).await?; + let null_key_value = JavaLangString::from_rust_string(&jvm, "null-key-value").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_key_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (null.clone(), null_key_value.clone()), + ) + .await?; + let null_key_entries: ClassInstanceRef = jvm.invoke_virtual(&null_key_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let null_key_contains: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (null.clone(), null.clone(), null_key_value.clone(), 0), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&null_key_entries, "contains", "(Ljava/lang/Object;)Z", (null_key_contains.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&null_key_contains, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&null_key_contains, "valueCalls", "I").await?, 1); + let null_key_remove: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (null.clone(), null.clone(), null_key_value, 0), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&null_key_entries, "remove", "(Ljava/lang/Object;)Z", (null_key_remove.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&null_key_remove, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&null_key_remove, "valueCalls", "I").await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&null_key_map, "size", "()I", ()).await?, 0); + + for map_class in ["java/util/HashMap", "java/util/Hashtable"] { + let map: ClassInstanceRef = jvm.new_class(map_class, "()V", ()).await?.into(); + let key = JavaLangString::from_rust_string(&jvm, "direction-key").await?; + let stored_value: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (true,)).await?.into(); + let candidate_value: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (false,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(), stored_value.clone()), + ) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let contains_candidate: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (key.clone(), key.clone(), candidate_value.clone(), 0), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (contains_candidate.clone(),)) + .await?, + "{map_class} EntrySet.contains must use storedValue.equals(candidateValue)" + ); + assert_eq!(jvm.get_field::(&contains_candidate, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&contains_candidate, "valueCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&stored_value, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_value, "equalsCalls", "I").await?, 0); + + let remove_candidate: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (key.clone(), key, candidate_value.clone(), 0), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "remove", "(Ljava/lang/Object;)Z", (remove_candidate.clone(),)) + .await?, + "{map_class} EntrySet.remove must use storedValue.equals(candidateValue)" + ); + assert_eq!(jvm.get_field::(&remove_candidate, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&remove_candidate, "valueCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&stored_value, "equalsCalls", "I").await?, 2); + assert_eq!(jvm.get_field::(&candidate_value, "equalsCalls", "I").await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&map, "size", "()I", ()).await?, 0); + + let opposite_map: ClassInstanceRef = jvm.new_class(map_class, "()V", ()).await?.into(); + let opposite_key = JavaLangString::from_rust_string(&jvm, "opposite-key").await?; + let rejecting_stored: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (false,)).await?.into(); + let accepting_candidate: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (true,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &opposite_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (opposite_key.clone(), rejecting_stored.clone()), + ) + .await?; + let opposite_entries: ClassInstanceRef = jvm.invoke_virtual(&opposite_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let opposite_candidate: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (opposite_key.clone(), opposite_key, accepting_candidate.clone(), 0), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&opposite_entries, "contains", "(Ljava/lang/Object;)Z", (opposite_candidate,),) + .await? + ); + assert_eq!(jvm.get_field::(&rejecting_stored, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&accepting_candidate, "equalsCalls", "I").await?, 0); + + let stateful_map: ClassInstanceRef = jvm.new_class(map_class, "()V", ()).await?.into(); + let first_key = JavaLangString::from_rust_string(&jvm, "first-key").await?; + let later_key = JavaLangString::from_rust_string(&jvm, "later-key").await?; + let first_value = JavaLangString::from_rust_string(&jvm, "first-value").await?; + let later_value = JavaLangString::from_rust_string(&jvm, "later-value").await?; + for (key, value) in [(first_key.clone(), first_value.clone()), (later_key.clone(), later_value.clone())] { + let _: ClassInstanceRef = jvm + .invoke_virtual( + &stateful_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, value), + ) + .await?; + } + let stateful_entries: ClassInstanceRef = jvm.invoke_virtual(&stateful_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let alternating: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + ( + first_key.clone(), + later_key.clone(), + JavaLangString::from_rust_string(&jvm, "first-value").await?, + 0, + ), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&stateful_entries, "remove", "(Ljava/lang/Object;)Z", (alternating.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&alternating, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&alternating, "valueCalls", "I").await?, 1); + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_map, "containsKey", "(Ljava/lang/Object;)Z", (first_key,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&stateful_map, "containsKey", "(Ljava/lang/Object;)Z", (later_key.clone(),)) + .await? + ); + let preserved: ClassInstanceRef = jvm + .invoke_virtual(&stateful_map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (later_key.clone(),)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &preserved).await?, "later-value"); + + let missing_key = JavaLangString::from_rust_string(&jvm, "missing-key").await?; + let missing_contains: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (missing_key.clone(), missing_key.clone(), null.clone(), 2), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_entries, "contains", "(Ljava/lang/Object;)Z", (missing_contains.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&missing_contains, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&missing_contains, "valueCalls", "I").await?, 0); + + let missing_remove: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (missing_key.clone(), missing_key, null.clone(), 2), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_entries, "remove", "(Ljava/lang/Object;)Z", (missing_remove.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&missing_remove, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&missing_remove, "valueCalls", "I").await?, 0); + + let throwing_key: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (later_key.clone(), later_key.clone(), null.clone(), 1), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&stateful_entries, "contains", "(Ljava/lang/Object;)Z", (throwing_key.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{map_class} EntrySet.contains must propagate getKey exceptions"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_key, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_key, "valueCalls", "I").await?, 0); + + let throwing_remove_key: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (later_key.clone(), later_key.clone(), null.clone(), 1), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&stateful_entries, "remove", "(Ljava/lang/Object;)Z", (throwing_remove_key.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{map_class} EntrySet.remove must propagate getKey exceptions"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_remove_key, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_remove_key, "valueCalls", "I").await?, 0); + + let throwing_contains_value: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (later_key.clone(), later_key.clone(), null.clone(), 2), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&stateful_entries, "contains", "(Ljava/lang/Object;)Z", (throwing_contains_value.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{map_class} EntrySet.contains must propagate getValue exceptions for a found key"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_contains_value, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_contains_value, "valueCalls", "I").await?, 1); + + let throwing_value: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (later_key.clone(), later_key.clone(), null.clone(), 2), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&stateful_entries, "remove", "(Ljava/lang/Object;)Z", (throwing_value.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{map_class} EntrySet.remove must propagate getValue exceptions for a found key"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_value, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_value, "valueCalls", "I").await?, 1); + assert!( + jvm.invoke_virtual::<_, bool>(&stateful_map, "containsKey", "(Ljava/lang/Object;)Z", (later_key.clone(),)) + .await? + ); + + let non_entry: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + for candidate in [null.clone(), non_entry] { + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_entries, "contains", "(Ljava/lang/Object;)Z", (candidate.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_entries, "remove", "(Ljava/lang/Object;)Z", (candidate,)) + .await? + ); + } + + if map_class == "java/util/Hashtable" { + let null_key: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (null.clone(), null.clone(), null.clone(), 2), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_entries, "contains", "(Ljava/lang/Object;)Z", (null_key.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&stateful_entries, "remove", "(Ljava/lang/Object;)Z", (null_key.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&null_key, "keyCalls", "I").await?, 2); + assert_eq!(jvm.get_field::(&null_key, "valueCalls", "I").await?, 0); + } + } + + Ok(()) +} + +#[tokio::test] +async fn hashtable_equals_and_hash_code_match_map_value_contracts() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + let hashtable: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let hash_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let tree_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + + for map in [&hashtable, &hash_map, &tree_map] { + for (key, value) in [("alpha", 17), ("beta", 29)] { + let key: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, key).await?.into(); + let value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + } + let unmodifiable: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableMap", + "(Ljava/util/Map;)Ljava/util/Map;", + (hash_map.clone(),), + ) + .await?; + let maps = [&hashtable, &hash_map, &tree_map, &unmodifiable]; + let expected_hash: i32 = jvm.invoke_virtual(&hashtable, "hashCode", "()I", ()).await?; + for left in maps { + assert_eq!(jvm.invoke_virtual::<_, i32>(left, "hashCode", "()I", ()).await?, expected_hash); + for right in maps { + assert!( + jvm.invoke_virtual::<_, bool>(left, "equals", "(Ljava/lang/Object;)Z", (right.clone(),)) + .await?, + "{} must equal {}", + left.class_definition().name(), + right.class_definition().name() + ); + } + } + + assert!( + jvm.invoke_virtual::<_, bool>(&hashtable, "equals", "(Ljava/lang/Object;)Z", (hashtable.clone(),)) + .await? + ); + let null: ClassInstanceRef = None.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&hashtable, "equals", "(Ljava/lang/Object;)Z", (null,)) + .await? + ); + let non_map: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&hashtable, "equals", "(Ljava/lang/Object;)Z", (non_map,)) + .await? + ); + let different_size: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&hashtable, "equals", "(Ljava/lang/Object;)Z", (different_size,)) + .await? + ); + + for mode in [0, 1] { + let throwing: ClassInstanceRef = jvm.new_class("ThrowingMap", "(II)V", (2, mode)).await?.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&hashtable, "equals", "(Ljava/lang/Object;)Z", (throwing,)) + .await?, + "Hashtable.equals must convert mode {mode} NPE/CCE to false" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn hashtable_hash_code_guards_self_key_and_value_recursion() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + + let self_value: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let value_key: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "self-value").await?.into(); + let expected_value_hash: i32 = jvm.invoke_virtual(&value_key, "hashCode", "()I", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &self_value, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (value_key, self_value.clone()), + ) + .await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&self_value, "hashCode", "()I", ()).await?, + expected_value_hash + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&self_value, "hashCode", "()I", ()).await?, + expected_value_hash + ); + + let self_key: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let key_value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (37,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &self_key, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (self_key.clone(), key_value), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&self_key, "hashCode", "()I", ()).await?, 37); + + let self_both: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &self_both, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (self_both.clone(), self_both.clone()), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&self_both, "hashCode", "()I", ()).await?, 0); + + Ok(()) +} + +#[tokio::test] +async fn hashtable_entry_equals_and_hash_code_follow_map_entry_contract() -> Result<()> { + let jvm = collection_contract_fixture_jvm().await?; + let table: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let key: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "entry-key").await?.into(); + let value: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "entry-value").await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &table, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(), value.clone()), + ) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&table, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + + let different_key: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "different").await?.into(); + let short_circuit: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (different_key.clone(), different_key, value.clone(), 2), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&entry, "equals", "(Ljava/lang/Object;)Z", (short_circuit.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&short_circuit, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&short_circuit, "valueCalls", "I").await?, 0); + + let throwing_key: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (key.clone(), key.clone(), value.clone(), 1), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&entry, "equals", "(Ljava/lang/Object;)Z", (throwing_key.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Hashtable.Entry.equals must propagate getKey exceptions"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_key, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_key, "valueCalls", "I").await?, 0); + + let throwing_value: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (key.clone(), key.clone(), value.clone(), 2), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&entry, "equals", "(Ljava/lang/Object;)Z", (throwing_value.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Hashtable.Entry.equals must propagate getValue exceptions after matching keys"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_value, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_value, "valueCalls", "I").await?, 1); + + let directional_table: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + let stored_key: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (true,)).await?.into(); + let candidate_key: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (false,)).await?.into(); + let stored_value: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (true,)).await?.into(); + let candidate_value: ClassInstanceRef = jvm.new_class("ConfigurableEqualsValue", "(Z)V", (false,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &directional_table, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (stored_key.clone(), stored_value.clone()), + ) + .await?; + let directional_entries: ClassInstanceRef = jvm.invoke_virtual(&directional_table, "entrySet", "()Ljava/util/Set;", ()).await?; + let directional_iterator: ClassInstanceRef = jvm.invoke_virtual(&directional_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let directional_entry: ClassInstanceRef = jvm.invoke_virtual(&directional_iterator, "next", "()Ljava/lang/Object;", ()).await?; + let directional_candidate: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (candidate_key.clone(), candidate_key.clone(), candidate_value.clone(), 0), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&directional_entry, "equals", "(Ljava/lang/Object;)Z", (directional_candidate.clone(),),) + .await? + ); + assert_eq!(jvm.get_field::(&directional_candidate, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&directional_candidate, "valueCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&stored_key, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_key, "equalsCalls", "I").await?, 0); + assert_eq!(jvm.get_field::(&stored_value, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_value, "equalsCalls", "I").await?, 0); + + let integer_key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?.into(); + let integer_value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (11,)).await?.into(); + let null: ClassInstanceRef = None.into(); + let integer_entry: ClassInstanceRef = jvm + .new_class( + "java/util/Hashtable$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/Hashtable$Entry;)V", + (7, integer_key, integer_value, null.clone()), + ) + .await? + .into(); + assert_eq!(jvm.invoke_virtual::<_, i32>(&integer_entry, "hashCode", "()I", ()).await?, 7 ^ 11); + + let null_entry: ClassInstanceRef = jvm + .new_class( + "java/util/Hashtable$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/Hashtable$Entry;)V", + (0, null.clone(), null.clone(), null.clone()), + ) + .await? + .into(); + let null_candidate: ClassInstanceRef = jvm + .new_class( + "StatefulMapEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V", + (null.clone(), null.clone(), null.clone(), 0), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&null_entry, "equals", "(Ljava/lang/Object;)Z", (null_candidate.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&null_candidate, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&null_candidate, "valueCalls", "I").await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&null_entry, "hashCode", "()I", ()).await?, 0); + assert!( + !jvm.invoke_virtual::<_, bool>(&null_entry, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let non_entry: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&null_entry, "equals", "(Ljava/lang/Object;)Z", (non_entry,)) + .await? + ); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_collections.rs b/java_runtime/tests/classes/java/util/test_collections.rs new file mode 100644 index 00000000..9aa59a5e --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_collections.rs @@ -0,0 +1,3196 @@ +use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, 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 CollectionsSortValue; + +impl CollectionsSortValue { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsSortValue", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "(IIZ)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("key", "I", Default::default()), + JavaFieldProto::new("id", "I", Default::default()), + JavaFieldProto::new("fail", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, key: i32, id: i32, fail: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "key", "I", key).await?; + jvm.put_field(&mut this, "id", "I", id).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(other.as_ref(), "CollectionsSortValue") { + return Err(jvm.exception("java/lang/ClassCastException", "other").await); + } + if jvm.get_field::(&this, "fail", "Z").await? || jvm.get_field::(&other, "fail", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "comparison failure").await); + } + + Ok(jvm + .get_field::(&this, "key", "I") + .await? + .cmp(&jvm.get_field::(&other, "key", "I").await?) as i32) + } +} + +struct CollectionsComparator; + +impl CollectionsComparator { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsComparator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Comparator"], + methods: vec![ + JavaMethodProto::new("", "(ZZ)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(ZZZ)V", Self::init_with_nulls, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + Self::compare, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![ + JavaFieldProto::new("reverse", "Z", Default::default()), + JavaFieldProto::new("fail", "Z", Default::default()), + JavaFieldProto::new("allowNull", "Z", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, reverse: bool, fail: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "reverse", "Z", reverse).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await?; + jvm.put_field(&mut this, "allowNull", "Z", false).await + } + + async fn init_with_nulls( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + reverse: bool, + fail: bool, + allow_null: bool, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "reverse", "Z", reverse).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await?; + jvm.put_field(&mut this, "allowNull", "Z", allow_null).await + } + + async fn compare( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + left: ClassInstanceRef, + right: ClassInstanceRef, + ) -> Result { + if jvm.get_field::(&this, "fail", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "comparison failure").await); + } + let comparison = if left.is_null() || right.is_null() { + if !jvm.get_field::(&this, "allowNull", "Z").await? { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + match (left.is_null(), right.is_null()) { + (true, true) => 0, + (true, false) => -1, + (false, true) => 1, + (false, false) => unreachable!(), + } + } else { + jvm.get_field::(&left, "key", "I") + .await? + .cmp(&jvm.get_field::(&right, "key", "I").await?) as i32 + }; + Ok(if jvm.get_field::(&this, "reverse", "Z").await? { + -comparison + } else { + comparison + }) + } +} + +struct CollectionsProbeList; + +impl CollectionsProbeList { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsProbeList", + parent_class: Some("java/util/AbstractList"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "([Ljava/lang/Object;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(I)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("set", "(ILjava/lang/Object;)Ljava/lang/Object;", Self::set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "listIterator", + "()Ljava/util/ListIterator;", + Self::list_iterator, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "listIterator", + "(I)Ljava/util/ListIterator;", + Self::list_iterator_at, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![ + JavaFieldProto::new("elements", "[Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("listIteratorCalls", "I", Default::default()), + JavaFieldProto::new("setCalls", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, elements: ClassInstanceRef>) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractList", "", "()V", ()).await?; + jvm.put_field(&mut this, "elements", "[Ljava/lang/Object;", elements).await?; + jvm.put_field(&mut this, "listIteratorCalls", "I", 0).await?; + jvm.put_field(&mut this, "setCalls", "I", 0).await + } + + async fn size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + Ok(jvm.array_length(&elements).await? as i32) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + let length = jvm.array_length(&elements).await? as i32; + if index < 0 || index >= length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "index").await); + } + Ok(jvm + .load_array::>(&elements, index as usize, 1) + .await? + .into_iter() + .next() + .unwrap()) + } + + async fn set( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + index: i32, + element: ClassInstanceRef, + ) -> Result> { + let mut elements: ClassInstanceRef> = jvm.get_field(&this, "elements", "[Ljava/lang/Object;").await?; + let length = jvm.array_length(&elements).await? as i32; + if index < 0 || index >= length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "index").await); + } + let previous = jvm + .load_array::>(&elements, index as usize, 1) + .await? + .into_iter() + .next() + .unwrap(); + jvm.store_array(&mut elements, index as usize, core::iter::once(element)).await?; + let calls: i32 = jvm.get_field(&this, "setCalls", "I").await?; + jvm.put_field(&mut this, "setCalls", "I", calls + 1).await?; + Ok(previous) + } + + async fn list_iterator(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "listIteratorCalls", "I").await?; + jvm.put_field(&mut this, "listIteratorCalls", "I", calls + 1).await?; + jvm.invoke_special(&this, "java/util/AbstractList", "listIterator", "()Ljava/util/ListIterator;", ()) + .await + } + + async fn list_iterator_at(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, index: i32) -> Result> { + let calls: i32 = jvm.get_field(&this, "listIteratorCalls", "I").await?; + jvm.put_field(&mut this, "listIteratorCalls", "I", calls + 1).await?; + jvm.invoke_special(&this, "java/util/AbstractList", "listIterator", "(I)Ljava/util/ListIterator;", (index,)) + .await + } +} + +struct CollectionsInvalidRandom; + +impl CollectionsInvalidRandom { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsInvalidRandom", + parent_class: Some("java/util/Random"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextInt", "(I)I", Self::next_int, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("result", "I", Default::default())], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, result: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/Random", "", "(J)V", (0i64,)).await?; + jvm.put_field(&mut this, "result", "I", result).await + } + + async fn next_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: i32) -> Result { + jvm.get_field(&this, "result", "I").await + } +} + +struct CollectionsExceptionalSet; + +impl CollectionsExceptionalSet { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsExceptionalSet", + parent_class: Some("java/util/AbstractSet"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/Object;I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("iterator", "()Ljava/util/Iterator;", Self::iterator, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("contains", "(Ljava/lang/Object;)Z", Self::contains, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("element", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("mode", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, element: ClassInstanceRef, mode: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/AbstractSet", "", "()V", ()).await?; + jvm.put_field(&mut this, "element", "Ljava/lang/Object;", element).await?; + jvm.put_field(&mut this, "mode", "I", mode).await + } + + async fn size(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(1) + } + + async fn iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + let list = jvm + .new_class("java/util/Collections$CopiesList", "(ILjava/lang/Object;)V", (1, element)) + .await?; + jvm.invoke_virtual(&list, "iterator", "()Ljava/util/Iterator;", ()).await + } + + async fn contains(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, target: ClassInstanceRef) -> Result { + match jvm.get_field::(&this, "mode", "I").await? { + 0 => Err(jvm.exception("java/lang/ClassCastException", "incompatible element").await), + 1 if target.is_null() => Err(jvm.exception("java/lang/NullPointerException", "null element").await), + 2 => Err(jvm.exception("java/lang/IllegalStateException", "unexpected contains failure").await), + _ => { + let element: ClassInstanceRef = jvm.get_field(&this, "element", "Ljava/lang/Object;").await?; + if element.is_null() { + Ok(target.is_null()) + } else if target.is_null() { + Ok(false) + } else { + jvm.invoke_virtual(&element, "equals", "(Ljava/lang/Object;)Z", (target,)).await + } + } + } + } +} + +struct CollectionsAsymmetricEquals; + +impl CollectionsAsymmetricEquals { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsAsymmetricEquals", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Z)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("result", "Z", Default::default())], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, result: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "result", "Z", result).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + jvm.get_field(&this, "result", "Z").await + } +} + +struct CollectionsEqualsProbe; + +impl CollectionsEqualsProbe { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsEqualsProbe", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(ZZ)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("result", "Z", Default::default()), + JavaFieldProto::new("fail", "Z", Default::default()), + JavaFieldProto::new("equalsCalls", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, result: bool, fail: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "result", "Z", result).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await?; + jvm.put_field(&mut this, "equalsCalls", "I", 0).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + let calls: i32 = jvm.get_field(&this, "equalsCalls", "I").await?; + jvm.put_field(&mut this, "equalsCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "fail", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "equals failure").await); + } + jvm.get_field(&this, "result", "Z").await + } +} + +struct CollectionsEntryProbe; + +impl CollectionsEntryProbe { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "CollectionsEntryProbe", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map$Entry"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + Self::init, + MethodAccessFlags::PUBLIC, + ), + 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, + ), + ], + fields: vec![ + JavaFieldProto::new("key", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("value", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("throwKey", "Z", Default::default()), + JavaFieldProto::new("throwValue", "Z", Default::default()), + JavaFieldProto::new("keyCalls", "I", Default::default()), + JavaFieldProto::new("valueCalls", "I", Default::default()), + JavaFieldProto::new("setValueCalls", "I", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + throw_key: bool, + throw_value: bool, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "key", "Ljava/lang/Object;", key).await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + jvm.put_field(&mut this, "throwKey", "Z", throw_key).await?; + jvm.put_field(&mut this, "throwValue", "Z", throw_value).await?; + jvm.put_field(&mut this, "keyCalls", "I", 0).await?; + jvm.put_field(&mut this, "valueCalls", "I", 0).await?; + jvm.put_field(&mut this, "setValueCalls", "I", 0).await + } + + async fn get_key(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "keyCalls", "I").await?; + jvm.put_field(&mut this, "keyCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "throwKey", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "getKey failure").await); + } + jvm.get_field(&this, "key", "Ljava/lang/Object;").await + } + + async fn get_value(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "valueCalls", "I").await?; + jvm.put_field(&mut this, "valueCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "throwValue", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "getValue failure").await); + } + jvm.get_field(&this, "value", "Ljava/lang/Object;").await + } + + async fn set_value( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + let calls: i32 = jvm.get_field(&this, "setValueCalls", "I").await?; + jvm.put_field(&mut this, "setValueCalls", "I", calls + 1).await?; + let previous: ClassInstanceRef = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + Ok(previous) + } +} + +async fn collections_test_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + for proto in [ + CollectionsSortValue::as_proto(), + CollectionsComparator::as_proto(), + CollectionsProbeList::as_proto(), + CollectionsInvalidRandom::as_proto(), + CollectionsExceptionalSet::as_proto(), + CollectionsAsymmetricEquals::as_proto(), + CollectionsEqualsProbe::as_proto(), + CollectionsEntryProbe::as_proto(), + ] { + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto(proto, Box::new(runtime.clone()) as Box<_>)), + None, + ) + .await?; + } + Ok(jvm) +} + +async fn integer_list(jvm: &Jvm, values: &[i32]) -> Result> { + let list: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + for value in values { + let element: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (*value,)).await?.into(); + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (element,)).await?; + } + Ok(list) +} + +async fn integer_values(jvm: &Jvm, list: &ClassInstanceRef) -> Result> { + let size: i32 = jvm.invoke_virtual(list, "size", "()I", ()).await?; + let mut values = Vec::with_capacity(size as usize); + for index in 0..size { + let element: ClassInstanceRef = jvm.invoke_virtual(list, "get", "(I)Ljava/lang/Object;", (index,)).await?; + values.push(jvm.invoke_virtual(&element, "intValue", "()I", ()).await?); + } + Ok(values) +} + +#[tokio::test] +async fn test_coll_01_exact_descriptors_access_and_singletons() -> Result<()> { + let jvm = test_jvm().await?; + let class = jvm.resolve_class("java/util/Collections").await?; + assert!(class.definition.access_flags().contains(ClassAccessFlags::PUBLIC)); + assert!( + class + .definition + .method("", "()V", false) + .expect("Collections private constructor") + .access_flags() + .contains(MethodAccessFlags::PRIVATE) + ); + + let descriptors = [ + ("sort", "(Ljava/util/List;)V"), + ("sort", "(Ljava/util/List;Ljava/util/Comparator;)V"), + ("binarySearch", "(Ljava/util/List;Ljava/lang/Object;)I"), + ("binarySearch", "(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I"), + ("reverse", "(Ljava/util/List;)V"), + ("fill", "(Ljava/util/List;Ljava/lang/Object;)V"), + ("copy", "(Ljava/util/List;Ljava/util/List;)V"), + ("shuffle", "(Ljava/util/List;)V"), + ("shuffle", "(Ljava/util/List;Ljava/util/Random;)V"), + ("min", "(Ljava/util/Collection;)Ljava/lang/Object;"), + ("min", "(Ljava/util/Collection;Ljava/util/Comparator;)Ljava/lang/Object;"), + ("max", "(Ljava/util/Collection;)Ljava/lang/Object;"), + ("max", "(Ljava/util/Collection;Ljava/util/Comparator;)Ljava/lang/Object;"), + ("nCopies", "(ILjava/lang/Object;)Ljava/util/List;"), + ("singleton", "(Ljava/lang/Object;)Ljava/util/Set;"), + ]; + for (name, descriptor) in descriptors { + let method = class + .definition + .method(name, descriptor, true) + .unwrap_or_else(|| panic!("missing Collections.{name}{descriptor}")); + assert!( + method.access_flags().contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + "wrong access for Collections.{name}{descriptor}" + ); + } + + for (name, descriptor) in [("EMPTY_LIST", "Ljava/util/List;"), ("EMPTY_SET", "Ljava/util/Set;")] { + let field = class + .definition + .field(name, descriptor, true) + .unwrap_or_else(|| panic!("missing Collections.{name}:{descriptor}")); + assert!( + field + .access_flags() + .contains(FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL) + ); + } + + let empty_list: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_LIST", "Ljava/util/List;").await?; + let same_empty_list: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_LIST", "Ljava/util/List;").await?; + let empty_set: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_SET", "Ljava/util/Set;").await?; + assert_eq!(empty_list.identity(), same_empty_list.identity()); + assert!(jvm.is_instance(empty_list.as_ref(), "java/util/List")); + assert!(jvm.is_instance(empty_set.as_ref(), "java/util/Set")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&empty_list, "size", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&empty_set, "size", "()I", ()).await?, 0); + + for (name, parent) in [ + ("java/util/Collections$EmptyList", "java/util/AbstractList"), + ("java/util/Collections$CopiesList", "java/util/AbstractList"), + ("java/util/Collections$EmptySet", "java/util/AbstractSet"), + ("java/util/Collections$SingletonSet", "java/util/AbstractSet"), + ] { + let inner = jvm.resolve_class(name).await?; + assert_eq!(inner.definition.super_class_name().as_deref(), Some(parent)); + assert!(inner.definition.interface_names().iter().any(|name| name == "java/io/Serializable")); + assert!(!inner.definition.access_flags().contains(ClassAccessFlags::PUBLIC)); + } + let copies_class = jvm.resolve_class("java/util/Collections$CopiesList").await?; + assert!( + copies_class + .definition + .field("n", "I", false) + .expect("CopiesList.n") + .access_flags() + .contains(FieldAccessFlags::FINAL) + ); + assert!( + copies_class + .definition + .field("element", "Ljava/lang/Object;", false) + .expect("CopiesList.element") + .access_flags() + .contains(FieldAccessFlags::FINAL) + ); + + for name in ["java/util/AbstractList", "java/util/AbstractSet"] { + let abstract_class = jvm.resolve_class(name).await?; + assert_eq!( + abstract_class.definition.access_flags(), + ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + "{name} must have exact public abstract access" + ); + assert_eq!( + abstract_class + .definition + .method("", "()V", false) + .expect("abstract collection constructor") + .access_flags(), + MethodAccessFlags::PROTECTED, + "{name} constructor must have exact protected access" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_coll_02_stable_sort_and_comparator_failure_is_atomic() -> Result<()> { + let jvm = collections_test_jvm().await?; + let mut elements: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 4).await?.into(); + let original: [ClassInstanceRef; 4] = [ + jvm.new_class("CollectionsSortValue", "(IIZ)V", (2, 0, false)).await?.into(), + jvm.new_class("CollectionsSortValue", "(IIZ)V", (1, 1, false)).await?.into(), + jvm.new_class("CollectionsSortValue", "(IIZ)V", (2, 2, false)).await?.into(), + jvm.new_class("CollectionsSortValue", "(IIZ)V", (1, 3, false)).await?.into(), + ]; + jvm.store_array(&mut elements, 0, original.clone()).await?; + let list: ClassInstanceRef = jvm.new_class("CollectionsProbeList", "([Ljava/lang/Object;)V", (elements,)).await?.into(); + + jvm.invoke_static::<_, ()>("java/util/Collections", "sort", "(Ljava/util/List;)V", (list.clone(),)) + .await?; + let sorted: ClassInstanceRef> = jvm.get_field(&list, "elements", "[Ljava/lang/Object;").await?; + let sorted = jvm.load_array::>(&sorted, 0, 4).await?; + let mut keys_and_ids = vec![]; + for value in sorted { + keys_and_ids.push(( + jvm.get_field::(&value, "key", "I").await?, + jvm.get_field::(&value, "id", "I").await?, + )); + } + assert_eq!(keys_and_ids, vec![(1, 1), (1, 3), (2, 0), (2, 2)]); + assert_eq!(jvm.get_field::(&list, "listIteratorCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&list, "setCalls", "I").await?, 4); + + let reverse: ClassInstanceRef = jvm.new_class("CollectionsComparator", "(ZZ)V", (true, false)).await?.into(); + jvm.invoke_static::<_, ()>( + "java/util/Collections", + "sort", + "(Ljava/util/List;Ljava/util/Comparator;)V", + (list.clone(), reverse), + ) + .await?; + let reverse_sorted: ClassInstanceRef> = jvm.get_field(&list, "elements", "[Ljava/lang/Object;").await?; + let reverse_sorted = jvm.load_array::>(&reverse_sorted, 0, 4).await?; + let mut reverse_keys_and_ids = vec![]; + for value in reverse_sorted { + reverse_keys_and_ids.push(( + jvm.get_field::(&value, "key", "I").await?, + jvm.get_field::(&value, "id", "I").await?, + )); + } + assert_eq!(reverse_keys_and_ids, vec![(2, 0), (2, 2), (1, 1), (1, 3)]); + + let mut failing_elements: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + let failing_original: [ClassInstanceRef; 3] = [ + jvm.new_class("CollectionsSortValue", "(IIZ)V", (3, 10, false)).await?.into(), + jvm.new_class("CollectionsSortValue", "(IIZ)V", (2, 11, false)).await?.into(), + jvm.new_class("CollectionsSortValue", "(IIZ)V", (1, 12, false)).await?.into(), + ]; + jvm.store_array(&mut failing_elements, 0, failing_original.clone()).await?; + let failing_list: ClassInstanceRef = jvm + .new_class("CollectionsProbeList", "([Ljava/lang/Object;)V", (failing_elements,)) + .await? + .into(); + let comparator: ClassInstanceRef = jvm.new_class("CollectionsComparator", "(ZZ)V", (false, true)).await?.into(); + let result: Result<()> = jvm + .invoke_static( + "java/util/Collections", + "sort", + "(Ljava/util/List;Ljava/util/Comparator;)V", + (failing_list.clone(), comparator), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("failing comparator must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + let after: ClassInstanceRef> = jvm.get_field(&failing_list, "elements", "[Ljava/lang/Object;").await?; + let after = jvm.load_array::>(&after, 0, 3).await?; + assert_eq!( + after.iter().map(|element| element.identity()).collect::>(), + failing_original.iter().map(|element| element.identity()).collect::>() + ); + assert_eq!(jvm.get_field::(&failing_list, "listIteratorCalls", "I").await?, 0); + assert_eq!(jvm.get_field::(&failing_list, "setCalls", "I").await?, 0); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_03_binary_search_insertion_duplicates_and_comparator() -> Result<()> { + let jvm = collections_test_jvm().await?; + let list = integer_list(&jvm, &[1, 2, 2, 2, 4]).await?; + let duplicate: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (2,)).await?.into(); + let found: i32 = jvm + .invoke_static( + "java/util/Collections", + "binarySearch", + "(Ljava/util/List;Ljava/lang/Object;)I", + (list.clone(), duplicate), + ) + .await?; + assert!((1..=3).contains(&found)); + + for (key, expected) in [(0, -1), (3, -5), (5, -6)] { + let key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (key,)).await?.into(); + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/util/Collections", + "binarySearch", + "(Ljava/util/List;Ljava/lang/Object;)I", + (list.clone(), key), + ) + .await?, + expected + ); + } + + let reverse_list: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + for key in [3, 2, 1] { + let value: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (key, key, false)).await?.into(); + let _: bool = jvm.invoke_virtual(&reverse_list, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + let comparator: ClassInstanceRef = jvm.new_class("CollectionsComparator", "(ZZ)V", (true, false)).await?.into(); + let key: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (2, 9, false)).await?.into(); + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/util/Collections", + "binarySearch", + "(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I", + (reverse_list.clone(), key, comparator), + ) + .await?, + 1 + ); + let null_comparator: ClassInstanceRef = None.into(); + let natural_key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (4,)).await?.into(); + assert_eq!( + jvm.invoke_static::<_, i32>( + "java/util/Collections", + "binarySearch", + "(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I", + (list, natural_key, null_comparator), + ) + .await?, + 4 + ); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_04_reverse_fill_copy_use_list_iterator_and_validate_destination() -> Result<()> { + let jvm = collections_test_jvm().await?; + let mut elements: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 4).await?.into(); + let mut values: Vec> = vec![]; + for value in [1, 2, 3, 4] { + values.push(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()); + } + jvm.store_array(&mut elements, 0, values).await?; + let list: ClassInstanceRef = jvm.new_class("CollectionsProbeList", "([Ljava/lang/Object;)V", (elements,)).await?.into(); + + jvm.invoke_static::<_, ()>("java/util/Collections", "reverse", "(Ljava/util/List;)V", (list.clone(),)) + .await?; + assert_eq!(integer_values(&jvm, &list).await?, vec![4, 3, 2, 1]); + assert_eq!(jvm.get_field::(&list, "listIteratorCalls", "I").await?, 2); + assert_eq!(jvm.get_field::(&list, "setCalls", "I").await?, 4); + + let fill_value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?.into(); + jvm.invoke_static::<_, ()>( + "java/util/Collections", + "fill", + "(Ljava/util/List;Ljava/lang/Object;)V", + (list.clone(), fill_value), + ) + .await?; + assert_eq!(integer_values(&jvm, &list).await?, vec![7, 7, 7, 7]); + assert_eq!(jvm.get_field::(&list, "listIteratorCalls", "I").await?, 3); + + let source = integer_list(&jvm, &[8, 9]).await?; + jvm.invoke_static::<_, ()>( + "java/util/Collections", + "copy", + "(Ljava/util/List;Ljava/util/List;)V", + (list.clone(), source), + ) + .await?; + assert_eq!(integer_values(&jvm, &list).await?, vec![8, 9, 7, 7]); + assert_eq!(jvm.get_field::(&list, "listIteratorCalls", "I").await?, 4); + + let odd = integer_list(&jvm, &[1, 2, 3, 4, 5]).await?; + jvm.invoke_static::<_, ()>("java/util/Collections", "reverse", "(Ljava/util/List;)V", (odd.clone(),)) + .await?; + assert_eq!(integer_values(&jvm, &odd).await?, vec![5, 4, 3, 2, 1]); + + let short = integer_list(&jvm, &[10]).await?; + let source = integer_list(&jvm, &[1, 2]).await?; + let result: Result<()> = jvm + .invoke_static( + "java/util/Collections", + "copy", + "(Ljava/util/List;Ljava/util/List;)V", + (short.clone(), source), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("short destination must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IndexOutOfBoundsException")); + assert_eq!(integer_values(&jvm, &short).await?, vec![10]); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_05_shuffle_is_a_reproducible_permutation() -> Result<()> { + let jvm = test_jvm().await?; + let first = integer_list(&jvm, &[0, 1, 2, 3, 4, 5, 6, 7]).await?; + let second = integer_list(&jvm, &[0, 1, 2, 3, 4, 5, 6, 7]).await?; + let first_random: ClassInstanceRef = jvm.new_class("java/util/Random", "(J)V", (12345i64,)).await?.into(); + let second_random: ClassInstanceRef = jvm.new_class("java/util/Random", "(J)V", (12345i64,)).await?.into(); + + jvm.invoke_static::<_, ()>( + "java/util/Collections", + "shuffle", + "(Ljava/util/List;Ljava/util/Random;)V", + (first.clone(), first_random), + ) + .await?; + jvm.invoke_static::<_, ()>( + "java/util/Collections", + "shuffle", + "(Ljava/util/List;Ljava/util/Random;)V", + (second.clone(), second_random), + ) + .await?; + let first_values = integer_values(&jvm, &first).await?; + assert_eq!(first_values, integer_values(&jvm, &second).await?); + let mut permutation = first_values; + permutation.sort(); + assert_eq!(permutation, vec![0, 1, 2, 3, 4, 5, 6, 7]); + + let default_shuffle = integer_list(&jvm, &[1, 2, 3, 4]).await?; + jvm.invoke_static::<_, ()>("java/util/Collections", "shuffle", "(Ljava/util/List;)V", (default_shuffle.clone(),)) + .await?; + let mut default_values = integer_values(&jvm, &default_shuffle).await?; + default_values.sort(); + assert_eq!(default_values, vec![1, 2, 3, 4]); + + let null_random: ClassInstanceRef = None.into(); + for list in [integer_list(&jvm, &[]).await?, integer_list(&jvm, &[1]).await?] { + jvm.invoke_static::<_, ()>( + "java/util/Collections", + "shuffle", + "(Ljava/util/List;Ljava/util/Random;)V", + (list, null_random.clone()), + ) + .await?; + } + let list = integer_list(&jvm, &[1, 2]).await?; + let result: Result<()> = jvm + .invoke_static( + "java/util/Collections", + "shuffle", + "(Ljava/util/List;Ljava/util/Random;)V", + (list, null_random), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("shuffle must use a non-null random when a swap is required"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_05_invalid_random_indices_throw_java_exceptions_without_mutation() -> Result<()> { + let jvm = collections_test_jvm().await?; + for result in [-1, 3] { + let list = integer_list(&jvm, &[0, 1, 2]).await?; + let random: ClassInstanceRef = jvm.new_class("CollectionsInvalidRandom", "(I)V", (result,)).await?.into(); + let shuffle_result: Result<()> = jvm + .invoke_static( + "java/util/Collections", + "shuffle", + "(Ljava/util/List;Ljava/util/Random;)V", + (list.clone(), random), + ) + .await; + let Err(JavaError::JavaException(exception)) = shuffle_result else { + panic!("invalid Random result {result} must throw a Java exception"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayIndexOutOfBoundsException")); + assert_eq!(integer_values(&jvm, &list).await?, vec![0, 1, 2]); + } + + Ok(()) +} + +#[tokio::test] +async fn test_coll_06_min_max_natural_comparator_empty_and_null() -> Result<()> { + let jvm = collections_test_jvm().await?; + let values = integer_list(&jvm, &[3, -2, 7, 1]).await?; + let min: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "min", + "(Ljava/util/Collection;)Ljava/lang/Object;", + (values.clone(),), + ) + .await?; + let max: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "max", + "(Ljava/util/Collection;)Ljava/lang/Object;", + (values.clone(),), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&min, "intValue", "()I", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&max, "intValue", "()I", ()).await?, 7); + + let comparable_values: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + for key in [1, 3, 2] { + let value: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (key, key, false)).await?.into(); + let _: bool = jvm.invoke_virtual(&comparable_values, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + let reverse: ClassInstanceRef = jvm.new_class("CollectionsComparator", "(ZZ)V", (true, false)).await?.into(); + let comparator_min: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "min", + "(Ljava/util/Collection;Ljava/util/Comparator;)Ljava/lang/Object;", + (comparable_values.clone(), reverse.clone()), + ) + .await?; + let comparator_max: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "max", + "(Ljava/util/Collection;Ljava/util/Comparator;)Ljava/lang/Object;", + (comparable_values, reverse), + ) + .await?; + assert_eq!(jvm.get_field::(&comparator_min, "key", "I").await?, 3); + assert_eq!(jvm.get_field::(&comparator_max, "key", "I").await?, 1); + + let empty = integer_list(&jvm, &[]).await?; + for name in ["min", "max"] { + let result: Result> = jvm + .invoke_static( + "java/util/Collections", + name, + "(Ljava/util/Collection;)Ljava/lang/Object;", + (empty.clone(),), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} on an empty collection must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/util/NoSuchElementException")); + } + + let nulls: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + let null: ClassInstanceRef = None.into(); + let value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (1,)).await?.into(); + let _: bool = jvm.invoke_virtual(&nulls, "add", "(Ljava/lang/Object;)Z", (null,)).await?; + let _: bool = jvm.invoke_virtual(&nulls, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + let result: Result> = jvm + .invoke_static("java/util/Collections", "min", "(Ljava/util/Collection;)Ljava/lang/Object;", (nulls,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("natural min with null must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_07_ncopies_singleton_and_empty_are_immutable() -> Result<()> { + let jvm = test_jvm().await?; + let element: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (11,)).await?.into(); + let copies: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (3, element.clone()), + ) + .await?; + assert_eq!(integer_values(&jvm, &copies).await?, vec![11, 11, 11]); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&copies, "indexOf", "(Ljava/lang/Object;)I", (element.clone(),)) + .await?, + 0 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&copies, "lastIndexOf", "(Ljava/lang/Object;)I", (element.clone(),)) + .await?, + 2 + ); + + let negative: Result> = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (-1, element.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = negative else { + panic!("negative nCopies count must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + + let add_result: Result = jvm.invoke_virtual(&copies, "add", "(Ljava/lang/Object;)Z", (element.clone(),)).await; + let Err(JavaError::JavaException(exception)) = add_result else { + panic!("nCopies.add must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let clear_result: Result<()> = jvm.invoke_virtual(&copies, "clear", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = clear_result else { + panic!("non-empty nCopies.clear must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let zero_copies: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (0, element.clone()), + ) + .await?; + let _: () = jvm.invoke_virtual(&zero_copies, "clear", "()V", ()).await?; + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&copies, "iterator", "()Ljava/util/Iterator;", ()).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let remove_result: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove_result else { + panic!("nCopies iterator.remove must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let list_iterator: ClassInstanceRef = jvm.invoke_virtual(&copies, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&list_iterator, "next", "()Ljava/lang/Object;", ()).await?; + for (name, descriptor) in [("set", "(Ljava/lang/Object;)V"), ("add", "(Ljava/lang/Object;)V")] { + let result: Result<()> = jvm.invoke_virtual(&list_iterator, name, descriptor, (element.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("nCopies listIterator.{name} must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + + let singleton: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (element.clone(),), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&singleton, "size", "()I", ()).await?, 1); + assert!( + jvm.invoke_virtual::<_, bool>(&singleton, "contains", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await? + ); + let singleton_iterator: ClassInstanceRef = jvm.invoke_virtual(&singleton, "iterator", "()Ljava/util/Iterator;", ()).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&singleton_iterator, "next", "()Ljava/lang/Object;", ()).await?; + let remove_result: Result<()> = jvm.invoke_virtual(&singleton_iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove_result else { + panic!("singleton iterator.remove must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let singleton_clear: Result<()> = jvm.invoke_virtual(&singleton, "clear", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = singleton_clear else { + panic!("singleton.clear must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let absent: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (99,)).await?.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&singleton, "remove", "(Ljava/lang/Object;)Z", (absent,)) + .await? + ); + + let one_copy: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (1, element.clone()), + ) + .await?; + for target in [zero_copies.clone(), one_copy] { + jvm.invoke_static::<_, ()>("java/util/Collections", "reverse", "(Ljava/util/List;)V", (target,)) + .await?; + } + + let empty: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_LIST", "Ljava/util/List;").await?; + let _: () = jvm.invoke_virtual(&empty, "clear", "()V", ()).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&empty, "remove", "(Ljava/lang/Object;)Z", (element.clone(),)) + .await? + ); + let add_result: Result = jvm.invoke_virtual(&empty, "add", "(Ljava/lang/Object;)Z", (element.clone(),)).await; + let Err(JavaError::JavaException(exception)) = add_result else { + panic!("EMPTY_LIST.add must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let empty_set: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_SET", "Ljava/util/Set;").await?; + let _: () = jvm.invoke_virtual(&empty_set, "clear", "()V", ()).await?; + let add_result: Result = jvm.invoke_virtual(&empty_set, "add", "(Ljava/lang/Object;)Z", (element.clone(),)).await; + let Err(JavaError::JavaException(exception)) = add_result else { + panic!("EMPTY_SET.add must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let removal_source = integer_list(&jvm, &[11]).await?; + for (target, name, descriptor, argument) in [ + (copies.clone(), "addAll", "(Ljava/util/Collection;)Z", removal_source.clone()), + (copies.clone(), "removeAll", "(Ljava/util/Collection;)Z", removal_source.clone()), + (copies.clone(), "retainAll", "(Ljava/util/Collection;)Z", empty.clone()), + (singleton.clone(), "removeAll", "(Ljava/util/Collection;)Z", removal_source), + ] { + let result: Result = jvm.invoke_virtual(&target, name, descriptor, (argument,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} requiring mutation must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + + assert!( + !jvm.invoke_virtual::<_, bool>(&copies, "addAll", "(Ljava/util/Collection;)Z", (zero_copies.clone(),)) + .await? + ); + + let text = JavaLangString::from_rust_string(&jvm, "text").await?; + let text_copies: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (2, text.clone()), + ) + .await?; + let destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 3).await?.into(); + let typed: ClassInstanceRef> = jvm + .invoke_virtual(&text_copies, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination,)) + .await?; + let typed_values = jvm.load_array::>(&typed, 0, 3).await?; + assert_eq!(typed_values[0].identity(), text.identity()); + assert_eq!(typed_values[1].identity(), text.identity()); + assert!(typed_values[2].is_null()); + + let null: ClassInstanceRef = None.into(); + let null_copies: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (2, null.clone()), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&null_copies, "get", "(I)Ljava/lang/Object;", (1,)) + .await? + .is_null() + ); + let null_singleton: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (null.clone(),), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&null_singleton, "contains", "(Ljava/lang/Object;)Z", (null,)) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_07_immutable_equals_and_hash_code_match_standard_collections() -> Result<()> { + let jvm = test_jvm().await?; + let null: ClassInstanceRef = None.into(); + let empty_list: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_LIST", "Ljava/util/List;").await?; + let empty_set: ClassInstanceRef = jvm.get_static_field("java/util/Collections", "EMPTY_SET", "Ljava/util/Set;").await?; + let standard_empty_list = integer_list(&jvm, &[]).await?; + let standard_empty_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + + for collection in [empty_list.clone(), empty_set.clone()] { + assert!( + !jvm.invoke_virtual::<_, bool>(&collection, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let first_hash: i32 = jvm.invoke_virtual(&collection, "hashCode", "()I", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&collection, "hashCode", "()I", ()).await?, first_hash); + } + assert!( + jvm.invoke_virtual::<_, bool>(&empty_list, "equals", "(Ljava/lang/Object;)Z", (standard_empty_list.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&standard_empty_list, "equals", "(Ljava/lang/Object;)Z", (empty_list.clone(),),) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&empty_list, "hashCode", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&standard_empty_list, "hashCode", "()I", ()).await?, 1); + + assert!( + jvm.invoke_virtual::<_, bool>(&empty_set, "equals", "(Ljava/lang/Object;)Z", (standard_empty_set.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&standard_empty_set, "equals", "(Ljava/lang/Object;)Z", (empty_set.clone(),),) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&empty_set, "hashCode", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&standard_empty_set, "hashCode", "()I", ()).await?, 0); + + let element: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (11,)).await?.into(); + let element_hash: i32 = jvm.invoke_virtual(&element, "hashCode", "()I", ()).await?; + let copies: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (2, element.clone()), + ) + .await?; + let peer_list = integer_list(&jvm, &[11, 11]).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&copies, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&copies, "equals", "(Ljava/lang/Object;)Z", (peer_list.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&peer_list, "equals", "(Ljava/lang/Object;)Z", (copies.clone(),)) + .await? + ); + let copies_hash: i32 = jvm.invoke_virtual(&copies, "hashCode", "()I", ()).await?; + assert_eq!(copies_hash, 31i32.wrapping_mul(31 + element_hash).wrapping_add(element_hash)); + assert_eq!(jvm.invoke_virtual::<_, i32>(&copies, "hashCode", "()I", ()).await?, copies_hash); + let one_copy: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (1, element.clone()), + ) + .await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&copies, "equals", "(Ljava/lang/Object;)Z", (one_copy.clone(),)) + .await? + ); + assert_ne!(copies_hash, jvm.invoke_virtual::<_, i32>(&one_copy, "hashCode", "()I", ()).await?); + + let singleton: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (element.clone(),), + ) + .await?; + let peer_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + let peer_element: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (11,)).await?.into(); + let _: bool = jvm.invoke_virtual(&peer_set, "add", "(Ljava/lang/Object;)Z", (peer_element,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&singleton, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&singleton, "equals", "(Ljava/lang/Object;)Z", (peer_set.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&peer_set, "equals", "(Ljava/lang/Object;)Z", (singleton.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&singleton, "hashCode", "()I", ()).await?, element_hash); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&singleton, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&peer_set, "hashCode", "()I", ()).await? + ); + + let null_copies: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "nCopies", + "(ILjava/lang/Object;)Ljava/util/List;", + (2, null.clone()), + ) + .await?; + let null_peer_list: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + for _ in 0..2 { + let _: bool = jvm + .invoke_virtual(&null_peer_list, "add", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await?; + } + assert!( + jvm.invoke_virtual::<_, bool>(&null_copies, "equals", "(Ljava/lang/Object;)Z", (null_peer_list.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&null_peer_list, "equals", "(Ljava/lang/Object;)Z", (null_copies.clone(),),) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&null_copies, "hashCode", "()I", ()).await?, 961); + + let null_singleton: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (null.clone(),), + ) + .await?; + let null_peer_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + let _: bool = jvm + .invoke_virtual(&null_peer_set, "add", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&null_singleton, "equals", "(Ljava/lang/Object;)Z", (null_peer_set.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&null_peer_set, "equals", "(Ljava/lang/Object;)Z", (null_singleton.clone(),),) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&null_singleton, "hashCode", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&null_peer_set, "hashCode", "()I", ()).await?, 0); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_07_abstract_set_equals_catches_only_jdk_compatibility_exceptions() -> Result<()> { + let jvm = collections_test_jvm().await?; + let value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?.into(); + let peer: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (value.clone(),), + ) + .await?; + + let incompatible: ClassInstanceRef = jvm + .new_class("CollectionsExceptionalSet", "(Ljava/lang/Object;I)V", (value.clone(), 0)) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&incompatible, "equals", "(Ljava/lang/Object;)Z", (peer.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&peer, "equals", "(Ljava/lang/Object;)Z", (incompatible,)) + .await? + ); + + let null: ClassInstanceRef = None.into(); + let null_peer: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (null.clone(),), + ) + .await?; + let null_rejecting: ClassInstanceRef = jvm + .new_class("CollectionsExceptionalSet", "(Ljava/lang/Object;I)V", (null.clone(), 1)) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&null_rejecting, "equals", "(Ljava/lang/Object;)Z", (null_peer.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&null_peer, "equals", "(Ljava/lang/Object;)Z", (null_rejecting,)) + .await? + ); + + let unexpected: ClassInstanceRef = jvm + .new_class("CollectionsExceptionalSet", "(Ljava/lang/Object;I)V", (value, 2)) + .await? + .into(); + let result: Result = jvm.invoke_virtual(&unexpected, "equals", "(Ljava/lang/Object;)Z", (peer,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("AbstractSet.equals must propagate non-compatibility exceptions"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_07_singleton_set_equals_uses_other_element_direction() -> Result<()> { + let jvm = collections_test_jvm().await?; + let accepts: ClassInstanceRef = jvm.new_class("CollectionsAsymmetricEquals", "(Z)V", (true,)).await?.into(); + let rejects: ClassInstanceRef = jvm.new_class("CollectionsAsymmetricEquals", "(Z)V", (false,)).await?.into(); + + let accepting_singleton: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "singleton", + "(Ljava/lang/Object;)Ljava/util/Set;", + (accepts.clone(),), + ) + .await?; + let rejecting_peer: ClassInstanceRef = jvm + .new_class("CollectionsExceptionalSet", "(Ljava/lang/Object;I)V", (rejects.clone(), 3)) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&accepting_singleton, "equals", "(Ljava/lang/Object;)Z", (rejecting_peer,),) + .await? + ); + + let rejecting_singleton: ClassInstanceRef = jvm + .invoke_static("java/util/Collections", "singleton", "(Ljava/lang/Object;)Ljava/util/Set;", (rejects,)) + .await?; + let accepting_peer: ClassInstanceRef = jvm + .new_class("CollectionsExceptionalSet", "(Ljava/lang/Object;I)V", (accepts, 3)) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&rejecting_singleton, "equals", "(Ljava/lang/Object;)Z", (accepting_peer,),) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_exact_descriptors_access_and_null_factories() -> Result<()> { + let jvm = test_jvm().await?; + let collections = jvm.resolve_class("java/util/Collections").await?; + let factories = [ + ("unmodifiableCollection", "(Ljava/util/Collection;)Ljava/util/Collection;"), + ("unmodifiableList", "(Ljava/util/List;)Ljava/util/List;"), + ("unmodifiableSet", "(Ljava/util/Set;)Ljava/util/Set;"), + ("unmodifiableMap", "(Ljava/util/Map;)Ljava/util/Map;"), + ("unmodifiableSortedSet", "(Ljava/util/SortedSet;)Ljava/util/SortedSet;"), + ("unmodifiableSortedMap", "(Ljava/util/SortedMap;)Ljava/util/SortedMap;"), + ]; + for (name, descriptor) in factories { + let method = collections + .definition + .method(name, descriptor, true) + .unwrap_or_else(|| panic!("missing Collections.{name}{descriptor}")); + assert_eq!(method.access_flags(), MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC); + + let null: ClassInstanceRef = None.into(); + let result: Result> = jvm.invoke_static("java/util/Collections", name, descriptor, (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Collections.{name}(null) must fail"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + + type WrapperShape<'a> = ( + &'a str, + &'a str, + &'a [&'a str], + &'a str, + &'a [(&'a str, &'a str, FieldAccessFlags)], + &'a [(&'a str, &'a str)], + bool, + ); + let wrapper_shapes: &[WrapperShape<'_>] = &[ + ( + "java/util/Collections$UnmodifiableCollection", + "java/lang/Object", + &["java/util/Collection", "java/io/Serializable"], + "(Ljava/util/Collection;)V", + &[("c", "Ljava/util/Collection;", FieldAccessFlags::FINAL)], + &[ + ("size", "()I"), + ("isEmpty", "()Z"), + ("contains", "(Ljava/lang/Object;)Z"), + ("iterator", "()Ljava/util/Iterator;"), + ("toArray", "()[Ljava/lang/Object;"), + ("toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;"), + ("containsAll", "(Ljava/util/Collection;)Z"), + ("toString", "()Ljava/lang/String;"), + ("add", "(Ljava/lang/Object;)Z"), + ("remove", "(Ljava/lang/Object;)Z"), + ("addAll", "(Ljava/util/Collection;)Z"), + ("removeAll", "(Ljava/util/Collection;)Z"), + ("retainAll", "(Ljava/util/Collection;)Z"), + ("clear", "()V"), + ], + true, + ), + ( + "java/util/Collections$UnmodifiableCollection$1", + "java/lang/Object", + &["java/util/Iterator"], + "(Ljava/util/Iterator;)V", + &[("i", "Ljava/util/Iterator;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + &[("hasNext", "()Z"), ("next", "()Ljava/lang/Object;"), ("remove", "()V")], + false, + ), + ( + "java/util/Collections$UnmodifiableList", + "java/util/Collections$UnmodifiableCollection", + &["java/util/List"], + "(Ljava/util/List;)V", + &[("list", "Ljava/util/List;", FieldAccessFlags::FINAL)], + &[ + ("equals", "(Ljava/lang/Object;)Z"), + ("hashCode", "()I"), + ("get", "(I)Ljava/lang/Object;"), + ("indexOf", "(Ljava/lang/Object;)I"), + ("lastIndexOf", "(Ljava/lang/Object;)I"), + ("listIterator", "()Ljava/util/ListIterator;"), + ("listIterator", "(I)Ljava/util/ListIterator;"), + ("subList", "(II)Ljava/util/List;"), + ("set", "(ILjava/lang/Object;)Ljava/lang/Object;"), + ("add", "(ILjava/lang/Object;)V"), + ("addAll", "(ILjava/util/Collection;)Z"), + ("remove", "(I)Ljava/lang/Object;"), + ], + true, + ), + ( + "java/util/Collections$UnmodifiableList$1", + "java/lang/Object", + &["java/util/ListIterator"], + "(Ljava/util/ListIterator;)V", + &[("i", "Ljava/util/ListIterator;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + &[ + ("hasNext", "()Z"), + ("next", "()Ljava/lang/Object;"), + ("hasPrevious", "()Z"), + ("previous", "()Ljava/lang/Object;"), + ("nextIndex", "()I"), + ("previousIndex", "()I"), + ("remove", "()V"), + ("set", "(Ljava/lang/Object;)V"), + ("add", "(Ljava/lang/Object;)V"), + ], + false, + ), + ( + "java/util/Collections$UnmodifiableSet", + "java/util/Collections$UnmodifiableCollection", + &["java/util/Set", "java/io/Serializable"], + "(Ljava/util/Set;)V", + &[], + &[("equals", "(Ljava/lang/Object;)Z"), ("hashCode", "()I")], + true, + ), + ( + "java/util/Collections$UnmodifiableSortedSet", + "java/util/Collections$UnmodifiableSet", + &["java/util/SortedSet", "java/io/Serializable"], + "(Ljava/util/SortedSet;)V", + &[("ss", "Ljava/util/SortedSet;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + &[ + ("comparator", "()Ljava/util/Comparator;"), + ("first", "()Ljava/lang/Object;"), + ("last", "()Ljava/lang/Object;"), + ("subSet", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;"), + ], + true, + ), + ( + "java/util/Collections$UnmodifiableMap", + "java/lang/Object", + &["java/util/Map", "java/io/Serializable"], + "(Ljava/util/Map;)V", + &[ + ("m", "Ljava/util/Map;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ("keySet", "Ljava/util/Set;", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + ("entrySet", "Ljava/util/Set;", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + ( + "values", + "Ljava/util/Collection;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT, + ), + ], + &[ + ("size", "()I"), + ("isEmpty", "()Z"), + ("containsKey", "(Ljava/lang/Object;)Z"), + ("containsValue", "(Ljava/lang/Object;)Z"), + ("get", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("keySet", "()Ljava/util/Set;"), + ("values", "()Ljava/util/Collection;"), + ("entrySet", "()Ljava/util/Set;"), + ("equals", "(Ljava/lang/Object;)Z"), + ("hashCode", "()I"), + ("toString", "()Ljava/lang/String;"), + ("put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + ("remove", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("putAll", "(Ljava/util/Map;)V"), + ("clear", "()V"), + ], + true, + ), + ( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet", + "java/util/Collections$UnmodifiableSet", + &[], + "(Ljava/util/Set;)V", + &[], + &[ + ("iterator", "()Ljava/util/Iterator;"), + ("toArray", "()[Ljava/lang/Object;"), + ("toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;"), + ("contains", "(Ljava/lang/Object;)Z"), + ("containsAll", "(Ljava/util/Collection;)Z"), + ("equals", "(Ljava/lang/Object;)Z"), + ], + true, + ), + ( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1", + "java/lang/Object", + &["java/util/Iterator"], + "(Ljava/util/Iterator;)V", + &[("i", "Ljava/util/Iterator;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + &[("hasNext", "()Z"), ("next", "()Ljava/lang/Object;"), ("remove", "()V")], + false, + ), + ( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "java/lang/Object", + &["java/util/Map$Entry"], + "(Ljava/util/Map$Entry;)V", + &[("e", "Ljava/util/Map$Entry;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + &[ + ("getKey", "()Ljava/lang/Object;"), + ("getValue", "()Ljava/lang/Object;"), + ("setValue", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("equals", "(Ljava/lang/Object;)Z"), + ("hashCode", "()I"), + ("toString", "()Ljava/lang/String;"), + ], + false, + ), + ( + "java/util/Collections$UnmodifiableSortedMap", + "java/util/Collections$UnmodifiableMap", + &["java/util/SortedMap", "java/io/Serializable"], + "(Ljava/util/SortedMap;)V", + &[("sm", "Ljava/util/SortedMap;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL)], + &[ + ("comparator", "()Ljava/util/Comparator;"), + ("firstKey", "()Ljava/lang/Object;"), + ("lastKey", "()Ljava/lang/Object;"), + ("subMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ], + true, + ), + ]; + let serializable = jvm.resolve_class("java/io/Serializable").await?.java_class(); + for (name, parent, interfaces, constructor, fields, methods, is_serializable) in wrapper_shapes { + let proto = get_runtime_class_proto(name).unwrap_or_else(|| panic!("missing {name}")); + assert_eq!(proto.parent_class, Some(*parent), "{name} superclass"); + assert_eq!(proto.interfaces.as_slice(), *interfaces, "{name} direct interfaces"); + assert_eq!(proto.access_flags, ClassAccessFlags::default(), "{name} access"); + assert_eq!(proto.fields.len(), fields.len(), "{name} field count"); + for (field_name, descriptor, access_flags) in *fields { + let field = proto + .fields + .iter() + .find(|field| field.name == *field_name && field.descriptor == *descriptor) + .unwrap_or_else(|| panic!("missing {name}.{field_name}:{descriptor}")); + assert_eq!(field.access_flags, *access_flags, "{name}.{field_name}:{descriptor}"); + } + assert_eq!(proto.methods.len(), methods.len() + 1, "{name} method count"); + let constructor_method = proto + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == *constructor) + .unwrap_or_else(|| panic!("missing {name}.{constructor}")); + assert_eq!( + constructor_method.access_flags, + MethodAccessFlags::default(), + "{name}.{constructor}" + ); + for (method_name, descriptor) in *methods { + let method = proto + .methods + .iter() + .find(|method| method.name == *method_name && method.descriptor == *descriptor) + .unwrap_or_else(|| panic!("missing {name}.{method_name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC, "{name}.{method_name}{descriptor}"); + } + + let wrapper_class = jvm.resolve_class(name).await?.java_class(); + assert_eq!( + jvm.invoke_virtual::<_, bool>(&serializable, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class,),) + .await?, + *is_serializable, + "{name} Serializable" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_collection_is_live_identity_based_and_always_throws() -> Result<()> { + let jvm = test_jvm().await?; + let backing = integer_list(&jvm, &[1]).await?; + let wrapper: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableCollection", + "(Ljava/util/Collection;)Ljava/util/Collection;", + (backing.clone(),), + ) + .await?; + let nested: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableCollection", + "(Ljava/util/Collection;)Ljava/util/Collection;", + (wrapper.clone(),), + ) + .await?; + assert_ne!(wrapper.identity(), nested.identity()); + assert!( + jvm.invoke_virtual::<_, bool>(&wrapper, "equals", "(Ljava/lang/Object;)Z", (wrapper.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapper, "equals", "(Ljava/lang/Object;)Z", (backing.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapper, "equals", "(Ljava/lang/Object;)Z", (nested.clone(),)) + .await? + ); + let null: ClassInstanceRef = None.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapper, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + + let value: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (2,)).await?.into(); + let _: bool = jvm.invoke_virtual(&backing, "add", "(Ljava/lang/Object;)Z", (value.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&wrapper, "size", "()I", ()).await?, 2); + assert!( + jvm.invoke_virtual::<_, bool>(&wrapper, "contains", "(Ljava/lang/Object;)Z", (value.clone(),)) + .await? + ); + let backing_text: ClassInstanceRef = jvm.invoke_virtual(&backing, "toString", "()Ljava/lang/String;", ()).await?; + let wrapper_text: ClassInstanceRef = jvm.invoke_virtual(&wrapper, "toString", "()Ljava/lang/String;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&wrapper_text, "equals", "(Ljava/lang/Object;)Z", (backing_text,)) + .await? + ); + + let mut destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + let sentinel: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (99,)).await?.into(); + jvm.store_array(&mut destination, 2, core::iter::once(sentinel)).await?; + let destination_identity = destination.identity(); + let result: ClassInstanceRef> = jvm + .invoke_virtual(&wrapper, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination,)) + .await?; + assert_eq!(result.identity(), destination_identity); + assert!(jvm.load_array::>(&result, 2, 1).await?[0].is_null()); + + let empty = integer_list(&jvm, &[]).await?; + for (name, argument) in [ + ("add", null.clone()), + ("remove", jvm.new_class("java/lang/Integer", "(I)V", (404,)).await?.into()), + ] { + let result: Result = jvm.invoke_virtual(&wrapper, name, "(Ljava/lang/Object;)Z", (argument,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} must throw even when no change is possible"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + for (name, argument) in [ + ("addAll", empty.clone()), + ("addAll", null.clone()), + ("removeAll", empty.clone()), + ("removeAll", null.clone()), + ("retainAll", wrapper.clone()), + ("retainAll", null.clone()), + ] { + let result: Result = jvm.invoke_virtual(&wrapper, name, "(Ljava/util/Collection;)Z", (argument,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name} must throw before no-op or null handling"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + + let empty_wrapper: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableCollection", + "(Ljava/util/Collection;)Ljava/util/Collection;", + (empty,), + ) + .await?; + let clear: Result<()> = jvm.invoke_virtual(&empty_wrapper, "clear", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = clear else { + panic!("clear on an empty wrapper must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&wrapper, "iterator", "()Ljava/util/Iterator;", ()).await?; + let remove: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("iterator.remove before next must throw UOE"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let remove: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("iterator.remove after next must throw UOE"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_list_delegates_value_contract_and_wraps_sub_lists() -> Result<()> { + let jvm = test_jvm().await?; + let backing = integer_list(&jvm, &[1, 2, 3]).await?; + let list: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableList", + "(Ljava/util/List;)Ljava/util/List;", + (backing.clone(),), + ) + .await?; + let nested: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableList", + "(Ljava/util/List;)Ljava/util/List;", + (list.clone(),), + ) + .await?; + assert_ne!(list.identity(), nested.identity()); + assert!( + jvm.invoke_virtual::<_, bool>(&list, "equals", "(Ljava/lang/Object;)Z", (backing.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&nested, "equals", "(Ljava/lang/Object;)Z", (list.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&list, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&backing, "hashCode", "()I", ()).await? + ); + let null: ClassInstanceRef = None.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&list, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let backing_text: ClassInstanceRef = jvm.invoke_virtual(&backing, "toString", "()Ljava/lang/String;", ()).await?; + let list_text: ClassInstanceRef = jvm.invoke_virtual(&list, "toString", "()Ljava/lang/String;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&list_text, "equals", "(Ljava/lang/Object;)Z", (backing_text,)) + .await? + ); + + let replacement: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (20,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&backing, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (1, replacement.clone())) + .await?; + assert_eq!(integer_values(&jvm, &list).await?, vec![1, 20, 3]); + + let sub_list: ClassInstanceRef = jvm.invoke_virtual(&list, "subList", "(II)Ljava/util/List;", (0, 2)).await?; + let nested_sub_list: ClassInstanceRef = jvm.invoke_virtual(&sub_list, "subList", "(II)Ljava/util/List;", (1, 2)).await?; + assert_eq!(integer_values(&jvm, &sub_list).await?, vec![1, 20]); + assert_eq!(integer_values(&jvm, &nested_sub_list).await?, vec![20]); + let changed: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (21,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&backing, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (1, changed.clone())) + .await?; + assert_eq!(integer_values(&jvm, &nested_sub_list).await?, vec![21]); + + let set: Result> = jvm + .invoke_virtual(&list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (-1, changed.clone())) + .await; + let Err(JavaError::JavaException(exception)) = set else { + panic!("set with invalid index must throw UOE first"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let add: Result<()> = jvm.invoke_virtual(&list, "add", "(ILjava/lang/Object;)V", (-1, changed.clone())).await; + let Err(JavaError::JavaException(exception)) = add else { + panic!("add with invalid index must throw UOE first"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let remove: Result> = jvm.invoke_virtual(&list, "remove", "(I)Ljava/lang/Object;", (-1,)).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("remove with invalid index must throw UOE first"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let add_all: Result = jvm + .invoke_virtual(&list, "addAll", "(ILjava/util/Collection;)Z", (-1, null.clone())) + .await; + let Err(JavaError::JavaException(exception)) = add_all else { + panic!("indexed addAll must throw UOE before index and null validation"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let empty = integer_list(&jvm, &[]).await?; + let add_all: Result = jvm.invoke_virtual(&list, "addAll", "(ILjava/util/Collection;)Z", (0, empty)).await; + let Err(JavaError::JavaException(exception)) = add_all else { + panic!("indexed addAll of an empty collection must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let sub_set: Result> = jvm + .invoke_virtual(&sub_list, "set", "(ILjava/lang/Object;)Ljava/lang/Object;", (0, changed.clone())) + .await; + let Err(JavaError::JavaException(exception)) = sub_set else { + panic!("subList must remain unmodifiable"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 0); + for (name, descriptor, argument) in [ + ("set", "(Ljava/lang/Object;)V", changed.clone()), + ("add", "(Ljava/lang/Object;)V", changed.clone()), + ] { + let result: Result<()> = jvm.invoke_virtual(&iterator, name, descriptor, (argument,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("listIterator.{name} before next must throw UOE"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + let remove: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("listIterator.remove before next must throw UOE"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "intValue", "()I", ()).await?, 1); + assert!(jvm.invoke_virtual::<_, bool>(&iterator, "hasPrevious", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "previousIndex", "()I", ()).await?, 0); + for (name, descriptor, argument) in [ + ("set", "(Ljava/lang/Object;)V", changed.clone()), + ("add", "(Ljava/lang/Object;)V", changed), + ] { + let result: Result<()> = jvm.invoke_virtual(&iterator, name, descriptor, (argument,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("listIterator.{name} after next must throw UOE"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + let remove: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("listIterator.remove after next must throw UOE"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_set_and_map_views_are_live_and_always_unmodifiable() -> Result<()> { + let jvm = test_jvm().await?; + let set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + let first: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (1,)).await?.into(); + let second: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (2,)).await?.into(); + let _: bool = jvm.invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (first.clone(),)).await?; + let wrapped_set: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSet", + "(Ljava/util/Set;)Ljava/util/Set;", + (set.clone(),), + ) + .await?; + let nested_set: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSet", + "(Ljava/util/Set;)Ljava/util/Set;", + (wrapped_set.clone(),), + ) + .await?; + assert_ne!(wrapped_set.identity(), nested_set.identity()); + assert!( + jvm.invoke_virtual::<_, bool>(&wrapped_set, "equals", "(Ljava/lang/Object;)Z", (set.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&wrapped_set, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&set, "hashCode", "()I", ()).await? + ); + let null: ClassInstanceRef = None.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapped_set, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let _: bool = jvm.invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (second.clone(),)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&wrapped_set, "size", "()I", ()).await?, 2); + let absent: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (404,)).await?.into(); + let remove: Result = jvm + .invoke_virtual(&wrapped_set, "remove", "(Ljava/lang/Object;)Z", (absent.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("set removal of an absent element must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let value = JavaLangString::from_rust_string(&jvm, "one").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (first.clone(), value.clone()), + ) + .await?; + let wrapped_map: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableMap", + "(Ljava/util/Map;)Ljava/util/Map;", + (map.clone(),), + ) + .await?; + let nested_map: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableMap", + "(Ljava/util/Map;)Ljava/util/Map;", + (wrapped_map.clone(),), + ) + .await?; + assert_ne!(wrapped_map.identity(), nested_map.identity()); + assert!( + jvm.invoke_virtual::<_, bool>(&wrapped_map, "equals", "(Ljava/lang/Object;)Z", (map.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&nested_map, "equals", "(Ljava/lang/Object;)Z", (wrapped_map.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&wrapped_map, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&map, "hashCode", "()I", ()).await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapped_map, "equals", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let map_text: ClassInstanceRef = jvm.invoke_virtual(&map, "toString", "()Ljava/lang/String;", ()).await?; + let wrapped_text: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "toString", "()Ljava/lang/String;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&wrapped_text, "equals", "(Ljava/lang/Object;)Z", (map_text,)) + .await? + ); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "keySet", "()Ljava/util/Set;", ()).await?; + let same_keys: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "keySet", "()Ljava/util/Set;", ()).await?; + let values: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "values", "()Ljava/util/Collection;", ()).await?; + let same_values: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "values", "()Ljava/util/Collection;", ()).await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let same_entries: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "entrySet", "()Ljava/util/Set;", ()).await?; + assert_eq!(keys.identity(), same_keys.identity()); + assert_eq!(values.identity(), same_values.identity()); + assert_eq!(entries.identity(), same_entries.identity()); + let second_value = JavaLangString::from_rust_string(&jvm, "two").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (second.clone(), second_value.clone()), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&keys, "size", "()I", ()).await?, 2); + assert!( + jvm.invoke_virtual::<_, bool>(&values, "contains", "(Ljava/lang/Object;)Z", (second_value,)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&entries, "size", "()I", ()).await?, 2); + + let put: Result> = jvm + .invoke_virtual( + &wrapped_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (null.clone(), null.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = put else { + panic!("map.put must throw UOE before key/value validation"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let remove: Result> = jvm + .invoke_virtual(&wrapped_map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (absent.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("map.remove of absent key must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + for source in [ + jvm.new_class("java/util/HashMap", "()V", ()).await?.into(), + wrapped_map.clone(), + null.clone(), + ] { + let put_all: Result<()> = jvm.invoke_virtual(&wrapped_map, "putAll", "(Ljava/util/Map;)V", (source,)).await; + let Err(JavaError::JavaException(exception)) = put_all else { + panic!("map.putAll must throw for empty, self, and null inputs"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + let empty_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let empty_wrapper: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableMap", + "(Ljava/util/Map;)Ljava/util/Map;", + (empty_map,), + ) + .await?; + let clear: Result<()> = jvm.invoke_virtual(&empty_wrapper, "clear", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = clear else { + panic!("map.clear on empty map must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let empty_collection = integer_list(&jvm, &[]).await?; + for view in [keys, values, entries] { + let add: Result = jvm.invoke_virtual(&view, "add", "(Ljava/lang/Object;)Z", (null.clone(),)).await; + let Err(JavaError::JavaException(exception)) = add else { + panic!("map view add must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let remove: Result = jvm.invoke_virtual(&view, "remove", "(Ljava/lang/Object;)Z", (absent.clone(),)).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("map view remove of absent value must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + for name in ["addAll", "removeAll", "retainAll"] { + let bulk: Result = jvm + .invoke_virtual(&view, name, "(Ljava/util/Collection;)Z", (empty_collection.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = bulk else { + panic!("map view {name} must throw for an empty input"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + let clear: Result<()> = jvm.invoke_virtual(&view, "clear", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = clear else { + panic!("map view clear must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + let iterator: ClassInstanceRef = jvm.invoke_virtual(&view, "iterator", "()Ljava/util/Iterator;", ()).await?; + let remove: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = remove else { + panic!("map view iterator.remove must throw before next"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_entry_set_wraps_iterator_and_all_array_results() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?.into(); + let original = JavaLangString::from_rust_string(&jvm, "before").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(), original), + ) + .await?; + let raw_entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let raw_iterator: ClassInstanceRef = jvm.invoke_virtual(&raw_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let raw_entry: ClassInstanceRef = jvm.invoke_virtual(&raw_iterator, "next", "()Ljava/lang/Object;", ()).await?; + + let wrapped_map: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableMap", + "(Ljava/util/Map;)Ljava/util/Map;", + (map.clone(),), + ) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "entrySet", "()Ljava/util/Set;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (raw_entry.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "containsAll", "(Ljava/util/Collection;)Z", (raw_entries.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "equals", "(Ljava/lang/Object;)Z", (raw_entries.clone(),)) + .await? + ); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let wrapped_entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!( + wrapped_entry.class_definition().name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + assert!( + jvm.invoke_virtual::<_, bool>(&wrapped_entry, "equals", "(Ljava/lang/Object;)Z", (raw_entry.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&wrapped_entry, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&raw_entry, "hashCode", "()I", ()).await? + ); + let null_entry: ClassInstanceRef = None.into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapped_entry, "equals", "(Ljava/lang/Object;)Z", (null_entry,),) + .await? + ); + let raw_text: ClassInstanceRef = jvm.invoke_virtual(&raw_entry, "toString", "()Ljava/lang/String;", ()).await?; + let wrapped_text: ClassInstanceRef = jvm.invoke_virtual(&wrapped_entry, "toString", "()Ljava/lang/String;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&wrapped_text, "equals", "(Ljava/lang/Object;)Z", (raw_text,)) + .await? + ); + let replacement = JavaLangString::from_rust_string(&jvm, "after").await?; + let set_value: Result> = jvm + .invoke_virtual( + &wrapped_entry, + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (replacement.clone(),), + ) + .await; + let Err(JavaError::JavaException(exception)) = set_value else { + panic!("wrapped entry.setValue must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, replacement.clone()), + ) + .await?; + let live_value: ClassInstanceRef = jvm.invoke_virtual(&wrapped_entry, "getValue", "()Ljava/lang/Object;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&live_value, "equals", "(Ljava/lang/Object;)Z", (replacement,)) + .await? + ); + + let array: ClassInstanceRef> = jvm.invoke_virtual(&entries, "toArray", "()[Ljava/lang/Object;", ()).await?; + let array_entry = jvm.load_array::>(&array, 0, 1).await?[0].clone(); + assert_eq!( + array_entry.class_definition().name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + let null_value: ClassInstanceRef = None.into(); + let set_value: Result> = jvm + .invoke_virtual(&array_entry, "setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", (null_value,)) + .await; + let Err(JavaError::JavaException(exception)) = set_value else { + panic!("entry returned by toArray must be unmodifiable"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let mut typed_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Map$Entry;", 2).await?.into(); + jvm.store_array(&mut typed_destination, 1, core::iter::once(raw_entry.clone())).await?; + let destination_identity = typed_destination.identity(); + let typed: ClassInstanceRef> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (typed_destination,)) + .await?; + assert_eq!(typed.identity(), destination_identity); + let typed_values = jvm.load_array::>(&typed, 0, 2).await?; + assert_eq!( + typed_values[0].class_definition().name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + assert!(typed_values[1].is_null()); + + let small: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Map$Entry;", 0).await?.into(); + let grown: ClassInstanceRef> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (small,)) + .await?; + assert_eq!(jvm.array_length(&grown).await?, 1); + assert_eq!(grown.class_definition().name(), "[Ljava/util/Map$Entry;"); + assert_eq!( + jvm.load_array::>(&grown, 0, 1).await?[0] + .class_definition() + .name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + + let null_array: ClassInstanceRef> = None.into(); + let result: Result>> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (null_array,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed entry toArray(null) must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + let incompatible: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", 2).await?.into(); + let result: Result>> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (incompatible.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("concrete mutable-entry array must reject wrapped entries"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + for element in jvm.load_array::>(&incompatible, 0, 2).await? { + assert!(element.is_null(), "typed array failure must not leak raw entries"); + } + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_entry_equals_and_contains_preserve_jdk_ordering() -> Result<()> { + let jvm = collections_test_jvm().await?; + + let rejecting_key: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (false, false)).await?.into(); + let unused_value: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (true, false)).await?.into(); + let backing_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (rejecting_key.clone(), unused_value, false, true), + ) + .await? + .into(); + let candidate_key: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (true, true)).await?.into(); + let candidate_value: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (true, true)).await?.into(); + let candidate_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (candidate_key.clone(), candidate_value, false, true), + ) + .await? + .into(); + let wrapped_entry: ClassInstanceRef = jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (backing_entry.clone(),), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&wrapped_entry, "equals", "(Ljava/lang/Object;)Z", (candidate_entry.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&backing_entry, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_entry, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing_entry, "valueCalls", "I").await?, 0); + assert_eq!(jvm.get_field::(&candidate_entry, "valueCalls", "I").await?, 0); + assert_eq!(jvm.get_field::(&rejecting_key, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_key, "equalsCalls", "I").await?, 0); + + let accepting_key: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (true, false)).await?.into(); + let accepting_value: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (true, false)).await?.into(); + let backing_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (accepting_key.clone(), accepting_value.clone(), false, false), + ) + .await? + .into(); + let reverse_direction_key: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (false, true)).await?.into(); + let reverse_direction_value: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (false, true)).await?.into(); + let candidate_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (reverse_direction_key.clone(), reverse_direction_value.clone(), false, false), + ) + .await? + .into(); + let wrapped_entry: ClassInstanceRef = jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (backing_entry.clone(),), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&wrapped_entry, "equals", "(Ljava/lang/Object;)Z", (candidate_entry.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&backing_entry, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&backing_entry, "valueCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_entry, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&candidate_entry, "valueCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&accepting_key, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&accepting_value, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&reverse_direction_key, "equalsCalls", "I").await?, 0); + assert_eq!(jvm.get_field::(&reverse_direction_value, "equalsCalls", "I").await?, 0); + + let replacement: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let result: Result> = jvm + .invoke_virtual(&wrapped_entry, "setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", (replacement,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("wrapped custom entry.setValue must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + assert_eq!(jvm.get_field::(&backing_entry, "setValueCalls", "I").await?, 0); + + let throwing_key_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (accepting_key.clone(), accepting_value.clone(), true, false), + ) + .await? + .into(); + let throwing_key_wrapper: ClassInstanceRef = jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (throwing_key_entry.clone(),), + ) + .await? + .into(); + let untouched_candidate: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (accepting_key.clone(), accepting_value.clone(), false, false), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&throwing_key_wrapper, "equals", "(Ljava/lang/Object;)Z", (untouched_candidate.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("backing getKey exception must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_key_entry, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&untouched_candidate, "keyCalls", "I").await?, 0); + + let throwing_equals_key: ClassInstanceRef = jvm.new_class("CollectionsEqualsProbe", "(ZZ)V", (true, true)).await?.into(); + let throwing_equals_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (throwing_equals_key.clone(), accepting_value.clone(), false, false), + ) + .await? + .into(); + let throwing_equals_wrapper: ClassInstanceRef = jvm + .new_class( + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry", + "(Ljava/util/Map$Entry;)V", + (throwing_equals_entry.clone(),), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&throwing_equals_wrapper, "equals", "(Ljava/lang/Object;)Z", (candidate_entry.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("backing key.equals exception must propagate"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_equals_key, "equalsCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_equals_entry, "valueCalls", "I").await?, 0); + + let required_throwing_value_entry: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (reverse_direction_key, reverse_direction_value, false, true), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual( + &wrapped_entry, + "equals", + "(Ljava/lang/Object;)Z", + (required_throwing_value_entry.clone(),), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("candidate getValue exception must propagate after equal keys"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&required_throwing_value_entry, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&required_throwing_value_entry, "valueCalls", "I").await?, 1); + + let map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let map_key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?.into(); + let map_value: ClassInstanceRef = JavaLangString::from_rust_string(&jvm, "seven").await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (map_key.clone(), map_value.clone()), + ) + .await?; + let wrapped_map: ClassInstanceRef = jvm + .invoke_static("java/util/Collections", "unmodifiableMap", "(Ljava/util/Map;)Ljava/util/Map;", (map,)) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let matching: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (map_key, map_value, false, false), + ) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (matching.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&matching, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&matching, "valueCalls", "I").await?, 1); + + let missing_key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (8,)).await?.into(); + let null: ClassInstanceRef = None.into(); + let missing: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (missing_key, null.clone(), false, true), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (missing.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&missing, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&missing, "valueCalls", "I").await?, 0); + + let existing_key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?.into(); + let required_throwing_value: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (existing_key, null.clone(), false, true), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&entries, "contains", "(Ljava/lang/Object;)Z", (required_throwing_value.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("contains must propagate getValue failure after finding the key"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&required_throwing_value, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&required_throwing_value, "valueCalls", "I").await?, 1); + + let throwing_key: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (null.clone(), null.clone(), true, true), + ) + .await? + .into(); + let result: Result = jvm + .invoke_virtual(&entries, "contains", "(Ljava/lang/Object;)Z", (throwing_key.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("contains must propagate getKey failure"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&throwing_key, "keyCalls", "I").await?, 1); + assert_eq!(jvm.get_field::(&throwing_key, "valueCalls", "I").await?, 0); + + let never_reached: ClassInstanceRef = jvm + .new_class( + "CollectionsEntryProbe", + "(Ljava/lang/Object;Ljava/lang/Object;ZZ)V", + (null.clone(), null, true, true), + ) + .await? + .into(); + let candidates: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + let _: bool = jvm + .invoke_virtual(&candidates, "add", "(Ljava/lang/Object;)Z", (matching.clone(),)) + .await?; + let _: bool = jvm + .invoke_virtual(&candidates, "add", "(Ljava/lang/Object;)Z", (missing.clone(),)) + .await?; + let _: bool = jvm + .invoke_virtual(&candidates, "add", "(Ljava/lang/Object;)Z", (never_reached.clone(),)) + .await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&entries, "containsAll", "(Ljava/util/Collection;)Z", (candidates,)) + .await? + ); + assert_eq!(jvm.get_field::(&never_reached, "keyCalls", "I").await?, 0); + assert_eq!(jvm.get_field::(&never_reached, "valueCalls", "I").await?, 0); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_entry_arrays_preserve_runtime_type_and_partial_failure_state() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + for value in [1, 2] { + let key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into(); + let mapped: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (value * 10,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, mapped)) + .await?; + } + let raw_entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let raw_array: ClassInstanceRef> = jvm.invoke_virtual(&raw_entries, "toArray", "()[Ljava/lang/Object;", ()).await?; + let raw_values = jvm.load_array::>(&raw_array, 0, 2).await?; + let wrapped_map: ClassInstanceRef = jvm + .invoke_static("java/util/Collections", "unmodifiableMap", "(Ljava/util/Map;)Ljava/util/Map;", (map,)) + .await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&wrapped_map, "entrySet", "()Ljava/util/Set;", ()).await?; + + let untyped: ClassInstanceRef> = jvm.invoke_virtual(&entries, "toArray", "()[Ljava/lang/Object;", ()).await?; + for entry in jvm.load_array::>(&untyped, 0, 2).await? { + assert_eq!( + entry.class_definition().name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + let null: ClassInstanceRef = None.into(); + let result: Result> = jvm + .invoke_virtual(&entry, "setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", (null,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("untyped entry array must contain only unmodifiable entries"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + } + + let mut oversized: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Map$Entry;", 4).await?.into(); + jvm.store_array(&mut oversized, 2, [raw_values[0].clone(), raw_values[1].clone()]).await?; + let oversized_identity = oversized.identity(); + let reused: ClassInstanceRef> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (oversized,)) + .await?; + assert_eq!(reused.identity(), oversized_identity); + let reused_values = jvm.load_array::>(&reused, 0, 4).await?; + for entry in &reused_values[..2] { + assert_eq!( + entry.class_definition().name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + } + assert!(reused_values[2].is_null(), "oversized typed array needs a null terminator"); + assert_eq!( + reused_values[3].identity(), + raw_values[1].identity(), + "elements after the terminator remain untouched" + ); + + let small: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Map$Entry;", 0).await?.into(); + let grown: ClassInstanceRef> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (small,)) + .await?; + assert_eq!(grown.class_definition().name(), "[Ljava/util/Map$Entry;"); + assert_eq!(jvm.array_length(&grown).await?, 2); + for entry in jvm.load_array::>(&grown, 0, 2).await? { + assert_eq!( + entry.class_definition().name(), + "java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry" + ); + } + + let mut concrete: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", 2).await?.into(); + jvm.store_array(&mut concrete, 0, [raw_values[0].clone(), raw_values[1].clone()]).await?; + let result: Result>> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (concrete.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("HashMap.Entry[] must reject wrapped entries"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + let concrete_values = jvm.load_array::>(&concrete, 0, 2).await?; + assert_eq!(concrete_values[0].identity(), raw_values[0].identity()); + assert_eq!(concrete_values[1].identity(), raw_values[1].identity()); + + let sentinel_row: ClassInstanceRef = jvm.instantiate_array("Ljava/util/Map$Entry;", 1).await?.into(); + let mut matrix: ClassInstanceRef> = jvm.instantiate_array("[Ljava/util/Map$Entry;", 3).await?.into(); + jvm.store_array(&mut matrix, 0, [sentinel_row.clone(), sentinel_row.clone(), sentinel_row.clone()]) + .await?; + let result: Result>> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (matrix.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Map.Entry[][] must reject scalar wrapped entries"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + for row in jvm.load_array::>(&matrix, 0, 3).await? { + assert_eq!(row.identity(), sentinel_row.identity(), "ASE must preserve every not-yet-written slot"); + } + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_sorted_ranges_remain_live_and_unmodifiable() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let mut keys = Vec::new(); + for key in [1, 3, 5] { + let key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (key,)).await?.into(); + 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.clone(), value), + ) + .await?; + keys.push(key); + } + let sorted_map: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSortedMap", + "(Ljava/util/SortedMap;)Ljava/util/SortedMap;", + (map.clone(),), + ) + .await?; + let nested_sorted_map: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSortedMap", + "(Ljava/util/SortedMap;)Ljava/util/SortedMap;", + (sorted_map.clone(),), + ) + .await?; + assert_ne!(sorted_map.identity(), nested_sorted_map.identity()); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&sorted_map, "comparator", "()Ljava/util/Comparator;", ()) + .await? + .is_null() + ); + let first: ClassInstanceRef = jvm.invoke_virtual(&sorted_map, "firstKey", "()Ljava/lang/Object;", ()).await?; + let last: ClassInstanceRef = jvm.invoke_virtual(&sorted_map, "lastKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "intValue", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&last, "intValue", "()I", ()).await?, 5); + + let range: ClassInstanceRef = jvm + .invoke_virtual( + &sorted_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (keys[0].clone(), keys[2].clone()), + ) + .await?; + let nested_range: ClassInstanceRef = jvm + .invoke_virtual(&range, "headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (keys[1].clone(),)) + .await?; + let head_range: ClassInstanceRef = jvm + .invoke_virtual(&sorted_map, "headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (keys[2].clone(),)) + .await?; + let tail_range: ClassInstanceRef = jvm + .invoke_virtual(&sorted_map, "tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (keys[0].clone(),)) + .await?; + for wrapped_range in [&range, &nested_range, &head_range, &tail_range] { + assert_eq!(wrapped_range.class_definition().name(), "java/util/Collections$UnmodifiableSortedMap"); + } + let inserted_key: ClassInstanceRef = jvm.new_class("java/lang/Integer", "(I)V", (2,)).await?.into(); + let inserted_value = JavaLangString::from_rust_string(&jvm, "inserted").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (inserted_key.clone(), inserted_value.clone()), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&range, "containsKey", "(Ljava/lang/Object;)Z", (inserted_key.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&nested_range, "containsKey", "(Ljava/lang/Object;)Z", (inserted_key.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&head_range, "containsKey", "(Ljava/lang/Object;)Z", (inserted_key.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&tail_range, "containsKey", "(Ljava/lang/Object;)Z", (inserted_key.clone(),)) + .await? + ); + let put: Result> = jvm + .invoke_virtual( + &range, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (inserted_key.clone(), inserted_value), + ) + .await; + let Err(JavaError::JavaException(exception)) = put else { + panic!("sorted map range must remain unmodifiable"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + let set: ClassInstanceRef = jvm.new_class("java/util/TreeSet", "()V", ()).await?.into(); + for key in &keys { + let _: bool = jvm.invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (key.clone(),)).await?; + } + let sorted_set: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSortedSet", + "(Ljava/util/SortedSet;)Ljava/util/SortedSet;", + (set.clone(),), + ) + .await?; + let nested_sorted_set: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSortedSet", + "(Ljava/util/SortedSet;)Ljava/util/SortedSet;", + (sorted_set.clone(),), + ) + .await?; + assert_ne!(sorted_set.identity(), nested_sorted_set.identity()); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&sorted_set, "comparator", "()Ljava/util/Comparator;", ()) + .await? + .is_null() + ); + let first: ClassInstanceRef = jvm.invoke_virtual(&sorted_set, "first", "()Ljava/lang/Object;", ()).await?; + let last: ClassInstanceRef = jvm.invoke_virtual(&sorted_set, "last", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "intValue", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&last, "intValue", "()I", ()).await?, 5); + let set_range: ClassInstanceRef = jvm + .invoke_virtual( + &sorted_set, + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + (keys[0].clone(), keys[2].clone()), + ) + .await?; + let nested_set_range: ClassInstanceRef = jvm + .invoke_virtual( + &set_range, + "tailSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + (inserted_key.clone(),), + ) + .await?; + let head_set_range: ClassInstanceRef = jvm + .invoke_virtual(&sorted_set, "headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (keys[2].clone(),)) + .await?; + let tail_set_range: ClassInstanceRef = jvm + .invoke_virtual(&sorted_set, "tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (keys[0].clone(),)) + .await?; + for wrapped_range in [&set_range, &nested_set_range, &head_set_range, &tail_set_range] { + assert_eq!(wrapped_range.class_definition().name(), "java/util/Collections$UnmodifiableSortedSet"); + } + let _: bool = jvm.invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (inserted_key.clone(),)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&set_range, "contains", "(Ljava/lang/Object;)Z", (inserted_key.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&nested_set_range, "contains", "(Ljava/lang/Object;)Z", (inserted_key.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&head_set_range, "contains", "(Ljava/lang/Object;)Z", (inserted_key.clone(),),) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&tail_set_range, "contains", "(Ljava/lang/Object;)Z", (inserted_key.clone(),),) + .await? + ); + let add: Result = jvm.invoke_virtual(&set_range, "add", "(Ljava/lang/Object;)Z", (inserted_key,)).await; + let Err(JavaError::JavaException(exception)) = add else { + panic!("sorted set range must remain unmodifiable"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + Ok(()) +} + +#[tokio::test] +async fn test_coll_08_unmodifiable_sorted_ranges_preserve_custom_comparator_boundaries() -> Result<()> { + let jvm = collections_test_jvm().await?; + let comparator: ClassInstanceRef = jvm.new_class("CollectionsComparator", "(ZZZ)V", (true, false, true)).await?.into(); + let high: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (30, 30, false)).await?.into(); + let middle: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (20, 20, false)).await?.into(); + let low: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (10, 10, false)).await?.into(); + let live_key: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (25, 25, false)).await?.into(); + let outside: ClassInstanceRef = jvm.new_class("CollectionsSortValue", "(IIZ)V", (40, 40, false)).await?.into(); + let null: ClassInstanceRef = None.into(); + + let map: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (comparator.clone(),)) + .await? + .into(); + for key in [high.clone(), middle.clone(), low.clone(), null.clone()] { + 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 sorted_map: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSortedMap", + "(Ljava/util/SortedMap;)Ljava/util/SortedMap;", + (map.clone(),), + ) + .await?; + let returned_comparator: ClassInstanceRef = jvm.invoke_virtual(&sorted_map, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(returned_comparator.identity(), comparator.identity()); + assert!( + jvm.invoke_virtual::<_, bool>(&sorted_map, "containsKey", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let first_key: ClassInstanceRef = jvm.invoke_virtual(&sorted_map, "firstKey", "()Ljava/lang/Object;", ()).await?; + let last_key: ClassInstanceRef = jvm.invoke_virtual(&sorted_map, "lastKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(first_key.identity(), high.identity()); + assert!(last_key.is_null()); + + let map_range: ClassInstanceRef = jvm + .invoke_virtual( + &sorted_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (high.clone(), null.clone()), + ) + .await?; + let map_head: ClassInstanceRef = jvm + .invoke_virtual(&sorted_map, "headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (null.clone(),)) + .await?; + let map_tail: ClassInstanceRef = jvm + .invoke_virtual(&sorted_map, "tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (null.clone(),)) + .await?; + let nested_map: ClassInstanceRef = jvm + .invoke_virtual( + &map_range, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (middle.clone(), low.clone()), + ) + .await?; + let equal_map: ClassInstanceRef = jvm + .invoke_virtual( + &map_range, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (middle.clone(), middle.clone()), + ) + .await?; + for range in [&map_range, &map_head, &map_tail, &nested_map, &equal_map] { + assert_eq!(range.class_definition().name(), "java/util/Collections$UnmodifiableSortedMap"); + let range_comparator: ClassInstanceRef = jvm.invoke_virtual(range, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(range_comparator.identity(), comparator.identity()); + } + assert_eq!(jvm.invoke_virtual::<_, i32>(&equal_map, "size", "()I", ()).await?, 0); + assert!( + !jvm.invoke_virtual::<_, bool>(&map_head, "containsKey", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&map_tail, "containsKey", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + + let result: Result> = jvm + .invoke_virtual( + &sorted_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (low.clone(), high.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("reverse comparator must reject reversed map boundaries"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + let result: Result> = jvm + .invoke_virtual(&map_range, "tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (outside.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("nested map range must reject an endpoint outside its lower boundary"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + + let live_value = JavaLangString::from_rust_string(&jvm, "live").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (live_key.clone(), live_value.clone()), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&map_range, "containsKey", "(Ljava/lang/Object;)Z", (live_key.clone(),)) + .await? + ); + + jvm.put_field(&mut comparator.clone(), "fail", "Z", true).await?; + let result: Result> = jvm + .invoke_virtual(&map_range, "headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", (middle.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("range factory must propagate the backing comparator exception"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + let result: Result> = jvm + .invoke_virtual( + &map_range, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (outside.clone(), live_value), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("unmodifiable map mutation must throw before comparator range validation"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + jvm.put_field(&mut comparator.clone(), "fail", "Z", false).await?; + + let set: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/Comparator;)V", (comparator.clone(),)) + .await? + .into(); + for key in [high.clone(), middle.clone(), low.clone(), null.clone()] { + let _: bool = jvm.invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (key,)).await?; + } + let sorted_set: ClassInstanceRef = jvm + .invoke_static( + "java/util/Collections", + "unmodifiableSortedSet", + "(Ljava/util/SortedSet;)Ljava/util/SortedSet;", + (set.clone(),), + ) + .await?; + let returned_comparator: ClassInstanceRef = jvm.invoke_virtual(&sorted_set, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(returned_comparator.identity(), comparator.identity()); + assert!( + jvm.invoke_virtual::<_, bool>(&sorted_set, "contains", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + let first: ClassInstanceRef = jvm.invoke_virtual(&sorted_set, "first", "()Ljava/lang/Object;", ()).await?; + let last: ClassInstanceRef = jvm.invoke_virtual(&sorted_set, "last", "()Ljava/lang/Object;", ()).await?; + assert_eq!(first.identity(), high.identity()); + assert!(last.is_null()); + + let set_range: ClassInstanceRef = jvm + .invoke_virtual( + &sorted_set, + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + (high.clone(), null.clone()), + ) + .await?; + let set_head: ClassInstanceRef = jvm + .invoke_virtual(&sorted_set, "headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (null.clone(),)) + .await?; + let set_tail: ClassInstanceRef = jvm + .invoke_virtual(&sorted_set, "tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (null.clone(),)) + .await?; + let nested_set: ClassInstanceRef = jvm + .invoke_virtual( + &set_range, + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + (middle.clone(), low.clone()), + ) + .await?; + let equal_set: ClassInstanceRef = jvm + .invoke_virtual( + &set_range, + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + (middle.clone(), middle.clone()), + ) + .await?; + for range in [&set_range, &set_head, &set_tail, &nested_set, &equal_set] { + assert_eq!(range.class_definition().name(), "java/util/Collections$UnmodifiableSortedSet"); + let range_comparator: ClassInstanceRef = jvm.invoke_virtual(range, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(range_comparator.identity(), comparator.identity()); + } + assert_eq!(jvm.invoke_virtual::<_, i32>(&equal_set, "size", "()I", ()).await?, 0); + assert!( + !jvm.invoke_virtual::<_, bool>(&set_head, "contains", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&set_tail, "contains", "(Ljava/lang/Object;)Z", (null.clone(),)) + .await? + ); + + let result: Result> = jvm + .invoke_virtual( + &sorted_set, + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + (low, high), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("reverse comparator must reject reversed set boundaries"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + let result: Result> = jvm + .invoke_virtual(&set_range, "tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (outside.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("nested set range must reject an endpoint outside its lower boundary"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + + let _: bool = jvm.invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (live_key.clone(),)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&set_range, "contains", "(Ljava/lang/Object;)Z", (live_key,)) + .await? + ); + + jvm.put_field(&mut comparator.clone(), "fail", "Z", true).await?; + let result: Result> = jvm + .invoke_virtual(&set_range, "headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (middle,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("set range factory must propagate the backing comparator exception"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + let result: Result = jvm.invoke_virtual(&set_range, "add", "(Ljava/lang/Object;)Z", (outside,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("unmodifiable set mutation must throw before comparator range validation"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/UnsupportedOperationException")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_date.rs b/java_runtime/tests/classes/java/util/test_date.rs index 3fcf7f53..6a253736 100644 --- a/java_runtime/tests/classes/java/util/test_date.rs +++ b/java_runtime/tests/classes/java/util/test_date.rs @@ -1,5 +1,5 @@ use java_runtime::classes::java::lang::Object; -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -21,3 +21,77 @@ async fn test_date_cldc11_value_contract() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_date_01_compare_to_typed_and_object_contract() -> Result<()> { + let jvm = test_jvm().await?; + let earlier = jvm.new_class("java/util/Date", "(J)V", (-10i64,)).await?; + let same = jvm.new_class("java/util/Date", "(J)V", (-10i64,)).await?; + let later = jvm.new_class("java/util/Date", "(J)V", (20i64,)).await?; + + assert_eq!( + jvm.invoke_virtual::<_, i32>(&earlier, "compareTo", "(Ljava/util/Date;)I", (later.clone(),)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&later, "compareTo", "(Ljava/util/Date;)I", (earlier.clone(),)) + .await?, + 1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&earlier, "compareTo", "(Ljava/lang/Object;)I", (same,)) + .await?, + 0 + ); + + let null: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&earlier, "compareTo", "(Ljava/util/Date;)I", (null.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Date.compareTo(Date) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let result: Result = jvm.invoke_virtual(&earlier, "compareTo", "(Ljava/lang/Object;)I", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Date.compareTo(Object) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&earlier, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Date.compareTo(Object) must reject non-Date values"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + Ok(()) +} + +#[tokio::test] +async fn test_date_02_before_after_and_null_contract() -> Result<()> { + let jvm = test_jvm().await?; + let earlier = jvm.new_class("java/util/Date", "(J)V", (10i64,)).await?; + let later = jvm.new_class("java/util/Date", "(J)V", (20i64,)).await?; + + assert!( + jvm.invoke_virtual::<_, bool>(&earlier, "before", "(Ljava/util/Date;)Z", (later.clone(),)) + .await? + ); + assert!(!jvm.invoke_virtual::<_, bool>(&earlier, "after", "(Ljava/util/Date;)Z", (later,)).await?); + + let null: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&earlier, "before", "(Ljava/util/Date;)Z", (null.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Date.before must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let result: Result = jvm.invoke_virtual(&earlier, "after", "(Ljava/util/Date;)Z", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Date.after must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_list_views.rs b/java_runtime/tests/classes/java/util/test_list_views.rs new file mode 100644 index 00000000..477b3a54 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_list_views.rs @@ -0,0 +1,725 @@ +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::{classes::java::lang::Object, get_runtime_class_proto}; +use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn list_01_iterators_are_live_and_support_every_state_transition() -> Result<()> { + let jvm = test_jvm().await?; + + for class_name in ["java/util/ArrayList", "java/util/Vector"] { + let list = jvm.new_class(class_name, "()V", ()).await?; + for value in ["a", "b", "c"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (1,)).await?; + assert!(jvm.is_instance(&**iterator, "java/util/ListIterator")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "previousIndex", "()I", ()).await?, 0); + + let previous: ClassInstanceRef = jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &previous).await?, "a"); + + let replacement = JavaLangString::from_rust_string(&jvm, "A").await?; + let _: () = jvm.invoke_virtual(&iterator, "set", "(Ljava/lang/Object;)V", (replacement,)).await?; + let inserted = JavaLangString::from_rust_string(&jvm, "x").await?; + let _: () = jvm.invoke_virtual(&iterator, "add", "(Ljava/lang/Object;)V", (inserted,)).await?; + + let result: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("remove after add must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + let result: Result<()> = jvm + .invoke_virtual( + &iterator, + "set", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "invalid").await?,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("set after add must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + + let next: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &next).await?, "A"); + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + + let tail = JavaLangString::from_rust_string(&jvm, "d").await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (tail,)).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + } + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 4); + + 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")); + + for invalid_index in [-1, 5] { + let result: Result> = jvm + .invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (invalid_index,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid list iterator index must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + } + } + + Ok(()) +} + +#[tokio::test] +async fn list_01_descriptors_flags_and_iterator_state_fields_match_contract() -> Result<()> { + let array_list_iterator = get_runtime_class_proto("java/util/ArrayList$Itr").unwrap(); + assert_eq!(array_list_iterator.methods.iter().filter(|method| method.name == "").count(), 1); + assert!( + array_list_iterator + .methods + .iter() + .any(|method| method.name == "" && method.descriptor == "(Ljava/util/List;I)V") + ); + + for class_name in ["java/util/ArrayList", "java/util/Vector"] { + let proto = get_runtime_class_proto(class_name).unwrap(); + for descriptor in ["()Ljava/util/ListIterator;", "(I)Ljava/util/ListIterator;"] { + let method = proto + .methods + .iter() + .find(|method| method.name == "listIterator" && method.descriptor == descriptor) + .unwrap(); + let expected = if class_name == "java/util/Vector" { + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED + } else { + MethodAccessFlags::PUBLIC + }; + assert_eq!(method.access_flags, expected); + } + } + + for (class_name, list_descriptor) in [ + ("java/util/AbstractList$ListItr", "Ljava/util/List;"), + ("java/util/ArrayList$ListItr", "Ljava/util/List;"), + ("java/util/Vector$ListItr", "Ljava/util/Vector;"), + ("java/util/LinkedList$ListItr", "Ljava/util/LinkedList;"), + ] { + let proto = get_runtime_class_proto(class_name).unwrap(); + assert_eq!(proto.interfaces, vec!["java/util/ListIterator"]); + let field_proto = if class_name == "java/util/LinkedList$ListItr" { + get_runtime_class_proto(class_name).unwrap() + } else { + get_runtime_class_proto(proto.parent_class.unwrap()).unwrap() + }; + for (name, descriptor) in [("list", list_descriptor), ("cursor", "I"), ("lastReturned", "I")] { + assert!( + field_proto + .fields + .iter() + .any(|field| field.name == name && field.descriptor == descriptor), + "missing {class_name}.{name}:{descriptor}" + ); + } + for (name, descriptor) in [ + ("hasPrevious", "()Z"), + ("previous", "()Ljava/lang/Object;"), + ("nextIndex", "()I"), + ("previousIndex", "()I"), + ("set", "(Ljava/lang/Object;)V"), + ("add", "(Ljava/lang/Object;)V"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap(); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + } + + let vector = get_runtime_class_proto("java/util/Vector").unwrap(); + for (name, descriptor) in [ + ("size", "()I"), + ("get", "(I)Ljava/lang/Object;"), + ("set", "(ILjava/lang/Object;)Ljava/lang/Object;"), + ("add", "(Ljava/lang/Object;)Z"), + ("add", "(ILjava/lang/Object;)V"), + ("remove", "(I)Ljava/lang/Object;"), + ] { + let method = vector + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap(); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED); + } + + Ok(()) +} + +#[tokio::test] +async fn linked_list_list_iterator_covers_boundaries_and_every_legal_transition() -> Result<()> { + let jvm = test_jvm().await?; + let list = jvm.new_class("java/util/LinkedList", "()V", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "()Ljava/util/ListIterator;", ()).await?; + assert!(jvm.is_instance(&**iterator, "java/util/ListIterator")); + assert!(jvm.is_instance(&**iterator, "java/util/Iterator")); + assert!(!jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await?); + assert!(!jvm.invoke_virtual::<_, bool>(&iterator, "hasPrevious", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "previousIndex", "()I", ()).await?, -1); + + for method in ["next", "previous"] { + let result: Result> = jvm.invoke_virtual(&iterator, method, "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method} on an empty iterator must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + } + let result: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("remove before traversal must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + let result: Result<()> = jvm + .invoke_virtual( + &iterator, + "set", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "invalid").await?,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("set before traversal must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + + for value in ["a", "b", "c"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (0,)).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "a"); + let _: () = jvm + .invoke_virtual( + &iterator, + "set", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "A").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "previousIndex", "()I", ()).await?, -1); + + for method in ["remove", "set"] { + let result: Result<()> = if method == "remove" { + jvm.invoke_virtual(&iterator, method, "()V", ()).await + } else { + jvm.invoke_virtual( + &iterator, + method, + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "invalid").await?,), + ) + .await + }; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method} after remove must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + } + + let _: () = jvm + .invoke_virtual( + &iterator, + "add", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "x").await?,), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "previousIndex", "()I", ()).await?, 0); + for method in ["remove", "set"] { + let result: Result<()> = if method == "remove" { + jvm.invoke_virtual(&iterator, method, "()V", ()).await + } else { + jvm.invoke_virtual( + &iterator, + method, + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "invalid").await?,), + ) + .await + }; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method} after add must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + } + + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "x"); + let _: () = jvm + .invoke_virtual( + &iterator, + "set", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "X").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&iterator, "nextIndex", "()I", ()).await?, 0); + + let result: Result> = jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("previous at the start boundary must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + + let middle: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (1,)).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&middle, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "b"); + let value: ClassInstanceRef = jvm.invoke_virtual(&middle, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "b"); + let _: () = jvm + .invoke_virtual( + &middle, + "set", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "B").await?,), + ) + .await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&middle, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "c"); + assert!(!jvm.invoke_virtual::<_, bool>(&middle, "hasNext", "()Z", ()).await?); + + let result: Result> = jvm.invoke_virtual(&middle, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("next at the end boundary must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + + let value: ClassInstanceRef = jvm.invoke_virtual(&middle, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "c"); + let _: () = jvm.invoke_virtual(&middle, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&middle, "nextIndex", "()I", ()).await?, 1); + let value: ClassInstanceRef = jvm.invoke_virtual(&middle, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "B"); + let _: () = jvm.invoke_virtual(&middle, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 0); + + let header: ClassInstanceRef = jvm.get_field(&list, "header", "Ljava/util/LinkedList$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + let previous: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (next,)).await?); + assert!( + jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (previous,)) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn linked_list_sentinel_unlinks_middle_single_and_last_entries() -> Result<()> { + let jvm = test_jvm().await?; + let list = jvm.new_class("java/util/LinkedList", "()V", ()).await?; + for value in ["first", "middle", "last"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + + let removed: ClassInstanceRef = jvm.invoke_virtual(&list, "remove", "(I)Ljava/lang/Object;", (1,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &removed).await?, "middle"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 2); + for (index, expected) in ["first", "last"].into_iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&list, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, expected); + } + + let header: ClassInstanceRef = jvm.get_field(&list, "header", "Ljava/util/LinkedList$Entry;").await?; + let first: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + let last: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + let first_next: ClassInstanceRef = jvm.get_field(&first, "next", "Ljava/util/LinkedList$Entry;").await?; + let last_previous: ClassInstanceRef = jvm.get_field(&last, "previous", "Ljava/util/LinkedList$Entry;").await?; + assert!( + jvm.invoke_virtual::<_, bool>(&last, "equals", "(Ljava/lang/Object;)Z", (first_next,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&first, "equals", "(Ljava/lang/Object;)Z", (last_previous,)) + .await? + ); + + let removed: ClassInstanceRef = jvm.invoke_virtual(&list, "removeFirst", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &removed).await?, "first"); + let header: ClassInstanceRef = jvm.get_field(&list, "header", "Ljava/util/LinkedList$Entry;").await?; + let only_next: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + let only_previous: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + assert!( + jvm.invoke_virtual::<_, bool>(&only_next, "equals", "(Ljava/lang/Object;)Z", (only_previous,)) + .await? + ); + + let removed: ClassInstanceRef = jvm.invoke_virtual(&list, "removeLast", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &removed).await?, "last"); + let next: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + let previous: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (next,)).await?); + assert!( + jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (previous,)) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn list_02_to_04_linked_list_uses_sentinel_and_live_list_iterator() -> Result<()> { + let proto = get_runtime_class_proto("java/util/LinkedList").expect("LinkedList registration"); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC); + assert_eq!(proto.interfaces, vec!["java/util/List", "java/lang/Cloneable", "java/io/Serializable"]); + for (name, descriptor) in [ + ("", "()V"), + ("", "(Ljava/util/Collection;)V"), + ("addFirst", "(Ljava/lang/Object;)V"), + ("addLast", "(Ljava/lang/Object;)V"), + ("getFirst", "()Ljava/lang/Object;"), + ("getLast", "()Ljava/lang/Object;"), + ("removeFirst", "()Ljava/lang/Object;"), + ("removeLast", "()Ljava/lang/Object;"), + ("listIterator", "(I)Ljava/util/ListIterator;"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing LinkedList.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + + let jvm = test_jvm().await?; + let list = jvm.new_class("java/util/LinkedList", "()V", ()).await?; + let header: ClassInstanceRef = jvm.get_field(&list, "header", "Ljava/util/LinkedList$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + let previous: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (next,)).await?); + assert!( + jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (previous,)) + .await? + ); + + let result: Result> = jvm.invoke_virtual(&list, "getFirst", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("empty LinkedList.getFirst must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + + let null: ClassInstanceRef = None.into(); + let first = JavaLangString::from_rust_string(&jvm, "first").await?; + let last = JavaLangString::from_rust_string(&jvm, "last").await?; + let _: () = jvm.invoke_virtual(&list, "addFirst", "(Ljava/lang/Object;)V", (first,)).await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (null,)).await?; + let _: () = jvm.invoke_virtual(&list, "addLast", "(Ljava/lang/Object;)V", (last,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 3); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&list, "listIterator", "(I)Ljava/util/ListIterator;", (3,)).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "last"); + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&list, "size", "()I", ()).await?, 2); + + let duplicate = JavaLangString::from_rust_string(&jvm, "first").await?; + let _: bool = jvm.invoke_virtual(&list, "add", "(Ljava/lang/Object;)Z", (duplicate,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>( + &list, + "indexOf", + "(Ljava/lang/Object;)I", + (JavaLangString::from_rust_string(&jvm, "first").await?,) + ) + .await?, + 0 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>( + &list, + "lastIndexOf", + "(Ljava/lang/Object;)I", + (JavaLangString::from_rust_string(&jvm, "first").await?,), + ) + .await?, + 2 + ); + + let copy = jvm + .new_class("java/util/LinkedList", "(Ljava/util/Collection;)V", (list.clone(),)) + .await?; + let destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 4).await?.into(); + let array: ClassInstanceRef> = jvm + .invoke_virtual(©, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination,)) + .await?; + assert_eq!(jvm.array_length(&array).await?, 4); + + let _: () = jvm.invoke_virtual(&list, "clear", "()V", ()).await?; + let header: ClassInstanceRef = jvm.get_field(&list, "header", "Ljava/util/LinkedList$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&header, "next", "Ljava/util/LinkedList$Entry;").await?; + let previous: ClassInstanceRef = jvm.get_field(&header, "previous", "Ljava/util/LinkedList$Entry;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (next,)).await?); + assert!( + jvm.invoke_virtual::<_, bool>(&header, "equals", "(Ljava/lang/Object;)Z", (previous,)) + .await? + ); + + Ok(()) +} + +#[tokio::test] +async fn nested_sub_lists_propagate_every_mutation_through_three_ancestors() -> Result<()> { + let jvm = test_jvm().await?; + let root: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + for value in ["a", "b", "c", "d", "e", "f", "g", "h"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&root, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + + let first: ClassInstanceRef = jvm.invoke_virtual(&root, "subList", "(II)Ljava/util/List;", (1, 7)).await?; + let second: ClassInstanceRef = jvm.invoke_virtual(&first, "subList", "(II)Ljava/util/List;", (1, 5)).await?; + let third: ClassInstanceRef = jvm.invoke_virtual(&second, "subList", "(II)Ljava/util/List;", (1, 3)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 8); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 4); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 2); + + let source = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + for value in ["x", "y"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&source, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + assert!( + jvm.invoke_virtual::<_, bool>(&third, "addAll", "(ILjava/util/Collection;)Z", (1, source)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 10); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 8); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 4); + for (target, expected) in [ + (root.clone(), &["a", "b", "c", "d", "x", "y", "e", "f", "g", "h"][..]), + (first.clone(), &["b", "c", "d", "x", "y", "e", "f", "g"][..]), + (second.clone(), &["c", "d", "x", "y", "e", "f"][..]), + (third.clone(), &["d", "x", "y", "e"][..]), + ] { + for (index, expected) in expected.iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&target, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, *expected); + } + } + + let removed: ClassInstanceRef = jvm.invoke_virtual(&third, "remove", "(I)Ljava/lang/Object;", (0,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &removed).await?, "d"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 9); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 7); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 5); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 3); + for (target, expected) in [ + (root.clone(), &["a", "b", "c", "x", "y", "e", "f", "g", "h"][..]), + (first.clone(), &["b", "c", "x", "y", "e", "f", "g"][..]), + (second.clone(), &["c", "x", "y", "e", "f"][..]), + (third.clone(), &["x", "y", "e"][..]), + ] { + for (index, expected) in expected.iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&target, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, *expected); + } + } + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&third, "listIterator", "(I)Ljava/util/ListIterator;", (1,)).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "y"); + let _: () = jvm + .invoke_virtual( + &iterator, + "set", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "Y").await?,), + ) + .await?; + let _: () = jvm + .invoke_virtual( + &iterator, + "add", + "(Ljava/lang/Object;)V", + (JavaLangString::from_rust_string(&jvm, "z").await?,), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 10); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 8); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 4); + for (target, expected) in [ + (root.clone(), &["a", "b", "c", "x", "Y", "z", "e", "f", "g", "h"][..]), + (first.clone(), &["b", "c", "x", "Y", "z", "e", "f", "g"][..]), + (second.clone(), &["c", "x", "Y", "z", "e", "f"][..]), + (third.clone(), &["x", "Y", "z", "e"][..]), + ] { + for (index, expected) in expected.iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&target, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, *expected); + } + } + + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "z"); + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 9); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 7); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 5); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 3); + for (target, expected) in [ + (root.clone(), &["a", "b", "c", "x", "Y", "e", "f", "g", "h"][..]), + (first.clone(), &["b", "c", "x", "Y", "e", "f", "g"][..]), + (second.clone(), &["c", "x", "Y", "e", "f"][..]), + (third.clone(), &["x", "Y", "e"][..]), + ] { + for (index, expected) in expected.iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&target, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, *expected); + } + } + + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "previous", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "Y"); + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 8); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 4); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 2); + + for (target, expected) in [ + (root.clone(), &["a", "b", "c", "x", "e", "f", "g", "h"][..]), + (first.clone(), &["b", "c", "x", "e", "f", "g"][..]), + (second.clone(), &["c", "x", "e", "f"][..]), + (third.clone(), &["x", "e"][..]), + ] { + for (index, expected) in expected.iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&target, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, *expected); + } + } + + let sentinel = JavaLangString::from_rust_string(&jvm, "sentinel").await?; + let mut destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 4).await?.into(); + jvm.store_array( + &mut destination, + 0, + [sentinel.clone(), sentinel.clone(), sentinel.clone(), sentinel.clone()], + ) + .await?; + let typed: ClassInstanceRef> = jvm + .invoke_virtual(&third, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination,)) + .await?; + let values: Vec> = jvm.load_array(&typed, 0, 4).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &values[0]).await?, "x"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &values[1]).await?, "e"); + assert!(values[2].is_null()); + assert_eq!(JavaLangString::to_rust_string(&jvm, &values[3]).await?, "sentinel"); + + let _: () = jvm.invoke_virtual(&third, "clear", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "size", "()I", ()).await?, 4); + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "size", "()I", ()).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&third, "size", "()I", ()).await?, 0); + for (target, expected) in [ + (root.clone(), &["a", "b", "c", "f", "g", "h"][..]), + (first.clone(), &["b", "c", "f", "g"][..]), + (second.clone(), &["c", "f"][..]), + (third.clone(), &[] as &[&str]), + ] { + for (index, expected) in expected.iter().enumerate() { + let value: ClassInstanceRef = jvm.invoke_virtual(&target, "get", "(I)Ljava/lang/Object;", (index as i32,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, *expected); + } + } + + Ok(()) +} + +#[tokio::test] +async fn sl_01_to_02_nested_sub_lists_are_live_in_both_directions() -> Result<()> { + let jvm = test_jvm().await?; + let root = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + for value in ["a", "b", "c", "d", "e"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&root, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + + let view: ClassInstanceRef = jvm.invoke_virtual(&root, "subList", "(II)Ljava/util/List;", (1, 5)).await?; + let nested: ClassInstanceRef = jvm.invoke_virtual(&view, "subList", "(II)Ljava/util/List;", (1, 3)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&view, "size", "()I", ()).await?, 4); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nested, "size", "()I", ()).await?, 2); + + let inserted = JavaLangString::from_rust_string(&jvm, "x").await?; + let _: () = jvm.invoke_virtual(&nested, "add", "(ILjava/lang/Object;)V", (1, inserted)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&view, "size", "()I", ()).await?, 5); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nested, "size", "()I", ()).await?, 3); + + let root_value: ClassInstanceRef = jvm.invoke_virtual(&root, "get", "(I)Ljava/lang/Object;", (3,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &root_value).await?, "x"); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&nested, "listIterator", "(I)Ljava/util/ListIterator;", (0,)).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let replacement = JavaLangString::from_rust_string(&jvm, "C").await?; + let _: () = jvm.invoke_virtual(&iterator, "set", "(Ljava/lang/Object;)V", (replacement,)).await?; + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 5); + assert_eq!(jvm.invoke_virtual::<_, i32>(&view, "size", "()I", ()).await?, 4); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nested, "size", "()I", ()).await?, 2); + + let _: () = jvm.invoke_virtual(&nested, "clear", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 3); + assert_eq!(jvm.invoke_virtual::<_, i32>(&view, "size", "()I", ()).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nested, "size", "()I", ()).await?, 0); + + let source = jvm.new_class("java/util/ArrayList", "()V", ()).await?; + for value in ["y", "z"] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: bool = jvm.invoke_virtual(&source, "add", "(Ljava/lang/Object;)Z", (value,)).await?; + } + let modified: bool = jvm.invoke_virtual(&view, "addAll", "(ILjava/util/Collection;)Z", (1, source)).await?; + assert!(modified); + assert_eq!(jvm.invoke_virtual::<_, i32>(&root, "size", "()I", ()).await?, 5); + assert_eq!(jvm.invoke_virtual::<_, i32>(&view, "size", "()I", ()).await?, 4); + + let typed: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 0).await?.into(); + let typed: ClassInstanceRef> = jvm + .invoke_virtual(&view, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (typed,)) + .await?; + assert_eq!(jvm.array_length(&typed).await?, 4); + + for (from, to, expected) in [ + (-1, 0, "java/lang/IndexOutOfBoundsException"), + (0, 5, "java/lang/IndexOutOfBoundsException"), + (3, 2, "java/lang/IllegalArgumentException"), + ] { + let result: Result> = jvm.invoke_virtual(&view, "subList", "(II)Ljava/util/List;", (from, to)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid subList range must fail"); + }; + assert!(jvm.is_instance(&*exception, expected)); + } + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_properties.rs b/java_runtime/tests/classes/java/util/test_properties.rs index fe322dc6..b0e130ef 100644 --- a/java_runtime/tests/classes/java/util/test_properties.rs +++ b/java_runtime/tests/classes/java/util/test_properties.rs @@ -1,7 +1,127 @@ -use java_runtime::classes::java::lang::{Object, String}; -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use alloc::{boxed::Box, collections::BTreeMap, string::String as RustString, vec, vec::Vec}; -use test_utils::test_jvm; +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{InputStream, OutputStream}, + lang::{Object, String}, + util::Properties, + }, +}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; + +struct FailingInputStream; + +impl FailingInputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "FailingInputStream", + parent_class: Some("java/io/InputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "()I", Self::read, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/io/InputStream", "", "()V", ()).await + } + + async fn read(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Err(jvm.exception("java/io/IOException", "read failed").await) + } +} + +struct FailingOutputStream; + +impl FailingOutputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "FailingOutputStream", + parent_class: Some("java/io/OutputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await + } + + async fn write(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result<()> { + Err(jvm.exception("java/io/IOException", "write failed").await) + } +} + +struct FailingAfterInputStream; + +impl FailingAfterInputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "FailingAfterInputStream", + parent_class: Some("java/io/InputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "([B)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("read", "()I", Self::read, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("data", "[B", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("position", "I", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, data: ClassInstanceRef>) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/io/InputStream", "", "()V", ()).await?; + jvm.put_field(&mut this, "data", "[B", data).await?; + jvm.put_field(&mut this, "position", "I", 0).await + } + + async fn read(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result { + let data: ClassInstanceRef> = jvm.get_field(&this, "data", "[B").await?; + let position: i32 = jvm.get_field(&this, "position", "I").await?; + if position as usize >= jvm.array_length(&data).await? { + return Err(jvm.exception("java/io/IOException", "read failed after data").await); + } + + let value: i8 = jvm.load_array(&data, position as usize, 1).await?.into_iter().next().unwrap(); + jvm.put_field(&mut this, "position", "I", position + 1).await?; + Ok(value as u8 as i32) + } +} + +async fn properties_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + for proto in [ + FailingInputStream::as_proto(), + FailingOutputStream::as_proto(), + FailingAfterInputStream::as_proto(), + ] { + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto(proto, Box::new(runtime.clone()) as Box<_>)), + None, + ) + .await?; + } + Ok(jvm) +} #[tokio::test] async fn test_properties_inherits_hashtable_map_contract() -> Result<()> { @@ -57,3 +177,364 @@ async fn test_properties_inherits_hashtable_map_contract() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn prop_01_constructors_and_defaults_field() -> Result<()> { + let proto = Properties::as_proto(); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC)); + let defaults = proto.fields.iter().find(|field| field.name == "defaults").expect("defaults field"); + assert_eq!(defaults.descriptor, "Ljava/util/Properties;"); + assert_eq!(defaults.access_flags, FieldAccessFlags::PROTECTED); + for descriptor in ["()V", "(Ljava/util/Properties;)V"] { + let constructor = proto + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == descriptor) + .expect("Properties constructor"); + assert!(constructor.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + let jvm = test_jvm().await?; + let empty = jvm.new_class("java/util/Properties", "()V", ()).await?; + let defaults: ClassInstanceRef = jvm.get_field(&empty, "defaults", "Ljava/util/Properties;").await?; + assert!(defaults.is_null()); + + let parent = jvm.new_class("java/util/Properties", "()V", ()).await?; + let child = jvm + .new_class("java/util/Properties", "(Ljava/util/Properties;)V", (parent.clone(),)) + .await?; + let actual: ClassInstanceRef = jvm.get_field(&child, "defaults", "Ljava/util/Properties;").await?; + let key = JavaLangString::from_rust_string(&jvm, "shared-instance").await?; + let value = JavaLangString::from_rust_string(&jvm, "yes").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &actual, + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), value), + ) + .await?; + let inherited: ClassInstanceRef = jvm + .invoke_virtual(&parent, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &inherited).await?, "yes"); + + Ok(()) +} + +#[tokio::test] +async fn prop_02_get_property_uses_string_values_and_defaults_chain() -> Result<()> { + let jvm = test_jvm().await?; + let root = jvm.new_class("java/util/Properties", "()V", ()).await?; + let middle = jvm + .new_class("java/util/Properties", "(Ljava/util/Properties;)V", (root.clone(),)) + .await?; + let child = jvm + .new_class("java/util/Properties", "(Ljava/util/Properties;)V", (middle.clone(),)) + .await?; + + for (properties, key, value) in [ + (&root, "root", "root-value"), + (&root, "shared", "root-shared"), + (&middle, "middle", "middle-value"), + (&child, "shared", "child-shared"), + ] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + properties, + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key, value), + ) + .await?; + } + + for (key, expected) in [("root", "root-value"), ("middle", "middle-value"), ("shared", "child-shared")] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&child, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, expected); + } + + let key = JavaLangString::from_rust_string(&jvm, "root").await?; + let non_string = jvm.new_class("java/lang/Object", "()V", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &child, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key.clone(), non_string), + ) + .await?; + let inherited: ClassInstanceRef = jvm + .invoke_virtual(&child, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &inherited).await?, "root-value"); + + let missing = JavaLangString::from_rust_string(&jvm, "missing").await?; + let fallback = JavaLangString::from_rust_string(&jvm, "fallback").await?; + let value: ClassInstanceRef = jvm + .invoke_virtual( + &child, + "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (missing, fallback), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "fallback"); + + Ok(()) +} + +#[tokio::test] +async fn prop_03_load_store_property_names_and_failures() -> Result<()> { + let proto = Properties::as_proto(); + for (name, descriptor) in [ + ("load", "(Ljava/io/InputStream;)V"), + ("store", "(Ljava/io/OutputStream;Ljava/lang/String;)V"), + ("propertyNames", "()Ljava/util/Enumeration;"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .expect("Properties method"); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + assert!(method.access_flags.contains(MethodAccessFlags::SYNCHRONIZED)); + } + + let jvm = properties_jvm().await?; + let source = + b"# comment ending in backslash\\\ncontinued=hello\\\n world\nescaped\\ key\\:=value\\tend\nlatin=\xE9\nunicode=\\u00E9\ntail=slash\\"; + let mut bytes = jvm.instantiate_array("B", source.len()).await?; + jvm.store_array(&mut bytes, 0, source.iter().map(|byte| *byte as i8)).await?; + let input: ClassInstanceRef = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?.into(); + let properties = jvm.new_class("java/util/Properties", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&properties, "load", "(Ljava/io/InputStream;)V", (input,)).await?; + + for (key, expected) in [ + ("continued", "helloworld"), + ("escaped key:", "value\tend"), + ("latin", "é"), + ("unicode", "é"), + ("tail", "slash"), + ] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&properties, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, expected); + } + + let defaults = jvm.new_class("java/util/Properties", "()V", ()).await?; + for (key, value) in [("shared", "default"), ("inherited", "yes")] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &defaults, + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key, value), + ) + .await?; + } + let child = jvm.new_class("java/util/Properties", "(Ljava/util/Properties;)V", (defaults,)).await?; + for (key, value) in [("shared", "child"), ("local", "yes")] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &child, + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key, value), + ) + .await?; + } + let names: ClassInstanceRef = jvm.invoke_virtual(&child, "propertyNames", "()Ljava/util/Enumeration;", ()).await?; + let mut actual_names = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&names, "hasMoreElements", "()Z", ()).await? { + let name: ClassInstanceRef = jvm.invoke_virtual(&names, "nextElement", "()Ljava/lang/Object;", ()).await?; + actual_names.push(JavaLangString::to_rust_string(&jvm, &name).await?); + } + actual_names.sort(); + assert_eq!(actual_names, ["inherited", "local", "shared"]); + + let child_output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual( + &child, + "store", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (child_output.clone(), ClassInstanceRef::::from(None)), + ) + .await?; + let child_bytes: ClassInstanceRef> = jvm.invoke_virtual(&child_output, "toByteArray", "()[B", ()).await?; + let child_values: Vec = jvm.load_array(&child_bytes, 0, jvm.array_length(&child_bytes).await?).await?; + let child_text = RustString::from_utf8(child_values.iter().map(|byte| *byte as u8).collect()).expect("ASCII properties output"); + assert!(child_text.contains("local=yes")); + assert!(child_text.contains("shared=child")); + assert!(!child_text.contains("inherited")); + + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let comments = JavaLangString::from_rust_string(&jvm, "round trip").await?; + let _: () = jvm + .invoke_virtual( + &properties, + "store", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (output.clone(), comments), + ) + .await?; + let stored: ClassInstanceRef> = jvm.invoke_virtual(&output, "toByteArray", "()[B", ()).await?; + let stored_values: Vec = jvm.load_array(&stored, 0, jvm.array_length(&stored).await?).await?; + let stored_ascii = RustString::from_utf8(stored_values.iter().map(|byte| *byte as u8).collect()).expect("ASCII properties output"); + assert!(stored_ascii.contains("#round trip")); + assert!(stored_ascii.contains("\\u00E9")); + + let input: ClassInstanceRef = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (stored,)).await?.into(); + let round_trip = jvm.new_class("java/util/Properties", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&round_trip, "load", "(Ljava/io/InputStream;)V", (input,)).await?; + let latin = JavaLangString::from_rust_string(&jvm, "latin").await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&round_trip, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (latin,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "é"); + + let malformed = b"broken=\\u12G4\n"; + let mut bytes = jvm.instantiate_array("B", malformed.len()).await?; + jvm.store_array(&mut bytes, 0, malformed.iter().map(|byte| *byte as i8)).await?; + let input: ClassInstanceRef = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?.into(); + let malformed_result: Result<()> = jvm.invoke_virtual(&properties, "load", "(Ljava/io/InputStream;)V", (input,)).await; + let Err(JavaError::JavaException(exception)) = malformed_result else { + panic!("malformed unicode escape must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + let broken = JavaLangString::from_rust_string(&jvm, "broken").await?; + let broken_value: ClassInstanceRef = jvm + .invoke_virtual(&properties, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (broken,)) + .await?; + assert!(broken_value.is_null(), "a malformed logical line must not be committed"); + + let partial_source = b"committed=first\r\nunfinished=discarded"; + let mut partial_bytes = jvm.instantiate_array("B", partial_source.len()).await?; + jvm.store_array(&mut partial_bytes, 0, partial_source.iter().map(|byte| *byte as i8)) + .await?; + let partial_input: ClassInstanceRef = jvm.new_class("FailingAfterInputStream", "([B)V", (partial_bytes,)).await?.into(); + let partial_properties = jvm.new_class("java/util/Properties", "()V", ()).await?; + let partial_result: Result<()> = jvm + .invoke_virtual(&partial_properties, "load", "(Ljava/io/InputStream;)V", (partial_input,)) + .await; + let Err(JavaError::JavaException(exception)) = partial_result else { + panic!("an IOException after a complete property must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + let committed = JavaLangString::from_rust_string(&jvm, "committed").await?; + let committed_value: ClassInstanceRef = jvm + .invoke_virtual(&partial_properties, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (committed,)) + .await?; + assert!( + !committed_value.is_null(), + "the complete property must be committed before the later read" + ); + assert_eq!(JavaLangString::to_rust_string(&jvm, &committed_value).await?, "first"); + let unfinished = JavaLangString::from_rust_string(&jvm, "unfinished").await?; + let unfinished_value: ClassInstanceRef = jvm + .invoke_virtual( + &partial_properties, + "getProperty", + "(Ljava/lang/String;)Ljava/lang/String;", + (unfinished,), + ) + .await?; + assert!(unfinished_value.is_null(), "an incomplete logical line must not be committed"); + + let failing_input: ClassInstanceRef = jvm.new_class("FailingInputStream", "()V", ()).await?.into(); + let failed_load: Result<()> = jvm + .invoke_virtual(&properties, "load", "(Ljava/io/InputStream;)V", (failing_input,)) + .await; + let Err(JavaError::JavaException(exception)) = failed_load else { + panic!("load IOException must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + let null_input: ClassInstanceRef = None.into(); + let null_load: Result<()> = jvm.invoke_virtual(&properties, "load", "(Ljava/io/InputStream;)V", (null_input,)).await; + let Err(JavaError::JavaException(exception)) = null_load else { + panic!("null input must throw NullPointerException before stream processing"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let failing_output: ClassInstanceRef = jvm.new_class("FailingOutputStream", "()V", ()).await?.into(); + let failed_store: Result<()> = jvm + .invoke_virtual( + &properties, + "store", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (failing_output, ClassInstanceRef::::from(None)), + ) + .await; + let Err(JavaError::JavaException(exception)) = failed_store else { + panic!("store IOException must propagate"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + + for non_string_key in [true, false] { + let invalid = jvm.new_class("java/util/Properties", "()V", ()).await?; + let string = JavaLangString::from_rust_string(&jvm, "text").await?; + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let (key, value): (ClassInstanceRef, ClassInstanceRef) = if non_string_key { + (object.into(), string.into()) + } else { + (string.into(), object.into()) + }; + let _: ClassInstanceRef = jvm + .invoke_virtual(&invalid, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let result: Result<()> = jvm + .invoke_virtual( + &invalid, + "store", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (output, ClassInstanceRef::::from(None)), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("non-String property must throw ClassCastException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + } + + Ok(()) +} + +#[tokio::test] +async fn prop_03_eof_backslash_parity_and_incomplete_logical_line() -> Result<()> { + let jvm = properties_jvm().await?; + + for (source, key, expected) in [ + (&b"odd=slash\\"[..], "odd", "slash"), + (&b"even=slash\\\\"[..], "even", "slash\\"), + (&b"incomplete=value"[..], "incomplete", "value"), + ] { + let mut bytes = jvm.instantiate_array("B", source.len()).await?; + jvm.store_array(&mut bytes, 0, source.iter().map(|byte| *byte as i8)).await?; + + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?; + let properties = jvm.new_class("java/util/Properties", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&properties, "load", "(Ljava/io/InputStream;)V", (input,)).await?; + + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&properties, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, expected); + } + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_random.rs b/java_runtime/tests/classes/java/util/test_random.rs index 6f061b7e..d7697e2b 100644 --- a/java_runtime/tests/classes/java/util/test_random.rs +++ b/java_runtime/tests/classes/java/util/test_random.rs @@ -1,4 +1,6 @@ -use jvm::{JavaError, Result}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::util::Random; +use jvm::{Array, ClassInstanceRef, JavaError, Result}; use test_utils::test_jvm; @@ -44,3 +46,139 @@ async fn test_random_cldc11_algorithms() -> Result<()> { Ok(()) } + +#[test] +fn rng_01_descriptors_and_access_flags_match_jdk_12() { + let proto = Random::as_proto(); + assert_eq!(proto.parent_class, Some("java/lang/Object")); + assert_eq!(proto.interfaces, vec!["java/io/Serializable"]); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC); + + let expected_methods = [ + ("", "()V", MethodAccessFlags::PUBLIC), + ("", "(J)V", MethodAccessFlags::PUBLIC), + ("next", "(I)I", MethodAccessFlags::PROTECTED | MethodAccessFlags::SYNCHRONIZED), + ("nextBoolean", "()Z", MethodAccessFlags::PUBLIC), + ("nextBytes", "([B)V", MethodAccessFlags::PUBLIC), + ("nextInt", "()I", MethodAccessFlags::PUBLIC), + ("nextInt", "(I)I", MethodAccessFlags::PUBLIC), + ("nextLong", "()J", MethodAccessFlags::PUBLIC), + ("nextFloat", "()F", MethodAccessFlags::PUBLIC), + ("nextDouble", "()D", MethodAccessFlags::PUBLIC), + ("nextGaussian", "()D", MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + ("setSeed", "(J)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + ]; + assert_eq!(proto.methods.len(), expected_methods.len()); + for (name, descriptor, access_flags) in expected_methods { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing java/util/Random.{name}{descriptor}")); + assert_eq!(method.access_flags, access_flags, "wrong access flags for {name}{descriptor}"); + } + + let expected_fields = [("seed", "J"), ("nextNextGaussian", "D"), ("haveNextNextGaussian", "Z")]; + assert_eq!(proto.fields.len(), expected_fields.len()); + for (name, descriptor) in expected_fields { + let field = proto + .fields + .iter() + .find(|field| field.name == name && field.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing java/util/Random.{name}:{descriptor}")); + assert_eq!(field.access_flags, FieldAccessFlags::PRIVATE); + } +} + +#[tokio::test] +async fn rng_01_boolean_and_bytes_match_jdk_seed_oracle() -> Result<()> { + let jvm = test_jvm().await?; + + let boolean_random = jvm.new_class("java/util/Random", "(J)V", (42i64,)).await?; + assert!(jvm.invoke_virtual::<_, bool>(&boolean_random, "nextBoolean", "()Z", ()).await?); + + let bytes_random = jvm.new_class("java/util/Random", "(J)V", (42i64,)).await?; + let mut bytes: ClassInstanceRef> = jvm.instantiate_array("B", 10).await?.into(); + let _: () = jvm.invoke_virtual(&bytes_random, "nextBytes", "([B)V", (bytes.clone(),)).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, 10).await?, + vec![53, -99, 65, -70, -9, -118, -2, 13, -31, -69] + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&bytes_random, "nextInt", "()I", ()).await?, 205897768); + + jvm.store_array(&mut bytes, 0, [0i8; 10]).await?; + let _: () = jvm.invoke_virtual(&bytes_random, "setSeed", "(J)V", (42i64,)).await?; + let _: () = jvm.invoke_virtual(&bytes_random, "nextBytes", "([B)V", (bytes.clone(),)).await?; + assert_eq!( + jvm.load_array::(&bytes, 0, 10).await?, + vec![53, -99, 65, -70, -9, -118, -2, 13, -31, -69] + ); + + Ok(()) +} + +#[tokio::test] +async fn rng_01_next_bytes_null_and_empty_arrays_do_not_advance_seed() -> Result<()> { + let jvm = test_jvm().await?; + + let random = jvm.new_class("java/util/Random", "(J)V", (42i64,)).await?; + let null_bytes: ClassInstanceRef> = None.into(); + let result: Result<()> = jvm.invoke_virtual(&random, "nextBytes", "([B)V", (null_bytes,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("nextBytes(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&random, "nextInt", "()I", ()).await?, -1170105035); + + let _: () = jvm.invoke_virtual(&random, "setSeed", "(J)V", (42i64,)).await?; + let empty: ClassInstanceRef> = jvm.instantiate_array("B", 0).await?.into(); + let _: () = jvm.invoke_virtual(&random, "nextBytes", "([B)V", (empty,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&random, "nextInt", "()I", ()).await?, -1170105035); + + let _: () = jvm.invoke_virtual(&random, "setSeed", "(J)V", (42i64,)).await?; + let five: ClassInstanceRef> = jvm.instantiate_array("B", 5).await?.into(); + let _: () = jvm.invoke_virtual(&random, "nextBytes", "([B)V", (five,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&random, "nextInt", "()I", ()).await?, -1360544799); + + Ok(()) +} + +#[tokio::test] +async fn rng_01_gaussian_matches_jdk_seed_oracle_and_consumes_cache() -> Result<()> { + let jvm = test_jvm().await?; + + let random = jvm.new_class("java/util/Random", "(J)V", (42i64,)).await?; + let first: f64 = jvm.invoke_virtual(&random, "nextGaussian", "()D", ()).await?; + assert!((first - 1.1419053154730547).abs() < 1e-15); + assert!(jvm.get_field::(&random, "haveNextNextGaussian", "Z").await?); + let cached: f64 = jvm.get_field(&random, "nextNextGaussian", "D").await?; + + let second: f64 = jvm.invoke_virtual(&random, "nextGaussian", "()D", ()).await?; + assert_eq!(second.to_bits(), cached.to_bits()); + assert!((second - 0.9194079489827879).abs() < 1e-15); + assert!(!jvm.get_field::(&random, "haveNextNextGaussian", "Z").await?); + + let after_cached: i32 = jvm.invoke_virtual(&random, "nextInt", "()I", ()).await?; + let control = jvm.new_class("java/util/Random", "(J)V", (42i64,)).await?; + let _: f64 = jvm.invoke_virtual(&control, "nextDouble", "()D", ()).await?; + let _: f64 = jvm.invoke_virtual(&control, "nextDouble", "()D", ()).await?; + assert_eq!(after_cached, jvm.invoke_virtual::<_, i32>(&control, "nextInt", "()I", ()).await?); + + Ok(()) +} + +#[tokio::test] +async fn rng_02_set_seed_clears_gaussian_cache() -> Result<()> { + let jvm = test_jvm().await?; + + let random = jvm.new_class("java/util/Random", "(J)V", (42i64,)).await?; + let first: f64 = jvm.invoke_virtual(&random, "nextGaussian", "()D", ()).await?; + assert!(jvm.get_field::(&random, "haveNextNextGaussian", "Z").await?); + + let _: () = jvm.invoke_virtual(&random, "setSeed", "(J)V", (42i64,)).await?; + assert!(!jvm.get_field::(&random, "haveNextNextGaussian", "Z").await?); + let reset_first: f64 = jvm.invoke_virtual(&random, "nextGaussian", "()D", ()).await?; + assert_eq!(reset_first.to_bits(), first.to_bits()); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_string_tokenizer.rs b/java_runtime/tests/classes/java/util/test_string_tokenizer.rs new file mode 100644 index 00000000..4ecde72a --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_string_tokenizer.rs @@ -0,0 +1,160 @@ +use alloc::vec::Vec; + +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::{ + lang::{Object, String}, + util::StringTokenizer, +}; +use jvm::{ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn tok_01_constructors_and_utf16_delimiters() -> Result<()> { + let proto = StringTokenizer::as_proto(); + assert_eq!(proto.parent_class, Some("java/lang/Object")); + assert_eq!(proto.interfaces, alloc::vec!["java/util/Enumeration"]); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC)); + for descriptor in [ + "(Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;Z)V", + ] { + let constructor = proto + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == descriptor) + .expect("StringTokenizer constructor"); + assert!(constructor.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + for (name, descriptor) in [ + ("hasMoreTokens", "()Z"), + ("nextToken", "()Ljava/lang/String;"), + ("nextToken", "(Ljava/lang/String;)Ljava/lang/String;"), + ("countTokens", "()I"), + ("hasMoreElements", "()Z"), + ("nextElement", "()Ljava/lang/Object;"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .expect("StringTokenizer token method"); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + let fields = proto + .fields + .iter() + .map(|field| (field.name.as_str(), field.descriptor.as_str())) + .collect::>(); + assert_eq!( + fields, + [ + ("str", "Ljava/lang/String;"), + ("delimiters", "Ljava/lang/String;"), + ("currentPosition", "I"), + ("maxPosition", "I"), + ("returnDelimiters", "Z"), + ] + ); + + let jvm = test_jvm().await?; + let input = JavaLangString::from_rust_string(&jvm, " one\t two\nthree ").await?; + let tokenizer = jvm.new_class("java/util/StringTokenizer", "(Ljava/lang/String;)V", (input,)).await?; + assert_eq!(jvm.get_field::(&tokenizer, "maxPosition", "I").await?, 16); + assert_eq!(jvm.invoke_virtual::<_, i32>(&tokenizer, "countTokens", "()I", ()).await?, 3); + for expected in ["one", "two", "three"] { + let token: ClassInstanceRef = jvm.invoke_virtual(&tokenizer, "nextToken", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &token).await?, expected); + } + + let mut input_chars = jvm.instantiate_array("C", 5).await?; + jvm.store_array( + &mut input_chars, + 0, + [b'a' as JavaChar, 0xD800, b'b' as JavaChar, 0xD800, b'c' as JavaChar], + ) + .await?; + let utf16_input = jvm.new_class("java/lang/String", "([C)V", (input_chars,)).await?; + let mut delimiter_chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut delimiter_chars, 0, [0xD800 as JavaChar]).await?; + let utf16_delimiter = jvm.new_class("java/lang/String", "([C)V", (delimiter_chars,)).await?; + let tokenizer = jvm + .new_class( + "java/util/StringTokenizer", + "(Ljava/lang/String;Ljava/lang/String;Z)V", + (utf16_input, utf16_delimiter, true), + ) + .await?; + + assert_eq!(jvm.invoke_virtual::<_, i32>(&tokenizer, "countTokens", "()I", ()).await?, 5); + let first: ClassInstanceRef = jvm.invoke_virtual(&tokenizer, "nextToken", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + let delimiter: ClassInstanceRef = jvm.invoke_virtual(&tokenizer, "nextToken", "()Ljava/lang/String;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&delimiter, "length", "()I", ()).await?, 1); + let delimiter_value = jvm.get_field(&delimiter, "value", "[C").await?; + let delimiter_chars: Vec = jvm.load_array(&delimiter_value, 0, 1).await?; + assert_eq!(delimiter_chars, [0xD800]); + + let null_input: ClassInstanceRef = None.into(); + let result = jvm.new_class("java/util/StringTokenizer", "(Ljava/lang/String;)V", (null_input,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null input must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn tok_02_token_api_enumeration_and_delimiter_change() -> Result<()> { + let jvm = test_jvm().await?; + let input = JavaLangString::from_rust_string(&jvm, "a,b;c").await?; + let comma = JavaLangString::from_rust_string(&jvm, ",").await?; + let tokenizer = jvm + .new_class("java/util/StringTokenizer", "(Ljava/lang/String;Ljava/lang/String;)V", (input, comma)) + .await?; + + assert!(jvm.invoke_virtual::<_, bool>(&tokenizer, "hasMoreTokens", "()Z", ()).await?); + assert!(jvm.invoke_virtual::<_, bool>(&tokenizer, "hasMoreElements", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&tokenizer, "countTokens", "()I", ()).await?, 2); + + let first: ClassInstanceRef = jvm.invoke_virtual(&tokenizer, "nextToken", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + + let semicolon = JavaLangString::from_rust_string(&jvm, ";").await?; + let second: ClassInstanceRef = jvm + .invoke_virtual(&tokenizer, "nextToken", "(Ljava/lang/String;)Ljava/lang/String;", (semicolon,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &second).await?, ",b"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&tokenizer, "countTokens", "()I", ()).await?, 1); + + let last: ClassInstanceRef = jvm.invoke_virtual(&tokenizer, "nextElement", "()Ljava/lang/Object;", ()).await?; + assert!(jvm.is_instance(&**last, "java/lang/String")); + assert_eq!(JavaLangString::to_rust_string(&jvm, &last).await?, "c"); + assert!(!jvm.invoke_virtual::<_, bool>(&tokenizer, "hasMoreTokens", "()Z", ()).await?); + + let exhausted: Result> = jvm.invoke_virtual(&tokenizer, "nextToken", "()Ljava/lang/String;", ()).await; + let Err(JavaError::JavaException(exception)) = exhausted else { + panic!("exhausted tokenizer must throw NoSuchElementException"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + + let input = JavaLangString::from_rust_string(&jvm, "a,,b").await?; + let comma = JavaLangString::from_rust_string(&jvm, ",").await?; + let tokenizer = jvm + .new_class( + "java/util/StringTokenizer", + "(Ljava/lang/String;Ljava/lang/String;Z)V", + (input, comma, true), + ) + .await?; + let mut tokens = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&tokenizer, "hasMoreElements", "()Z", ()).await? { + let token: ClassInstanceRef = jvm.invoke_virtual(&tokenizer, "nextElement", "()Ljava/lang/Object;", ()).await?; + tokens.push(JavaLangString::to_rust_string(&jvm, &token).await?); + } + assert_eq!(tokens, ["a", ",", ",", "b"]); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_timer.rs b/java_runtime/tests/classes/java/util/test_timer.rs index 8d768187..88e481b6 100644 --- a/java_runtime/tests/classes/java/util/test_timer.rs +++ b/java_runtime/tests/classes/java/util/test_timer.rs @@ -1,101 +1,681 @@ -use alloc::{boxed::Box, collections::BTreeMap, vec}; +use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; +use core::time::Duration; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_runtime::{RuntimeClassProto, RuntimeContext}; -use jvm::{ClassInstanceRef, Jvm, Result}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::util::{Date, Timer, TimerTask, TimerTaskQueue, TimerThread}, +}; +use jvm::{ClassInstanceRef, JavaError, Jvm, MonitorWait, Result}; use jvm_rust::ClassDefinitionImpl; use test_utils::{TestRuntime, create_test_jvm}; -struct TestClass; -impl TestClass { - pub fn as_proto() -> RuntimeClassProto { +const TEST_BARRIER_TIMEOUT: Duration = Duration::from_secs(2); + +struct TestTimerTask; + +impl TestTimerTask { + fn as_proto() -> RuntimeClassProto { RuntimeClassProto { - name: "TestClass", + name: "TestTimerTask", parent_class: Some("java/util/TimerTask"), - interfaces: vec!["java/lang/Runnable"], + interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("run", "()V", Self::run, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("run", "()V", Self::run, MethodAccessFlags::PUBLIC), ], - fields: vec![JavaFieldProto::new("runCount", "I", Default::default())], - access_flags: Default::default(), + fields: vec![ + JavaFieldProto::new("runCount", "I", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("firstScheduledTime", "J", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("lastScheduledTime", "J", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("firstActualTime", "J", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("lastActualTime", "J", FieldAccessFlags::PUBLIC), + JavaFieldProto::new("throwOnRun", "Z", FieldAccessFlags::PUBLIC), + ], + access_flags: ClassAccessFlags::PUBLIC, } } async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { let _: () = jvm.invoke_special(&this, "java/util/TimerTask", "", "()V", ()).await?; - Ok(()) } - async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { - let count: i32 = jvm.get_field(&this, "runCount", "I").await?; - jvm.put_field(&mut this, "runCount", "I", count + 1).await?; + async fn run(jvm: &Jvm, context: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.monitor_enter(&this).await?; + let update_result = async { + let run_count: i32 = jvm.get_field(&this, "runCount", "I").await?; + let scheduled_time: i64 = jvm.invoke_virtual(&this, "scheduledExecutionTime", "()J", ()).await?; + let actual_time = context.now() as i64; + if run_count == 0 { + jvm.put_field(&mut this, "firstScheduledTime", "J", scheduled_time).await?; + jvm.put_field(&mut this, "firstActualTime", "J", actual_time).await?; + } + jvm.put_field(&mut this, "lastScheduledTime", "J", scheduled_time).await?; + jvm.put_field(&mut this, "lastActualTime", "J", actual_time).await?; + jvm.put_field(&mut this, "runCount", "I", run_count + 1).await?; + jvm.object_notify(&this, usize::MAX).await + } + .await; + let exit_result = jvm.monitor_exit(&this).await; + update_result.and(exit_result)?; + if jvm.get_field::(&this, "throwOnRun", "Z").await? { + return Err(jvm.exception("java/lang/RuntimeException", "timer task failed").await); + } Ok(()) } } -#[tokio::test] -async fn test_timer() -> Result<()> { - let runtime = TestRuntime::new(BTreeMap::new()); +async fn timer_test_jvm(now: u64) -> Result<(TestRuntime, Jvm)> { + let runtime = TestRuntime::new_with_queued_spawns_and_manual_clock(BTreeMap::new(), now); let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + TestTimerTask::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + Ok((runtime, jvm)) +} + +async fn next_spawn(runtime: &TestRuntime) -> tokio::task::JoinHandle> { + let callback = tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_spawn_callback()) + .await + .expect("timed out waiting for queued callback") + .expect("queued callback"); + tokio::spawn(async move { callback.call().await }) +} + +async fn next_spawn_while_worker_runs( + runtime: &TestRuntime, + worker: &mut tokio::task::JoinHandle>, +) -> tokio::task::JoinHandle> { + let callback = tokio::time::timeout(TEST_BARRIER_TIMEOUT, async { + tokio::select! { + callback = runtime.next_spawn_callback() => callback.expect("queued callback"), + result = worker => panic!("timer worker exited before queuing its timeout: {result:?}"), + } + }) + .await + .expect("timed out waiting for timer worker timeout callback"); + tokio::spawn(async move { callback.call().await }) +} + +async fn prepare_task_notification(jvm: &Jvm, task: &ClassInstanceRef) -> Result { + tokio::time::timeout(TEST_BARRIER_TIMEOUT, async { + jvm.monitor_enter(task).await?; + let (wait, _) = jvm.object_wait_prepare(task).await?; + Ok(wait) + }) + .await + .expect("timed out preparing task notification") +} + +async fn wait_for_task_notification(jvm: &Jvm, task: &ClassInstanceRef, wait: MonitorWait) -> Result<()> { + tokio::time::timeout(TEST_BARRIER_TIMEOUT, jvm.object_wait(wait)) + .await + .expect("timed out waiting for timer task notification")?; + jvm.monitor_exit(task).await +} + +async fn await_spawn(callback: tokio::task::JoinHandle>, stage: &str) -> Result<()> { + let joined = tokio::time::timeout(TEST_BARRIER_TIMEOUT, callback) + .await + .unwrap_or_else(|_| panic!("timed out waiting for {stage}")); + joined.unwrap_or_else(|error| panic!("{stage} panicked: {error}")) +} + +async fn assert_exception(result: Result, expected: &str) { + let Err(JavaError::JavaException(exception)) = result else { + panic!("expected {expected}"); + }; + assert_eq!(exception.class_definition().name(), expected); +} + +#[tokio::test] +async fn timer_01_to_05_registers_jdk_shaped_api_and_state() -> Result<()> { + let timer = Timer::as_proto(); + assert_eq!(timer.access_flags, ClassAccessFlags::PUBLIC); + assert_eq!( + timer + .fields + .iter() + .map(|field| (field.name.as_str(), field.descriptor.as_str(), field.access_flags)) + .collect::>(), + vec![( + "thread", + "Ljava/util/Timer$TimerThread;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL + )] + ); + for (name, descriptor) in [ + ("", "()V"), + ("schedule", "(Ljava/util/TimerTask;J)V"), + ("schedule", "(Ljava/util/TimerTask;Ljava/util/Date;)V"), + ("schedule", "(Ljava/util/TimerTask;JJ)V"), + ("schedule", "(Ljava/util/TimerTask;Ljava/util/Date;J)V"), + ("scheduleAtFixedRate", "(Ljava/util/TimerTask;JJ)V"), + ("cancel", "()V"), + ] { + let method = timer + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap(); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + + let task = TimerTask::as_proto(); + assert_eq!(task.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT); + assert_eq!( + task.methods + .iter() + .map(|method| (method.name.as_str(), method.descriptor.as_str(), method.access_flags)) + .collect::>(), + vec![ + ("", "()V", MethodAccessFlags::PROTECTED), + ("run", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ("cancel", "()Z", MethodAccessFlags::PUBLIC), + ("scheduledExecutionTime", "()J", MethodAccessFlags::PUBLIC), + ] + ); + assert_eq!( + task.fields + .iter() + .map(|field| (field.name.as_str(), field.descriptor.as_str(), field.access_flags)) + .collect::>(), + vec![ + ("lock", "Ljava/lang/Object;", FieldAccessFlags::FINAL), + ("state", "I", FieldAccessFlags::empty()), + ("nextExecutionTime", "J", FieldAccessFlags::empty()), + ("period", "J", FieldAccessFlags::empty()), + ("lastScheduledExecutionTime", "J", FieldAccessFlags::empty()), + ] + ); + + let thread = TimerThread::as_proto(); + assert_eq!( + thread + .fields + .iter() + .map(|field| (field.name.as_str(), field.descriptor.as_str(), field.access_flags)) + .collect::>(), + vec![ + ("queue", "Ljava/util/Timer$TaskQueue;", FieldAccessFlags::PRIVATE), + ("newTasksMayBeScheduled", "Z", FieldAccessFlags::empty()), + ] + ); + let queue = TimerTaskQueue::as_proto(); + assert_eq!( + queue + .fields + .iter() + .map(|field| (field.name.as_str(), field.descriptor.as_str(), field.access_flags)) + .collect::>(), + vec![ + ("queue", "[Ljava/util/TimerTask;", FieldAccessFlags::PRIVATE), + ("size", "I", FieldAccessFlags::PRIVATE), + ] + ); + + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let instance: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + assert_eq!(jvm.get_field::(&instance, "state", "I").await?, 0); + assert!(!jvm.invoke_virtual::<_, bool>(&instance, "cancel", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&instance, "state", "I").await?, 3); + assert_eq!(jvm.invoke_virtual::<_, i64>(&instance, "scheduledExecutionTime", "()J", ()).await?, 0); + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(next_spawn(&runtime).await, "cancelled timer worker").await?; + Ok(()) +} + +#[tokio::test] +async fn timer_validation_happens_before_task_mutation() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(i64::MAX as u64 - 5).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let null_task: ClassInstanceRef = None.into(); + + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (null_task, -1i64)) + .await, + "java/lang/NullPointerException", + ) + .await; + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (task.clone(), -1i64)) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (task.clone(), 10i64)) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + for method in ["schedule", "scheduleAtFixedRate"] { + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, method, "(Ljava/util/TimerTask;JJ)V", (task.clone(), 10i64, 1i64)) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + } + for method in ["schedule", "scheduleAtFixedRate"] { + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, method, "(Ljava/util/TimerTask;JJ)V", (task.clone(), 0i64, 0i64)) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + } - let class = Box::new(ClassDefinitionImpl::from_class_proto( - TestClass::as_proto(), - Box::new(runtime.clone()) as Box<_>, - )); - jvm.register_class(class, None).await?; + let negative_date = jvm.new_class("java/util/Date", "(J)V", (-1i64,)).await?; + assert_exception( + jvm.invoke_virtual::<_, ()>( + &timer, + "schedule", + "(Ljava/util/TimerTask;Ljava/util/Date;)V", + (task.clone(), negative_date), + ) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + let null_date: ClassInstanceRef = None.into(); + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;Ljava/util/Date;)V", (task.clone(), null_date)) + .await, + "java/lang/NullPointerException", + ) + .await; + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 0); + assert_eq!(jvm.get_field::(&task, "nextExecutionTime", "J").await?, 0); + assert_eq!(jvm.get_field::(&task, "period", "J").await?, 0); - let test_class = jvm.new_class("TestClass", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (task.clone(), 0i64)) + .await, + "java/lang/IllegalStateException", + ) + .await; + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 0); + await_spawn(next_spawn(&runtime).await, "validation timer worker").await?; + Ok(()) +} +#[tokio::test] +async fn timer_normalizes_long_max_period_and_rejects_first_recurrence_overflow() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let fixed_delay: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let fixed_rate: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (fixed_delay.clone(), 0i64, i64::MAX)) + .await?; let _: () = jvm - .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (test_class.clone(), 100i64, 0i64)) + .invoke_virtual( + &timer, + "scheduleAtFixedRate", + "(Ljava/util/TimerTask;JJ)V", + (fixed_rate.clone(), 0i64, i64::MAX), + ) .await?; + assert_eq!(jvm.get_field::(&fixed_delay, "period", "J").await?, (-i64::MAX) >> 1); + assert_eq!(jvm.get_field::(&fixed_rate, "period", "J").await?, i64::MAX >> 1); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(next_spawn(&runtime).await, "large-period timer worker").await?; - let _: () = jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (500i64,)).await?; - let run_count: i32 = jvm.get_field(&test_class, "runCount", "I").await?; - assert_eq!(run_count, 1); + let (runtime, jvm) = timer_test_jvm(i64::MAX as u64 - 100).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + for method in ["schedule", "scheduleAtFixedRate"] { + let task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, method, "(Ljava/util/TimerTask;JJ)V", (task.clone(), 0i64, 101i64)) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 0); + assert_eq!(jvm.get_field::(&task, "period", "J").await?, 0); + } + let date = jvm.new_class("java/util/Date", "(J)V", (i64::MAX - 50,)).await?; + let dated: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + assert_exception( + jvm.invoke_virtual::<_, ()>( + &timer, + "schedule", + "(Ljava/util/TimerTask;Ljava/util/Date;J)V", + (dated.clone(), date, 60i64), + ) + .await, + "java/lang/IllegalArgumentException", + ) + .await; + assert_eq!(jvm.get_field::(&dated, "state", "I").await?, 0); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(next_spawn(&runtime).await, "near-boundary validation timer worker").await?; + Ok(()) +} +#[tokio::test] +async fn timer_worker_stops_periodic_tasks_when_next_deadline_overflows() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(i64::MAX as u64 - 100).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let fixed_rate: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); let _: () = jvm .invoke_virtual( &timer, "scheduleAtFixedRate", "(Ljava/util/TimerTask;JJ)V", - (test_class.clone(), 100i64, 0i64), + (fixed_rate.clone(), 0i64, 60i64), ) .await?; - let _: () = jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (500i64,)).await?; - let run_count: i32 = jvm.get_field(&test_class, "runCount", "I").await?; - assert_eq!(run_count, 2); + let first_notification = prepare_task_notification(&jvm, &fixed_rate).await?; + let mut worker = next_spawn(&runtime).await; + wait_for_task_notification(&jvm, &fixed_rate, first_notification).await?; + assert_eq!(jvm.get_field::(&fixed_rate, "runCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&fixed_rate, "nextExecutionTime", "J").await?, i64::MAX - 40); + + let timeout = next_spawn_while_worker_runs(&runtime, &mut worker).await; + assert_eq!( + tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_sleep_deadline()) + .await + .expect("timed out waiting for near-boundary fixed-rate sleep"), + i64::MAX as u64 - 40 + ); + let second_notification = prepare_task_notification(&jvm, &fixed_rate).await?; + runtime.advance_time(Duration::from_millis(60)); + await_spawn(timeout, "near-boundary fixed-rate timeout callback").await?; + wait_for_task_notification(&jvm, &fixed_rate, second_notification).await?; + assert_eq!(jvm.get_field::(&fixed_rate, "runCount", "I").await?, 2); + assert_eq!(jvm.get_field::(&fixed_rate, "state", "I").await?, 2); + let thread: ClassInstanceRef = jvm.get_field(&timer, "thread", "Ljava/util/Timer$TimerThread;").await?; + let queue: ClassInstanceRef = jvm.get_field(&thread, "queue", "Ljava/util/Timer$TaskQueue;").await?; + assert_eq!(jvm.get_field::(&queue, "size", "I").await?, 0); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "near-boundary fixed-rate timer worker").await?; + let (runtime, jvm) = timer_test_jvm(i64::MAX as u64 - 100).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let fixed_delay: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (fixed_delay.clone(), 0i64, 60i64)) + .await?; + runtime.advance_time(Duration::from_millis(50)); + let notification = prepare_task_notification(&jvm, &fixed_delay).await?; + let worker = next_spawn(&runtime).await; + wait_for_task_notification(&jvm, &fixed_delay, notification).await?; + assert_eq!(jvm.get_field::(&fixed_delay, "runCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&fixed_delay, "state", "I").await?, 2); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "near-boundary fixed-delay timer worker").await?; Ok(()) } #[tokio::test] -async fn test_timer_periodic() -> Result<()> { - let runtime = TestRuntime::new(BTreeMap::new()); - let jvm = create_test_jvm(runtime.clone()).await?; +async fn timer_uses_min_heap_and_rejects_task_reuse() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let later: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let earlier: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (later.clone(), 200i64)) + .await?; + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (earlier.clone(), 100i64)) + .await?; + + let thread: ClassInstanceRef = jvm.get_field(&timer, "thread", "Ljava/util/Timer$TimerThread;").await?; + let queue: ClassInstanceRef = jvm.get_field(&thread, "queue", "Ljava/util/Timer$TaskQueue;").await?; + assert_eq!(jvm.get_field::(&queue, "size", "I").await?, 2); + let heap: ClassInstanceRef> = jvm.get_field(&queue, "queue", "[Ljava/util/TimerTask;").await?; + let first: ClassInstanceRef = jvm.load_array(&heap, 1, 1).await?.remove(0); + assert_eq!(first.identity(), earlier.identity()); + + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (earlier.clone(), 0i64)) + .await, + "java/lang/IllegalStateException", + ) + .await; + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + assert_eq!(jvm.get_field::(&queue, "size", "I").await?, 0); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(next_spawn(&runtime).await, "heap timer worker").await?; + Ok(()) +} + +#[tokio::test] +async fn timer_one_shot_executes_once_and_records_scheduled_time() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (task.clone(), 100i64)) + .await?; + + let notification = prepare_task_notification(&jvm, &task).await?; + let mut worker = next_spawn(&runtime).await; + let timeout = next_spawn_while_worker_runs(&runtime, &mut worker).await; + assert_eq!( + tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_sleep_deadline()) + .await + .expect("timed out waiting for one-shot sleep registration"), + 1_100 + ); + runtime.advance_time(Duration::from_millis(100)); + await_spawn(timeout, "one-shot timeout callback").await?; + wait_for_task_notification(&jvm, &task, notification).await?; + + assert_eq!(jvm.get_field::(&task, "runCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 2); + assert_eq!(jvm.get_field::(&task, "firstScheduledTime", "J").await?, 1_100); + assert_eq!(jvm.get_field::(&task, "firstActualTime", "J").await?, 1_100); + assert_eq!(jvm.invoke_virtual::<_, i64>(&task, "scheduledExecutionTime", "()J", ()).await?, 1_100); + assert!(!jvm.invoke_virtual::<_, bool>(&task, "cancel", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 3); + + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "one-shot timer worker").await?; + Ok(()) +} + +#[tokio::test] +async fn timer_new_first_task_wakes_worker_and_recomputes_deadline() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let later: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let earlier: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (later.clone(), 200i64)) + .await?; + + let mut worker = next_spawn(&runtime).await; + let old_timeout = next_spawn_while_worker_runs(&runtime, &mut worker).await; + assert_eq!( + tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_sleep_deadline()) + .await + .expect("timed out waiting for initial later sleep registration"), + 1_200 + ); + let notification = prepare_task_notification(&jvm, &earlier).await?; + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (earlier.clone(), 100i64)) + .await?; + let new_timeout = next_spawn_while_worker_runs(&runtime, &mut worker).await; + tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.wait_for_sleep_deadline(1_100)) + .await + .expect("timed out waiting for earlier sleep registration"); + + runtime.advance_time(Duration::from_millis(100)); + await_spawn(new_timeout, "earlier timeout callback").await?; + wait_for_task_notification(&jvm, &earlier, notification).await?; + assert_eq!(jvm.get_field::(&earlier, "runCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&later, "runCount", "I").await?, 0); + + let stale_later_timeout = tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_spawn_callback()) + .await + .expect("timed out waiting for later timeout callback") + .expect("later timeout"); + drop(stale_later_timeout); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "new-first timer worker").await?; + runtime.advance_time(Duration::from_millis(100)); + await_spawn(old_timeout, "stale timeout callback").await?; + Ok(()) +} + +#[tokio::test] +async fn timer_task_cancel_only_reports_a_live_schedule() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (task.clone(), 100i64)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&task, "cancel", "()Z", ()).await?); + assert!(!jvm.invoke_virtual::<_, bool>(&task, "cancel", "()Z", ()).await?); + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 3); + + let worker = next_spawn(&runtime).await; + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "cancelled-task timer worker").await?; + assert_eq!(jvm.get_field::(&task, "runCount", "I").await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i64>(&task, "scheduledExecutionTime", "()J", ()).await?, 0); + Ok(()) +} + +#[tokio::test] +async fn timer_fixed_delay_uses_actual_execution_time() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (task.clone(), 100i64, 50i64)) + .await?; + assert_eq!(jvm.get_field::(&task, "period", "J").await?, -50); + + let notification = prepare_task_notification(&jvm, &task).await?; + let mut worker = next_spawn(&runtime).await; + let first_timeout = next_spawn_while_worker_runs(&runtime, &mut worker).await; + assert_eq!( + tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_sleep_deadline()) + .await + .expect("timed out waiting for fixed-delay sleep registration"), + 1_100 + ); + runtime.advance_time(Duration::from_millis(200)); + await_spawn(first_timeout, "first fixed-delay timeout callback").await?; + wait_for_task_notification(&jvm, &task, notification).await?; + let next_timeout = tokio::time::timeout(TEST_BARRIER_TIMEOUT, async { + tokio::select! { + callback = runtime.next_spawn_callback() => callback.expect("next fixed-delay timeout"), + result = &mut worker => panic!("timer worker exited before fixed-delay reschedule: {result:?}"), + } + }) + .await + .expect("timed out waiting for fixed-delay reschedule"); + + assert_eq!(jvm.get_field::(&task, "runCount", "I").await?, 1); + assert_eq!(jvm.get_field::(&task, "state", "I").await?, 1); + assert_eq!(jvm.get_field::(&task, "firstScheduledTime", "J").await?, 1_100); + assert_eq!(jvm.get_field::(&task, "firstActualTime", "J").await?, 1_200); + assert_eq!(jvm.get_field::(&task, "nextExecutionTime", "J").await?, 1_250); + + drop(next_timeout); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "fixed-delay timer worker").await?; + Ok(()) +} + +#[tokio::test] +async fn timer_fixed_rate_catches_up_from_previous_deadline() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; + let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&timer, "scheduleAtFixedRate", "(Ljava/util/TimerTask;JJ)V", (task.clone(), 100i64, 50i64)) + .await?; + assert_eq!(jvm.get_field::(&task, "period", "J").await?, 50); + + let notification = prepare_task_notification(&jvm, &task).await?; + let mut worker = next_spawn(&runtime).await; + let first_timeout = next_spawn_while_worker_runs(&runtime, &mut worker).await; + assert_eq!( + tokio::time::timeout(TEST_BARRIER_TIMEOUT, runtime.next_sleep_deadline()) + .await + .expect("timed out waiting for fixed-rate sleep registration"), + 1_100 + ); + runtime.advance_time(Duration::from_millis(200)); + await_spawn(first_timeout, "first fixed-rate timeout callback").await?; + wait_for_task_notification(&jvm, &task, notification).await?; + let next_timeout = tokio::time::timeout(TEST_BARRIER_TIMEOUT, async { + tokio::select! { + callback = runtime.next_spawn_callback() => callback.expect("next fixed-rate timeout"), + result = &mut worker => panic!("timer worker exited before fixed-rate reschedule: {result:?}"), + } + }) + .await + .expect("timed out waiting for fixed-rate reschedule"); - let class = Box::new(ClassDefinitionImpl::from_class_proto( - TestClass::as_proto(), - Box::new(runtime.clone()) as Box<_>, - )); - jvm.register_class(class, None).await?; + assert_eq!(jvm.get_field::(&task, "runCount", "I").await?, 3); + assert_eq!(jvm.get_field::(&task, "firstScheduledTime", "J").await?, 1_100); + assert_eq!(jvm.get_field::(&task, "lastScheduledTime", "J").await?, 1_200); + assert_eq!(jvm.get_field::(&task, "nextExecutionTime", "J").await?, 1_250); - let test_class = jvm.new_class("TestClass", "()V", ()).await?; + drop(next_timeout); + let _: () = jvm.invoke_virtual(&timer, "cancel", "()V", ()).await?; + await_spawn(worker, "fixed-rate timer worker").await?; + Ok(()) +} +#[tokio::test] +async fn timer_date_schedule_and_task_exception_close_worker() -> Result<()> { + let (runtime, jvm) = timer_test_jvm(1_000).await?; let timer = jvm.new_class("java/util/Timer", "()V", ()).await?; + let mut task: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + let date = jvm.new_class("java/util/Date", "(J)V", (1_000i64,)).await?; let _: () = jvm - .invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (test_class.clone(), 0i64, 50i64)) + .invoke_virtual( + &timer, + "schedule", + "(Ljava/util/TimerTask;Ljava/util/Date;J)V", + (task.clone(), date, 25i64), + ) .await?; + assert_eq!(jvm.get_field::(&task, "nextExecutionTime", "J").await?, 1_000); + assert_eq!(jvm.get_field::(&task, "period", "J").await?, -25); + jvm.put_field(&mut task, "throwOnRun", "Z", true).await?; + + let notification = prepare_task_notification(&jvm, &task).await?; + let worker = next_spawn(&runtime).await; + wait_for_task_notification(&jvm, &task, notification).await?; + await_spawn(worker, "exception timer worker").await?; - let _: () = jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (500i64,)).await?; - let run_count: i32 = jvm.get_field(&test_class, "runCount", "I").await?; - assert!(run_count > 2); + let thread: ClassInstanceRef = jvm.get_field(&timer, "thread", "Ljava/util/Timer$TimerThread;").await?; + let queue: ClassInstanceRef = jvm.get_field(&thread, "queue", "Ljava/util/Timer$TaskQueue;").await?; + assert!(!jvm.get_field::(&thread, "newTasksMayBeScheduled", "Z").await?); + assert!(!jvm.get_field::(&thread, "alive", "Z").await?); + assert_eq!(jvm.get_field::(&queue, "size", "I").await?, 0); + let fresh: ClassInstanceRef = jvm.new_class("TestTimerTask", "()V", ()).await?.into(); + assert_exception( + jvm.invoke_virtual::<_, ()>(&timer, "schedule", "(Ljava/util/TimerTask;J)V", (fresh.clone(), 0i64)) + .await, + "java/lang/IllegalStateException", + ) + .await; + assert_eq!(jvm.get_field::(&fresh, "state", "I").await?, 0); Ok(()) } diff --git a/java_runtime/tests/classes/java/util/test_timezone.rs b/java_runtime/tests/classes/java/util/test_timezone.rs index 1ec3f9b6..6cf4a4ae 100644 --- a/java_runtime/tests/classes/java/util/test_timezone.rs +++ b/java_runtime/tests/classes/java/util/test_timezone.rs @@ -1,4 +1,8 @@ -use java_runtime::classes::java::{lang::String, util::TimeZone}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::{ + lang::String, + util::{SimpleTimeZone, TimeZone}, +}; use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -66,12 +70,33 @@ async fn test_simple_timezone_constructors_and_offset_validation() -> Result<()> .await?, 3_600_000 ); + for (year, month, day) in [(2024, 1, 29), (2000, 1, 29), (2026, 3, 30), (2026, 11, 31)] { + assert_eq!( + jvm.invoke_virtual::<_, i32>(&timezone, "getOffset", "(IIIIII)I", (1, year, month, day, 1, 0)) + .await?, + 3_600_000 + ); + } - let invalid: Result = jvm.invoke_virtual(&timezone, "getOffset", "(IIIIII)I", (2, 2026, 0, 1, 1, 0)).await; - let Err(JavaError::JavaException(exception)) = invalid else { - panic!("invalid era must throw IllegalArgumentException"); - }; - assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + for (era, year, month, day) in [ + (2, 2026, 0, 1), + (1, 2023, 1, 29), + (1, 1900, 1, 29), + (1, 2024, 1, 30), + (1, 2026, 3, 31), + (1, 2026, 5, 31), + (1, 2026, 8, 31), + (1, 2026, 10, 31), + ] { + let invalid: Result = jvm + .invoke_virtual(&timezone, "getOffset", "(IIIIII)I", (era, year, month, day, 1, 0)) + .await; + let Err(JavaError::JavaException(exception)) = invalid else { + panic!("invalid calendar fields must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&timezone, "getRawOffset", "()I", ()).await?, 3_600_000); + } let id = JavaLangString::from_rust_string(&jvm, "Legacy").await?; let legacy = jvm.new_class("java/util/SimpleTimeZone", "(Ljava/lang/String;)V", (id,)).await?; @@ -87,3 +112,134 @@ async fn test_simple_timezone_constructors_and_offset_validation() -> Result<()> Ok(()) } + +#[tokio::test] +async fn tz_01_default_is_cloned_on_set_and_get_and_null_resets_gmt() -> Result<()> { + let proto = TimeZone::as_proto(); + assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT)); + let default = proto + .fields + .iter() + .find(|field| field.name == "defaultTimeZone") + .expect("defaultTimeZone field"); + assert_eq!(default.descriptor, "Ljava/util/TimeZone;"); + assert!(default.access_flags.contains(FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC)); + let set_default = proto + .methods + .iter() + .find(|method| method.name == "setDefault" && method.descriptor == "(Ljava/util/TimeZone;)V") + .expect("setDefault"); + assert!( + set_default + .access_flags + .contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::SYNCHRONIZED) + ); + + let jvm = test_jvm().await?; + let id = JavaLangString::from_rust_string(&jvm, "Custom").await?; + let configured: ClassInstanceRef = jvm + .new_class("java/util/SimpleTimeZone", "(ILjava/lang/String;)V", (3_600_000, id)) + .await? + .into(); + let _: () = jvm + .invoke_static("java/util/TimeZone", "setDefault", "(Ljava/util/TimeZone;)V", (configured.clone(),)) + .await?; + + let _: () = jvm.invoke_virtual(&configured, "setRawOffset", "(I)V", (7_200_000,)).await?; + let changed = JavaLangString::from_rust_string(&jvm, "Changed").await?; + let _: () = jvm.invoke_virtual(&configured, "setID", "(Ljava/lang/String;)V", (changed,)).await?; + + let first: ClassInstanceRef = jvm + .invoke_static("java/util/TimeZone", "getDefault", "()Ljava/util/TimeZone;", ()) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "getRawOffset", "()I", ()).await?, 3_600_000); + let first_id: ClassInstanceRef = jvm.invoke_virtual(&first, "getID", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first_id).await?, "Custom"); + + let _: () = jvm.invoke_virtual(&first, "setRawOffset", "(I)V", (10_800_000,)).await?; + let second: ClassInstanceRef = jvm + .invoke_static("java/util/TimeZone", "getDefault", "()Ljava/util/TimeZone;", ()) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&second, "getRawOffset", "()I", ()).await?, 3_600_000); + + let null: ClassInstanceRef = None.into(); + let _: () = jvm + .invoke_static("java/util/TimeZone", "setDefault", "(Ljava/util/TimeZone;)V", (null,)) + .await?; + let reset: ClassInstanceRef = jvm + .invoke_static("java/util/TimeZone", "getDefault", "()Ljava/util/TimeZone;", ()) + .await?; + let id: ClassInstanceRef = jvm.invoke_virtual(&reset, "getID", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &id).await?, "GMT"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&reset, "getRawOffset", "()I", ()).await?, 0); + + Ok(()) +} + +#[tokio::test] +async fn tz_02_to_04_id_abstract_contract_and_simple_timezone_mutation() -> Result<()> { + let timezone_proto = TimeZone::as_proto(); + for (name, descriptor) in [ + ("setID", "(Ljava/lang/String;)V"), + ("inDaylightTime", "(Ljava/util/Date;)Z"), + ("setRawOffset", "(I)V"), + ] { + let method = timezone_proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .expect("TimeZone method"); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + if name != "setID" { + assert!(method.access_flags.contains(MethodAccessFlags::ABSTRACT)); + } + } + let simple_proto = SimpleTimeZone::as_proto(); + for (name, descriptor) in [("inDaylightTime", "(Ljava/util/Date;)Z"), ("setRawOffset", "(I)V")] { + let method = simple_proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .expect("SimpleTimeZone method"); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + let jvm = test_jvm().await?; + let id = JavaLangString::from_rust_string(&jvm, "Initial").await?; + let timezone = jvm.new_class("java/util/SimpleTimeZone", "(ILjava/lang/String;)V", (1_000, id)).await?; + let changed = JavaLangString::from_rust_string(&jvm, "Changed").await?; + let _: () = jvm.invoke_virtual(&timezone, "setID", "(Ljava/lang/String;)V", (changed,)).await?; + let id: ClassInstanceRef = jvm.invoke_virtual(&timezone, "getID", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &id).await?, "Changed"); + + let _: () = jvm.invoke_virtual(&timezone, "setRawOffset", "(I)V", (-2_000,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&timezone, "getRawOffset", "()I", ()).await?, -2_000); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&timezone, "getOffset", "(IIIIII)I", (1, 2026, 0, 1, 1, 0)) + .await?, + -2_000 + ); + let date = jvm.new_class("java/util/Date", "(J)V", (0i64,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&timezone, "inDaylightTime", "(Ljava/util/Date;)Z", (date,)) + .await? + ); + assert!(!jvm.invoke_virtual::<_, bool>(&timezone, "useDaylightTime", "()Z", ()).await?); + + let null_date: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&timezone, "inDaylightTime", "(Ljava/util/Date;)Z", (null_date,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null Date must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&timezone, "getRawOffset", "()I", ()).await?, -2_000); + + let null_id: ClassInstanceRef = None.into(); + let result: Result<()> = jvm.invoke_virtual(&timezone, "setID", "(Ljava/lang/String;)V", (null_id,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null ID must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_tree_map.rs b/java_runtime/tests/classes/java/util/test_tree_map.rs new file mode 100644 index 00000000..19a83557 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_tree_map.rs @@ -0,0 +1,2263 @@ +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + vec, + vec::Vec, +}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::{lang::Object, util::TreeMapEntry}, + get_runtime_class_proto, +}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm}; + +struct TreeTestComparator; + +impl TreeTestComparator { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "TreeTestComparator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Comparator"], + methods: vec![ + JavaMethodProto::new("", "(ZZZ)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "compare", + "(Ljava/lang/Object;Ljava/lang/Object;)I", + Self::compare, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![ + JavaFieldProto::new("reverse", "Z", Default::default()), + JavaFieldProto::new("allowNull", "Z", Default::default()), + JavaFieldProto::new("absolute", "Z", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + reverse: bool, + allow_null: bool, + absolute: bool, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "reverse", "Z", reverse).await?; + jvm.put_field(&mut this, "allowNull", "Z", allow_null).await?; + jvm.put_field(&mut this, "absolute", "Z", absolute).await + } + + async fn compare( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + left: ClassInstanceRef, + right: ClassInstanceRef, + ) -> Result { + let allow_null: bool = jvm.get_field(&this, "allowNull", "Z").await?; + let mut comparison = if left.is_null() || right.is_null() { + if !allow_null { + return Err(jvm.exception("java/lang/NullPointerException", "null key").await); + } + match (left.is_null(), right.is_null()) { + (true, true) => 0, + (true, false) => -1, + (false, true) => 1, + (false, false) => unreachable!(), + } + } else { + if !jvm.is_instance(left.as_ref(), "java/lang/Integer") || !jvm.is_instance(right.as_ref(), "java/lang/Integer") { + return Err(jvm.exception("java/lang/ClassCastException", "integer keys required").await); + } + let mut left_value: i32 = jvm.invoke_virtual(&left, "intValue", "()I", ()).await?; + let mut right_value: i32 = jvm.invoke_virtual(&right, "intValue", "()I", ()).await?; + if jvm.get_field::(&this, "absolute", "Z").await? { + left_value = left_value.saturating_abs(); + right_value = right_value.saturating_abs(); + } + left_value.cmp(&right_value) as i32 + }; + if jvm.get_field::(&this, "reverse", "Z").await? { + comparison = -comparison; + } + Ok(comparison) + } +} + +struct TreeDirectionalKey; + +impl TreeDirectionalKey { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "TreeDirectionalKey", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "(IZ)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("value", "I", Default::default()), + JavaFieldProto::new("fail", "Z", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32, fail: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "I", value).await?; + jvm.put_field(&mut this, "fail", "Z", fail).await + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if jvm.get_field::(&this, "fail", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "stored key comparison").await); + } + if other.is_null() || !jvm.is_instance(other.as_ref(), "TreeDirectionalKey") { + return Err(jvm.exception("java/lang/ClassCastException", "TreeDirectionalKey required").await); + } + Ok(jvm + .get_field::(&this, "value", "I") + .await? + .cmp(&jvm.get_field::(&other, "value", "I").await?) as i32) + } +} + +struct TreeEqualsValue; + +impl TreeEqualsValue { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "TreeEqualsValue", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(IZ)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), + ], + fields: vec![ + JavaFieldProto::new("group", "I", Default::default()), + JavaFieldProto::new("answer", "Z", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, group: i32, answer: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "group", "I", group).await?; + jvm.put_field(&mut this, "answer", "Z", answer).await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(other.as_ref(), "TreeEqualsValue") { + return Ok(false); + } + Ok( + jvm.get_field::(&this, "group", "I").await? == jvm.get_field::(&other, "group", "I").await? + && jvm.get_field::(&this, "answer", "Z").await?, + ) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "group", "I").await + } +} + +struct TreeChangingEntry; + +impl TreeChangingEntry { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "TreeChangingEntry", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map$Entry"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + Self::init, + MethodAccessFlags::PUBLIC, + ), + 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), + ], + fields: vec![ + JavaFieldProto::new("firstKey", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("secondKey", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("value", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("keyCalls", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + first_key: ClassInstanceRef, + second_key: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "firstKey", "Ljava/lang/Object;", first_key).await?; + jvm.put_field(&mut this, "secondKey", "Ljava/lang/Object;", second_key).await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await + } + + async fn get_key(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "keyCalls", "I").await?; + jvm.put_field(&mut this, "keyCalls", "I", calls + 1).await?; + if calls == 0 { + jvm.get_field(&this, "firstKey", "Ljava/lang/Object;").await + } else { + jvm.get_field(&this, "secondKey", "Ljava/lang/Object;").await + } + } + + async fn get_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "value", "Ljava/lang/Object;").await + } + + async fn set_value( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + let old = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + Ok(old) + } + + async fn equals(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn hash_code(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } +} + +struct TreeValueProbeEntry; + +impl TreeValueProbeEntry { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "TreeValueProbeEntry", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Map$Entry"], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/lang/Object;Ljava/lang/Object;Z)V", + Self::init, + MethodAccessFlags::PUBLIC, + ), + 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), + ], + fields: vec![ + JavaFieldProto::new("key", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("value", "Ljava/lang/Object;", Default::default()), + JavaFieldProto::new("valueCalls", "I", Default::default()), + JavaFieldProto::new("throwValue", "Z", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + throw_value: bool, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "key", "Ljava/lang/Object;", key).await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + jvm.put_field(&mut this, "throwValue", "Z", throw_value).await + } + + async fn get_key(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "key", "Ljava/lang/Object;").await + } + + async fn get_value(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let calls: i32 = jvm.get_field(&this, "valueCalls", "I").await?; + jvm.put_field(&mut this, "valueCalls", "I", calls + 1).await?; + if jvm.get_field::(&this, "throwValue", "Z").await? { + return Err(jvm.exception("java/lang/IllegalStateException", "getValue called").await); + } + jvm.get_field(&this, "value", "Ljava/lang/Object;").await + } + + async fn set_value( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result> { + let old = jvm.get_field(&this, "value", "Ljava/lang/Object;").await?; + jvm.put_field(&mut this, "value", "Ljava/lang/Object;", value).await?; + Ok(old) + } + + async fn equals(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn hash_code(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result { + Ok(0) + } +} + +async fn tree_test_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + TreeTestComparator::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + TreeDirectionalKey::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + TreeEqualsValue::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + TreeChangingEntry::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + TreeValueProbeEntry::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + Ok(jvm) +} + +async fn integer(jvm: &Jvm, value: i32) -> Result> { + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) +} + +async fn put_integer(jvm: &Jvm, map: &ClassInstanceRef, key: i32, value: Option) -> Result> { + let key = integer(jvm, key).await?; + let value = match value { + Some(value) => integer(jvm, value).await?, + None => None.into(), + }; + jvm.invoke_virtual(map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await +} + +async fn ordered_integer_keys(jvm: &Jvm, map: &ClassInstanceRef) -> Result> { + 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 expected_size: i32 = jvm.invoke_virtual(map, "size", "()I", ()).await?; + let mut result = Vec::with_capacity(expected_size as usize); + for _ in 0..expected_size { + if !jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + break; + } + let key: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + result.push(jvm.invoke_virtual(&key, "intValue", "()I", ()).await?); + } + assert!( + !jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await?, + "TreeMap iterator exceeded map size, indicating a cycle or duplicate traversal" + ); + Ok(result) +} + +async fn assert_red_black_invariants(jvm: &Jvm, map: &ClassInstanceRef) -> Result<()> { + let root: ClassInstanceRef = jvm.get_field(map, "root", "Ljava/util/TreeMap$Entry;").await?; + let expected_size: i32 = jvm.invoke_virtual(map, "size", "()I", ()).await?; + if root.is_null() { + assert_eq!(expected_size, 0); + return Ok(()); + } + assert!(jvm.get_field::(&root, "color", "Z").await?, "root must be black"); + let root_parent: ClassInstanceRef = jvm.get_field(&root, "parent", "Ljava/util/TreeMap$Entry;").await?; + assert!(root_parent.is_null()); + + let mut seen = BTreeSet::new(); + let mut black_height = None; + let mut stack = vec![(root, None::, None::, 0i32)]; + while let Some((node, lower, upper, black_count)) = stack.pop() { + assert!(seen.insert(node.identity()), "tree must not contain a cycle"); + let key: ClassInstanceRef = jvm.get_field(&node, "key", "Ljava/lang/Object;").await?; + let key: i32 = jvm.invoke_virtual(&key, "intValue", "()I", ()).await?; + assert!(lower.is_none_or(|lower| key > lower)); + assert!(upper.is_none_or(|upper| key < upper)); + let black = jvm.get_field::(&node, "color", "Z").await?; + let next_black_count = black_count + i32::from(black); + let left: ClassInstanceRef = jvm.get_field(&node, "left", "Ljava/util/TreeMap$Entry;").await?; + let right: ClassInstanceRef = jvm.get_field(&node, "right", "Ljava/util/TreeMap$Entry;").await?; + if !black { + if !left.is_null() { + assert!(jvm.get_field::(&left, "color", "Z").await?, "red node has red left child"); + } + if !right.is_null() { + assert!(jvm.get_field::(&right, "color", "Z").await?, "red node has red right child"); + } + } + for (child, child_lower, child_upper) in [(left, lower, Some(key)), (right, Some(key), upper)] { + if child.is_null() { + let leaf_height = next_black_count + 1; + assert_eq!(*black_height.get_or_insert(leaf_height), leaf_height); + } else { + let parent: ClassInstanceRef = jvm.get_field(&child, "parent", "Ljava/util/TreeMap$Entry;").await?; + assert!(!parent.is_null() && parent.identity() == node.identity()); + stack.push((child, child_lower, child_upper, next_black_count)); + } + } + } + assert_eq!(seen.len(), expected_size as usize); + Ok(()) +} + +#[test] +fn tm_01_ts_01_sorted_interfaces_and_tree_classes_are_registered() { + for (name, parent, methods) in [ + ( + "java/util/SortedMap", + "java/util/Map", + vec![ + ("comparator", "()Ljava/util/Comparator;"), + ("subMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("firstKey", "()Ljava/lang/Object;"), + ("lastKey", "()Ljava/lang/Object;"), + ], + ), + ( + "java/util/SortedSet", + "java/util/Set", + vec![ + ("comparator", "()Ljava/util/Comparator;"), + ("subSet", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("first", "()Ljava/lang/Object;"), + ("last", "()Ljava/lang/Object;"), + ], + ), + ] { + let proto = get_runtime_class_proto(name).unwrap_or_else(|| panic!("missing {name}")); + assert_eq!(proto.parent_class, None); + assert_eq!(proto.interfaces, vec![parent]); + assert!(proto.fields.is_empty()); + assert_eq!( + proto.access_flags, + ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT + ); + for (method_name, descriptor) in methods { + let method = proto + .methods + .iter() + .find(|method| method.name == method_name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing {name}.{method_name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT); + } + } + + for name in [ + "java/util/TreeMap", + "java/util/TreeMap$Entry", + "java/util/TreeMap$SubMap", + "java/util/TreeMap$KeySet", + "java/util/TreeMap$Values", + "java/util/TreeMap$EntrySet", + "java/util/TreeMap$PrivateEntryIterator", + "java/util/TreeMap$KeyIterator", + "java/util/TreeMap$ValueIterator", + "java/util/TreeMap$EntryIterator", + "java/util/TreeSet", + ] { + assert!(get_runtime_class_proto(name).is_some(), "missing {name}"); + } + + let sub_map = get_runtime_class_proto("java/util/TreeMap$SubMap").unwrap(); + assert_eq!(sub_map.access_flags, ClassAccessFlags::empty()); + assert!(sub_map.methods.iter().any(|method| { + method.name == "containsValue" && method.descriptor == "(Ljava/lang/Object;)Z" && method.access_flags == MethodAccessFlags::PUBLIC + })); + + let tree_map = get_runtime_class_proto("java/util/TreeMap").unwrap(); + assert_eq!(tree_map.parent_class, Some("java/util/AbstractMap")); + assert_eq!( + tree_map.interfaces, + vec!["java/util/SortedMap", "java/lang/Cloneable", "java/io/Serializable"] + ); + assert_eq!(tree_map.access_flags, ClassAccessFlags::PUBLIC); + for (name, descriptor) in [ + ("", "()V"), + ("", "(Ljava/util/Comparator;)V"), + ("", "(Ljava/util/Map;)V"), + ("", "(Ljava/util/SortedMap;)V"), + ("size", "()I"), + ("containsKey", "(Ljava/lang/Object;)Z"), + ("containsValue", "(Ljava/lang/Object;)Z"), + ("get", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + ("putAll", "(Ljava/util/Map;)V"), + ("remove", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("clear", "()V"), + ("comparator", "()Ljava/util/Comparator;"), + ("firstKey", "()Ljava/lang/Object;"), + ("lastKey", "()Ljava/lang/Object;"), + ("subMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("keySet", "()Ljava/util/Set;"), + ("values", "()Ljava/util/Collection;"), + ("entrySet", "()Ljava/util/Set;"), + ] { + let method = tree_map + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeMap.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + for (name, descriptor, access_flags) in [ + ( + "root", + "Ljava/util/TreeMap$Entry;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT, + ), + ("size", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + ("comparator", "Ljava/util/Comparator;", FieldAccessFlags::PRIVATE), + ] { + let field = tree_map + .fields + .iter() + .find(|field| field.name == name && field.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeMap.{name}:{descriptor}")); + assert_eq!(field.access_flags, access_flags); + } + + for (name, descriptor) in [ + ("size", "()I"), + ("containsKey", "(Ljava/lang/Object;)Z"), + ("containsValue", "(Ljava/lang/Object;)Z"), + ("get", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + ("remove", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("clear", "()V"), + ("comparator", "()Ljava/util/Comparator;"), + ("firstKey", "()Ljava/lang/Object;"), + ("lastKey", "()Ljava/lang/Object;"), + ("subMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;"), + ("keySet", "()Ljava/util/Set;"), + ("values", "()Ljava/util/Collection;"), + ("entrySet", "()Ljava/util/Set;"), + ] { + let method = sub_map + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeMap$SubMap.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + + let entry = get_runtime_class_proto("java/util/TreeMap$Entry").unwrap(); + assert_eq!(entry.interfaces, vec!["java/util/Map$Entry"]); + assert_eq!(entry.access_flags, ClassAccessFlags::FINAL); + for (name, descriptor) in [ + ("getKey", "()Ljava/lang/Object;"), + ("getValue", "()Ljava/lang/Object;"), + ("setValue", "(Ljava/lang/Object;)Ljava/lang/Object;"), + ("equals", "(Ljava/lang/Object;)Z"), + ("hashCode", "()I"), + ] { + let method = entry + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeMap$Entry.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + + let tree_set = get_runtime_class_proto("java/util/TreeSet").unwrap(); + assert_eq!(tree_set.parent_class, Some("java/util/AbstractSet")); + assert_eq!( + tree_set.interfaces, + vec!["java/util/SortedSet", "java/lang/Cloneable", "java/io/Serializable"] + ); + assert_eq!(tree_set.access_flags, ClassAccessFlags::PUBLIC); + for (name, descriptor) in [ + ("", "()V"), + ("", "(Ljava/util/Comparator;)V"), + ("", "(Ljava/util/Collection;)V"), + ("", "(Ljava/util/SortedSet;)V"), + ("size", "()I"), + ("contains", "(Ljava/lang/Object;)Z"), + ("add", "(Ljava/lang/Object;)Z"), + ("remove", "(Ljava/lang/Object;)Z"), + ("clear", "()V"), + ("iterator", "()Ljava/util/Iterator;"), + ("comparator", "()Ljava/util/Comparator;"), + ("first", "()Ljava/lang/Object;"), + ("last", "()Ljava/lang/Object;"), + ("subSet", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("headSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;"), + ("tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;"), + ] { + let method = tree_set + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeSet.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + for (name, descriptor, access_flags) in [ + ("m", "Ljava/util/SortedMap;", FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT), + ( + "PRESENT", + "Ljava/lang/Object;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + ] { + let field = tree_set + .fields + .iter() + .find(|field| field.name == name && field.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeSet.{name}:{descriptor}")); + assert_eq!(field.access_flags, access_flags); + } + + let private_iterator = get_runtime_class_proto("java/util/TreeMap$PrivateEntryIterator").unwrap(); + assert_eq!(private_iterator.access_flags, ClassAccessFlags::ABSTRACT); + for (name, descriptor, access_flags) in [ + ("hasNext", "()Z", MethodAccessFlags::PUBLIC), + ("next", "()Ljava/lang/Object;", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ("remove", "()V", MethodAccessFlags::PUBLIC), + ] { + let method = private_iterator + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing TreeMap$PrivateEntryIterator.{name}{descriptor}")); + assert_eq!(method.access_flags, access_flags); + } + for name in [ + "java/util/TreeMap$KeyIterator", + "java/util/TreeMap$ValueIterator", + "java/util/TreeMap$EntryIterator", + ] { + let proto = get_runtime_class_proto(name).unwrap(); + let next = proto + .methods + .iter() + .find(|method| method.name == "next" && method.descriptor == "()Ljava/lang/Object;") + .unwrap_or_else(|| panic!("missing {name}.next")); + assert_eq!(next.access_flags, MethodAccessFlags::PUBLIC); + } +} + +#[tokio::test] +async fn tm_02_tm_03_red_black_invariants_hold_for_deterministic_insert_delete_permutations() -> Result<()> { + let permutations = [ + (0..31).collect::>(), + (0..31).rev().collect::>(), + vec![ + 15, 7, 23, 3, 11, 19, 27, 1, 5, 9, 13, 17, 21, 25, 29, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + ], + vec![ + 12, 3, 27, 8, 19, 1, 25, 14, 6, 30, 10, 21, 4, 17, 28, 0, 15, 9, 24, 2, 18, 7, 29, 13, 5, 23, 11, 26, 16, 20, 22, + ], + ]; + for (permutation_index, insertion_order) in permutations.iter().enumerate() { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let mut expected = BTreeSet::new(); + for key in insertion_order { + assert!(put_integer(&jvm, &map, *key, Some(*key * 10)).await?.is_null()); + expected.insert(*key); + assert_red_black_invariants(&jvm, &map).await?; + assert_eq!(ordered_integer_keys(&jvm, &map).await?, expected.iter().copied().collect::>()); + } + + let mut deletion_order = insertion_order.clone(); + if permutation_index % 2 == 0 { + deletion_order.reverse(); + } else { + deletion_order.rotate_left(11); + } + for key in deletion_order { + let removed: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, key).await?,)) + .await?; + assert!(!removed.is_null()); + expected.remove(&key); + assert_red_black_invariants(&jvm, &map).await?; + assert_eq!(ordered_integer_keys(&jvm, &map).await?, expected.iter().copied().collect::>()); + } + assert_eq!(jvm.invoke_virtual::<_, i32>(&map, "size", "()I", ()).await?, 0); + } + Ok(()) +} + +#[tokio::test] +async fn tm_02_tm_03_red_black_invariants_hold_for_seeded_randomized_mutations() -> Result<()> { + for seed in 1u64..=12 { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let mut state = seed.wrapping_mul(0x9e37_79b9_7f4a_7c15); + let mut insertion_order = (0..40).collect::>(); + for index in (1..insertion_order.len()).rev() { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + insertion_order.swap(index, state as usize % (index + 1)); + } + + let mut expected = BTreeSet::new(); + for key in insertion_order { + assert!(put_integer(&jvm, &map, key, Some(key.wrapping_mul(17))).await?.is_null()); + expected.insert(key); + assert_red_black_invariants(&jvm, &map).await?; + assert_eq!(ordered_integer_keys(&jvm, &map).await?, expected.iter().copied().collect::>()); + } + + let mut deletion_order = (0..40).collect::>(); + for index in (1..deletion_order.len()).rev() { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + deletion_order.swap(index, state as usize % (index + 1)); + } + for key in deletion_order { + let removed: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, key).await?,)) + .await?; + assert!(!removed.is_null()); + expected.remove(&key); + assert_red_black_invariants(&jvm, &map).await?; + assert_eq!(ordered_integer_keys(&jvm, &map).await?, expected.iter().copied().collect::>()); + } + assert!(expected.is_empty()); + } + Ok(()) +} + +#[tokio::test] +async fn tm_02_tm_03_natural_and_custom_comparator_contracts() -> Result<()> { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + + for method in ["firstKey", "lastKey"] { + let result: Result> = jvm.invoke_virtual(&map, method, "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("empty TreeMap.{method} must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/util/NoSuchElementException")); + } + + let null_result: Result = jvm + .invoke_virtual(&map, "containsKey", "(Ljava/lang/Object;)Z", (ClassInstanceRef::::from(None),)) + .await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("natural-order null query must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + let plain_object: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let non_comparable: Result = jvm + .invoke_virtual(&map, "containsKey", "(Ljava/lang/Object;)Z", (plain_object.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = non_comparable else { + panic!("natural-order non-comparable query must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException")); + + let failing_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let failing_key: ClassInstanceRef = jvm.new_class("TreeDirectionalKey", "(IZ)V", (1, true)).await?.into(); + let failing_put: Result> = jvm + .invoke_virtual( + &failing_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (failing_key, integer(&jvm, 1).await?), + ) + .await; + let Err(JavaError::JavaException(exception)) = failing_put else { + panic!("first natural-order put must compare key with itself"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.invoke_virtual::<_, i32>(&failing_map, "size", "()I", ()).await?, 0); + + let stored: ClassInstanceRef = jvm.new_class("TreeDirectionalKey", "(IZ)V", (7, false)).await?.into(); + let stored_for_mutation = stored.clone(); + let value = integer(&jvm, 70).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (stored, value.clone()), + ) + .await?; + let mut stored_for_mutation = stored_for_mutation; + jvm.put_field(&mut stored_for_mutation, "fail", "Z", true).await?; + let query: ClassInstanceRef = jvm.new_class("TreeDirectionalKey", "(IZ)V", (7, false)).await?.into(); + let found: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (query,)) + .await?; + assert_eq!(found.identity(), value.identity(), "lookup must call query.compareTo(stored)"); + + let reverse: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (true, false, false)).await?.into(); + let reverse_map: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (reverse.clone(),)) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&reverse_map, "containsKey", "(Ljava/lang/Object;)Z", (plain_object.clone(),)) + .await? + ); + let invalid_first_put: Result> = jvm + .invoke_virtual( + &reverse_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (plain_object, integer(&jvm, 1).await?), + ) + .await; + let Err(JavaError::JavaException(exception)) = invalid_first_put else { + panic!("first put must validate comparator compatibility"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ClassCastException")); + for key in [1, 3, 2] { + put_integer(&jvm, &reverse_map, key, Some(key)).await?; + } + assert_eq!(ordered_integer_keys(&jvm, &reverse_map).await?, vec![3, 2, 1]); + let comparator: ClassInstanceRef = jvm.invoke_virtual(&reverse_map, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(comparator.identity(), reverse.identity()); + + let nulls_first: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (false, true, false)).await?.into(); + let null_map: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (nulls_first,)) + .await? + .into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (ClassInstanceRef::::from(None), integer(&jvm, 9).await?), + ) + .await?; + put_integer(&jvm, &null_map, 2, Some(2)).await?; + assert!( + jvm.invoke_virtual::<_, bool>( + &null_map, + "containsKey", + "(Ljava/lang/Object;)Z", + (ClassInstanceRef::::from(None),) + ) + .await? + ); + let first: ClassInstanceRef = jvm.invoke_virtual(&null_map, "firstKey", "()Ljava/lang/Object;", ()).await?; + assert!(first.is_null()); + + let absolute: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (false, false, true)).await?.into(); + let equivalent_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (absolute,)).await?.into(); + assert!(put_integer(&jvm, &equivalent_map, -4, Some(1)).await?.is_null()); + let replaced = put_integer(&jvm, &equivalent_map, 4, Some(2)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&replaced, "intValue", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&equivalent_map, "size", "()I", ()).await?, 1); + let key: ClassInstanceRef = jvm.invoke_virtual(&equivalent_map, "firstKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&key, "intValue", "()I", ()).await?, -4); + + Ok(()) +} + +#[tokio::test] +async fn tm_02_tm_03_map_constructors_put_all_values_and_clear() -> Result<()> { + let jvm = tree_test_jvm().await?; + let source: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + for (key, value) in [(3, 30), (1, 10), (2, 20)] { + let _: ClassInstanceRef = jvm + .invoke_virtual( + &source, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, key).await?, integer(&jvm, value).await?), + ) + .await?; + } + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "(Ljava/util/Map;)V", (source.clone(),)).await?.into(); + assert_eq!(ordered_integer_keys(&jvm, &map).await?, vec![1, 2, 3]); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&map, "comparator", "()Ljava/util/Comparator;", ()) + .await? + .is_null() + ); + assert!( + jvm.invoke_virtual::<_, bool>(&map, "containsValue", "(Ljava/lang/Object;)Z", (integer(&jvm, 20).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsValue", "(Ljava/lang/Object;)Z", (integer(&jvm, 99).await?,)) + .await? + ); + put_integer(&jvm, &map, 4, None).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&map, "containsValue", "(Ljava/lang/Object;)Z", (ClassInstanceRef::::from(None),)) + .await? + ); + + let reverse: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (true, false, false)).await?.into(); + let sorted_source: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (reverse.clone(),)) + .await? + .into(); + for key in [2, 1, 3] { + put_integer(&jvm, &sorted_source, key, Some(key)).await?; + } + let sorted_copy: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/SortedMap;)V", (sorted_source.clone(),)) + .await? + .into(); + let copied_comparator: ClassInstanceRef = jvm.invoke_virtual(&sorted_copy, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(copied_comparator.identity(), reverse.identity()); + assert_eq!(ordered_integer_keys(&jvm, &sorted_copy).await?, vec![3, 2, 1]); + + let natural_copy: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "(Ljava/util/Map;)V", (sorted_source,)).await?.into(); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&natural_copy, "comparator", "()Ljava/util/Comparator;", ()) + .await? + .is_null() + ); + assert_eq!(ordered_integer_keys(&jvm, &natural_copy).await?, vec![1, 2, 3]); + + let destination: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let _: () = jvm.invoke_virtual(&destination, "putAll", "(Ljava/util/Map;)V", (source,)).await?; + assert_eq!(ordered_integer_keys(&jvm, &destination).await?, vec![1, 2, 3]); + let _: () = jvm + .invoke_virtual(&destination, "putAll", "(Ljava/util/Map;)V", (destination.clone(),)) + .await?; + assert_eq!(ordered_integer_keys(&jvm, &destination).await?, vec![1, 2, 3]); + let _: () = jvm.invoke_virtual(&destination, "clear", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&destination, "size", "()I", ()).await?, 0); + assert_red_black_invariants(&jvm, &destination).await?; + + for descriptor in ["(Ljava/util/Map;)V", "(Ljava/util/SortedMap;)V"] { + let result: Result> = jvm + .new_class("java/util/TreeMap", descriptor, (ClassInstanceRef::::from(None),)) + .await + .map(Into::into); + let Err(JavaError::JavaException(exception)) = result else { + panic!("TreeMap{descriptor} null source must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + Ok(()) +} + +#[tokio::test] +async fn tm_04_sm_01_submaps_are_live_bounded_and_validate_nested_ranges() -> Result<()> { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + for key in 0..10 { + put_integer(&jvm, &map, key, Some(key * 10)).await?; + } + let sub_map: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 2).await?, integer(&jvm, 8).await?), + ) + .await?; + assert_eq!(ordered_integer_keys(&jvm, &sub_map).await?, vec![2, 3, 4, 5, 6, 7]); + assert_eq!(jvm.invoke_virtual::<_, i32>(&sub_map, "size", "()I", ()).await?, 6); + let first: ClassInstanceRef = jvm.invoke_virtual(&sub_map, "firstKey", "()Ljava/lang/Object;", ()).await?; + let last: ClassInstanceRef = jvm.invoke_virtual(&sub_map, "lastKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "intValue", "()I", ()).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&last, "intValue", "()I", ()).await?, 7); + assert!( + jvm.invoke_virtual::<_, bool>(&sub_map, "containsValue", "(Ljava/lang/Object;)Z", (integer(&jvm, 50).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&sub_map, "containsValue", "(Ljava/lang/Object;)Z", (integer(&jvm, 90).await?,)) + .await? + ); + + for key in [1, 8] { + let key = integer(&jvm, key).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&sub_map, "containsKey", "(Ljava/lang/Object;)Z", (key.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&sub_map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await? + .is_null() + ); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&sub_map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key.clone(),)) + .await? + .is_null() + ); + let result: Result> = jvm + .invoke_virtual( + &sub_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (key, integer(&jvm, 100).await?), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("out-of-range SubMap.put must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + } + + let old = put_integer(&jvm, &sub_map, 3, Some(333)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&old, "intValue", "()I", ()).await?, 30); + let from_root: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, 3).await?,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&from_root, "intValue", "()I", ()).await?, 333); + put_integer(&jvm, &map, 6, Some(666)).await?; + let from_view: ClassInstanceRef = jvm + .invoke_virtual(&sub_map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, 6).await?,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&from_view, "intValue", "()I", ()).await?, 666); + + let nested: ClassInstanceRef = jvm + .invoke_virtual( + &sub_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 3).await?, integer(&jvm, 7).await?), + ) + .await?; + assert_eq!(ordered_integer_keys(&jvm, &nested).await?, vec![3, 4, 5, 6]); + let same_upper: ClassInstanceRef = jvm + .invoke_virtual( + &sub_map, + "headMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 8).await?,), + ) + .await?; + assert_eq!(ordered_integer_keys(&jvm, &same_upper).await?, vec![2, 3, 4, 5, 6, 7]); + let empty_at_lower: ClassInstanceRef = jvm + .invoke_virtual( + &sub_map, + "headMap", + "(Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 2).await?,), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&empty_at_lower, "size", "()I", ()).await?, 0); + for method in ["firstKey", "lastKey"] { + let result: Result> = jvm.invoke_virtual(&empty_at_lower, method, "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("empty nested range {method} must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/util/NoSuchElementException")); + } + + for (method, descriptor, endpoint) in [ + ("headMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", 1), + ("tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", 8), + ("tailMap", "(Ljava/lang/Object;)Ljava/util/SortedMap;", 9), + ] { + let result: Result> = jvm.invoke_virtual(&sub_map, method, descriptor, (integer(&jvm, endpoint).await?,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("nested out-of-range endpoint must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + } + let reversed: Result> = jvm + .invoke_virtual( + &sub_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 7).await?, integer(&jvm, 3).await?), + ) + .await; + let Err(JavaError::JavaException(exception)) = reversed else { + panic!("reversed nested range must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + + let incompatible_upper: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let out_of_range_first: Result> = jvm + .invoke_virtual( + &sub_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 1).await?, incompatible_upper), + ) + .await; + let Err(JavaError::JavaException(exception)) = out_of_range_first else { + panic!("nested subMap must reject the from endpoint before comparing endpoints"); + }; + assert!( + jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException"), + "from endpoint validation must precede a ClassCastException from the to endpoint" + ); + + let null_bound: Result> = jvm + .invoke_virtual( + &map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (ClassInstanceRef::::from(None), integer(&jvm, 3).await?), + ) + .await; + let Err(JavaError::JavaException(exception)) = null_bound else { + panic!("natural-order null bound must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + + let _: () = jvm.invoke_virtual(&nested, "clear", "()V", ()).await?; + assert_eq!(ordered_integer_keys(&jvm, &map).await?, vec![0, 1, 2, 7, 8, 9]); + assert_red_black_invariants(&jvm, &map).await?; + Ok(()) +} + +#[tokio::test] +async fn tm_03_sm_01_views_entries_and_iterators_are_live_and_mutable() -> Result<()> { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + for key in [4, 2, 6, 1, 3, 5, 7] { + put_integer(&jvm, &map, key, Some(key * 10)).await?; + } + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&keys, "size", "()I", ()).await?, 7); + assert!( + jvm.invoke_virtual::<_, bool>(&values, "contains", "(Ljava/lang/Object;)Z", (integer(&jvm, 50).await?,)) + .await? + ); + + let entry_iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let first_entry: ClassInstanceRef = jvm.invoke_virtual(&entry_iterator, "next", "()Ljava/lang/Object;", ()).await?; + let first_key: ClassInstanceRef = jvm.invoke_virtual(&first_entry, "getKey", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first_key, "intValue", "()I", ()).await?, 1); + let replacement = integer(&jvm, 111).await?; + let old: ClassInstanceRef = jvm + .invoke_virtual(&first_entry, "setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", (replacement.clone(),)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&old, "intValue", "()I", ()).await?, 10); + let stored: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, 1).await?,)) + .await?; + assert_eq!(stored.identity(), replacement.identity()); + + assert!( + jvm.invoke_virtual::<_, bool>(&keys, "remove", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&values, "remove", "(Ljava/lang/Object;)Z", (integer(&jvm, 30).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 3).await?,)) + .await? + ); + + let candidate_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + put_integer(&jvm, &candidate_map, 5, Some(50)).await?; + let candidate_entries: ClassInstanceRef = jvm.invoke_virtual(&candidate_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let candidate_iterator: ClassInstanceRef = jvm.invoke_virtual(&candidate_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let candidate: ClassInstanceRef = jvm.invoke_virtual(&candidate_iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (candidate.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "remove", "(Ljava/lang/Object;)Z", (candidate,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 5).await?,)) + .await? + ); + + let live_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + for key in [1, 3] { + put_integer(&jvm, &live_map, key, Some(key)).await?; + } + let live_keys: ClassInstanceRef = jvm.invoke_virtual(&live_map, "keySet", "()Ljava/util/Set;", ()).await?; + let live_iterator: ClassInstanceRef = jvm.invoke_virtual(&live_keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + put_integer(&jvm, &live_map, 2, Some(2)).await?; + let mut seen = Vec::new(); + for _ in 0..3 { + assert!(jvm.invoke_virtual::<_, bool>(&live_iterator, "hasNext", "()Z", ()).await?); + let key: ClassInstanceRef = jvm.invoke_virtual(&live_iterator, "next", "()Ljava/lang/Object;", ()).await?; + seen.push(jvm.invoke_virtual::<_, i32>(&key, "intValue", "()I", ()).await?); + } + assert!( + !jvm.invoke_virtual::<_, bool>(&live_iterator, "hasNext", "()Z", ()).await?, + "live TreeMap iterator must terminate after each key exactly once" + ); + assert_eq!(seen, vec![1, 2, 3], "iterator must traverse the live tree rather than a snapshot"); + + let remove_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + for key in [4, 2, 6, 1, 3, 5, 7] { + put_integer(&jvm, &remove_map, key, Some(key)).await?; + } + let remove_keys: ClassInstanceRef = jvm.invoke_virtual(&remove_map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&remove_keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let before_next: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = before_next else { + panic!("iterator.remove before next must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + let mut visited = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let key = jvm.invoke_virtual::<_, i32>(&key, "intValue", "()I", ()).await?; + visited.push(key); + if key == 4 { + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + let second_remove: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = second_remove else { + panic!("iterator.remove twice must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + } + } + assert_eq!(visited, vec![1, 2, 3, 4, 5, 6, 7]); + assert_eq!(ordered_integer_keys(&jvm, &remove_map).await?, vec![1, 2, 3, 5, 6, 7]); + assert_red_black_invariants(&jvm, &remove_map).await?; + let exhausted: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = exhausted else { + panic!("exhausted iterator.next must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/util/NoSuchElementException")); + + let range: ClassInstanceRef = jvm + .invoke_virtual( + &remove_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 2).await?, integer(&jvm, 7).await?), + ) + .await?; + let range_values: ClassInstanceRef = jvm.invoke_virtual(&range, "values", "()Ljava/util/Collection;", ()).await?; + let _: () = jvm.invoke_virtual(&range_values, "clear", "()V", ()).await?; + assert_eq!(ordered_integer_keys(&jvm, &remove_map).await?, vec![1, 7]); + assert_red_black_invariants(&jvm, &remove_map).await?; + Ok(()) +} + +#[tokio::test] +async fn sm_01_bounded_entry_views_delegate_comparator_equality_and_set_value() -> Result<()> { + let jvm = tree_test_jvm().await?; + let reverse: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (true, false, false)).await?.into(); + let map: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (reverse.clone(),)) + .await? + .into(); + for key in 1..=7 { + put_integer(&jvm, &map, key, Some(key * 10)).await?; + } + let range: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 6).await?, integer(&jvm, 2).await?), + ) + .await?; + let comparator: ClassInstanceRef = jvm.invoke_virtual(&range, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(comparator.identity(), reverse.identity()); + assert_eq!(ordered_integer_keys(&jvm, &range).await?, vec![6, 5, 4, 3]); + + let entries: ClassInstanceRef = jvm.invoke_virtual(&range, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).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?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&key, "intValue", "()I", ()).await?, 6); + let new_value = integer(&jvm, 600).await?; + let old: ClassInstanceRef = jvm + .invoke_virtual(&entry, "setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", (new_value.clone(),)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&old, "intValue", "()I", ()).await?, 60); + let root_value: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, 6).await?,)) + .await?; + assert_eq!(root_value.identity(), new_value.identity()); + + let same_entry_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (reverse,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &same_entry_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 6).await?, new_value), + ) + .await?; + let same_entries: ClassInstanceRef = jvm.invoke_virtual(&same_entry_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let same_iterator: ClassInstanceRef = jvm.invoke_virtual(&same_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let same_entry: ClassInstanceRef = jvm.invoke_virtual(&same_iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&entry, "equals", "(Ljava/lang/Object;)Z", (same_entry.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&same_entry, "equals", "(Ljava/lang/Object;)Z", (entry.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&entry, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&same_entry, "hashCode", "()I", ()).await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (same_entry.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "remove", "(Ljava/lang/Object;)Z", (same_entry,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 6).await?,)) + .await? + ); + assert_eq!(ordered_integer_keys(&jvm, &range).await?, vec![5, 4, 3]); + Ok(()) +} + +#[tokio::test] +async fn tm_03_views_use_jdk_value_equals_directions() -> Result<()> { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let stored_false: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (1, false)).await?.into(); + let query_true: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (1, true)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 1).await?, stored_false), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&map, "containsValue", "(Ljava/lang/Object;)Z", (query_true.clone(),)) + .await?, + "TreeMap.containsValue uses query.equals(stored)" + ); + + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&values, "remove", "(Ljava/lang/Object;)Z", (query_true.clone(),)) + .await?, + "TreeMap.Values.remove follows JDK stored.equals(query)" + ); + + let candidate_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &candidate_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 1).await?, query_true), + ) + .await?; + let candidate_entries: ClassInstanceRef = jvm.invoke_virtual(&candidate_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let candidate_iterator: ClassInstanceRef = jvm.invoke_virtual(&candidate_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let candidate: ClassInstanceRef = jvm.invoke_virtual(&candidate_iterator, "next", "()Ljava/lang/Object;", ()).await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (candidate,)) + .await?, + "TreeMap.EntrySet.contains follows JDK storedValue.equals(candidateValue)" + ); + + let stored_true: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (2, true)).await?.into(); + let query_false: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (2, false)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 2).await?, stored_true), + ) + .await?; + assert!( + jvm.invoke_virtual::<_, bool>(&values, "remove", "(Ljava/lang/Object;)Z", (query_false,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + + let stored_true: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (3, true)).await?.into(); + let candidate_false: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (3, false)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 3).await?, stored_true), + ) + .await?; + let candidate_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &candidate_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 3).await?, candidate_false), + ) + .await?; + let candidate_entries: ClassInstanceRef = jvm.invoke_virtual(&candidate_map, "entrySet", "()Ljava/util/Set;", ()).await?; + let candidate_iterator: ClassInstanceRef = jvm.invoke_virtual(&candidate_entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let candidate: ClassInstanceRef = jvm.invoke_virtual(&candidate_iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "contains", "(Ljava/lang/Object;)Z", (candidate.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&entries, "remove", "(Ljava/lang/Object;)Z", (candidate,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 3).await?,)) + .await? + ); + + let changing_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + put_integer(&jvm, &changing_map, 10, Some(100)).await?; + put_integer(&jvm, &changing_map, 20, Some(100)).await?; + let changing_entry: ClassInstanceRef = jvm + .new_class( + "TreeChangingEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + (integer(&jvm, 10).await?, integer(&jvm, 20).await?, integer(&jvm, 100).await?), + ) + .await? + .into(); + let changing_entries: ClassInstanceRef = jvm.invoke_virtual(&changing_map, "entrySet", "()Ljava/util/Set;", ()).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&changing_entries, "remove", "(Ljava/lang/Object;)Z", (changing_entry.clone(),)) + .await? + ); + assert_eq!(jvm.get_field::(&changing_entry, "keyCalls", "I").await?, 1); + assert!( + !jvm.invoke_virtual::<_, bool>(&changing_map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 10).await?,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&changing_map, "containsKey", "(Ljava/lang/Object;)Z", (integer(&jvm, 20).await?,)) + .await? + ); + Ok(()) +} + +#[tokio::test] +async fn tm_03_sm_01_typed_to_array_preserves_component_and_live_view_contracts() -> Result<()> { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + for key in 1..=4 { + put_integer(&jvm, &map, key, Some(key * 10)).await?; + } + let sub_map: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 2).await?, integer(&jvm, 4).await?), + ) + .await?; + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let sub_keys: ClassInstanceRef = jvm.invoke_virtual(&sub_map, "keySet", "()Ljava/util/Set;", ()).await?; + let sub_values: ClassInstanceRef = jvm.invoke_virtual(&sub_map, "values", "()Ljava/util/Collection;", ()).await?; + let sub_entries: ClassInstanceRef = jvm.invoke_virtual(&sub_map, "entrySet", "()Ljava/util/Set;", ()).await?; + + let sentinel = integer(&jvm, -1).await?; + let mut key_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Integer;", 6).await?.into(); + jvm.store_array( + &mut key_destination, + 0, + [ + sentinel.clone(), + sentinel.clone(), + sentinel.clone(), + sentinel.clone(), + sentinel.clone(), + sentinel.clone(), + ], + ) + .await?; + let key_result: ClassInstanceRef> = jvm + .invoke_virtual(&keys, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (key_destination.clone(),)) + .await?; + assert_eq!(key_result.identity(), key_destination.identity()); + let key_elements = jvm.load_array::>(&key_result, 0, 6).await?; + for (index, expected) in [1, 2, 3, 4].into_iter().enumerate() { + assert_eq!(jvm.invoke_virtual::<_, i32>(&key_elements[index], "intValue", "()I", ()).await?, expected); + } + assert!(key_elements[4].is_null(), "a reused oversized destination needs a null terminator"); + assert_eq!( + key_elements[5].identity(), + sentinel.identity(), + "elements after the terminator must be preserved" + ); + + let value_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Integer;", 0).await?.into(); + let value_result: ClassInstanceRef> = jvm + .invoke_virtual( + &values, + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (value_destination.clone(),), + ) + .await?; + assert_ne!(value_result.identity(), value_destination.identity()); + assert_eq!(value_result.class_definition().name(), "[Ljava/lang/Integer;"); + assert_eq!(jvm.array_length(&value_result).await?, 4); + let value_elements = jvm.load_array::>(&value_result, 0, 4).await?; + for (index, expected) in [10, 20, 30, 40].into_iter().enumerate() { + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value_elements[index], "intValue", "()I", ()).await?, + expected + ); + } + + let entry_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Map$Entry;", 0).await?.into(); + let entry_result: ClassInstanceRef> = jvm + .invoke_virtual(&entries, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (entry_destination,)) + .await?; + assert_eq!(entry_result.class_definition().name(), "[Ljava/util/Map$Entry;"); + let root_entries = jvm.load_array::>(&entry_result, 0, 4).await?; + let replacement = integer(&jvm, 111).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &root_entries[0], + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (replacement.clone(),), + ) + .await?; + let stored: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, 1).await?,)) + .await?; + assert_eq!( + stored.identity(), + replacement.identity(), + "typed entry arrays must retain live map entries" + ); + + let mut sub_key_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Integer;", 4).await?.into(); + jvm.store_array( + &mut sub_key_destination, + 0, + [sentinel.clone(), sentinel.clone(), sentinel.clone(), sentinel.clone()], + ) + .await?; + let sub_key_result: ClassInstanceRef> = jvm + .invoke_virtual( + &sub_keys, + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (sub_key_destination.clone(),), + ) + .await?; + assert_eq!(sub_key_result.identity(), sub_key_destination.identity()); + let sub_key_elements = jvm.load_array::>(&sub_key_result, 0, 4).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&sub_key_elements[0], "intValue", "()I", ()).await?, 2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&sub_key_elements[1], "intValue", "()I", ()).await?, 3); + assert!(sub_key_elements[2].is_null()); + assert_eq!(sub_key_elements[3].identity(), sentinel.identity()); + + let sub_value_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Integer;", 0).await?.into(); + let sub_value_result: ClassInstanceRef> = jvm + .invoke_virtual( + &sub_values, + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (sub_value_destination,), + ) + .await?; + assert_eq!(sub_value_result.class_definition().name(), "[Ljava/lang/Integer;"); + let sub_value_elements = jvm.load_array::>(&sub_value_result, 0, 2).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&sub_value_elements[0], "intValue", "()I", ()).await?, 20); + assert_eq!(jvm.invoke_virtual::<_, i32>(&sub_value_elements[1], "intValue", "()I", ()).await?, 30); + + let sub_entry_destination: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/Map$Entry;", 0).await?.into(); + let sub_entry_result: ClassInstanceRef> = jvm + .invoke_virtual( + &sub_entries, + "toArray", + "([Ljava/lang/Object;)[Ljava/lang/Object;", + (sub_entry_destination,), + ) + .await?; + let sub_entry_elements = jvm.load_array::>(&sub_entry_result, 0, 2).await?; + let replacement = integer(&jvm, 222).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &sub_entry_elements[0], + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + (replacement.clone(),), + ) + .await?; + let stored: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (integer(&jvm, 2).await?,)) + .await?; + assert_eq!( + stored.identity(), + replacement.identity(), + "SubMap typed entry arrays must remain root-backed" + ); + + for (name, view) in [ + ("TreeMap.keySet", keys.clone()), + ("TreeMap.values", values.clone()), + ("SubMap.keySet", sub_keys.clone()), + ("SubMap.values", sub_values.clone()), + ] { + let incompatible: ClassInstanceRef> = jvm.instantiate_array("LTreeEqualsValue;", 4).await?.into(); + let result: Result>> = jvm + .invoke_virtual(&view, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (incompatible,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name}.toArray must reject an incompatible component type"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + } + for (name, view) in [("TreeMap.entrySet", entries), ("SubMap.entrySet", sub_entries)] { + let incompatible: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Integer;", 4).await?.into(); + let result: Result>> = jvm + .invoke_virtual(&view, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (incompatible,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name}.toArray must reject an incompatible component type"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + } + + let partial_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let first_value: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (1, true)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &partial_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 1).await?, first_value.clone()), + ) + .await?; + put_integer(&jvm, &partial_map, 2, Some(20)).await?; + let third_value: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (3, true)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &partial_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (integer(&jvm, 3).await?, third_value), + ) + .await?; + let partial_sub_map: ClassInstanceRef = jvm + .invoke_virtual( + &partial_map, + "subMap", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedMap;", + (integer(&jvm, 1).await?, integer(&jvm, 3).await?), + ) + .await?; + let partial_values: ClassInstanceRef = jvm.invoke_virtual(&partial_map, "values", "()Ljava/util/Collection;", ()).await?; + let partial_sub_values: ClassInstanceRef = jvm.invoke_virtual(&partial_sub_map, "values", "()Ljava/util/Collection;", ()).await?; + for (name, view) in [("TreeMap.values", partial_values), ("SubMap.values", partial_sub_values)] { + let array_sentinel: ClassInstanceRef = jvm.new_class("TreeEqualsValue", "(IZ)V", (99, true)).await?.into(); + let mut destination: ClassInstanceRef> = jvm.instantiate_array("LTreeEqualsValue;", 4).await?.into(); + jvm.store_array( + &mut destination, + 0, + [ + array_sentinel.clone(), + array_sentinel.clone(), + array_sentinel.clone(), + array_sentinel.clone(), + ], + ) + .await?; + let result: Result>> = jvm + .invoke_virtual(&view, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", (destination.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{name}.toArray must fail at the first incompatible element"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/ArrayStoreException")); + let after = jvm.load_array::>(&destination, 0, 4).await?; + assert_eq!( + after[0].identity(), + first_value.identity(), + "compatible prefix must be written before ASE" + ); + assert_eq!(after[1].identity(), array_sentinel.identity(), "failing slot must remain unchanged"); + assert_eq!(after[2].identity(), array_sentinel.identity()); + assert_eq!(after[3].identity(), array_sentinel.identity()); + } + Ok(()) +} + +#[tokio::test] +async fn tm_03_entry_equals_short_circuits_before_reading_value() -> Result<()> { + let jvm = tree_test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + put_integer(&jvm, &map, 1, Some(10)).await?; + let entries: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&entries, "iterator", "()Ljava/util/Iterator;", ()).await?; + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + + let different_key: ClassInstanceRef = jvm + .new_class( + "TreeValueProbeEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Z)V", + (integer(&jvm, 2).await?, integer(&jvm, 10).await?, true), + ) + .await? + .into(); + assert!( + !jvm.invoke_virtual::<_, bool>(&entry, "equals", "(Ljava/lang/Object;)Z", (different_key.clone(),)) + .await? + ); + assert_eq!( + jvm.get_field::(&different_key, "valueCalls", "I").await?, + 0, + "TreeMap.Entry.equals must not call getValue after a key mismatch" + ); + + let equal_key: ClassInstanceRef = jvm + .new_class( + "TreeValueProbeEntry", + "(Ljava/lang/Object;Ljava/lang/Object;Z)V", + (integer(&jvm, 1).await?, integer(&jvm, 10).await?, true), + ) + .await? + .into(); + let result: Result = jvm.invoke_virtual(&entry, "equals", "(Ljava/lang/Object;)Z", (equal_key.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("TreeMap.Entry.equals must call getValue after an equal key and propagate its exception"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalStateException")); + assert_eq!(jvm.get_field::(&equal_key, "valueCalls", "I").await?, 1); + Ok(()) +} + +#[tokio::test] +async fn tm_03_ts_02_cross_implementation_equals_and_hash_code_follow_jdk_contracts() -> Result<()> { + let jvm = tree_test_jvm().await?; + let tree_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + let hash_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + for map in [tree_map.clone(), hash_map.clone()] { + put_integer(&jvm, &map, 1, None).await?; + put_integer(&jvm, &map, 2, Some(20)).await?; + } + assert!( + jvm.invoke_virtual::<_, bool>(&tree_map, "equals", "(Ljava/lang/Object;)Z", (hash_map.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&hash_map, "equals", "(Ljava/lang/Object;)Z", (tree_map.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&tree_map, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&hash_map, "hashCode", "()I", ()).await? + ); + + let incompatible_hash_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let plain_key: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &incompatible_hash_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (plain_key, ClassInstanceRef::::from(None)), + ) + .await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&incompatible_hash_map, "equals", "(Ljava/lang/Object;)Z", (tree_map.clone(),)) + .await?, + "AbstractMap.equals must convert TreeMap CCE to false" + ); + let null_key_hash_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &null_key_hash_map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (ClassInstanceRef::::from(None), ClassInstanceRef::::from(None)), + ) + .await?; + let single_tree_map: ClassInstanceRef = jvm.new_class("java/util/TreeMap", "()V", ()).await?.into(); + put_integer(&jvm, &single_tree_map, 1, None).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&null_key_hash_map, "equals", "(Ljava/lang/Object;)Z", (single_tree_map.clone(),)) + .await?, + "AbstractMap.equals must convert TreeMap NPE to false" + ); + + let absolute: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (false, false, true)).await?.into(); + let equivalent_tree_map: ClassInstanceRef = jvm + .new_class("java/util/TreeMap", "(Ljava/util/Comparator;)V", (absolute.clone(),)) + .await? + .into(); + put_integer(&jvm, &equivalent_tree_map, -5, None).await?; + let same_key_hash_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + put_integer(&jvm, &same_key_hash_map, -5, None).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&equivalent_tree_map, "equals", "(Ljava/lang/Object;)Z", (same_key_hash_map.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&same_key_hash_map, "equals", "(Ljava/lang/Object;)Z", (equivalent_tree_map.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&equivalent_tree_map, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&same_key_hash_map, "hashCode", "()I", ()).await? + ); + + let comparator_equivalent_hash_map: ClassInstanceRef = jvm.new_class("java/util/HashMap", "()V", ()).await?.into(); + put_integer(&jvm, &comparator_equivalent_hash_map, 5, None).await?; + assert!( + !jvm.invoke_virtual::<_, bool>( + &equivalent_tree_map, + "equals", + "(Ljava/lang/Object;)Z", + (comparator_equivalent_hash_map.clone(),) + ) + .await?, + "TreeMap equality iterates its retained key and therefore uses Object.equals in the peer map" + ); + assert!( + jvm.invoke_virtual::<_, bool>( + &comparator_equivalent_hash_map, + "equals", + "(Ljava/lang/Object;)Z", + (equivalent_tree_map.clone(),) + ) + .await?, + "HashMap equality queries TreeMap with the comparator-equivalent key" + ); + assert_ne!( + jvm.invoke_virtual::<_, i32>(&equivalent_tree_map, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&comparator_equivalent_hash_map, "hashCode", "()I", ()) + .await?, + "a comparator inconsistent with equals intentionally violates the general Map contract" + ); + + let tree_set: ClassInstanceRef = jvm.new_class("java/util/TreeSet", "()V", ()).await?.into(); + let hash_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + for set in [tree_set.clone(), hash_set.clone()] { + for key in [1, 2, 3] { + assert!( + jvm.invoke_virtual::<_, bool>(&set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, key).await?,)) + .await? + ); + } + } + assert!( + jvm.invoke_virtual::<_, bool>(&tree_set, "equals", "(Ljava/lang/Object;)Z", (hash_set.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&hash_set, "equals", "(Ljava/lang/Object;)Z", (tree_set.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&tree_set, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&hash_set, "hashCode", "()I", ()).await? + ); + + let nulls_first: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (false, true, false)).await?.into(); + let null_tree_set: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/Comparator;)V", (nulls_first,)) + .await? + .into(); + let null_hash_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + for set in [null_tree_set.clone(), null_hash_set.clone()] { + assert!( + jvm.invoke_virtual::<_, bool>(&set, "add", "(Ljava/lang/Object;)Z", (ClassInstanceRef::::from(None),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, 1).await?,)) + .await? + ); + } + assert!( + jvm.invoke_virtual::<_, bool>(&null_tree_set, "equals", "(Ljava/lang/Object;)Z", (null_hash_set.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&null_hash_set, "equals", "(Ljava/lang/Object;)Z", (null_tree_set.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&null_tree_set, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&null_hash_set, "hashCode", "()I", ()).await? + ); + + let equivalent_tree_set: ClassInstanceRef = jvm.new_class("java/util/TreeSet", "(Ljava/util/Comparator;)V", (absolute,)).await?.into(); + assert!( + jvm.invoke_virtual::<_, bool>(&equivalent_tree_set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, -5).await?,)) + .await? + ); + let same_key_hash_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + assert!( + jvm.invoke_virtual::<_, bool>(&same_key_hash_set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, -5).await?,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&equivalent_tree_set, "equals", "(Ljava/lang/Object;)Z", (same_key_hash_set.clone(),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&same_key_hash_set, "equals", "(Ljava/lang/Object;)Z", (equivalent_tree_set.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&equivalent_tree_set, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&same_key_hash_set, "hashCode", "()I", ()).await? + ); + + let comparator_equivalent_hash_set: ClassInstanceRef = jvm.new_class("java/util/HashSet", "()V", ()).await?.into(); + assert!( + jvm.invoke_virtual::<_, bool>( + &comparator_equivalent_hash_set, + "add", + "(Ljava/lang/Object;)Z", + (integer(&jvm, 5).await?,) + ) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>( + &equivalent_tree_set, + "equals", + "(Ljava/lang/Object;)Z", + (comparator_equivalent_hash_set.clone(),) + ) + .await?, + "TreeSet.containsAll uses comparator equivalence" + ); + assert!( + !jvm.invoke_virtual::<_, bool>( + &comparator_equivalent_hash_set, + "equals", + "(Ljava/lang/Object;)Z", + (equivalent_tree_set.clone(),) + ) + .await?, + "HashSet.containsAll uses Object.equals" + ); + assert_ne!( + jvm.invoke_virtual::<_, i32>(&equivalent_tree_set, "hashCode", "()I", ()).await?, + jvm.invoke_virtual::<_, i32>(&comparator_equivalent_hash_set, "hashCode", "()I", ()) + .await?, + "a comparator inconsistent with equals intentionally violates the general Set contract" + ); + Ok(()) +} + +#[tokio::test] +async fn ts_02_ts_03_tree_set_core_constructors_and_comparator_equivalence() -> Result<()> { + let jvm = tree_test_jvm().await?; + let set: ClassInstanceRef = jvm.new_class("java/util/TreeSet", "()V", ()).await?.into(); + for (key, expected_added) in [(3, true), (1, true), (2, true), (2, false)] { + assert_eq!( + jvm.invoke_virtual::<_, bool>(&set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, key).await?,)) + .await?, + expected_added + ); + } + assert_eq!(jvm.invoke_virtual::<_, i32>(&set, "size", "()I", ()).await?, 3); + assert!( + jvm.invoke_virtual::<_, bool>(&set, "contains", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + let first: ClassInstanceRef = jvm.invoke_virtual(&set, "first", "()Ljava/lang/Object;", ()).await?; + let last: ClassInstanceRef = jvm.invoke_virtual(&set, "last", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&first, "intValue", "()I", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i32>(&last, "intValue", "()I", ()).await?, 3); + assert!( + jvm.invoke_virtual::<_, bool>(&set, "remove", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&set, "remove", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + + let collection: ClassInstanceRef = jvm.new_class("java/util/ArrayList", "()V", ()).await?.into(); + for key in [4, 2, 3, 2] { + let _: bool = jvm + .invoke_virtual(&collection, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, key).await?,)) + .await?; + } + let collection_set: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/Collection;)V", (collection,)) + .await? + .into(); + let mut collection_values = Vec::new(); + let iterator: ClassInstanceRef = jvm.invoke_virtual(&collection_set, "iterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + collection_values.push(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?); + } + assert_eq!(collection_values, vec![2, 3, 4]); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&collection_set, "comparator", "()Ljava/util/Comparator;", ()) + .await? + .is_null() + ); + + let reverse: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (true, false, false)).await?.into(); + let reverse_set: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/Comparator;)V", (reverse.clone(),)) + .await? + .into(); + for key in [1, 3, 2] { + let _: bool = jvm + .invoke_virtual(&reverse_set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, key).await?,)) + .await?; + } + let sorted_copy: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/SortedSet;)V", (reverse_set.clone(),)) + .await? + .into(); + let copied_comparator: ClassInstanceRef = jvm.invoke_virtual(&sorted_copy, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(copied_comparator.identity(), reverse.identity()); + let mut copied_values = Vec::new(); + let iterator: ClassInstanceRef = jvm.invoke_virtual(&sorted_copy, "iterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + copied_values.push(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?); + } + assert_eq!(copied_values, vec![3, 2, 1]); + + let collection_copy: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/Collection;)V", (reverse_set,)) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, ClassInstanceRef>(&collection_copy, "comparator", "()Ljava/util/Comparator;", ()) + .await? + .is_null() + ); + let mut natural_values = Vec::new(); + let iterator: ClassInstanceRef = jvm.invoke_virtual(&collection_copy, "iterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + natural_values.push(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?); + } + assert_eq!(natural_values, vec![1, 2, 3]); + + let absolute: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (false, false, true)).await?.into(); + let equivalent: ClassInstanceRef = jvm.new_class("java/util/TreeSet", "(Ljava/util/Comparator;)V", (absolute,)).await?.into(); + assert!( + jvm.invoke_virtual::<_, bool>(&equivalent, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, -5).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&equivalent, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, 5).await?,)) + .await? + ); + let retained: ClassInstanceRef = jvm.invoke_virtual(&equivalent, "first", "()Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&retained, "intValue", "()I", ()).await?, -5); + + for descriptor in ["(Ljava/util/Collection;)V", "(Ljava/util/SortedSet;)V"] { + let result: Result> = jvm + .new_class("java/util/TreeSet", descriptor, (ClassInstanceRef::::from(None),)) + .await + .map(Into::into); + let Err(JavaError::JavaException(exception)) = result else { + panic!("TreeSet{descriptor} null source must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + + let _: () = jvm.invoke_virtual(&set, "clear", "()V", ()).await?; + for method in ["first", "last"] { + let result: Result> = jvm.invoke_virtual(&set, method, "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("empty TreeSet.{method} must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/util/NoSuchElementException")); + } + Ok(()) +} + +#[tokio::test] +async fn ts_03_tree_set_ranges_are_live_bounded_and_iterator_mutable() -> Result<()> { + let jvm = tree_test_jvm().await?; + let set: ClassInstanceRef = jvm.new_class("java/util/TreeSet", "()V", ()).await?.into(); + for key in 0..10 { + let _: bool = jvm + .invoke_virtual(&set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, key).await?,)) + .await?; + } + let range: ClassInstanceRef = jvm + .invoke_virtual( + &set, + "subSet", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/SortedSet;", + (integer(&jvm, 2).await?, integer(&jvm, 8).await?), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&range, "size", "()I", ()).await?, 6); + assert!( + jvm.invoke_virtual::<_, bool>(&range, "contains", "(Ljava/lang/Object;)Z", (integer(&jvm, 2).await?,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&range, "contains", "(Ljava/lang/Object;)Z", (integer(&jvm, 8).await?,)) + .await? + ); + + let nested: ClassInstanceRef = jvm + .invoke_virtual(&range, "tailSet", "(Ljava/lang/Object;)Ljava/util/SortedSet;", (integer(&jvm, 4).await?,)) + .await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&nested, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut nested_values = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let value = jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?; + nested_values.push(value); + if value == 5 { + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + } + } + assert_eq!(nested_values, vec![4, 5, 6, 7]); + assert!( + !jvm.invoke_virtual::<_, bool>(&set, "contains", "(Ljava/lang/Object;)Z", (integer(&jvm, 5).await?,)) + .await? + ); + + assert!( + jvm.invoke_virtual::<_, bool>(&range, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, 5).await?,)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&set, "contains", "(Ljava/lang/Object;)Z", (integer(&jvm, 5).await?,)) + .await? + ); + let outside_add: Result = jvm + .invoke_virtual(&range, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, 8).await?,)) + .await; + let Err(JavaError::JavaException(exception)) = outside_add else { + panic!("range add at exclusive upper bound must throw"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/IllegalArgumentException")); + + let _: () = jvm.invoke_virtual(&nested, "clear", "()V", ()).await?; + let mut remaining = Vec::new(); + let iterator: ClassInstanceRef = jvm.invoke_virtual(&set, "iterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + remaining.push(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?); + } + assert_eq!(remaining, vec![0, 1, 2, 3, 8, 9]); + + let nulls_first: ClassInstanceRef = jvm.new_class("TreeTestComparator", "(ZZZ)V", (false, true, false)).await?.into(); + let null_set: ClassInstanceRef = jvm + .new_class("java/util/TreeSet", "(Ljava/util/Comparator;)V", (nulls_first.clone(),)) + .await? + .into(); + assert!( + jvm.invoke_virtual::<_, bool>(&null_set, "add", "(Ljava/lang/Object;)Z", (ClassInstanceRef::::from(None),)) + .await? + ); + assert!( + jvm.invoke_virtual::<_, bool>(&null_set, "add", "(Ljava/lang/Object;)Z", (integer(&jvm, 1).await?,)) + .await? + ); + let first: ClassInstanceRef = jvm.invoke_virtual(&null_set, "first", "()Ljava/lang/Object;", ()).await?; + assert!(first.is_null()); + let comparator: ClassInstanceRef = jvm.invoke_virtual(&null_set, "comparator", "()Ljava/util/Comparator;", ()).await?; + assert_eq!(comparator.identity(), nulls_first.identity()); + let null_tail: ClassInstanceRef = jvm + .invoke_virtual( + &null_set, + "tailSet", + "(Ljava/lang/Object;)Ljava/util/SortedSet;", + (ClassInstanceRef::::from(None),), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&null_tail, "size", "()I", ()).await?, 2); + let first: ClassInstanceRef = jvm.invoke_virtual(&null_tail, "first", "()Ljava/lang/Object;", ()).await?; + assert!(first.is_null(), "null range bound must not be mistaken for an unbounded view"); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/test_vector.rs b/java_runtime/tests/classes/java/util/test_vector.rs index f6d7979a..411ce018 100644 --- a/java_runtime/tests/classes/java/util/test_vector.rs +++ b/java_runtime/tests/classes/java/util/test_vector.rs @@ -414,7 +414,7 @@ async fn test_vector_collection_and_list_wrappers() -> Result<()> { } #[tokio::test] -async fn test_vector_itr_wrapper() -> Result<()> { +async fn test_vector_itr_is_live_and_remove_writes_through() -> Result<()> { let jvm = test_jvm().await?; let vector = jvm.new_class("java/util/Vector", "()V", ()).await?; @@ -440,7 +440,13 @@ async fn test_vector_itr_wrapper() -> Result<()> { assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "second"); let has_next: bool = jvm.invoke_virtual(&iterator, "hasNext", "()Z", ()).await?; - assert!(!has_next); + assert!(has_next); + + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &value).await?, "third"); + + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&vector, "size", "()I", ()).await?, 2); let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; let Err(JavaError::JavaException(exception)) = result else { @@ -452,7 +458,7 @@ async fn test_vector_itr_wrapper() -> Result<()> { 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")); let empty_vector = jvm.new_class("java/util/Vector", "()V", ()).await?; let empty_iterator: ClassInstanceRef = jvm.invoke_virtual(&empty_vector, "iterator", "()Ljava/util/Iterator;", ()).await?; diff --git a/jvm/src/array_class_definition.rs b/jvm/src/array_class_definition.rs index 69d60d0d..9b6f90d5 100644 --- a/jvm/src/array_class_definition.rs +++ b/jvm/src/array_class_definition.rs @@ -2,6 +2,7 @@ use alloc::{ boxed::Box, format, string::{String, ToString}, + vec, vec::Vec, }; use dyn_clone::clone_trait_object; @@ -28,7 +29,7 @@ impl ClassDefinition for T { } fn interface_names(&self) -> Vec { - Vec::new() + vec!["java/lang/Cloneable".to_string(), "java/io/Serializable".to_string()] } fn access_flags(&self) -> ClassAccessFlags { diff --git a/jvm/src/jvm.rs b/jvm/src/jvm.rs index e9c22672..4f3f5018 100644 --- a/jvm/src/jvm.rs +++ b/jvm/src/jvm.rs @@ -3,11 +3,15 @@ use alloc::{borrow::ToOwned, boxed::Box, collections::BTreeMap, format, string::String, sync::Arc, vec::Vec}; use core::{ fmt::Debug, + future::{Future, poll_fn}, iter, + pin::pin, sync::atomic::{AtomicBool, AtomicU64, Ordering}, + task::Poll, }; use dyn_clone::clone_box; +use event_listener::Event; use hashbrown::HashSet; use parking_lot::RwLock; @@ -29,7 +33,7 @@ use crate::{ method::Method, monitor::{Monitor, MonitorWait, MonitorWaitTimeout}, runtime::{JavaLangClass, JavaLangClassLoader, JavaLangString}, - thread::JvmThread, + thread::{JvmThread, ThreadInterruptWaiter}, r#type::JavaType, value::JavaValue, }; @@ -595,7 +599,18 @@ impl Jvm { pub async fn object_wait_prepare(&self, obj: &(impl AsClassInstance + ?Sized)) -> Result<(MonitorWait, MonitorWaitTimeout)> { let thread_id = (self.inner.get_current_thread_id)(); match self.get_or_create_monitor(obj.as_class_instance()).prepare_wait(thread_id) { - Ok(wait) => Ok(wait), + Ok((wait, timeout)) => { + let interrupted = { + let mut threads = self.inner.threads.write(); + let thread = threads.get_mut(&thread_id).expect("current JVM thread must be attached"); + thread.interrupt_waiter = Some(ThreadInterruptWaiter::Monitor(timeout.clone())); + thread.interrupted + }; + if interrupted { + timeout.clone().notify(); + } + Ok((wait, timeout)) + } Err(_) => Err(self .exception("java/lang/IllegalMonitorStateException", "current thread does not own the monitor") .await), @@ -604,9 +619,119 @@ impl Jvm { pub async fn object_wait(&self, wait: MonitorWait) -> Result<()> { wait.wait().await; + + let thread_id = (self.inner.get_current_thread_id)(); + { + let mut threads = self.inner.threads.write(); + let thread = threads.get_mut(&thread_id).expect("current JVM thread must be attached"); + thread.interrupt_waiter = None; + } + let interrupted = self.current_java_thread_interrupted().await?; + if interrupted { + Err(self.exception("java/lang/InterruptedException", "thread interrupted").await) + } else { + Ok(()) + } + } + + pub async fn interrupt_java_thread(&self, thread: &mut Box) -> Result<()> { + // Resolve before locking so both interrupt states can be updated atomically without awaiting while the lock is held. + let field = self.find_field(&*thread.class_definition(), "interrupted", "Z")?.unwrap(); + let identity = thread.identity(); + let waiter = { + let mut threads = self.inner.threads.write(); + thread.put_field(&*field, JavaValue::from(true))?; + let target = threads + .values_mut() + .find(|candidate| candidate.java_thread().is_some_and(|java_thread| java_thread.identity() == identity)); + if let Some(target) = target { + target.interrupted = true; + target.interrupt_waiter.clone() + } else { + None + } + }; + if let Some(waiter) = waiter { + waiter.notify(); + } Ok(()) } + pub async fn is_java_thread_interrupted(&self, thread: &Box) -> Result { + // The Thread prototype guarantees this field, so avoid the async field API's impossible NoSuchFieldError path. + let field = self.find_field(&*thread.class_definition(), "interrupted", "Z")?.unwrap(); + let identity = thread.identity(); + let threads = self.inner.threads.read(); + let field_interrupted: bool = thread.get_field(&*field)?.into(); + let jvm_interrupted = threads + .values() + .find(|candidate| candidate.java_thread().is_some_and(|java_thread| java_thread.identity() == identity)) + .is_some_and(|target| target.interrupted); + Ok(field_interrupted || jvm_interrupted) + } + + pub async fn current_java_thread_interrupted(&self) -> Result { + let thread_id = (self.inner.get_current_thread_id)(); + let mut java_thread = self + .inner + .threads + .read() + .get(&thread_id) + .and_then(JvmThread::java_thread) + .expect("attached JVM thread must have a java.lang.Thread") + .clone(); + // Resolve before locking so both interrupt states can be read and cleared atomically without awaiting while the lock is held. + let field = self.find_field(&*java_thread.class_definition(), "interrupted", "Z")?.unwrap(); + let mut threads = self.inner.threads.write(); + let thread = threads.get_mut(&thread_id).expect("current JVM thread must be attached"); + let field_interrupted: bool = java_thread.get_field(&*field)?.into(); + let interrupted = thread.interrupted || field_interrupted; + thread.interrupted = false; + java_thread.put_field(&*field, JavaValue::from(false))?; + Ok(interrupted) + } + + pub async fn sleep_interruptibly(&self, sleep: F) -> Result<()> + where + F: Future, + { + let event = Arc::new(Event::new()); + let listener = event.listen(); + let thread_id = (self.inner.get_current_thread_id)(); + let interrupted = { + let mut threads = self.inner.threads.write(); + let thread = threads.get_mut(&thread_id).expect("current JVM thread must be attached"); + thread.interrupt_waiter = Some(ThreadInterruptWaiter::Event(event.clone())); + thread.interrupted + }; + if interrupted { + event.notify(1); + } + + let mut sleep = pin!(sleep); + let mut listener = pin!(listener); + poll_fn(|context| { + if listener.as_mut().poll(context).is_ready() || sleep.as_mut().poll(context).is_ready() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + { + let mut threads = self.inner.threads.write(); + let thread = threads.get_mut(&thread_id).expect("current JVM thread must be attached"); + thread.interrupt_waiter = None; + } + let interrupted = self.current_java_thread_interrupted().await?; + if interrupted { + Err(self.exception("java/lang/InterruptedException", "thread interrupted").await) + } else { + Ok(()) + } + } + pub async fn object_notify(&self, obj: &(impl AsClassInstance + ?Sized), count: usize) -> Result<()> { let thread_id = (self.inner.get_current_thread_id)(); match self.get_or_create_monitor(obj.as_class_instance()).notify(thread_id, count) { @@ -641,7 +766,8 @@ impl Jvm { if class_name.starts_with('[') { let stripped_name = class_name.trim_start_matches('['); if stripped_name.starts_with('L') { - self.resolve_class(&stripped_name[1..stripped_name.len() - 1]).await?; + self.resolve_class_internal(&stripped_name[1..stripped_name.len() - 1], class_loader_wrapper) + .await?; // ensure element type is loaded } } @@ -933,7 +1059,14 @@ impl Jvm { Some(x) => x, None => self.new_class("java/lang/Thread", "(Z)V", (true,)).await?, }; - self.inner.threads.write().get_mut(&thread_id).unwrap().set_java_thread(java_thread); + self.inner + .threads + .write() + .get_mut(&thread_id) + .unwrap() + .set_java_thread(java_thread.clone()); + let interrupted: bool = self.get_field(&java_thread, "interrupted", "Z").await?; + self.inner.threads.write().get_mut(&thread_id).unwrap().interrupted |= interrupted; Ok(()) } diff --git a/jvm/src/thread.rs b/jvm/src/thread.rs index b778dbf6..586a1e2e 100644 --- a/jvm/src/thread.rs +++ b/jvm/src/thread.rs @@ -1,10 +1,30 @@ use alloc::{ boxed::Box, string::{String, ToString}, + sync::Arc, vec::Vec, }; -use crate::{ClassInstance, JavaValue, class_loader::Class}; +use event_listener::Event; + +use crate::{ClassInstance, JavaValue, class_loader::Class, monitor::MonitorWaitTimeout}; + +#[derive(Clone)] +pub(crate) enum ThreadInterruptWaiter { + Monitor(MonitorWaitTimeout), + Event(Arc), +} + +impl ThreadInterruptWaiter { + pub(crate) fn notify(self) { + match self { + Self::Monitor(waiter) => waiter.notify(), + Self::Event(event) => { + event.notify(1); + } + } + } +} pub enum StackFrame { Java(JavaStackFrame), @@ -30,6 +50,8 @@ impl StackFrame { pub struct JvmThread { stack: Vec, java_thread: Option>, + pub(crate) interrupted: bool, + pub(crate) interrupt_waiter: Option, } impl JvmThread { @@ -37,6 +59,8 @@ impl JvmThread { Self { stack: Vec::new(), java_thread: None, + interrupted: false, + interrupt_waiter: None, } } diff --git a/jvm/tests/test_garbage_collection.rs b/jvm/tests/test_garbage_collection.rs index 6568e9b5..46298984 100644 --- a/jvm/tests/test_garbage_collection.rs +++ b/jvm/tests/test_garbage_collection.rs @@ -67,7 +67,8 @@ async fn test_garbage_collection() -> JvmResult<()> { .await?; let garbage_count = jvm.collect_garbage()?; - assert_eq!(garbage_count, 3); + assert!(garbage_count > 0); + assert_eq!(jvm.collect_garbage()?, 0); jvm.pop_frame(); diff --git a/src/runtime.rs b/src/runtime.rs index b6da89d5..c8be2697 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -12,7 +12,9 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use java_runtime::{File, FileDescriptorId, FileStat, FileType, IOError, IOResult, RT_RUSTJAR, Runtime, SpawnCallback, get_runtime_class_proto}; +use java_runtime::{ + File, FileDescriptorId, FileOpenOptions, FileStat, FileType, IOError, IOResult, RT_RUSTJAR, Runtime, SpawnCallback, get_runtime_class_proto, +}; use jvm::{ClassDefinition, Jvm}; use jvm_rust::{ArrayClassDefinitionImpl, ClassDefinitionError, ClassDefinitionImpl}; @@ -136,8 +138,8 @@ where Ok(self.register_file(file)) } - async fn open(&self, path: &str, write: bool) -> IOResult { - let file = FileImpl::new(path, write).map_err(|_| IOError::NotFound)?; + async fn open(&self, path: &str, options: FileOpenOptions) -> IOResult { + let file = FileImpl::new(path, options).map_err(|_| IOError::NotFound)?; Ok(self.register_file(Box::new(file))) } diff --git a/src/runtime/io.rs b/src/runtime/io.rs index 61243625..c773c5cc 100644 --- a/src/runtime/io.rs +++ b/src/runtime/io.rs @@ -1,11 +1,11 @@ use alloc::sync::Arc; use std::{ - fs::{self, OpenOptions}, + fs::{self, OpenOptions as StdOpenOptions}, io::{self, Read, Seek, Write}, sync::Mutex, }; -use java_runtime::{File, FileSize, FileStat, FileType, IOError, IOResult}; +use java_runtime::{File, FileOpenOptions, FileSize, FileStat, FileType, IOError, IOResult}; pub struct WriteStreamFile where @@ -131,9 +131,14 @@ pub struct FileImpl { } impl FileImpl { - pub fn new(path: &str, write: bool) -> io::Result { - let mut options = OpenOptions::new(); - let file = options.read(true).write(write).create(write).open(path)?; + pub fn new(path: &str, options: FileOpenOptions) -> io::Result { + let file = StdOpenOptions::new() + .read(options.read) + .write(options.write) + .append(options.append) + .truncate(options.truncate) + .create(options.create) + .open(path)?; Ok(Self { file: Arc::new(Mutex::new(file)), diff --git a/test_data/ClassMetadata$ByteLoader.class b/test_data/ClassMetadata$ByteLoader.class new file mode 100644 index 00000000..3677d58d Binary files /dev/null and b/test_data/ClassMetadata$ByteLoader.class differ diff --git a/test_data/ClassMetadata$First.class b/test_data/ClassMetadata$First.class new file mode 100644 index 00000000..b98fe838 Binary files /dev/null and b/test_data/ClassMetadata$First.class differ diff --git a/test_data/ClassMetadata$Payload.class b/test_data/ClassMetadata$Payload.class new file mode 100644 index 00000000..ca68eb7e Binary files /dev/null and b/test_data/ClassMetadata$Payload.class differ diff --git a/test_data/ClassMetadata$Sample.class b/test_data/ClassMetadata$Sample.class new file mode 100644 index 00000000..6e4a02ab Binary files /dev/null and b/test_data/ClassMetadata$Sample.class differ diff --git a/test_data/ClassMetadata$Second.class b/test_data/ClassMetadata$Second.class new file mode 100644 index 00000000..a92d9143 Binary files /dev/null and b/test_data/ClassMetadata$Second.class differ diff --git a/test_data/ClassMetadata.class b/test_data/ClassMetadata.class new file mode 100644 index 00000000..fd7882ff Binary files /dev/null and b/test_data/ClassMetadata.class differ diff --git a/test_data/ClassMetadata.txt b/test_data/ClassMetadata.txt new file mode 100644 index 00000000..058d99f0 --- /dev/null +++ b/test_data/ClassMetadata.txt @@ -0,0 +1,25 @@ +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/test_data/ThreadInterruption$DaemonWorker.class b/test_data/ThreadInterruption$DaemonWorker.class new file mode 100644 index 00000000..9bdd9c3d Binary files /dev/null and b/test_data/ThreadInterruption$DaemonWorker.class differ diff --git a/test_data/ThreadInterruption$JoinWorker.class b/test_data/ThreadInterruption$JoinWorker.class new file mode 100644 index 00000000..f48d2a1e Binary files /dev/null and b/test_data/ThreadInterruption$JoinWorker.class differ diff --git a/test_data/ThreadInterruption$PreInterruptedSleepWorker.class b/test_data/ThreadInterruption$PreInterruptedSleepWorker.class new file mode 100644 index 00000000..2bbe4f23 Binary files /dev/null and b/test_data/ThreadInterruption$PreInterruptedSleepWorker.class differ diff --git a/test_data/ThreadInterruption$TargetWorker.class b/test_data/ThreadInterruption$TargetWorker.class new file mode 100644 index 00000000..8b1c55a4 Binary files /dev/null and b/test_data/ThreadInterruption$TargetWorker.class differ diff --git a/test_data/ThreadInterruption$WaitWorker.class b/test_data/ThreadInterruption$WaitWorker.class new file mode 100644 index 00000000..e9b64d64 Binary files /dev/null and b/test_data/ThreadInterruption$WaitWorker.class differ diff --git a/test_data/ThreadInterruption.class b/test_data/ThreadInterruption.class new file mode 100644 index 00000000..f5f00d78 Binary files /dev/null and b/test_data/ThreadInterruption.class differ diff --git a/test_data/ThreadInterruption.txt b/test_data/ThreadInterruption.txt new file mode 100644 index 00000000..3132bf56 --- /dev/null +++ b/test_data/ThreadInterruption.txt @@ -0,0 +1,14 @@ +true +true +false +wait interrupted +false +true +pre sleep interrupted +false +join interrupted +false +true +true +false +true diff --git a/test_data/src/ClassMetadata.java b/test_data/src/ClassMetadata.java new file mode 100644 index 00000000..16719b37 --- /dev/null +++ b/test_data/src/ClassMetadata.java @@ -0,0 +1,88 @@ +public class ClassMetadata { + interface First { + } + + interface Second { + } + + static class Sample implements Second, First { + } + + static class Payload { + } + + static class ByteLoader extends ClassLoader { + ByteLoader(ClassLoader parent) { + super(parent); + } + + public Class loadClass(String name) throws ClassNotFoundException { + if (!name.equals("ClassMetadata$Payload")) { + return super.loadClass(name); + } + + Class loaded = findLoadedClass(name); + return loaded == null ? findClass(name) : loaded; + } + + protected Class findClass(String name) throws ClassNotFoundException { + try { + java.io.InputStream input = ClassMetadata.class.getResourceAsStream("ClassMetadata$Payload.class"); + byte[] bytes = new byte[input.available()]; + int offset = 0; + while (offset < bytes.length) { + int read = input.read(bytes, offset, bytes.length - offset); + if (read < 0) { + break; + } + offset += read; + } + input.close(); + return defineClass(name, bytes, 0, offset); + } catch (java.io.IOException exception) { + throw new ClassNotFoundException(name); + } + } + } + + public static void main(String[] args) throws Exception { + ClassLoader loader = ClassMetadata.class.getClassLoader(); + + System.out.println(Object.class.getSuperclass() == null); + System.out.println(First.class.getSuperclass() == null); + System.out.println(int.class.getSuperclass() == null); + System.out.println(String.class.getSuperclass() == Object.class); + System.out.println(int[].class.getSuperclass() == Object.class); + + System.out.println(int[].class.getClassLoader() == null); + System.out.println(String[].class.getClassLoader() == null); + System.out.println(Sample.class.getClassLoader() == loader); + System.out.println(Sample[].class.getClassLoader() == loader); + System.out.println(Sample[][].class.getClassLoader() == loader); + + System.out.println(int[].class.getComponentType() == Integer.TYPE); + System.out.println(Sample[].class.getComponentType() == Sample.class); + System.out.println(Sample[][].class.getComponentType() == Sample[].class); + System.out.println(String.class.getComponentType() == null); + + Class[] arrayInterfaces = Sample[].class.getInterfaces(); + System.out.println(arrayInterfaces.length == 2); + System.out.println(arrayInterfaces[0] == Cloneable.class); + System.out.println(arrayInterfaces[1] == java.io.Serializable.class); + + Class[] sampleInterfaces = Sample.class.getInterfaces(); + System.out.println(sampleInterfaces.length == 2); + System.out.println(sampleInterfaces[0] == Second.class); + System.out.println(sampleInterfaces[1] == First.class); + + ByteLoader customLoader = new ByteLoader(loader); + Class payload = customLoader.loadClass("ClassMetadata$Payload"); + Class payloadArray = customLoader.loadClass("[LClassMetadata$Payload;"); + Class payloadMatrix = customLoader.loadClass("[[LClassMetadata$Payload;"); + System.out.println(payload.getClassLoader() == customLoader); + System.out.println(payloadArray.getClassLoader() == customLoader); + System.out.println(payloadMatrix.getClassLoader() == customLoader); + System.out.println(payloadArray.getComponentType() == payload); + System.out.println(payloadMatrix.getComponentType() == payloadArray); + } +} diff --git a/test_data/src/ThreadInterruption.java b/test_data/src/ThreadInterruption.java new file mode 100644 index 00000000..cc5ddbcf --- /dev/null +++ b/test_data/src/ThreadInterruption.java @@ -0,0 +1,177 @@ +public class ThreadInterruption { + private static final Object WAIT_LOCK = new Object(); + private static final Object WAIT_ACK_LOCK = new Object(); + private static final Object TARGET_LOCK = new Object(); + private static final Object JOIN_LOCK = new Object(); + private static boolean waitReady; + private static boolean secondWaitReady; + private static boolean secondWaitWoke; + private static boolean targetReady; + private static boolean targetReleased; + private static boolean joinReady; + + private static class WaitWorker implements Runnable { + public void run() { + synchronized (WAIT_LOCK) { + waitReady = true; + WAIT_LOCK.notifyAll(); + try { + WAIT_LOCK.wait(); + System.out.println("wait returned"); + } catch (InterruptedException expected) { + System.out.println("wait interrupted"); + System.out.println(Thread.interrupted()); + } + + secondWaitReady = true; + WAIT_LOCK.notifyAll(); + try { + WAIT_LOCK.wait(500); + } catch (InterruptedException unexpected) { + throw new RuntimeException(unexpected.toString()); + } + } + synchronized (WAIT_ACK_LOCK) { + secondWaitWoke = true; + WAIT_ACK_LOCK.notifyAll(); + } + } + } + + private static class PreInterruptedSleepWorker implements Runnable { + public void run() { + Thread.currentThread().interrupt(); + try { + Thread.sleep(25); + System.out.println("pre sleep returned"); + } catch (InterruptedException expected) { + System.out.println("pre sleep interrupted"); + System.out.println(Thread.currentThread().isInterrupted()); + } + } + } + + private static class TargetWorker implements Runnable { + public void run() { + synchronized (TARGET_LOCK) { + targetReady = true; + TARGET_LOCK.notifyAll(); + while (!targetReleased) { + try { + TARGET_LOCK.wait(); + } catch (InterruptedException unexpected) { + throw new RuntimeException(unexpected.toString()); + } + } + } + } + } + + private static class JoinWorker implements Runnable { + private final Thread target; + + JoinWorker(Thread target) { + this.target = target; + } + + public void run() { + synchronized (target) { + synchronized (JOIN_LOCK) { + joinReady = true; + JOIN_LOCK.notifyAll(); + } + try { + target.join(); + System.out.println("join returned"); + } catch (InterruptedException expected) { + System.out.println("join interrupted"); + System.out.println(Thread.interrupted()); + } + } + } + } + + private static class DaemonWorker implements Runnable { + public void run() { + System.out.println(new Thread().isDaemon()); + } + } + + public static void main(String[] args) throws Exception { + Thread current = Thread.currentThread(); + current.interrupt(); + System.out.println(current.isInterrupted()); + System.out.println(Thread.interrupted()); + System.out.println(current.isInterrupted()); + + Thread waiter = new Thread(new WaitWorker()); + waiter.start(); + synchronized (WAIT_LOCK) { + while (!waitReady) { + WAIT_LOCK.wait(); + } + } + waiter.interrupt(); + synchronized (WAIT_LOCK) { + while (!secondWaitReady) { + WAIT_LOCK.wait(); + } + WAIT_LOCK.notify(); + } + synchronized (WAIT_ACK_LOCK) { + long deadline = System.currentTimeMillis() + 200; + while (!secondWaitWoke) { + long remaining = deadline - System.currentTimeMillis(); + if (remaining <= 0) { + break; + } + WAIT_ACK_LOCK.wait(remaining); + } + System.out.println(secondWaitWoke); + } + waiter.join(); + + Thread sleeper = new Thread(new PreInterruptedSleepWorker()); + sleeper.start(); + sleeper.join(); + + Thread target = new Thread(new TargetWorker()); + target.start(); + synchronized (TARGET_LOCK) { + while (!targetReady) { + TARGET_LOCK.wait(); + } + } + + Thread joiner = new Thread(new JoinWorker(target)); + joiner.start(); + synchronized (JOIN_LOCK) { + while (!joinReady) { + JOIN_LOCK.wait(); + } + } + synchronized (target) { + target.notifyAll(); + } + joiner.interrupt(); + joiner.join(); + + long before = System.currentTimeMillis(); + target.join(10); + long elapsed = System.currentTimeMillis() - before; + System.out.println(target.isAlive()); + System.out.println(elapsed >= 0); + + synchronized (TARGET_LOCK) { + targetReleased = true; + TARGET_LOCK.notifyAll(); + } + target.join(0); + System.out.println(target.isAlive()); + + Thread daemon = new Thread(new DaemonWorker()); + daemon.setDaemon(true); + daemon.start(); + daemon.join(); + } +} diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 11a2d435..478430bd 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true [dependencies] async-trait = { workspace = true } tracing = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "time"] } +tokio = { workspace = true, features = ["rt-multi-thread", "sync", "time"] } jvm = { workspace = true } jvm_rust = { workspace = true } diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 177c3aff..ada84014 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1,6 +1,13 @@ extern crate alloc; -use alloc::{boxed::Box, collections::BTreeMap, format, string::String, sync::Arc, vec::Vec}; +use alloc::{ + boxed::Box, + collections::{BTreeMap, VecDeque}, + format, + string::String, + sync::Arc, + vec::Vec, +}; use core::{ cmp::min, sync::atomic::{AtomicI64, AtomicU32, AtomicU64, Ordering}, @@ -10,33 +17,47 @@ use std::{ sync::Mutex, time::{SystemTime, UNIX_EPOCH}, }; +use tokio::sync::{Notify, oneshot}; use jvm::{ClassDefinition, Jvm, Result}; use jvm_rust::{ArrayClassDefinitionImpl, ClassDefinitionError, ClassDefinitionImpl}; use java_runtime::{ - File, FileDescriptorId, FileSize, FileStat, FileType, IOError, IOResult, RT_RUSTJAR, Runtime, SpawnCallback, get_bootstrap_class_loader, - get_runtime_class_proto, + File, FileDescriptorId, FileOpenOptions, FileSize, FileStat, FileType, IOError, IOResult, RT_RUSTJAR, Runtime, SpawnCallback, + get_bootstrap_class_loader, get_runtime_class_proto, }; -type SpawnCallbacks = Arc>>>; +struct SpawnCallbacks { + callbacks: Mutex>>, + available: Notify, +} + +struct ManualClock { + now: AtomicU64, + sleepers: Mutex)>>, + sleep_registered: Notify, +} pub struct TestRuntime { filesystem: BTreeMap>, + stdin: Option>, file_table: Arc>>>, next_fd: Arc, exit_status: Arc, - spawn_callbacks: Option, + spawn_callbacks: Option>, + manual_clock: Option>, } impl Clone for TestRuntime { fn clone(&self) -> Self { Self { filesystem: self.filesystem.clone(), + stdin: self.stdin.clone(), file_table: self.file_table.clone(), next_fd: self.next_fd.clone(), exit_status: self.exit_status.clone(), spawn_callbacks: self.spawn_callbacks.clone(), + manual_clock: self.manual_clock.clone(), } } } @@ -45,25 +66,111 @@ impl TestRuntime { pub fn new(filesystem: BTreeMap>) -> Self { Self { filesystem, + stdin: None, + file_table: Arc::new(Mutex::new(BTreeMap::new())), + next_fd: Arc::new(AtomicU32::new(1)), + exit_status: Arc::new(AtomicI64::new(i64::MIN)), + spawn_callbacks: None, + manual_clock: None, + } + } + + pub fn new_with_stdin(filesystem: BTreeMap>, stdin: Vec) -> Self { + Self { + filesystem, + stdin: Some(stdin), file_table: Arc::new(Mutex::new(BTreeMap::new())), next_fd: Arc::new(AtomicU32::new(1)), exit_status: Arc::new(AtomicI64::new(i64::MIN)), spawn_callbacks: None, + manual_clock: None, } } pub fn new_with_queued_spawns(filesystem: BTreeMap>) -> Self { Self { filesystem, + stdin: None, file_table: Arc::new(Mutex::new(BTreeMap::new())), next_fd: Arc::new(AtomicU32::new(1)), exit_status: Arc::new(AtomicI64::new(i64::MIN)), - spawn_callbacks: Some(Arc::new(Mutex::new(Vec::new()))), + spawn_callbacks: Some(Arc::new(SpawnCallbacks { + callbacks: Mutex::new(VecDeque::new()), + available: Notify::new(), + })), + manual_clock: None, + } + } + + pub fn new_with_queued_spawns_and_manual_clock(filesystem: BTreeMap>, now: u64) -> Self { + Self { + filesystem, + stdin: None, + file_table: Arc::new(Mutex::new(BTreeMap::new())), + next_fd: Arc::new(AtomicU32::new(1)), + exit_status: Arc::new(AtomicI64::new(i64::MIN)), + spawn_callbacks: Some(Arc::new(SpawnCallbacks { + callbacks: Mutex::new(VecDeque::new()), + available: Notify::new(), + })), + manual_clock: Some(Arc::new(ManualClock { + now: AtomicU64::new(now), + sleepers: Mutex::new(Vec::new()), + sleep_registered: Notify::new(), + })), } } pub fn take_spawn_callback(&self) -> Option> { - self.spawn_callbacks.as_ref()?.lock().unwrap().pop() + self.spawn_callbacks.as_ref()?.callbacks.lock().unwrap().pop_front() + } + + pub async fn next_spawn_callback(&self) -> Option> { + let spawn_callbacks = self.spawn_callbacks.as_ref()?; + loop { + let available = spawn_callbacks.available.notified(); + if let Some(callback) = spawn_callbacks.callbacks.lock().unwrap().pop_front() { + return Some(callback); + } + available.await; + } + } + + pub fn advance_time(&self, duration: Duration) { + let clock = self.manual_clock.as_ref().expect("manual clock is not enabled"); + let now = clock.now.fetch_add(duration.as_millis() as u64, Ordering::SeqCst) + duration.as_millis() as u64; + let mut sleepers = clock.sleepers.lock().unwrap(); + let mut index = 0; + while index < sleepers.len() { + if sleepers[index].0 <= now { + let (_, sender) = sleepers.swap_remove(index); + let _ = sender.send(()); + } else { + index += 1; + } + } + } + + pub async fn next_sleep_deadline(&self) -> u64 { + let clock = self.manual_clock.as_ref().expect("manual clock is not enabled"); + loop { + let registered = clock.sleep_registered.notified(); + if let Some(deadline) = clock.sleepers.lock().unwrap().iter().map(|(deadline, _)| *deadline).min() { + return deadline; + } + registered.await; + } + } + + pub async fn wait_for_sleep_deadline(&self, expected: u64) { + let clock = self.manual_clock.as_ref().expect("manual clock is not enabled"); + loop { + let registered = clock.sleep_registered.notified(); + if clock.sleepers.lock().unwrap().iter().any(|(deadline, _)| *deadline == expected) { + return; + } + registered.await; + } } pub fn exit_status(&self) -> Option { @@ -87,6 +194,23 @@ static LAST_TASK_ID: AtomicU64 = AtomicU64::new(1); #[async_trait::async_trait] impl Runtime for TestRuntime { async fn sleep(&self, duration: Duration) { + if let Some(clock) = &self.manual_clock { + if duration.is_zero() { + return; + } + let deadline = clock.now.load(Ordering::SeqCst).saturating_add(duration.as_millis() as u64); + let (sender, receiver) = oneshot::channel(); + { + let mut sleepers = clock.sleepers.lock().unwrap(); + if clock.now.load(Ordering::SeqCst) >= deadline { + return; + } + sleepers.push((deadline, sender)); + } + clock.sleep_registered.notify_one(); + let _ = receiver.await; + return; + } tokio::time::sleep(duration).await; } @@ -95,20 +219,30 @@ impl Runtime for TestRuntime { } fn spawn(&self, _jvm: &Jvm, callback: Box) { + struct TestSpawnCallback { + task_id: u64, + callback: Box, + } + + #[async_trait::async_trait] + impl SpawnCallback for TestSpawnCallback { + async fn call(&self) -> Result<()> { + TASK_ID.scope(self.task_id, self.callback.call()).await + } + } + + let task_id = LAST_TASK_ID.fetch_add(1, Ordering::SeqCst); + let callback: Box = Box::new(TestSpawnCallback { task_id, callback }); if let Some(spawn_callbacks) = &self.spawn_callbacks { - spawn_callbacks.lock().unwrap().push(callback); + spawn_callbacks.callbacks.lock().unwrap().push_back(callback); + spawn_callbacks.available.notify_one(); return; } - let task_id = LAST_TASK_ID.fetch_add(1, Ordering::SeqCst); tokio::spawn(async move { - TASK_ID - .scope(task_id, async move { - if let Err(error) = callback.call().await { - tracing::error!(?error, "spawned Java test task failed"); - } - }) - .await; + if let Err(error) = callback.call().await { + tracing::error!(?error, "spawned Java test task failed"); + } }); } @@ -117,6 +251,9 @@ impl Runtime for TestRuntime { } fn now(&self) -> u64 { + if let Some(clock) = &self.manual_clock { + return clock.now.load(Ordering::SeqCst); + } SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_millis() as u64 } @@ -125,7 +262,10 @@ impl Runtime for TestRuntime { } fn stdin(&self) -> IOResult { - Err(IOError::NotFound) + match &self.stdin { + Some(data) => Ok(self.register_file(Box::new(DummyFile::new(data.clone())))), + None => Err(IOError::NotFound), + } } fn stdout(&self) -> IOResult { @@ -136,7 +276,11 @@ impl Runtime for TestRuntime { Err(IOError::NotFound) } - async fn open(&self, path: &str, _write: bool) -> IOResult { + async fn open(&self, path: &str, options: FileOpenOptions) -> IOResult { + if !options.read || options.write || options.append || options.truncate || options.create { + return Err(IOError::Unsupported); + } + let entry = self.filesystem.get(path); if let Some(data) = entry { let file = Box::new(DummyFile::new(data.clone())) as Box;