diff --git a/java_runtime/src/classes/java/io.rs b/java_runtime/src/classes/java/io.rs index 823a91f0..9285cfd2 100644 --- a/java_runtime/src/classes/java/io.rs +++ b/java_runtime/src/classes/java/io.rs @@ -6,6 +6,7 @@ mod byte_array_input_stream; mod byte_array_output_stream; mod char_array_reader; mod char_array_writer; +mod closeable; mod data_input; mod data_input_stream; mod data_output; @@ -20,6 +21,7 @@ mod file_reader; mod file_writer; mod filter_input_stream; mod filter_output_stream; +mod flushable; mod input_stream; mod input_stream_reader; mod interrupted_io_exception; @@ -40,12 +42,13 @@ mod writer; pub use self::{ 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, + char_array_reader::CharArrayReader, char_array_writer::CharArrayWriter, closeable::Closeable, 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, flushable::Flushable, 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/closeable.rs b/java_runtime/src/classes/java/io/closeable.rs new file mode 100644 index 00000000..169ebdb1 --- /dev/null +++ b/java_runtime/src/classes/java/io/closeable.rs @@ -0,0 +1,26 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// public interface java.io.Closeable +pub struct Closeable; + +impl Closeable { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/Closeable", + parent_class: None, + interfaces: vec![], + methods: vec![JavaMethodProto::new_abstract( + "close", + "()V", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + )], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/io/flushable.rs b/java_runtime/src/classes/java/io/flushable.rs new file mode 100644 index 00000000..145df7db --- /dev/null +++ b/java_runtime/src/classes/java/io/flushable.rs @@ -0,0 +1,26 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// public interface java.io.Flushable +pub struct Flushable; + +impl Flushable { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/io/Flushable", + parent_class: None, + interfaces: vec![], + methods: vec![JavaMethodProto::new_abstract( + "flush", + "()V", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + )], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/io/output_stream.rs b/java_runtime/src/classes/java/io/output_stream.rs index 6ca8bcd2..8d57cbc2 100644 --- a/java_runtime/src/classes/java/io/output_stream.rs +++ b/java_runtime/src/classes/java/io/output_stream.rs @@ -1,7 +1,7 @@ use alloc::vec; use java_class_proto::JavaMethodProto; -use java_constants::ClassAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext}; @@ -14,17 +14,17 @@ impl OutputStream { RuntimeClassProto { name: "java/io/OutputStream", parent_class: Some("java/lang/Object"), - interfaces: vec![], + interfaces: vec!["java/io/Closeable", "java/io/Flushable"], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("write", "([B)V", Self::write_bytes, Default::default()), - JavaMethodProto::new("write", "([BII)V", Self::write_bytes_offset, Default::default()), - JavaMethodProto::new_abstract("write", "(I)V", Default::default()), - JavaMethodProto::new("flush", "()V", Self::flush, Default::default()), - JavaMethodProto::new("close", "()V", Self::close, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([B)V", Self::write_bytes, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([BII)V", Self::write_bytes_offset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract("write", "(I)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), ], fields: vec![], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } diff --git a/java_runtime/src/classes/java/io/output_stream_writer.rs b/java_runtime/src/classes/java/io/output_stream_writer.rs index adf4511f..ec25827d 100644 --- a/java_runtime/src/classes/java/io/output_stream_writer.rs +++ b/java_runtime/src/classes/java/io/output_stream_writer.rs @@ -66,23 +66,30 @@ impl OutputStreamWriter { ) -> Result<()> { tracing::debug!("java.io.OutputStreamWriter::({this:?}, {out:?}, {encoding:?})"); - if out.is_null() || encoding.is_null() { - return Err(jvm.exception("java/lang/NullPointerException", "output or encoding is null").await); + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); } + Self::validate_encoding(jvm, &encoding).await?; - let encoding_name = JavaLangString::to_rust_string(jvm, &encoding).await?.to_ascii_uppercase(); + let _: () = jvm.invoke_special(&this, "java/io/Writer", "", "()V", ()).await?; + jvm.put_field(&mut this, "out", "Ljava/io/OutputStream;", out).await?; + jvm.put_field(&mut this, "encoding", "Ljava/lang/String;", encoding).await?; + jvm.put_field(&mut this, "hasPendingHighSurrogate", "Z", false).await?; + + Ok(()) + } + + pub(crate) async fn validate_encoding(jvm: &Jvm, encoding: &ClassInstanceRef) -> Result<()> { + if encoding.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "encoding is null").await); + } + let encoding_name = JavaLangString::to_rust_string(jvm, encoding).await?.to_ascii_uppercase(); if !matches!( encoding_name.as_str(), "UTF-8" | "UTF8" | "EUC-KR" | "EUCKR" | "KS-C-5601-1987" | "MS949" | "CP949" ) { return Err(jvm.exception("java/io/UnsupportedEncodingException", &encoding_name).await); } - - let _: () = jvm.invoke_special(&this, "java/io/Writer", "", "()V", ()).await?; - jvm.put_field(&mut this, "out", "Ljava/io/OutputStream;", out).await?; - jvm.put_field(&mut this, "encoding", "Ljava/lang/String;", encoding).await?; - jvm.put_field(&mut this, "hasPendingHighSurrogate", "Z", false).await?; - Ok(()) } diff --git a/java_runtime/src/classes/java/io/print_stream.rs b/java_runtime/src/classes/java/io/print_stream.rs index c4ea09d2..8aedb9a2 100644 --- a/java_runtime/src/classes/java/io/print_stream.rs +++ b/java_runtime/src/classes/java/io/print_stream.rs @@ -9,9 +9,9 @@ use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::Ja use crate::{ RuntimeClassProto, RuntimeContext, classes::java::{ - io::{OutputStream, OutputStreamWriter}, - lang::{Object, String}, - util::Properties, + io::{File, OutputStream, OutputStreamWriter}, + lang::{Appendable, CharSequence, Object, String}, + util::{Formatter, Locale, Properties}, }, }; @@ -23,11 +23,32 @@ impl PrintStream { RuntimeClassProto { name: "java/io/PrintStream", parent_class: Some("java/io/FilterOutputStream"), - interfaces: vec![], + interfaces: vec!["java/lang/Appendable", "java/io/Closeable"], methods: vec![ 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( + "", + "(Ljava/io/OutputStream;ZLjava/lang/String;)V", + Self::init_auto_flush_encoding, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_path, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::init_path_encoding, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/io/File;)V", Self::init_file, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/File;Ljava/lang/String;)V", + Self::init_file_encoding, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("checkError", "()Z", Self::check_error, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setError", "()V", Self::set_error, MethodAccessFlags::PROTECTED), 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), @@ -48,11 +69,64 @@ impl PrintStream { 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), + JavaMethodProto::new( + "printf", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + Self::printf, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "printf", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + Self::printf_with_locale, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + Self::format, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + Self::format_with_locale, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/io/PrintStream;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/io/PrintStream;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("append", "(C)Ljava/io/PrintStream;", Self::append_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/Appendable;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), ], fields: vec![ JavaFieldProto::new("autoFlush", "Z", FieldAccessFlags::PRIVATE), @@ -73,20 +147,12 @@ impl PrintStream { async fn init_auto_flush( jvm: &Jvm, _: &mut RuntimeContext, - mut this: ClassInstanceRef, + 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.clone(),)) - .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() @@ -101,6 +167,33 @@ impl PrintStream { encoding } }; + jvm.invoke_special( + &this, + "java/io/PrintStream", + "", + "(Ljava/io/OutputStream;ZLjava/lang/String;)V", + (out, auto_flush, encoding), + ) + .await + } + + async fn init_auto_flush_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + out: ClassInstanceRef, + auto_flush: bool, + encoding: ClassInstanceRef, + ) -> Result<()> { + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + + let _: () = jvm + .invoke_special(&this, "java/io/FilterOutputStream", "", "(Ljava/io/OutputStream;)V", (out.clone(),)) + .await?; + let this_output: ClassInstanceRef = this.instance.clone().into(); let char_out = jvm .new_class( "java/io/OutputStreamWriter", @@ -114,6 +207,68 @@ impl PrintStream { jvm.put_field(&mut this, "closing", "Z", false).await } + async fn init_path(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, path: ClassInstanceRef) -> Result<()> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file name is null").await); + } + let file: ClassInstanceRef = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?.into(); + jvm.invoke_special(&this, "java/io/PrintStream", "", "(Ljava/io/File;)V", (file,)) + .await + } + + async fn init_path_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + path: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file name is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + let file: ClassInstanceRef = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?.into(); + jvm.invoke_special( + &this, + "java/io/PrintStream", + "", + "(Ljava/io/File;Ljava/lang/String;)V", + (file, encoding), + ) + .await + } + + async fn init_file(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + let output: ClassInstanceRef = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?.into(); + jvm.invoke_special(&this, "java/io/PrintStream", "", "(Ljava/io/OutputStream;)V", (output,)) + .await + } + + async fn init_file_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + file: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + let output: ClassInstanceRef = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?.into(); + jvm.invoke_special( + &this, + "java/io/PrintStream", + "", + "(Ljava/io/OutputStream;ZLjava/lang/String;)V", + (output, false, encoding), + ) + .await + } + async fn with_monitor(jvm: &Jvm, this: &ClassInstanceRef, operation: F) -> Result where F: Future>, @@ -149,6 +304,10 @@ impl PrintStream { .await } + async fn set_error(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "trouble", "Z", true).await + } + async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.PrintStream::close({this:?})"); @@ -384,46 +543,148 @@ impl PrintStream { .await } - async fn println_byte(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i8) -> Result<()> { + 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", "(I)V", (value as i32,)).await?; + let _: () = jvm.invoke_virtual(&this, "print", "(Z)V", (value,)).await?; Self::new_line(jvm, &this).await }) .await } - async fn println_short(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i16) -> Result<()> { + 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", "(I)V", (value as i32,)).await?; + let _: () = jvm.invoke_virtual(&this, "print", "(F)V", (value,)).await?; Self::new_line(jvm, &this).await }) .await } - async fn println_bool(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result<()> { + 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", "(Z)V", (value,)).await?; + let _: () = jvm.invoke_virtual(&this, "print", "(D)V", (value,)).await?; Self::new_line(jvm, &this).await }) .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 - }) + async fn printf( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + jvm.invoke_virtual( + &this, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + (format, arguments), + ) .await } - async fn println_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result<()> { + async fn printf_with_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + locale: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + jvm.invoke_virtual( + &this, + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + (locale, format, arguments), + ) + .await + } + + async fn format( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_virtual( + &this, + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + (locale, format, arguments), + ) + .await + } + + async fn format_with_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + locale: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { Self::with_monitor(jvm, &this, async { - let _: () = jvm.invoke_virtual(&this, "print", "(D)V", (value,)).await?; - Self::new_line(jvm, &this).await + let appendable: ClassInstanceRef = this.instance.clone().into(); + let formatter: ClassInstanceRef = jvm + .new_class("java/util/Formatter", "(Ljava/lang/Appendable;Ljava/util/Locale;)V", (appendable, locale)) + .await? + .into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + Ok(this.clone()) }) .await } + async fn append_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result> { + let string: ClassInstanceRef = if sequence.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + jvm.invoke_virtual(&sequence, "toString", "()Ljava/lang/String;", ()).await? + }; + let _: () = jvm.invoke_virtual(&this, "print", "(Ljava/lang/String;)V", (string,)).await?; + Ok(this) + } + + async fn append_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let sequence: ClassInstanceRef = if sequence.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + sequence + }; + let subsequence: ClassInstanceRef = jvm + .invoke_virtual(&sequence, "subSequence", "(II)Ljava/lang/CharSequence;", (start, end)) + .await?; + let string: ClassInstanceRef = jvm.invoke_virtual(&subsequence, "toString", "()Ljava/lang/String;", ()).await?; + let _: () = jvm.invoke_virtual(&this, "print", "(Ljava/lang/String;)V", (string,)).await?; + Ok(this) + } + + async fn append_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, character: JavaChar) -> Result> { + let _: () = jvm.invoke_virtual(&this, "print", "(C)V", (character,)).await?; + Ok(this) + } + 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 diff --git a/java_runtime/src/classes/java/io/print_writer.rs b/java_runtime/src/classes/java/io/print_writer.rs index f93ab88e..8165e9ab 100644 --- a/java_runtime/src/classes/java/io/print_writer.rs +++ b/java_runtime/src/classes/java/io/print_writer.rs @@ -9,8 +9,9 @@ use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::Ja use crate::{ RuntimeClassProto, RuntimeContext, classes::java::{ - io::{OutputStream, Writer}, - lang::{Object, String}, + io::{File, OutputStream, OutputStreamWriter, Writer}, + lang::{Appendable, CharSequence, Object, String}, + util::{Formatter, Locale}, }, }; @@ -33,6 +34,20 @@ impl PrintWriter { Self::init_output_stream_auto_flush, MethodAccessFlags::PUBLIC, ), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_path, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::init_path_encoding, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/io/File;)V", Self::init_file, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/File;Ljava/lang/String;)V", + Self::init_file_encoding, + 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), @@ -60,6 +75,80 @@ impl PrintWriter { 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), + JavaMethodProto::new("setError", "()V", Self::set_error, MethodAccessFlags::PROTECTED), + JavaMethodProto::new( + "printf", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + Self::printf, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "printf", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + Self::printf_with_locale, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + Self::format, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + Self::format_with_locale, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/io/PrintWriter;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("append", "(C)Ljava/io/PrintWriter;", Self::append_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/io/Writer;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/io/Writer;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/io/Writer;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/Appendable;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), ], fields: vec![ JavaFieldProto::new("out", "Ljava/io/Writer;", FieldAccessFlags::PROTECTED), @@ -124,6 +213,76 @@ impl PrintWriter { .await } + async fn init_path(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, path: ClassInstanceRef) -> Result<()> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file name is null").await); + } + let file: ClassInstanceRef = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?.into(); + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/File;)V", (file,)) + .await + } + + async fn init_path_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + path: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file name is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + let file: ClassInstanceRef = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?.into(); + jvm.invoke_special( + &this, + "java/io/PrintWriter", + "", + "(Ljava/io/File;Ljava/lang/String;)V", + (file, encoding), + ) + .await + } + + async fn init_file(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + let output: ClassInstanceRef = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?.into(); + let writer: ClassInstanceRef = jvm + .new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (output,)) + .await? + .into(); + let writer: ClassInstanceRef = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (writer,)).await?.into(); + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/Writer;Z)V", (writer, false)) + .await + } + + async fn init_file_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + file: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + let output: ClassInstanceRef = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?.into(); + let writer: ClassInstanceRef = jvm + .new_class( + "java/io/OutputStreamWriter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (output, encoding), + ) + .await? + .into(); + let writer: ClassInstanceRef = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (writer,)).await?.into(); + jvm.invoke_special(&this, "java/io/PrintWriter", "", "(Ljava/io/Writer;Z)V", (writer, false)) + .await + } + async fn with_lock(jvm: &Jvm, lock: &ClassInstanceRef, operation: F) -> Result where F: Future>, @@ -508,6 +667,136 @@ impl PrintWriter { .await } + async fn set_error(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "trouble", "Z", true).await + } + + async fn printf( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + jvm.invoke_virtual( + &this, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + (format, arguments), + ) + .await + } + + async fn printf_with_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + locale: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + jvm.invoke_virtual( + &this, + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + (locale, format, arguments), + ) + .await + } + + async fn format( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_virtual( + &this, + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + (locale, format, arguments), + ) + .await + } + + async fn format_with_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + locale: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let lock: ClassInstanceRef = jvm.get_field(&this, "lock", "Ljava/lang/Object;").await?; + Self::with_lock(jvm, &lock, async { + let appendable: ClassInstanceRef = this.instance.clone().into(); + let formatter: ClassInstanceRef = jvm + .new_class("java/util/Formatter", "(Ljava/lang/Appendable;Ljava/util/Locale;)V", (appendable, locale)) + .await? + .into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + if jvm.get_field::(&this, "autoFlush", "Z").await? { + let _: () = jvm.invoke_virtual(&this, "flush", "()V", ()).await?; + } + Ok(this.clone()) + }) + .await + } + + async fn append_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/io/Writer", + "append", + "(Ljava/lang/CharSequence;)Ljava/io/Writer;", + (sequence,), + ) + .await?; + Ok(this) + } + + async fn append_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/io/Writer", + "append", + "(Ljava/lang/CharSequence;II)Ljava/io/Writer;", + (sequence, start, end), + ) + .await?; + Ok(this) + } + + async fn append_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, character: JavaChar) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special(&this, "java/io/Writer", "append", "(C)Ljava/io/Writer;", (character,)) + .await?; + Ok(this) + } + async fn suppress_io_exception(jvm: &Jvm, this: &ClassInstanceRef, result: Result<()>) -> Result<()> { match result { Ok(()) => Ok(()), diff --git a/java_runtime/src/classes/java/io/writer.rs b/java_runtime/src/classes/java/io/writer.rs index 5fd05f0d..208ea58a 100644 --- a/java_runtime/src/classes/java/io/writer.rs +++ b/java_runtime/src/classes/java/io/writer.rs @@ -2,11 +2,11 @@ use alloc::vec; use java_class_proto::{JavaFieldProto, JavaMethodProto}; use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; -use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::lang::{Object, String}, + classes::java::lang::{CharSequence, Object, String}, }; // abstract class java.io.Writer @@ -17,20 +17,51 @@ impl Writer { RuntimeClassProto { name: "java/io/Writer", parent_class: Some("java/lang/Object"), - interfaces: vec![], + interfaces: vec!["java/lang/Appendable", "java/io/Closeable", "java/io/Flushable"], methods: vec![ JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), JavaMethodProto::new("", "(Ljava/lang/Object;)V", Self::init_with_lock, MethodAccessFlags::PROTECTED), - JavaMethodProto::new("write", "(I)V", Self::write_char, Default::default()), - JavaMethodProto::new("write", "([C)V", Self::write_chars, Default::default()), - JavaMethodProto::new_abstract("write", "([CII)V", Default::default()), - JavaMethodProto::new("write", "(Ljava/lang/String;)V", Self::write_string, Default::default()), - JavaMethodProto::new("write", "(Ljava/lang/String;II)V", Self::write_string_offset, Default::default()), - JavaMethodProto::new_abstract("flush", "()V", Default::default()), - JavaMethodProto::new_abstract("close", "()V", Default::default()), + JavaMethodProto::new("write", "(I)V", Self::write_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "([C)V", Self::write_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract("write", "([CII)V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new("write", "(Ljava/lang/String;)V", Self::write_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(Ljava/lang/String;II)V", Self::write_string_offset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/io/Writer;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/io/Writer;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("append", "(C)Ljava/io/Writer;", Self::append_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/Appendable;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new_abstract("flush", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("close", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), ], fields: vec![JavaFieldProto::new("lock", "Ljava/lang/Object;", FieldAccessFlags::PROTECTED)], - access_flags: ClassAccessFlags::ABSTRACT, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, } } @@ -96,4 +127,45 @@ impl Writer { let chars: ClassInstanceRef> = jvm.invoke_virtual(&string, "toCharArray", "()[C", ()).await?; jvm.invoke_virtual(&this, "write", "([CII)V", (chars, off, len)).await } + + async fn append_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result> { + let string: ClassInstanceRef = if sequence.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + jvm.invoke_virtual(&sequence, "toString", "()Ljava/lang/String;", ()).await? + }; + let _: () = jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;)V", (string,)).await?; + Ok(this) + } + + async fn append_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let sequence: ClassInstanceRef = if sequence.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + sequence + }; + let subsequence: ClassInstanceRef = jvm + .invoke_virtual(&sequence, "subSequence", "(II)Ljava/lang/CharSequence;", (start, end)) + .await?; + let string: ClassInstanceRef = jvm.invoke_virtual(&subsequence, "toString", "()Ljava/lang/String;", ()).await?; + let _: () = jvm.invoke_virtual(&this, "write", "(Ljava/lang/String;)V", (string,)).await?; + Ok(this) + } + + async fn append_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, character: JavaChar) -> Result> { + let _: () = jvm.invoke_virtual(&this, "write", "(I)V", (character as i32,)).await?; + Ok(this) + } } diff --git a/java_runtime/src/classes/java/lang.rs b/java_runtime/src/classes/java/lang.rs index f191c11d..4482399d 100644 --- a/java_runtime/src/classes/java/lang.rs +++ b/java_runtime/src/classes/java/lang.rs @@ -1,4 +1,6 @@ mod abstract_method_error; +mod abstract_string_builder; +mod appendable; mod arithmetic_exception; mod array_index_out_of_bounds_exception; mod array_store_exception; @@ -16,6 +18,7 @@ mod clone_not_supported_exception; mod cloneable; mod comparable; mod double; +mod r#enum; mod error; mod exception; mod exception_in_initializer_error; @@ -55,6 +58,7 @@ mod short; mod stack_overflow_error; mod string; mod string_buffer; +mod string_builder; mod string_index_out_of_bounds_exception; mod system; mod thread; @@ -68,23 +72,24 @@ mod verify_error; 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, - char_sequence::CharSequence, 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, + abstract_method_error::AbstractMethodError, abstract_string_builder::AbstractStringBuilder, appendable::Appendable, + arithmetic_exception::ArithmeticException, array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, + array_store_exception::ArrayStoreException, boolean::Boolean, byte::Byte, char_sequence::CharSequence, 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, r#enum::Enum, 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_builder::StringBuilder, 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, diff --git a/java_runtime/src/classes/java/lang/abstract_string_builder.rs b/java_runtime/src/classes/java/lang/abstract_string_builder.rs new file mode 100644 index 00000000..1296bec7 --- /dev/null +++ b/java_runtime/src/classes/java/lang/abstract_string_builder.rs @@ -0,0 +1,1014 @@ +use alloc::{format, string::ToString, vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{CharSequence, Object, String, StringBuffer}, +}; + +// abstract class java.lang.AbstractStringBuilder +pub struct AbstractStringBuilder; + +impl AbstractStringBuilder { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/AbstractStringBuilder", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Appendable", "java/lang/CharSequence"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, Default::default()), + JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + JavaMethodProto::new("length", "()I", Self::length, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("capacity", "()I", Self::capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("ensureCapacity", "(I)V", Self::ensure_capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("trimToSize", "()V", Self::trim_to_size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setLength", "(I)V", Self::set_length, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("charAt", "(I)C", Self::char_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("codePointAt", "(I)I", Self::code_point_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("codePointBefore", "(I)I", Self::code_point_before, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("codePointCount", "(II)I", Self::code_point_count, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("offsetByCodePoints", "(II)I", Self::offset_by_code_points, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getChars", "(II[CI)V", Self::get_chars, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setCharAt", "(IC)V", Self::set_char_at, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + Self::append_object, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::append_string, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;", + Self::append_string_buffer, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "([C)Ljava/lang/AbstractStringBuilder;", + Self::append_char_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "([CII)Ljava/lang/AbstractStringBuilder;", + Self::append_char_array_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Z)Ljava/lang/AbstractStringBuilder;", + Self::append_boolean, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/AbstractStringBuilder;", + Self::append_char, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::append_int, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(J)Ljava/lang/AbstractStringBuilder;", + Self::append_long, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(F)Ljava/lang/AbstractStringBuilder;", + Self::append_float, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(D)Ljava/lang/AbstractStringBuilder;", + Self::append_double, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "appendCodePoint", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::append_code_point, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("delete", "(II)Ljava/lang/AbstractStringBuilder;", Self::delete, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "deleteCharAt", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::delete_char_at, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "replace", + "(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::replace, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("substring", "(I)Ljava/lang/String;", Self::substring, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("substring", "(II)Ljava/lang/String;", Self::substring_range, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "subSequence", + "(II)Ljava/lang/CharSequence;", + Self::sub_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(I[CII)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_array_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + Self::insert_object, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::insert_string, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(I[C)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(IZ)Ljava/lang/AbstractStringBuilder;", + Self::insert_boolean, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(IC)Ljava/lang/AbstractStringBuilder;", + Self::insert_char, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(II)Ljava/lang/AbstractStringBuilder;", + Self::insert_int, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(IJ)Ljava/lang/AbstractStringBuilder;", + Self::insert_long, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(IF)Ljava/lang/AbstractStringBuilder;", + Self::insert_float, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ID)Ljava/lang/AbstractStringBuilder;", + Self::insert_double, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("indexOf", "(Ljava/lang/String;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/String;I)I", Self::index_of_from, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/String;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "lastIndexOf", + "(Ljava/lang/String;I)I", + Self::last_index_of_from, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("reverse", "()Ljava/lang/AbstractStringBuilder;", Self::reverse, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/Appendable;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + ], + fields: vec![ + JavaFieldProto::new("value", "[C", Default::default()), + JavaFieldProto::new("count", "I", Default::default()), + ], + access_flags: ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, capacity: i32) -> Result<()> { + 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 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 + } + + async fn characters(jvm: &Jvm, this: &ClassInstanceRef) -> Result> { + let count: i32 = jvm.get_field(this, "count", "I").await?; + let value: ClassInstanceRef> = jvm.get_field(this, "value", "[C").await?; + jvm.load_array(&value, 0, count as usize).await + } + + async fn replace_characters(jvm: &Jvm, this: &mut ClassInstanceRef, characters: Vec) -> Result<()> { + let value: ClassInstanceRef> = jvm.get_field(this, "value", "[C").await?; + let old_capacity = jvm.array_length(&value).await?; + if characters.len() > old_capacity { + let new_capacity = characters.len().max(old_capacity.saturating_mul(2).saturating_add(2)); + let mut new_value = jvm.instantiate_array("C", new_capacity).await?; + jvm.store_array(&mut new_value, 0, characters.clone()).await?; + jvm.put_field(this, "value", "[C", new_value).await?; + } else if !characters.is_empty() { + let mut value = value; + jvm.store_array(&mut value, 0, characters.clone()).await?; + } + jvm.put_field(this, "count", "I", characters.len() as i32).await + } + + async fn insert_characters(jvm: &Jvm, this: &mut ClassInstanceRef, offset: i32, inserted: Vec) -> Result<()> { + let mut characters = Self::characters(jvm, this).await?; + if offset < 0 || offset as usize > characters.len() { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &format!("offset {offset}, length {}", characters.len()), + ) + .await); + } + characters.splice(offset as usize..offset as usize, inserted); + Self::replace_characters(jvm, this, characters).await + } + + async fn char_sequence_characters(jvm: &Jvm, sequence: &ClassInstanceRef) -> Result> { + if sequence.is_null() { + return Ok("null".encode_utf16().collect()); + } + if jvm.is_instance(&***sequence, "java/lang/String") { + return JavaLangString::to_utf16(jvm, sequence).await; + } + let length: i32 = jvm.invoke_virtual(sequence, "length", "()I", ()).await?; + let mut characters = Vec::with_capacity(length as usize); + for index in 0..length { + characters.push(jvm.invoke_virtual(sequence, "charAt", "(I)C", (index,)).await?); + } + Ok(characters) + } + + async fn length(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "count", "I").await + } + + async fn capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + Ok(jvm.array_length(&value).await? as i32) + } + + async fn ensure_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, minimum_capacity: i32) -> Result<()> { + if minimum_capacity <= 0 { + return Ok(()); + } + 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 characters = Self::characters(jvm, &this).await?; + let mut new_value = jvm.instantiate_array("C", new_capacity as usize).await?; + jvm.store_array(&mut new_value, 0, characters).await?; + jvm.put_field(&mut this, "value", "[C", new_value).await + } + + async fn trim_to_size(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let characters = Self::characters(jvm, &this).await?; + let mut value = jvm.instantiate_array("C", characters.len()).await?; + jvm.store_array(&mut value, 0, characters).await?; + jvm.put_field(&mut this, "value", "[C", value).await + } + + async fn set_length(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, new_length: i32) -> Result<()> { + if new_length < 0 { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", &new_length.to_string()).await); + } + let mut characters = Self::characters(jvm, &this).await?; + characters.resize(new_length as usize, 0); + Self::replace_characters(jvm, &mut this, characters).await + } + + async fn char_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { + let characters = Self::characters(jvm, &this).await?; + if index < 0 || index as usize >= characters.len() { + return Err(jvm + .exception( + "java/lang/StringIndexOutOfBoundsException", + &format!("index {index}, length {}", characters.len()), + ) + .await); + } + Ok(characters[index as usize]) + } + + async fn code_point_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { + let characters = Self::characters(jvm, &this).await?; + if index < 0 || index as usize >= characters.len() { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", &index.to_string()).await); + } + let first = characters[index as usize]; + if (0xd800..=0xdbff).contains(&first) && (index as usize + 1) < characters.len() { + let second = characters[index as usize + 1]; + if (0xdc00..=0xdfff).contains(&second) { + return Ok((((first as i32 - 0xd800) << 10) | (second as i32 - 0xdc00)) + 0x10000); + } + } + Ok(first as i32) + } + + async fn code_point_before(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { + let characters = Self::characters(jvm, &this).await?; + if index <= 0 || index as usize > characters.len() { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", &index.to_string()).await); + } + let second = characters[index as usize - 1]; + if (0xdc00..=0xdfff).contains(&second) && index > 1 { + let first = characters[index as usize - 2]; + if (0xd800..=0xdbff).contains(&first) { + return Ok((((first as i32 - 0xd800) << 10) | (second as i32 - 0xdc00)) + 0x10000); + } + } + Ok(second as i32) + } + + async fn code_point_count(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, begin: i32, end: i32) -> Result { + let characters = Self::characters(jvm, &this).await?; + if begin < 0 || end > characters.len() as i32 || begin > end { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid code point range").await); + } + let mut index = begin as usize; + let mut count = 0; + while index < end as usize { + if (0xd800..=0xdbff).contains(&characters[index]) && index + 1 < end as usize && (0xdc00..=0xdfff).contains(&characters[index + 1]) { + index += 2; + } else { + index += 1; + } + count += 1; + } + Ok(count) + } + + async fn offset_by_code_points( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + code_point_offset: i32, + ) -> Result { + let characters = Self::characters(jvm, &this).await?; + if index < 0 || index as usize > characters.len() { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid index").await); + } + let mut cursor = index as usize; + if code_point_offset >= 0 { + for _ in 0..code_point_offset { + if cursor >= characters.len() { + return Err(jvm + .exception("java/lang/IndexOutOfBoundsException", "code point offset exceeds length") + .await); + } + if (0xd800..=0xdbff).contains(&characters[cursor]) + && cursor + 1 < characters.len() + && (0xdc00..=0xdfff).contains(&characters[cursor + 1]) + { + cursor += 2; + } else { + cursor += 1; + } + } + } else { + for _ in code_point_offset..0 { + if cursor == 0 { + return Err(jvm + .exception("java/lang/IndexOutOfBoundsException", "code point offset precedes start") + .await); + } + cursor -= 1; + if (0xdc00..=0xdfff).contains(&characters[cursor]) && cursor > 0 && (0xd800..=0xdbff).contains(&characters[cursor - 1]) { + cursor -= 1; + } + } + } + Ok(cursor as i32) + } + + async fn get_chars( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_begin: i32, + source_end: i32, + mut destination: ClassInstanceRef>, + destination_begin: i32, + ) -> Result<()> { + let characters = Self::characters(jvm, &this).await?; + if source_begin < 0 || source_end > characters.len() as i32 || source_begin > source_end { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", "invalid source range").await); + } + if destination.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "destination is null").await); + } + let copy_length = source_end - source_begin; + let destination_length = jvm.array_length(&destination).await? as i32; + if destination_begin < 0 || destination_begin > destination_length - copy_length { + return Err(jvm + .exception("java/lang/ArrayIndexOutOfBoundsException", "invalid destination range") + .await); + } + jvm.store_array( + &mut destination, + destination_begin as usize, + characters[source_begin as usize..source_end as usize].to_vec(), + ) + .await + } + + async fn set_char_at(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, index: i32, character: JavaChar) -> Result<()> { + let mut characters = Self::characters(jvm, &this).await?; + if index < 0 || index as usize >= characters.len() { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", &index.to_string()).await); + } + characters[index as usize] = character; + Self::replace_characters(jvm, &mut this, characters).await + } + + async fn append_object( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + object: ClassInstanceRef, + ) -> Result> { + 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, "append", "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_string( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + string: ClassInstanceRef, + ) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + if string.is_null() { + characters.extend("null".encode_utf16()); + } else { + characters.extend(JavaLangString::to_utf16(jvm, &string).await?); + } + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn append_string_buffer( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + buffer: ClassInstanceRef, + ) -> Result> { + let string: ClassInstanceRef = if buffer.is_null() { + JavaLangString::from_rust_string(jvm, "null").await?.into() + } else { + jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await? + }; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + characters.extend(Self::char_sequence_characters(jvm, &sequence).await?); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn append_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let source = Self::char_sequence_characters(jvm, &sequence).await?; + if start < 0 || end > source.len() as i32 || start > end { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid CharSequence range").await); + } + let mut characters = Self::characters(jvm, &this).await?; + characters.extend_from_slice(&source[start as usize..end as usize]); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn append_char_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + array: ClassInstanceRef>, + ) -> Result> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let length = jvm.array_length(&array).await? as i32; + jvm.invoke_virtual(&this, "append", "([CII)Ljava/lang/AbstractStringBuilder;", (array, 0, length)) + .await + } + + async fn append_char_array_range( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + array: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result> { + 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", "invalid char array range").await); + } + let mut characters = Self::characters(jvm, &this).await?; + characters.extend(jvm.load_array::(&array, offset as usize, length as usize).await?); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn append_boolean(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result> { + let string = JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_char(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: JavaChar) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + characters.push(value); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn append_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result> { + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result> { + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_float(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result> { + 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/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result> { + 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/AbstractStringBuilder;", (string,)) + .await + } + + async fn append_code_point( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + code_point: i32, + ) -> Result> { + if !(0..=0x10ffff).contains(&code_point) { + return Err(jvm.exception("java/lang/IllegalArgumentException", "invalid code point").await); + } + let mut characters = Self::characters(jvm, &this).await?; + if code_point < 0x10000 { + characters.push(code_point as JavaChar); + } else { + let value = code_point - 0x10000; + characters.push((0xd800 + (value >> 10)) as JavaChar); + characters.push((0xdc00 + (value & 0x3ff)) as JavaChar); + } + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn delete(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, start: i32, end: i32) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + let end = end.min(characters.len() as i32); + if start < 0 || start > end { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", "invalid delete range").await); + } + characters.drain(start as usize..end as usize); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn delete_char_at(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, index: i32) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + if index < 0 || index as usize >= characters.len() { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", &index.to_string()).await); + } + characters.remove(index as usize); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn replace( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + start: i32, + end: i32, + string: ClassInstanceRef, + ) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + let end = end.min(characters.len() as i32); + if start < 0 || start > characters.len() as i32 || start > end { + return Err(jvm.exception("java/lang/StringIndexOutOfBoundsException", "invalid replace range").await); + } + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + characters.splice(start as usize..end as usize, JavaLangString::to_utf16(jvm, &string).await?); + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } + + async fn substring(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, start: i32) -> Result> { + 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> { + let characters = Self::characters(jvm, &this).await?; + if start < 0 || end > characters.len() as i32 || start > end { + return Err(jvm + .exception("java/lang/StringIndexOutOfBoundsException", "invalid substring range") + .await); + } + Ok(JavaLangString::from_utf16(jvm, characters[start as usize..end as usize].to_vec()) + .await? + .into()) + } + + async fn sub_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + jvm.invoke_virtual(&this, "substring", "(II)Ljava/lang/String;", (start, end)).await + } + + async fn insert_char_array_range( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + array: ClassInstanceRef>, + source_offset: i32, + length: i32, + ) -> Result> { + 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 source_offset < 0 || length < 0 || source_offset > array_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid char array range").await); + } + let inserted = jvm.load_array(&array, source_offset as usize, length as usize).await?; + Self::insert_characters(jvm, &mut this, offset, inserted).await?; + Ok(this) + } + + async fn insert_object( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + object: ClassInstanceRef, + ) -> Result> { + 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/AbstractStringBuilder;", + (offset, string), + ) + .await + } + + async fn insert_string( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + string: ClassInstanceRef, + ) -> Result> { + let inserted = if string.is_null() { + "null".encode_utf16().collect() + } else { + JavaLangString::to_utf16(jvm, &string).await? + }; + Self::insert_characters(jvm, &mut this, offset, inserted).await?; + Ok(this) + } + + async fn insert_char_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + array: ClassInstanceRef>, + ) -> Result> { + if array.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let length = jvm.array_length(&array).await? as i32; + jvm.invoke_virtual(&this, "insert", "(I[CII)Ljava/lang/AbstractStringBuilder;", (offset, array, 0, length)) + .await + } + + async fn insert_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + sequence: ClassInstanceRef, + ) -> Result> { + let inserted = Self::char_sequence_characters(jvm, &sequence).await?; + Self::insert_characters(jvm, &mut this, offset, inserted).await?; + Ok(this) + } + + async fn insert_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let source = Self::char_sequence_characters(jvm, &sequence).await?; + if start < 0 || end > source.len() as i32 || start > end { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "invalid CharSequence range").await); + } + Self::insert_characters(jvm, &mut this, offset, source[start as usize..end as usize].to_vec()).await?; + Ok(this) + } + + async fn insert_boolean( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: bool, + ) -> Result> { + let string = JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?; + jvm.invoke_virtual( + &this, + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (offset, string), + ) + .await + } + + async fn insert_char( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + offset: i32, + value: JavaChar, + ) -> Result> { + Self::insert_characters(jvm, &mut this, offset, vec![value]).await?; + Ok(this) + } + + async fn insert_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, offset: i32, value: i32) -> Result> { + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual( + &this, + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (offset, string), + ) + .await + } + + async fn insert_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, offset: i32, value: i64) -> Result> { + let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + jvm.invoke_virtual( + &this, + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (offset, string), + ) + .await + } + + async fn insert_float( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: f32, + ) -> Result> { + 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/AbstractStringBuilder;", + (offset, string), + ) + .await + } + + async fn insert_double( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: f64, + ) -> Result> { + 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/AbstractStringBuilder;", + (offset, string), + ) + .await + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result { + jvm.invoke_virtual(&this, "indexOf", "(Ljava/lang/String;I)I", (string, 0)).await + } + + async fn index_of_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + from_index: i32, + ) -> Result { + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let characters = Self::characters(jvm, &this).await?; + let needle = JavaLangString::to_utf16(jvm, &string).await?; + let start = from_index.max(0) as usize; + if needle.is_empty() { + return Ok(start.min(characters.len()) as i32); + } + if start > characters.len().saturating_sub(needle.len()) { + return Ok(-1); + } + Ok(characters[start..] + .windows(needle.len()) + .position(|candidate| candidate == needle) + .map_or(-1, |index| (start + index) as i32)) + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result { + let count: i32 = jvm.get_field(&this, "count", "I").await?; + jvm.invoke_virtual(&this, "lastIndexOf", "(Ljava/lang/String;I)I", (string, count)).await + } + + async fn last_index_of_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + from_index: i32, + ) -> Result { + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let characters = Self::characters(jvm, &this).await?; + let needle = JavaLangString::to_utf16(jvm, &string).await?; + if needle.is_empty() { + return Ok(from_index.min(characters.len() as i32).max(-1)); + } + if from_index < 0 || needle.len() > characters.len() { + return Ok(-1); + } + let last_start = (characters.len() - needle.len()).min(from_index as usize); + for index in (0..=last_start).rev() { + if characters[index..index + needle.len()] == needle { + return Ok(index as i32); + } + } + Ok(-1) + } + + async fn reverse(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let mut characters = Self::characters(jvm, &this).await?; + characters.reverse(); + let mut index = 0; + while index + 1 < characters.len() { + if (0xdc00..=0xdfff).contains(&characters[index]) && (0xd800..=0xdbff).contains(&characters[index + 1]) { + characters.swap(index, index + 1); + index += 2; + } else { + index += 1; + } + } + Self::replace_characters(jvm, &mut this, characters).await?; + Ok(this) + } +} diff --git a/java_runtime/src/classes/java/lang/appendable.rs b/java_runtime/src/classes/java/lang/appendable.rs new file mode 100644 index 00000000..4bcf7158 --- /dev/null +++ b/java_runtime/src/classes/java/lang/appendable.rs @@ -0,0 +1,38 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// public interface java.lang.Appendable +pub struct Appendable; + +impl Appendable { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Appendable", + parent_class: None, + interfaces: vec![], + methods: vec![ + JavaMethodProto::new_abstract( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new_abstract( + "append", + "(C)Ljava/lang/Appendable;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/lang/boolean.rs b/java_runtime/src/classes/java/lang/boolean.rs index 18648c32..4bd6c99b 100644 --- a/java_runtime/src/classes/java/lang/boolean.rs +++ b/java_runtime/src/classes/java/lang/boolean.rs @@ -26,6 +26,12 @@ impl Boolean { JavaMethodProto::new("", "(Z)V", Self::init, MethodAccessFlags::PUBLIC), JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), JavaMethodProto::new("booleanValue", "()Z", Self::boolean_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "valueOf", + "(Z)Ljava/lang/Boolean;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new( "valueOf", "(Ljava/lang/String;)Ljava/lang/Boolean;", @@ -114,6 +120,11 @@ impl Boolean { } } + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: bool) -> Result> { + jvm.get_static_field("java/lang/Boolean", if value { "TRUE" } else { "FALSE" }, "Ljava/lang/Boolean;") + .await + } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { let value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; Ok(JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?.into()) diff --git a/java_runtime/src/classes/java/lang/byte.rs b/java_runtime/src/classes/java/lang/byte.rs index b0a28ecf..82ccfa28 100644 --- a/java_runtime/src/classes/java/lang/byte.rs +++ b/java_runtime/src/classes/java/lang/byte.rs @@ -37,6 +37,12 @@ impl Byte { Self::parse_byte_radix, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, ), + JavaMethodProto::new( + "valueOf", + "(B)Ljava/lang/Byte;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new( "valueOf", "(Ljava/lang/String;)Ljava/lang/Byte;", @@ -148,6 +154,10 @@ impl Byte { Ok(jvm.new_class("java/lang/Byte", "(B)V", (value,)).await?.into()) } + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: i8) -> Result> { + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value,)).await?.into()) + } + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { let value: i8 = jvm .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;I)B", (value, radix)) diff --git a/java_runtime/src/classes/java/lang/character.rs b/java_runtime/src/classes/java/lang/character.rs index 56993d4f..dea84a31 100644 --- a/java_runtime/src/classes/java/lang/character.rs +++ b/java_runtime/src/classes/java/lang/character.rs @@ -23,6 +23,12 @@ impl Character { methods: vec![ JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), JavaMethodProto::new("", "(C)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "valueOf", + "(C)Ljava/lang/Character;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new("charValue", "()C", Self::char_value, MethodAccessFlags::PUBLIC), JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), @@ -385,6 +391,10 @@ impl Character { jvm.get_field(&this, "value", "C").await } + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result> { + Ok(jvm.new_class("java/lang/Character", "(C)V", (value,)).await?.into()) + } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { let value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; jvm.invoke_static("java/lang/String", "valueOf", "(C)Ljava/lang/String;", (value,)).await diff --git a/java_runtime/src/classes/java/lang/double.rs b/java_runtime/src/classes/java/lang/double.rs index 89fc0fce..e49b7e48 100644 --- a/java_runtime/src/classes/java/lang/double.rs +++ b/java_runtime/src/classes/java/lang/double.rs @@ -31,6 +31,12 @@ impl Double { Self::parse_double, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, ), + JavaMethodProto::new( + "valueOf", + "(D)Ljava/lang/Double;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new( "valueOf", "(Ljava/lang/String;)Ljava/lang/Double;", @@ -221,6 +227,10 @@ impl Double { Ok(jvm.new_class("java/lang/Double", "(D)V", (value,)).await?.into()) } + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: f64) -> Result> { + Ok(jvm.new_class("java/lang/Double", "(D)V", (value,)).await?.into()) + } + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; Ok((value as i32) as i8) diff --git a/java_runtime/src/classes/java/lang/enum.rs b/java_runtime/src/classes/java/lang/enum.rs new file mode 100644 index 00000000..17905f95 --- /dev/null +++ b/java_runtime/src/classes/java/lang/enum.rs @@ -0,0 +1,180 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangClass}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Class, Object, String}, +}; + +// public abstract class java.lang.Enum +pub struct Enum; + +impl Enum { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Enum", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/lang/Comparable", "java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;I)V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new( + "name", + "()Ljava/lang/String;", + Self::name, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new("ordinal", "()I", Self::ordinal, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "equals", + "(Ljava/lang/Object;)Z", + Self::equals, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + JavaMethodProto::new( + "clone", + "()Ljava/lang/Object;", + Self::clone, + MethodAccessFlags::PROTECTED | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new( + "compareTo", + "(Ljava/lang/Enum;)I", + Self::compare_to, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new( + "compareTo", + "(Ljava/lang/Object;)I", + Self::compare_to_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "getDeclaringClass", + "()Ljava/lang/Class;", + Self::get_declaring_class, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("finalize", "()V", Self::finalize, MethodAccessFlags::PROTECTED | MethodAccessFlags::FINAL), + ], + fields: vec![ + JavaFieldProto::new("name", "Ljava/lang/String;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + JavaFieldProto::new("ordinal", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, name: ClassInstanceRef, ordinal: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "name", "Ljava/lang/String;", name).await?; + jvm.put_field(&mut this, "ordinal", "I", ordinal).await + } + + async fn name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "name", "Ljava/lang/String;").await + } + + async fn ordinal(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "ordinal", "I").await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "name", "Ljava/lang/String;").await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + jvm.invoke_special(&this, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", (other,)) + .await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.invoke_special(&this, "java/lang/Object", "hashCode", "()I", ()).await + } + + async fn clone(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result> { + Err(jvm + .exception("java/lang/CloneNotSupportedException", "enum types may not be cloned") + .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); + } + let this_class = this.class_definition(); + let this_declaring_class = match this_class.super_class_name() { + Some(parent) if parent != "java/lang/Enum" => parent, + _ => this_class.name(), + }; + let other_class = other.class_definition(); + let other_declaring_class = match other_class.super_class_name() { + Some(parent) if parent != "java/lang/Enum" => parent, + _ => other_class.name(), + }; + if this_declaring_class != other_declaring_class { + return Err(jvm.exception("java/lang/ClassCastException", "enum types differ").await); + } + let this_ordinal: i32 = jvm.get_field(&this, "ordinal", "I").await?; + let other_ordinal: i32 = jvm.get_field(&other, "ordinal", "I").await?; + Ok(this_ordinal - other_ordinal) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + jvm.invoke_virtual(&this, "compareTo", "(Ljava/lang/Enum;)I", (other,)).await + } + + async fn get_declaring_class(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let class = this.class_definition(); + let declaring_class = match class.super_class_name() { + Some(parent) if parent != "java/lang/Enum" => parent, + _ => class.name(), + }; + Ok(jvm.resolve_class(&declaring_class).await?.java_class().into()) + } + + async fn value_of( + jvm: &Jvm, + _: &mut RuntimeContext, + enum_type: ClassInstanceRef, + name: ClassInstanceRef, + ) -> Result> { + if enum_type.is_null() || name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "enumType or name is null").await); + } + + let class_name = JavaLangClass::name(jvm, &enum_type).await?; + let name = jvm::runtime::JavaLangString::to_rust_string(jvm, &name).await?; + let class = JavaLangClass::to_rust_class(jvm, &enum_type).await?; + for field in class.fields() { + if field.name() == name + && field + .access_flags() + .contains(FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::ENUM) + { + return jvm.get_static_field(&class_name, &field.name(), &field.descriptor()).await; + } + } + + Err(jvm + .exception( + "java/lang/IllegalArgumentException", + &format!("No enum constant {}.{name}", class_name.replace('/', ".")), + ) + .await) + } + + async fn finalize(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/lang/float.rs b/java_runtime/src/classes/java/lang/float.rs index 83d0c887..ebe76242 100644 --- a/java_runtime/src/classes/java/lang/float.rs +++ b/java_runtime/src/classes/java/lang/float.rs @@ -32,6 +32,12 @@ impl Float { Self::parse_float, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, ), + JavaMethodProto::new( + "valueOf", + "(F)Ljava/lang/Float;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new( "valueOf", "(Ljava/lang/String;)Ljava/lang/Float;", @@ -227,6 +233,10 @@ impl Float { Ok(jvm.new_class("java/lang/Float", "(F)V", (value,)).await?.into()) } + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: f32) -> Result> { + Ok(jvm.new_class("java/lang/Float", "(F)V", (value,)).await?.into()) + } + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; Ok((value as i32) as i8) diff --git a/java_runtime/src/classes/java/lang/long.rs b/java_runtime/src/classes/java/lang/long.rs index 7c0b1539..0f27c139 100644 --- a/java_runtime/src/classes/java/lang/long.rs +++ b/java_runtime/src/classes/java/lang/long.rs @@ -37,6 +37,12 @@ impl Long { Self::parse_long_radix, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, ), + JavaMethodProto::new( + "valueOf", + "(J)Ljava/lang/Long;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new( "valueOf", "(Ljava/lang/String;)Ljava/lang/Long;", @@ -217,6 +223,10 @@ impl Long { .await?; Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) } + + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { let value: i64 = jvm .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (value, radix)) diff --git a/java_runtime/src/classes/java/lang/short.rs b/java_runtime/src/classes/java/lang/short.rs index 21637c8a..a913b7ec 100644 --- a/java_runtime/src/classes/java/lang/short.rs +++ b/java_runtime/src/classes/java/lang/short.rs @@ -37,6 +37,12 @@ impl Short { Self::parse_short_radix, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, ), + JavaMethodProto::new( + "valueOf", + "(S)Ljava/lang/Short;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), JavaMethodProto::new( "valueOf", "(Ljava/lang/String;)Ljava/lang/Short;", @@ -148,6 +154,10 @@ impl Short { Ok(jvm.new_class("java/lang/Short", "(S)V", (value,)).await?.into()) } + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: i16) -> Result> { + Ok(jvm.new_class("java/lang/Short", "(S)V", (value,)).await?.into()) + } + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { let value: i16 = jvm .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (value, radix)) diff --git a/java_runtime/src/classes/java/lang/string.rs b/java_runtime/src/classes/java/lang/string.rs index 8e6f5a2f..f9650d68 100644 --- a/java_runtime/src/classes/java/lang/string.rs +++ b/java_runtime/src/classes/java/lang/string.rs @@ -15,7 +15,10 @@ use crate::{ RuntimeClassProto, RuntimeContext, classes::java::{ lang::{Object, System}, - util::regex::{Matcher, Pattern}, + util::{ + Formatter, Locale, + regex::{Matcher, Pattern}, + }, }, }; @@ -113,6 +116,18 @@ impl String { Self::split_with_limit, MethodAccessFlags::PUBLIC, ), + JavaMethodProto::new( + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + Self::format, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + Self::format_with_locale, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::VARARGS, + ), JavaMethodProto::new( "regionMatches", "(ILjava/lang/String;II)Z", @@ -1074,6 +1089,43 @@ impl String { .await } + async fn format( + jvm: &Jvm, + _: &mut RuntimeContext, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let formatter: ClassInstanceRef = jvm.new_class("java/util/Formatter", "()V", ()).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await + } + + async fn format_with_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + locale: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let formatter: ClassInstanceRef = jvm.new_class("java/util/Formatter", "(Ljava/util/Locale;)V", (locale,)).await?.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await + } + #[allow(clippy::too_many_arguments)] async fn region_matches( jvm: &Jvm, diff --git a/java_runtime/src/classes/java/lang/string_buffer.rs b/java_runtime/src/classes/java/lang/string_buffer.rs index d6d89cbd..51c638b0 100644 --- a/java_runtime/src/classes/java/lang/string_buffer.rs +++ b/java_runtime/src/classes/java/lang/string_buffer.rs @@ -1,12 +1,12 @@ use alloc::{string::ToString, vec, vec::Vec}; -use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::lang::{CharSequence, Object, String}, + classes::java::lang::{AbstractStringBuilder, CharSequence, Object, String}, }; // public final class java.lang.StringBuffer @@ -16,12 +16,18 @@ impl StringBuffer { pub fn as_proto() -> RuntimeClassProto { RuntimeClassProto { name: "java/lang/StringBuffer", - parent_class: Some("java/lang/Object"), - interfaces: vec!["java/lang/CharSequence"], + parent_class: Some("java/lang/AbstractStringBuilder"), + interfaces: vec!["java/io/Serializable", "java/lang/CharSequence"], methods: vec![ 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( + "", + "(Ljava/lang/CharSequence;)V", + Self::init_with_char_sequence, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new( "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", @@ -34,6 +40,24 @@ impl StringBuffer { Self::append_object, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), + JavaMethodProto::new( + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer;", + Self::append_string_buffer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/StringBuffer;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new( "append", "(Z)Ljava/lang/StringBuffer;", @@ -52,6 +76,12 @@ impl StringBuffer { Self::append_integer, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), + JavaMethodProto::new( + "appendCodePoint", + "(I)Ljava/lang/StringBuffer;", + Self::append_code_point, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new( "append", "(J)Ljava/lang/StringBuffer;", @@ -94,46 +124,39 @@ impl 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", "(IZ)Ljava/lang/StringBuffer;", Self::insert_boolean, MethodAccessFlags::PUBLIC), 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), + JavaMethodProto::new("insert", "(IJ)Ljava/lang/StringBuffer;", Self::insert_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(IF)Ljava/lang/StringBuffer;", Self::insert_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(ID)Ljava/lang/StringBuffer;", Self::insert_double, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "insert", - "(II)Ljava/lang/StringBuffer;", - Self::insert_integer, - MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, - ), - JavaMethodProto::new( - "insert", - "(IJ)Ljava/lang/StringBuffer;", - Self::insert_long, + "(I[C)Ljava/lang/StringBuffer;", + Self::insert_char_array, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), JavaMethodProto::new( "insert", - "(IF)Ljava/lang/StringBuffer;", - Self::insert_float, + "(I[CII)Ljava/lang/StringBuffer;", + Self::insert_char_array_range, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), JavaMethodProto::new( "insert", - "(ID)Ljava/lang/StringBuffer;", - Self::insert_double, - MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + "(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer;", + Self::insert_char_sequence, + MethodAccessFlags::PUBLIC, ), JavaMethodProto::new( "insert", - "(I[C)Ljava/lang/StringBuffer;", - Self::insert_char_array, + "(ILjava/lang/CharSequence;II)Ljava/lang/StringBuffer;", + Self::insert_char_sequence_range, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), JavaMethodProto::new( @@ -178,6 +201,20 @@ impl StringBuffer { Self::reverse, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), + JavaMethodProto::new("indexOf", "(Ljava/lang/String;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "indexOf", + "(Ljava/lang/String;I)I", + Self::index_of_from, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/String;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "lastIndexOf", + "(Ljava/lang/String;I)I", + Self::last_index_of_from, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new( "toString", "()Ljava/lang/String;", @@ -196,6 +233,12 @@ impl StringBuffer { Self::ensure_capacity, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), + JavaMethodProto::new( + "trimToSize", + "()V", + Self::trim_to_size, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new( "setLength", "(I)V", @@ -209,6 +252,30 @@ impl StringBuffer { Self::char_at, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), + JavaMethodProto::new( + "codePointAt", + "(I)I", + Self::code_point_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "codePointBefore", + "(I)I", + Self::code_point_before, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "codePointCount", + "(II)I", + Self::code_point_count, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "offsetByCodePoints", + "(II)I", + Self::offset_by_code_points, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), JavaMethodProto::new( "setCharAt", "(IC)V", @@ -221,11 +288,206 @@ impl StringBuffer { Self::get_chars, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, ), + JavaMethodProto::new( + "append", + "(Ljava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + Self::append_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::append_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;", + Self::append_string_buffer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "([C)Ljava/lang/AbstractStringBuilder;", + Self::append_char_array_all, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "([CII)Ljava/lang/AbstractStringBuilder;", + Self::append_char_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Z)Ljava/lang/AbstractStringBuilder;", + Self::append_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/AbstractStringBuilder;", + Self::append_character, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::append_integer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(J)Ljava/lang/AbstractStringBuilder;", + Self::append_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(F)Ljava/lang/AbstractStringBuilder;", + Self::append_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(D)Ljava/lang/AbstractStringBuilder;", + Self::append_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "appendCodePoint", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::append_code_point, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "delete", + "(II)Ljava/lang/AbstractStringBuilder;", + Self::delete, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "deleteCharAt", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::delete_char_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "replace", + "(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::replace, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(I[CII)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_array_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + Self::insert_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::insert_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(I[C)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IZ)Ljava/lang/AbstractStringBuilder;", + Self::insert_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IC)Ljava/lang/AbstractStringBuilder;", + Self::insert_character, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(II)Ljava/lang/AbstractStringBuilder;", + Self::insert_integer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IJ)Ljava/lang/AbstractStringBuilder;", + Self::insert_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IF)Ljava/lang/AbstractStringBuilder;", + Self::insert_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ID)Ljava/lang/AbstractStringBuilder;", + Self::insert_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "reverse", + "()Ljava/lang/AbstractStringBuilder;", + Self::reverse, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/Appendable;", + Self::append_character, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), ], - fields: vec![ - JavaFieldProto::new("value", "[C", FieldAccessFlags::PRIVATE), - JavaFieldProto::new("count", "I", FieldAccessFlags::PRIVATE), - ], + fields: vec![], access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, } } @@ -236,34 +498,46 @@ impl StringBuffer { jvm.invoke_special(&this, "java/lang/StringBuffer", "", "(I)V", (16,)).await } - async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, capacity: i32) -> Result<()> { + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, capacity: i32) -> Result<()> { tracing::debug!("java.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 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(()) + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "", "(I)V", (capacity,)) + .await } - async fn init_with_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { + async fn init_with_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { 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 chars = JavaLangString::to_utf16(jvm, &string).await?; - let count = chars.len(); - 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?; + let _: () = jvm + .invoke_special(&this, "java/lang/AbstractStringBuilder", "", "(I)V", ((chars.len() + 16) as i32,)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", (string,)) + .await?; + Ok(()) + } + + async fn init_with_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result<()> { + if sequence.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "seq is null").await); + } + let length: i32 = jvm.invoke_virtual(&sequence, "length", "()I", ()).await?; + let _: () = jvm + .invoke_special(&this, "java/lang/AbstractStringBuilder", "", "(I)V", (length + 16,)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "append", "(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;", (sequence,)) + .await?; Ok(()) } @@ -301,6 +575,62 @@ impl StringBuffer { .await } + async fn append_string_buffer( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + buffer: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;", + (buffer,), + ) + .await?; + Ok(this) + } + + async fn append_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + (sequence,), + ) + .await?; + Ok(this) + } + + async fn append_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + (sequence, start, end), + ) + .await?; + Ok(this) + } + async fn append_boolean(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result> { tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); @@ -329,6 +659,19 @@ impl StringBuffer { .await } + async fn append_code_point(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, code_point: i32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "appendCodePoint", + "(I)Ljava/lang/AbstractStringBuilder;", + (code_point,), + ) + .await?; + Ok(this) + } + async fn append_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result> { tracing::debug!("java.lang.StringBuffer::append({this:?}, {value})"); @@ -544,6 +887,79 @@ impl StringBuffer { Ok(this) } + async fn insert_char_array_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + array: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(I[CII)Ljava/lang/AbstractStringBuilder;", + (index, array, offset, length), + ) + .await?; + Ok(this) + } + + async fn insert_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + sequence: ClassInstanceRef, + ) -> Result> { + if sequence.is_null() { + let sequence = JavaLangString::from_rust_string(jvm, "null").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, sequence)) + .await?; + } else if jvm.is_instance(&**sequence, "java/lang/String") { + let sequence: ClassInstanceRef = ClassInstanceRef::new(sequence.instance); + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (offset, sequence)) + .await?; + } else { + let length: i32 = jvm.invoke_virtual(&sequence, "length", "()I", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &this, + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/StringBuffer;", + (offset, sequence, 0, length), + ) + .await?; + } + Ok(this) + } + + async fn insert_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + (offset, sequence, start, end), + ) + .await?; + Ok(this) + } + async fn delete(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, start: i32, end: i32) -> Result> { tracing::debug!("java.lang.StringBuffer::delete({this:?}, {start}, {end})"); @@ -677,6 +1093,56 @@ impl StringBuffer { Ok(this) } + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "indexOf", "(Ljava/lang/String;)I", (string,)) + .await + } + + async fn index_of_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + from_index: i32, + ) -> Result { + jvm.invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "indexOf", + "(Ljava/lang/String;I)I", + (string, from_index), + ) + .await + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result { + jvm.invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "lastIndexOf", + "(Ljava/lang/String;)I", + (string,), + ) + .await + } + + async fn last_index_of_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + from_index: i32, + ) -> Result { + jvm.invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "lastIndexOf", + "(Ljava/lang/String;I)I", + (string, from_index), + ) + .await + } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.StringBuffer::toString({this:?})"); @@ -705,6 +1171,11 @@ impl StringBuffer { Ok(()) } + async fn trim_to_size(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "trimToSize", "()V", ()) + .await + } + async fn set_length(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, new_length: i32) -> Result<()> { tracing::debug!("java.lang.StringBuffer::setLength({this:?}, {new_length})"); @@ -746,6 +1217,26 @@ impl StringBuffer { Ok(jvm.load_array(&value, index as usize, 1).await?[0]) } + async fn code_point_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "codePointAt", "(I)I", (index,)) + .await + } + + async fn code_point_before(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "codePointBefore", "(I)I", (index,)) + .await + } + + async fn code_point_count(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, begin: i32, end: i32) -> Result { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "codePointCount", "(II)I", (begin, end)) + .await + } + + async fn offset_by_code_points(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32, offset: i32) -> Result { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "offsetByCodePoints", "(II)I", (index, offset)) + .await + } + 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})"); diff --git a/java_runtime/src/classes/java/lang/string_builder.rs b/java_runtime/src/classes/java/lang/string_builder.rs new file mode 100644 index 00000000..5d64a110 --- /dev/null +++ b/java_runtime/src/classes/java/lang/string_builder.rs @@ -0,0 +1,973 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{AbstractStringBuilder, CharSequence, Object, String, StringBuffer}, +}; + +// public final class java.lang.StringBuilder +pub struct StringBuilder; + +impl StringBuilder { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/StringBuilder", + parent_class: Some("java/lang/AbstractStringBuilder"), + interfaces: vec!["java/io/Serializable", "java/lang/CharSequence"], + methods: vec![ + 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( + "", + "(Ljava/lang/CharSequence;)V", + Self::init_with_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", + Self::append_object, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", + Self::append_string, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;", + Self::append_string_buffer, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "([C)Ljava/lang/StringBuilder;", + Self::append_char_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "append", + "([CII)Ljava/lang/StringBuilder;", + Self::append_char_array_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("append", "(Z)Ljava/lang/StringBuilder;", Self::append_boolean, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("append", "(C)Ljava/lang/StringBuilder;", Self::append_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("append", "(I)Ljava/lang/StringBuilder;", Self::append_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("append", "(J)Ljava/lang/StringBuilder;", Self::append_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("append", "(F)Ljava/lang/StringBuilder;", Self::append_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("append", "(D)Ljava/lang/StringBuilder;", Self::append_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "appendCodePoint", + "(I)Ljava/lang/StringBuilder;", + Self::append_code_point, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("delete", "(II)Ljava/lang/StringBuilder;", Self::delete, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "deleteCharAt", + "(I)Ljava/lang/StringBuilder;", + Self::delete_char_at, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "replace", + "(IILjava/lang/String;)Ljava/lang/StringBuilder;", + Self::replace, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(I[CII)Ljava/lang/StringBuilder;", + Self::insert_char_array_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/Object;)Ljava/lang/StringBuilder;", + Self::insert_object, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/String;)Ljava/lang/StringBuilder;", + Self::insert_string, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(I[C)Ljava/lang/StringBuilder;", + Self::insert_char_array, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/StringBuilder;", + Self::insert_char_sequence, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/StringBuilder;", + Self::insert_char_sequence_range, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("insert", "(IZ)Ljava/lang/StringBuilder;", Self::insert_boolean, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(IC)Ljava/lang/StringBuilder;", Self::insert_char, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(II)Ljava/lang/StringBuilder;", Self::insert_int, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(IJ)Ljava/lang/StringBuilder;", Self::insert_long, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(IF)Ljava/lang/StringBuilder;", Self::insert_float, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("insert", "(ID)Ljava/lang/StringBuilder;", Self::insert_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/String;)I", Self::index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("indexOf", "(Ljava/lang/String;I)I", Self::index_of_from, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("lastIndexOf", "(Ljava/lang/String;)I", Self::last_index_of, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "lastIndexOf", + "(Ljava/lang/String;I)I", + Self::last_index_of_from, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("reverse", "()Ljava/lang/StringBuilder;", Self::reverse, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "append", + "(Ljava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + Self::append_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::append_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;", + Self::append_string_buffer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "([C)Ljava/lang/AbstractStringBuilder;", + Self::append_char_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "([CII)Ljava/lang/AbstractStringBuilder;", + Self::append_char_array_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Z)Ljava/lang/AbstractStringBuilder;", + Self::append_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/AbstractStringBuilder;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::append_int, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(J)Ljava/lang/AbstractStringBuilder;", + Self::append_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(F)Ljava/lang/AbstractStringBuilder;", + Self::append_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(D)Ljava/lang/AbstractStringBuilder;", + Self::append_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "appendCodePoint", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::append_code_point, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "delete", + "(II)Ljava/lang/AbstractStringBuilder;", + Self::delete, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "deleteCharAt", + "(I)Ljava/lang/AbstractStringBuilder;", + Self::delete_char_at, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "replace", + "(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::replace, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(I[CII)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_array_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + Self::insert_object, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + Self::insert_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(I[C)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_array, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + Self::insert_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IZ)Ljava/lang/AbstractStringBuilder;", + Self::insert_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IC)Ljava/lang/AbstractStringBuilder;", + Self::insert_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(II)Ljava/lang/AbstractStringBuilder;", + Self::insert_int, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IJ)Ljava/lang/AbstractStringBuilder;", + Self::insert_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(IF)Ljava/lang/AbstractStringBuilder;", + Self::insert_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "insert", + "(ID)Ljava/lang/AbstractStringBuilder;", + Self::insert_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "reverse", + "()Ljava/lang/AbstractStringBuilder;", + Self::reverse, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + Self::append_char_sequence, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + Self::append_char_sequence_range, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + JavaMethodProto::new( + "append", + "(C)Ljava/lang/Appendable;", + Self::append_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "", "(I)V", (16,)) + .await + } + + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, capacity: i32) -> Result<()> { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "", "(I)V", (capacity,)) + .await + } + + async fn init_with_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "str is null").await); + } + let characters = JavaLangString::to_utf16(jvm, &string).await?; + let _: () = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "", + "(I)V", + ((characters.len() + 16) as i32,), + ) + .await?; + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (string,), + ) + .await?; + Ok(()) + } + + async fn init_with_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result<()> { + if sequence.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "seq is null").await); + } + let length: i32 = jvm.invoke_virtual(&sequence, "length", "()I", ()).await?; + let _: () = jvm + .invoke_special(&this, "java/lang/AbstractStringBuilder", "", "(I)V", (length + 16,)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + (sequence,), + ) + .await?; + Ok(()) + } + + async fn append_object( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + object: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + (object,), + ) + .await?; + Ok(this) + } + + async fn append_string( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (string,), + ) + .await?; + Ok(this) + } + + async fn append_string_buffer( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + buffer: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;", + (buffer,), + ) + .await?; + Ok(this) + } + + async fn append_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + (sequence,), + ) + .await?; + Ok(this) + } + + async fn append_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + (sequence, start, end), + ) + .await?; + Ok(this) + } + + async fn append_char_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + array: ClassInstanceRef>, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "([C)Ljava/lang/AbstractStringBuilder;", + (array,), + ) + .await?; + Ok(this) + } + + async fn append_char_array_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + array: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "([CII)Ljava/lang/AbstractStringBuilder;", + (array, offset, length), + ) + .await?; + Ok(this) + } + + async fn append_boolean(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: bool) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(Z)Ljava/lang/AbstractStringBuilder;", + (value,), + ) + .await?; + Ok(this) + } + + async fn append_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: JavaChar) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(C)Ljava/lang/AbstractStringBuilder;", + (value,), + ) + .await?; + Ok(this) + } + + async fn append_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(I)Ljava/lang/AbstractStringBuilder;", + (value,), + ) + .await?; + Ok(this) + } + + async fn append_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: i64) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(J)Ljava/lang/AbstractStringBuilder;", + (value,), + ) + .await?; + Ok(this) + } + + async fn append_float(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(F)Ljava/lang/AbstractStringBuilder;", + (value,), + ) + .await?; + Ok(this) + } + + async fn append_double(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: f64) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "append", + "(D)Ljava/lang/AbstractStringBuilder;", + (value,), + ) + .await?; + Ok(this) + } + + async fn append_code_point(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, code_point: i32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "appendCodePoint", + "(I)Ljava/lang/AbstractStringBuilder;", + (code_point,), + ) + .await?; + Ok(this) + } + + async fn delete(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, start: i32, end: i32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "delete", + "(II)Ljava/lang/AbstractStringBuilder;", + (start, end), + ) + .await?; + Ok(this) + } + + async fn delete_char_at(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, index: i32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "deleteCharAt", + "(I)Ljava/lang/AbstractStringBuilder;", + (index,), + ) + .await?; + Ok(this) + } + + async fn replace( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + start: i32, + end: i32, + string: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "replace", + "(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (start, end, string), + ) + .await?; + Ok(this) + } + + async fn insert_char_array_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + index: i32, + array: ClassInstanceRef>, + offset: i32, + length: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(I[CII)Ljava/lang/AbstractStringBuilder;", + (index, array, offset, length), + ) + .await?; + Ok(this) + } + + async fn insert_object( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + object: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(ILjava/lang/Object;)Ljava/lang/AbstractStringBuilder;", + (offset, object), + ) + .await?; + Ok(this) + } + + async fn insert_string( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + string: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;", + (offset, string), + ) + .await?; + Ok(this) + } + + async fn insert_char_array( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + array: ClassInstanceRef>, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(I[C)Ljava/lang/AbstractStringBuilder;", + (offset, array), + ) + .await?; + Ok(this) + } + + async fn insert_char_sequence( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + sequence: ClassInstanceRef, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;", + (offset, sequence), + ) + .await?; + Ok(this) + } + + async fn insert_char_sequence_range( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + sequence: ClassInstanceRef, + start: i32, + end: i32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(ILjava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;", + (offset, sequence, start, end), + ) + .await?; + Ok(this) + } + + async fn insert_boolean( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: bool, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(IZ)Ljava/lang/AbstractStringBuilder;", + (offset, value), + ) + .await?; + Ok(this) + } + + async fn insert_char( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: JavaChar, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(IC)Ljava/lang/AbstractStringBuilder;", + (offset, value), + ) + .await?; + Ok(this) + } + + async fn insert_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, offset: i32, value: i32) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(II)Ljava/lang/AbstractStringBuilder;", + (offset, value), + ) + .await?; + Ok(this) + } + + async fn insert_long(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, offset: i32, value: i64) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(IJ)Ljava/lang/AbstractStringBuilder;", + (offset, value), + ) + .await?; + Ok(this) + } + + async fn insert_float( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: f32, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(IF)Ljava/lang/AbstractStringBuilder;", + (offset, value), + ) + .await?; + Ok(this) + } + + async fn insert_double( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + offset: i32, + value: f64, + ) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "insert", + "(ID)Ljava/lang/AbstractStringBuilder;", + (offset, value), + ) + .await?; + Ok(this) + } + + async fn index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result { + jvm.invoke_special(&this, "java/lang/AbstractStringBuilder", "indexOf", "(Ljava/lang/String;)I", (string,)) + .await + } + + async fn index_of_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + from_index: i32, + ) -> Result { + jvm.invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "indexOf", + "(Ljava/lang/String;I)I", + (string, from_index), + ) + .await + } + + async fn last_index_of(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result { + jvm.invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "lastIndexOf", + "(Ljava/lang/String;)I", + (string,), + ) + .await + } + + async fn last_index_of_from( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + string: ClassInstanceRef, + from_index: i32, + ) -> Result { + jvm.invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "lastIndexOf", + "(Ljava/lang/String;I)I", + (string, from_index), + ) + .await + } + + async fn reverse(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let _: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/lang/AbstractStringBuilder", + "reverse", + "()Ljava/lang/AbstractStringBuilder;", + (), + ) + .await?; + Ok(this) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let count: i32 = jvm.get_field(&this, "count", "I").await?; + let value: ClassInstanceRef> = jvm.get_field(&this, "value", "[C").await?; + Ok(JavaLangString::from_utf16(jvm, jvm.load_array(&value, 0, count as usize).await?) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util.rs b/java_runtime/src/classes/java/util.rs index 36de5584..0adaf164 100644 --- a/java_runtime/src/classes/java/util.rs +++ b/java_runtime/src/classes/java/util.rs @@ -33,8 +33,15 @@ mod comparator; mod concurrent_modification_exception; mod date; mod dictionary; +mod duplicate_format_flags_exception; mod empty_stack_exception; mod enumeration; +mod format_flags_conversion_mismatch_exception; +mod formattable; +mod formattable_flags; +mod formatter; +mod formatter_big_decimal_layout_form; +mod formatter_closed_exception; mod gregorian_calendar; mod hash_map; mod hash_map_entry; @@ -52,6 +59,12 @@ mod hashtable_entry_set; mod hashtable_enumerator; mod hashtable_key_set; mod hashtable_values; +mod illegal_format_code_point_exception; +mod illegal_format_conversion_exception; +mod illegal_format_exception; +mod illegal_format_flags_exception; +mod illegal_format_precision_exception; +mod illegal_format_width_exception; mod iterator; mod linked_list; mod linked_list_entry; @@ -61,6 +74,8 @@ mod list_iterator; mod locale; mod map; mod map_entry; +mod missing_format_argument_exception; +mod missing_format_width_exception; mod no_such_element_exception; mod properties; mod random; @@ -86,6 +101,8 @@ mod tree_map_sub_map; mod tree_map_value_iterator; mod tree_map_values; mod tree_set; +mod unknown_format_conversion_exception; +mod unknown_format_flags_exception; mod vector; mod vector_itr; @@ -121,8 +138,15 @@ pub use self::{ concurrent_modification_exception::ConcurrentModificationException, date::Date, dictionary::Dictionary, + duplicate_format_flags_exception::DuplicateFormatFlagsException, empty_stack_exception::EmptyStackException, enumeration::Enumeration, + format_flags_conversion_mismatch_exception::FormatFlagsConversionMismatchException, + formattable::Formattable, + formattable_flags::FormattableFlags, + formatter::Formatter, + formatter_big_decimal_layout_form::FormatterBigDecimalLayoutForm, + formatter_closed_exception::FormatterClosedException, gregorian_calendar::GregorianCalendar, hash_map::HashMap, hash_map_entry::HashMapEntry, @@ -140,6 +164,12 @@ pub use self::{ hashtable_enumerator::HashtableEnumerator, hashtable_key_set::HashtableKeySet, hashtable_values::HashtableValues, + illegal_format_code_point_exception::IllegalFormatCodePointException, + illegal_format_conversion_exception::IllegalFormatConversionException, + illegal_format_exception::IllegalFormatException, + illegal_format_flags_exception::IllegalFormatFlagsException, + illegal_format_precision_exception::IllegalFormatPrecisionException, + illegal_format_width_exception::IllegalFormatWidthException, iterator::Iterator, linked_list::LinkedList, linked_list_entry::LinkedListEntry, @@ -149,6 +179,8 @@ pub use self::{ locale::Locale, map::Map, map_entry::MapEntry, + missing_format_argument_exception::MissingFormatArgumentException, + missing_format_width_exception::MissingFormatWidthException, no_such_element_exception::NoSuchElementException, properties::Properties, random::Random, @@ -174,6 +206,8 @@ pub use self::{ tree_map_value_iterator::TreeMapValueIterator, tree_map_values::TreeMapValues, tree_set::TreeSet, + unknown_format_conversion_exception::UnknownFormatConversionException, + unknown_format_flags_exception::UnknownFormatFlagsException, vector::Vector, vector_itr::VectorItr, }; diff --git a/java_runtime/src/classes/java/util/duplicate_format_flags_exception.rs b/java_runtime/src/classes/java/util/duplicate_format_flags_exception.rs new file mode 100644 index 00000000..7d646943 --- /dev/null +++ b/java_runtime/src/classes/java/util/duplicate_format_flags_exception.rs @@ -0,0 +1,46 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.DuplicateFormatFlagsException +pub struct DuplicateFormatFlagsException; + +impl DuplicateFormatFlagsException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/DuplicateFormatFlagsException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFlags", "()Ljava/lang/String;", Self::get_flags, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("flags", "Ljava/lang/String;", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, flags: ClassInstanceRef) -> Result<()> { + if flags.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "flags is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "flags", "Ljava/lang/String;", flags).await + } + + async fn get_flags(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "flags", "Ljava/lang/String;").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let flags: ClassInstanceRef = jvm.get_field(&this, "flags", "Ljava/lang/String;").await?; + JavaLangString::from_rust_string(jvm, &alloc::format!("Flags = '{}'", JavaLangString::to_rust_string(jvm, &flags).await?)) + .await + .map(Into::into) + } +} diff --git a/java_runtime/src/classes/java/util/format_flags_conversion_mismatch_exception.rs b/java_runtime/src/classes/java/util/format_flags_conversion_mismatch_exception.rs new file mode 100644 index 00000000..7b0612c9 --- /dev/null +++ b/java_runtime/src/classes/java/util/format_flags_conversion_mismatch_exception.rs @@ -0,0 +1,69 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.FormatFlagsConversionMismatchException +pub struct FormatFlagsConversionMismatchException; + +impl FormatFlagsConversionMismatchException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/FormatFlagsConversionMismatchException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;C)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFlags", "()Ljava/lang/String;", Self::get_flags, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getConversion", "()C", Self::get_conversion, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("flags", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("conversion", "C", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + flags: ClassInstanceRef, + conversion: JavaChar, + ) -> Result<()> { + if flags.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "flags is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "flags", "Ljava/lang/String;", flags).await?; + jvm.put_field(&mut this, "conversion", "C", conversion).await + } + + async fn get_flags(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "flags", "Ljava/lang/String;").await + } + + async fn get_conversion(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "conversion", "C").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let flags: ClassInstanceRef = jvm.get_field(&this, "flags", "Ljava/lang/String;").await?; + let conversion: JavaChar = jvm.get_field(&this, "conversion", "C").await?; + Ok(JavaLangString::from_rust_string( + jvm, + &format!( + "Conversion = {}, Flags = {}", + char::from_u32(conversion as u32).unwrap_or('\u{fffd}'), + JavaLangString::to_rust_string(jvm, &flags).await? + ), + ) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/formattable.rs b/java_runtime/src/classes/java/util/formattable.rs new file mode 100644 index 00000000..201bba65 --- /dev/null +++ b/java_runtime/src/classes/java/util/formattable.rs @@ -0,0 +1,26 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; + +use crate::RuntimeClassProto; + +// public interface java.util.Formattable +pub struct Formattable; + +impl Formattable { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Formattable", + parent_class: None, + interfaces: vec![], + methods: vec![JavaMethodProto::new_abstract( + "formatTo", + "(Ljava/util/Formatter;III)V", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + )], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/util/formattable_flags.rs b/java_runtime/src/classes/java/util/formattable_flags.rs new file mode 100644 index 00000000..816edba3 --- /dev/null +++ b/java_runtime/src/classes/java/util/formattable_flags.rs @@ -0,0 +1,52 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext}; + +// public class java.util.FormattableFlags +pub struct FormattableFlags; + +impl FormattableFlags { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/FormattableFlags", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PRIVATE), + ], + fields: vec![ + JavaFieldProto::new( + "LEFT_JUSTIFY", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "UPPERCASE", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "ALTERNATE", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/util/FormattableFlags", "LEFT_JUSTIFY", "I", 1).await?; + jvm.put_static_field("java/util/FormattableFlags", "UPPERCASE", "I", 2).await?; + jvm.put_static_field("java/util/FormattableFlags", "ALTERNATE", "I", 4).await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/formatter.rs b/java_runtime/src/classes/java/util/formatter.rs new file mode 100644 index 00000000..7d5bad6d --- /dev/null +++ b/java_runtime/src/classes/java/util/formatter.rs @@ -0,0 +1,1387 @@ +use alloc::{format, string::String as RustString, string::ToString, vec, vec::Vec}; +use core::fmt::Write; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{File, IOException, OutputStream, OutputStreamWriter, PrintStream, Writer}, + lang::{Appendable, CharSequence, Object, String}, + util::Locale, + }, +}; + +struct FormatSpecifier { + text: RustString, + argument_index: Option, + reuse_previous: bool, + flags: Vec, + width: Option, + precision: Option, + conversion: char, +} + +enum NumericKind { + Finite, + Nan, + Infinite, +} + +// public final class java.util.Formatter +pub struct Formatter; + +impl Formatter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Formatter", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Closeable", "java/io/Flushable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/lang/Appendable;)V", + Self::init_with_appendable, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/util/Locale;)V", Self::init_with_locale, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + Self::init_with_appendable_locale, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_path, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::init_with_path_encoding, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Locale;)V", + Self::init_with_path_encoding_locale, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/io/File;)V", Self::init_with_file, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/File;Ljava/lang/String;)V", + Self::init_with_file_encoding, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/io/File;Ljava/lang/String;Ljava/util/Locale;)V", + Self::init_with_file_encoding_locale, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/io/PrintStream;)V", + Self::init_with_print_stream, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/io/OutputStream;)V", + Self::init_with_output_stream, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + Self::init_with_output_stream_encoding, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "", + "(Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Locale;)V", + Self::init_with_output_stream_encoding_locale, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("locale", "()Ljava/util/Locale;", Self::locale, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("out", "()Ljava/lang/Appendable;", Self::out, 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), + JavaMethodProto::new("ioException", "()Ljava/io/IOException;", Self::io_exception, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + Self::format, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + JavaMethodProto::new( + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + Self::format_with_locale, + MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS, + ), + ], + fields: vec![ + JavaFieldProto::new("a", "Ljava/lang/Appendable;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("l", "Ljava/util/Locale;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + JavaFieldProto::new("lastException", "Ljava/io/IOException;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (ClassInstanceRef::::new(None), locale), + ) + .await + } + + async fn init_with_appendable( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + appendable: ClassInstanceRef, + ) -> Result<()> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (appendable, locale), + ) + .await + } + + async fn init_with_locale(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, locale: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (ClassInstanceRef::::new(None), locale), + ) + .await + } + + async fn init_with_appendable_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + appendable: ClassInstanceRef, + locale: ClassInstanceRef, + ) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let appendable: ClassInstanceRef = if appendable.is_null() { + jvm.new_class("java/lang/StringBuilder", "()V", ()).await?.into() + } else { + appendable + }; + jvm.put_field(&mut this, "a", "Ljava/lang/Appendable;", appendable).await?; + jvm.put_field(&mut this, "l", "Ljava/util/Locale;", locale).await?; + jvm.put_field( + &mut this, + "lastException", + "Ljava/io/IOException;", + ClassInstanceRef::::new(None), + ) + .await + } + + async fn init_with_path(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, path: ClassInstanceRef) -> Result<()> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file name is null").await); + } + let file: ClassInstanceRef = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?.into(); + jvm.invoke_special(&this, "java/util/Formatter", "", "(Ljava/io/File;)V", (file,)) + .await + } + + async fn init_with_path_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + path: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Locale;)V", + (path, encoding, locale), + ) + .await + } + + async fn init_with_path_encoding_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + path: ClassInstanceRef, + encoding: ClassInstanceRef, + locale: ClassInstanceRef, + ) -> Result<()> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file name is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + let file: ClassInstanceRef = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?.into(); + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/io/File;Ljava/lang/String;Ljava/util/Locale;)V", + (file, encoding, locale), + ) + .await + } + + async fn init_with_file(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + let stream: ClassInstanceRef = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?.into(); + let writer: ClassInstanceRef = jvm + .new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (stream,)) + .await? + .into(); + let appendable: ClassInstanceRef = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (writer,)).await?.into(); + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (appendable, locale), + ) + .await + } + + async fn init_with_file_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + file: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/io/File;Ljava/lang/String;Ljava/util/Locale;)V", + (file, encoding, locale), + ) + .await + } + + async fn init_with_file_encoding_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + file: ClassInstanceRef, + encoding: ClassInstanceRef, + locale: ClassInstanceRef, + ) -> Result<()> { + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + let stream: ClassInstanceRef = jvm.new_class("java/io/FileOutputStream", "(Ljava/io/File;)V", (file,)).await?.into(); + let writer: ClassInstanceRef = jvm + .new_class( + "java/io/OutputStreamWriter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (stream, encoding), + ) + .await? + .into(); + let appendable: ClassInstanceRef = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (writer,)).await?.into(); + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (appendable, locale), + ) + .await + } + + async fn init_with_print_stream( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + stream: ClassInstanceRef, + ) -> Result<()> { + if stream.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "print stream is null").await); + } + let appendable: ClassInstanceRef = stream.instance.into(); + jvm.invoke_special(&this, "java/util/Formatter", "", "(Ljava/lang/Appendable;)V", (appendable,)) + .await + } + + async fn init_with_output_stream( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + stream: ClassInstanceRef, + ) -> Result<()> { + if stream.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output stream is null").await); + } + let writer: ClassInstanceRef = jvm + .new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (stream,)) + .await? + .into(); + let appendable: ClassInstanceRef = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (writer,)).await?.into(); + jvm.invoke_special(&this, "java/util/Formatter", "", "(Ljava/lang/Appendable;)V", (appendable,)) + .await + } + + async fn init_with_output_stream_encoding( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + stream: ClassInstanceRef, + encoding: ClassInstanceRef, + ) -> Result<()> { + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Locale;)V", + (stream, encoding, locale), + ) + .await + } + + async fn init_with_output_stream_encoding_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + stream: ClassInstanceRef, + encoding: ClassInstanceRef, + locale: ClassInstanceRef, + ) -> Result<()> { + if stream.is_null() || encoding.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output stream or encoding is null").await); + } + let writer: ClassInstanceRef = jvm + .new_class( + "java/io/OutputStreamWriter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (stream, encoding), + ) + .await? + .into(); + let appendable: ClassInstanceRef = jvm.new_class("java/io/BufferedWriter", "(Ljava/io/Writer;)V", (writer,)).await?.into(); + jvm.invoke_special( + &this, + "java/util/Formatter", + "", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (appendable, locale), + ) + .await + } + + async fn locale(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let _: ClassInstanceRef = jvm.invoke_virtual(&this, "out", "()Ljava/lang/Appendable;", ()).await?; + jvm.get_field(&this, "l", "Ljava/util/Locale;").await + } + + async fn out(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let appendable: ClassInstanceRef = jvm.get_field(&this, "a", "Ljava/lang/Appendable;").await?; + if appendable.is_null() { + return Err(JavaError::JavaException( + jvm.new_class("java/util/FormatterClosedException", "()V", ()).await?, + )); + } + Ok(appendable) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let appendable: ClassInstanceRef = jvm.invoke_virtual(&this, "out", "()Ljava/lang/Appendable;", ()).await?; + jvm.invoke_virtual(&appendable, "toString", "()Ljava/lang/String;", ()).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let appendable: ClassInstanceRef = jvm.invoke_virtual(&this, "out", "()Ljava/lang/Appendable;", ()).await?; + if !jvm.is_instance(&**appendable, "java/io/Flushable") { + return Ok(()); + } + match jvm.invoke_virtual(&appendable, "flush", "()V", ()).await { + Ok(()) => Ok(()), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + jvm.put_field( + &mut this, + "lastException", + "Ljava/io/IOException;", + ClassInstanceRef::::from(exception), + ) + .await + } + Err(error) => Err(error), + } + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let appendable: ClassInstanceRef = jvm.get_field(&this, "a", "Ljava/lang/Appendable;").await?; + if appendable.is_null() { + return Ok(()); + } + let result = if jvm.is_instance(&**appendable, "java/io/Closeable") { + jvm.invoke_virtual(&appendable, "close", "()V", ()).await + } else { + Ok(()) + }; + jvm.put_field(&mut this, "a", "Ljava/lang/Appendable;", ClassInstanceRef::::new(None)) + .await?; + match result { + Ok(()) => Ok(()), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + jvm.put_field( + &mut this, + "lastException", + "Ljava/io/IOException;", + ClassInstanceRef::::from(exception), + ) + .await + } + Err(error) => Err(error), + } + } + + async fn io_exception(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "lastException", "Ljava/io/IOException;").await + } + + async fn append_output(jvm: &Jvm, this: &mut ClassInstanceRef, characters: Vec) -> Result<()> { + if characters.is_empty() { + return Ok(()); + } + let appendable: ClassInstanceRef = jvm.invoke_virtual(this, "out", "()Ljava/lang/Appendable;", ()).await?; + let text: ClassInstanceRef = JavaLangString::from_utf16(jvm, characters).await?.into(); + match jvm + .invoke_virtual::<_, ClassInstanceRef>(&appendable, "append", "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", (text,)) + .await + { + Ok(_) => Ok(()), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/io/IOException") => { + jvm.put_field( + this, + "lastException", + "Ljava/io/IOException;", + ClassInstanceRef::::from(exception), + ) + .await + } + Err(error) => Err(error), + } + } + + async fn format( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let locale: ClassInstanceRef = jvm.get_field(&this, "l", "Ljava/util/Locale;").await?; + jvm.invoke_virtual( + &this, + "format", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (locale, format, arguments), + ) + .await + } + + async fn format_with_locale( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + locale: ClassInstanceRef, + format: ClassInstanceRef, + arguments: ClassInstanceRef>, + ) -> Result> { + let _: ClassInstanceRef = jvm.invoke_virtual(&this, "out", "()Ljava/lang/Appendable;", ()).await?; + if format.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "format is null").await); + } + + let characters = JavaLangString::to_utf16(jvm, &format).await?; + let argument_values: Option>> = if arguments.is_null() { + None + } else { + Some(jvm.load_array(&arguments, 0, jvm.array_length(&arguments).await?).await?) + }; + for validation_only in [true, false] { + let mut cursor = 0; + let mut literal_start = 0; + let mut ordinary_index = 0; + let mut previous_index = None; + + while cursor < characters.len() { + if characters[cursor] != '%' as JavaChar { + cursor += 1; + continue; + } + if !validation_only { + Self::append_output(jvm, &mut this, characters[literal_start..cursor].to_vec()).await?; + } + let specifier_start = cursor; + cursor += 1; + if cursor == characters.len() { + let conversion = JavaLangString::from_rust_string(jvm, "%").await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/UnknownFormatConversionException", "(Ljava/lang/String;)V", (conversion,)) + .await?, + )); + } + + let mut argument_index = None; + let number_start = cursor; + while cursor < characters.len() && char::from_u32(characters[cursor] as u32).is_some_and(|value| value.is_ascii_digit()) { + cursor += 1; + } + if cursor > number_start && cursor < characters.len() && characters[cursor] == '$' as JavaChar { + let value = RustString::from_utf16_lossy(&characters[number_start..cursor]) + .parse::() + .unwrap_or(0); + argument_index = value.checked_sub(1); + cursor += 1; + } else { + cursor = number_start; + } + + let mut flags = Vec::new(); + while cursor < characters.len() { + let flag = char::from_u32(characters[cursor] as u32).unwrap_or('\u{fffd}'); + if !matches!(flag, '-' | '#' | '+' | ' ' | '0' | ',' | '(' | '<') { + break; + } + if flags.contains(&flag) { + let duplicate = JavaLangString::from_rust_string(jvm, &flag.to_string()).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/DuplicateFormatFlagsException", "(Ljava/lang/String;)V", (duplicate,)) + .await?, + )); + } + flags.push(flag); + cursor += 1; + } + + let width_start = cursor; + while cursor < characters.len() && char::from_u32(characters[cursor] as u32).is_some_and(|value| value.is_ascii_digit()) { + cursor += 1; + } + let width = if cursor > width_start { + match RustString::from_utf16_lossy(&characters[width_start..cursor]).parse::() { + Ok(width) => Some(width as usize), + Err(_) => { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatWidthException", "(I)V", (-1,)).await?, + )); + } + } + } else { + None + }; + + let precision = if cursor < characters.len() && characters[cursor] == '.' as JavaChar { + cursor += 1; + let precision_start = cursor; + while cursor < characters.len() && char::from_u32(characters[cursor] as u32).is_some_and(|value| value.is_ascii_digit()) { + cursor += 1; + } + if precision_start == cursor { + let conversion = JavaLangString::from_rust_string(jvm, ".").await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/UnknownFormatConversionException", "(Ljava/lang/String;)V", (conversion,)) + .await?, + )); + } + match RustString::from_utf16_lossy(&characters[precision_start..cursor]).parse::() { + Ok(precision) => Some(precision as usize), + Err(_) => { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatPrecisionException", "(I)V", (-1,)).await?, + )); + } + } + } else { + None + }; + + if cursor == characters.len() { + let conversion = JavaLangString::from_rust_string(jvm, "%").await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/UnknownFormatConversionException", "(Ljava/lang/String;)V", (conversion,)) + .await?, + )); + } + let conversion = char::from_u32(characters[cursor] as u32).unwrap_or('\u{fffd}'); + cursor += 1; + let text = RustString::from_utf16_lossy(&characters[specifier_start..cursor]); + let specifier = FormatSpecifier { + text, + argument_index, + reuse_previous: flags.contains(&'<'), + flags, + width, + precision, + conversion, + }; + + if !matches!( + specifier.conversion, + 's' | 'S' | 'b' | 'B' | 'h' | 'H' | 'c' | 'C' | 'd' | 'o' | 'x' | 'X' | 'e' | 'E' | 'f' | 'g' | 'G' | '%' | 'n' + ) { + let conversion = JavaLangString::from_rust_string(jvm, &specifier.conversion.to_string()).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/UnknownFormatConversionException", "(Ljava/lang/String;)V", (conversion,)) + .await?, + )); + } + + if specifier.conversion == '%' { + if let Some(precision) = specifier.precision { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatPrecisionException", "(I)V", (precision as i32,)) + .await?, + )); + } + let illegal_flags: RustString = specifier.flags.iter().filter(|flag| **flag != '-').collect(); + if !illegal_flags.is_empty() { + let flags = JavaLangString::from_rust_string(jvm, &specifier.flags.iter().collect::()).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatFlagsException", "(Ljava/lang/String;)V", (flags,)) + .await?, + )); + } + if specifier.flags.contains(&'-') && specifier.width.is_none() { + let text = JavaLangString::from_rust_string(jvm, &specifier.text).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/MissingFormatWidthException", "(Ljava/lang/String;)V", (text,)) + .await?, + )); + } + if !validation_only { + let output = Self::apply_width(jvm, vec!['%' as JavaChar], &specifier, ' ' as JavaChar).await?; + Self::append_output(jvm, &mut this, output).await?; + } + literal_start = cursor; + continue; + } + if specifier.conversion == 'n' { + if let Some(precision) = specifier.precision { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatPrecisionException", "(I)V", (precision as i32,)) + .await?, + )); + } + if let Some(width) = specifier.width { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatWidthException", "(I)V", (width as i32,)).await?, + )); + } + if !specifier.flags.is_empty() { + let flags = JavaLangString::from_rust_string(jvm, &specifier.flags.iter().collect::()).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatFlagsException", "(Ljava/lang/String;)V", (flags,)) + .await?, + )); + } + if !validation_only { + 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 line = if separator.is_null() { + vec!['\n' as JavaChar] + } else { + JavaLangString::to_utf16(jvm, &separator).await? + }; + Self::append_output(jvm, &mut this, line).await?; + } + literal_start = cursor; + continue; + } + + if specifier.flags.contains(&'-') && specifier.width.is_none() { + let text = JavaLangString::from_rust_string(jvm, &specifier.text).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/MissingFormatWidthException", "(Ljava/lang/String;)V", (text,)) + .await?, + )); + } + Self::validate_specifier(jvm, &specifier).await?; + if validation_only { + literal_start = cursor; + continue; + } + + let selected_index = if specifier.reuse_previous { + previous_index + } else if let Some(index) = specifier.argument_index { + Some(index) + } else { + let index = ordinary_index; + ordinary_index += 1; + Some(index) + }; + let Some(selected_index) = selected_index else { + let text = JavaLangString::from_rust_string(jvm, &specifier.text).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/MissingFormatArgumentException", "(Ljava/lang/String;)V", (text,)) + .await?, + )); + }; + previous_index = Some(selected_index); + let argument = match &argument_values { + None => ClassInstanceRef::::new(None), + Some(values) if selected_index < values.len() => values[selected_index].clone(), + Some(_) => { + let text = JavaLangString::from_rust_string(jvm, &specifier.text).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/MissingFormatArgumentException", "(Ljava/lang/String;)V", (text,)) + .await?, + )); + } + }; + if let Some(output) = Self::format_argument(jvm, &this, &locale, &specifier, argument).await? { + Self::append_output(jvm, &mut this, output).await?; + } + literal_start = cursor; + } + if !validation_only { + Self::append_output(jvm, &mut this, characters[literal_start..].to_vec()).await?; + } + } + Ok(this) + } + + async fn validate_flags(jvm: &Jvm, specifier: &FormatSpecifier, allowed: &str) -> Result<()> { + let invalid: RustString = specifier.flags.iter().filter(|flag| **flag != '<' && !allowed.contains(**flag)).collect(); + if invalid.is_empty() { + return Ok(()); + } + let flags = JavaLangString::from_rust_string(jvm, &invalid).await?; + Err(JavaError::JavaException( + jvm.new_class( + "java/util/FormatFlagsConversionMismatchException", + "(Ljava/lang/String;C)V", + (flags, specifier.conversion as JavaChar), + ) + .await?, + )) + } + + async fn validate_specifier(jvm: &Jvm, specifier: &FormatSpecifier) -> Result<()> { + let conversion = specifier.conversion.to_ascii_lowercase(); + match conversion { + 's' => Self::validate_flags(jvm, specifier, "-#").await, + 'b' | 'h' => Self::validate_flags(jvm, specifier, "-").await, + 'c' => { + if let Some(precision) = specifier.precision { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatPrecisionException", "(I)V", (precision as i32,)) + .await?, + )); + } + Self::validate_flags(jvm, specifier, "-").await + } + 'd' | 'o' | 'x' => { + if specifier.flags.contains(&'0') && specifier.width.is_none() { + let text = JavaLangString::from_rust_string(jvm, &specifier.text).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/MissingFormatWidthException", "(Ljava/lang/String;)V", (text,)) + .await?, + )); + } + Self::validate_numeric_flags(jvm, specifier).await?; + if let Some(precision) = specifier.precision { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatPrecisionException", "(I)V", (precision as i32,)) + .await?, + )); + } + Self::validate_flags(jvm, specifier, if conversion == 'd' { "-+ 0,(" } else { "-#+ 0(" }).await + } + 'e' | 'f' | 'g' => { + if specifier.flags.contains(&'0') && specifier.width.is_none() { + let text = JavaLangString::from_rust_string(jvm, &specifier.text).await?; + return Err(JavaError::JavaException( + jvm.new_class("java/util/MissingFormatWidthException", "(Ljava/lang/String;)V", (text,)) + .await?, + )); + } + Self::validate_numeric_flags(jvm, specifier).await?; + Self::validate_flags( + jvm, + specifier, + match conversion { + 'e' => "-+ 0(#", + 'f' => "-+ 0,(#", + 'g' => "-+ 0,(", + _ => "", + }, + ) + .await + } + _ => Ok(()), + } + } + + async fn illegal_conversion(jvm: &Jvm, specifier: &FormatSpecifier, argument: &ClassInstanceRef) -> Result { + let class_name = argument.class_definition().name(); + let argument_class = jvm.resolve_class(&class_name).await?.java_class(); + Ok(JavaError::JavaException( + jvm.new_class( + "java/util/IllegalFormatConversionException", + "(CLjava/lang/Class;)V", + (specifier.conversion as JavaChar, argument_class), + ) + .await?, + )) + } + + async fn uppercase_utf16(jvm: &Jvm, text: Vec) -> Result> { + let Some(capacity) = text.len().checked_mul(6) else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + let mut output = Vec::new(); + if output.try_reserve_exact(capacity).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + for character in char::decode_utf16(text) { + match character { + Ok(character) => { + for uppercase in character.to_uppercase() { + let mut encoded = [0; 2]; + output.extend_from_slice(uppercase.encode_utf16(&mut encoded)); + } + } + Err(error) => output.push(error.unpaired_surrogate()), + } + } + Ok(output) + } + + async fn format_argument( + jvm: &Jvm, + formatter: &ClassInstanceRef, + locale: &ClassInstanceRef, + specifier: &FormatSpecifier, + argument: ClassInstanceRef, + ) -> Result>> { + let conversion = specifier.conversion.to_ascii_lowercase(); + let uppercase = specifier.conversion.is_ascii_uppercase(); + + match conversion { + 's' => { + if !argument.is_null() && jvm.is_instance(&**argument, "java/util/Formattable") { + let mut flags = 0; + if specifier.flags.contains(&'-') { + flags |= 1; + } + if uppercase { + flags |= 2; + } + if specifier.flags.contains(&'#') { + flags |= 4; + } + let formatter_locale: ClassInstanceRef = jvm.get_field(formatter, "l", "Ljava/util/Locale;").await?; + let callback_formatter = if (formatter_locale.is_null() && locale.is_null()) + || (!formatter_locale.is_null() && !locale.is_null() && formatter_locale.identity() == locale.identity()) + { + formatter.clone() + } else { + let appendable: ClassInstanceRef = jvm.invoke_virtual(formatter, "out", "()Ljava/lang/Appendable;", ()).await?; + jvm.new_class( + "java/util/Formatter", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + (appendable, locale.clone()), + ) + .await? + .into() + }; + let _: () = jvm + .invoke_virtual( + &argument, + "formatTo", + "(Ljava/util/Formatter;III)V", + ( + callback_formatter, + flags, + specifier.width.map_or(-1, |width| width as i32), + specifier.precision.map_or(-1, |precision| precision as i32), + ), + ) + .await?; + return Ok(None); + } + if specifier.flags.contains(&'#') { + let flags = JavaLangString::from_rust_string(jvm, "#").await?; + return Err(JavaError::JavaException( + jvm.new_class( + "java/util/FormatFlagsConversionMismatchException", + "(Ljava/lang/String;C)V", + (flags, specifier.conversion as JavaChar), + ) + .await?, + )); + } + let mut text = if argument.is_null() { + "null".encode_utf16().collect() + } else { + let value: ClassInstanceRef = jvm.invoke_virtual(&argument, "toString", "()Ljava/lang/String;", ()).await?; + JavaLangString::to_utf16(jvm, &value).await? + }; + if let Some(precision) = specifier.precision { + text.truncate(precision); + } + if uppercase { + text = Self::uppercase_utf16(jvm, text).await?; + } + Ok(Some(Self::apply_width(jvm, text, specifier, ' ' as JavaChar).await?)) + } + 'b' => { + let value = if argument.is_null() { + false + } else if jvm.is_instance(&**argument, "java/lang/Boolean") { + jvm.invoke_virtual(&argument, "booleanValue", "()Z", ()).await? + } else { + true + }; + let mut text: Vec = if value { "true" } else { "false" }.encode_utf16().collect(); + if let Some(precision) = specifier.precision { + text.truncate(precision); + } + if uppercase { + text = Self::uppercase_utf16(jvm, text).await?; + } + Ok(Some(Self::apply_width(jvm, text, specifier, ' ' as JavaChar).await?)) + } + 'h' => { + let mut text: Vec = if argument.is_null() { + "null".encode_utf16().collect() + } else { + let hash: i32 = jvm.invoke_virtual(&argument, "hashCode", "()I", ()).await?; + format!("{:x}", hash as u32).encode_utf16().collect() + }; + if let Some(precision) = specifier.precision { + text.truncate(precision); + } + if uppercase { + text = Self::uppercase_utf16(jvm, text).await?; + } + Ok(Some(Self::apply_width(jvm, text, specifier, ' ' as JavaChar).await?)) + } + 'c' => { + if argument.is_null() { + let text = if uppercase { "NULL" } else { "null" }.encode_utf16().collect(); + return Ok(Some(Self::apply_width(jvm, text, specifier, ' ' as JavaChar).await?)); + } + let code_point = if jvm.is_instance(&**argument, "java/lang/Character") { + jvm.invoke_virtual::<_, JavaChar>(&argument, "charValue", "()C", ()).await? as i32 + } else if jvm.is_instance(&**argument, "java/lang/Byte") + || jvm.is_instance(&**argument, "java/lang/Short") + || jvm.is_instance(&**argument, "java/lang/Integer") + { + jvm.invoke_virtual(&argument, "intValue", "()I", ()).await? + } else { + return Err(Self::illegal_conversion(jvm, specifier, &argument).await?); + }; + if !(0..=0x10ffff).contains(&code_point) { + return Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatCodePointException", "(I)V", (code_point,)).await?, + )); + } + let mut text = if code_point < 0x10000 { + vec![code_point as JavaChar] + } else { + let value = code_point - 0x10000; + vec![(0xd800 + (value >> 10)) as JavaChar, (0xdc00 + (value & 0x3ff)) as JavaChar] + }; + if uppercase { + text = Self::uppercase_utf16(jvm, text).await?; + } + Ok(Some(Self::apply_width(jvm, text, specifier, ' ' as JavaChar).await?)) + } + 'd' | 'o' | 'x' => Self::format_integral(jvm, specifier, argument, uppercase).await.map(Some), + 'e' | 'f' | 'g' => Self::format_floating(jvm, specifier, argument, uppercase).await.map(Some), + _ => Ok(Some(Vec::new())), + } + } + + async fn apply_width(jvm: &Jvm, mut text: Vec, specifier: &FormatSpecifier, padding: JavaChar) -> Result> { + let width = specifier.width.unwrap_or(0); + if text.len() >= width { + return Ok(text); + } + let padding_length = width - text.len(); + if specifier.flags.contains(&'-') { + if text.try_reserve_exact(padding_length).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + text.resize(width, padding); + Ok(text) + } else { + let mut output = Vec::new(); + if output.try_reserve_exact(width).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + output.resize(padding_length, padding); + output.extend(text); + Ok(output) + } + } + + async fn validate_numeric_flags(jvm: &Jvm, specifier: &FormatSpecifier) -> Result<()> { + let conflicting = + (specifier.flags.contains(&'+') && specifier.flags.contains(&' ')) || (specifier.flags.contains(&'-') && specifier.flags.contains(&'0')); + if !conflicting { + return Ok(()); + } + let flags = JavaLangString::from_rust_string(jvm, &specifier.flags.iter().collect::()).await?; + Err(JavaError::JavaException( + jvm.new_class("java/util/IllegalFormatFlagsException", "(Ljava/lang/String;)V", (flags,)) + .await?, + )) + } + + async fn format_integral(jvm: &Jvm, specifier: &FormatSpecifier, argument: ClassInstanceRef, uppercase: bool) -> Result> { + let conversion = specifier.conversion.to_ascii_lowercase(); + if argument.is_null() { + return Self::apply_width( + jvm, + if uppercase { "NULL" } else { "null" }.encode_utf16().collect(), + specifier, + ' ' as JavaChar, + ) + .await; + } + + let class_name = argument.class_definition().name(); + let bits = match class_name.as_str() { + "java/lang/Byte" => 8, + "java/lang/Short" => 16, + "java/lang/Integer" => 32, + "java/lang/Long" => 64, + _ => return Err(Self::illegal_conversion(jvm, specifier, &argument).await?), + }; + if conversion != 'd' { + Self::validate_flags(jvm, specifier, "-#0").await?; + } + let value: i64 = jvm.invoke_virtual(&argument, "longValue", "()J", ()).await?; + let (digits, negative, prefix) = if conversion == 'd' { + (value.unsigned_abs().to_string(), value < 0, RustString::new()) + } else { + let unsigned = match bits { + 8 => value as i8 as u8 as u64, + 16 => value as i16 as u16 as u64, + 32 => value as i32 as u32 as u64, + _ => value as u64, + }; + let digits = if conversion == 'o' { + format!("{unsigned:o}") + } else if uppercase { + format!("{unsigned:X}") + } else { + format!("{unsigned:x}") + }; + let prefix = if specifier.flags.contains(&'#') { + if conversion == 'o' { + "0".into() + } else if uppercase { + "0X".into() + } else { + "0x".into() + } + } else { + RustString::new() + }; + (digits, false, prefix) + }; + Self::finish_numeric( + jvm, + digits, + negative, + prefix, + specifier, + conversion == 'd' && specifier.flags.contains(&','), + NumericKind::Finite, + ) + .await + } + + async fn format_floating(jvm: &Jvm, specifier: &FormatSpecifier, argument: ClassInstanceRef, uppercase: bool) -> Result> { + let conversion = specifier.conversion.to_ascii_lowercase(); + if argument.is_null() { + return Self::apply_width( + jvm, + if uppercase { "NULL" } else { "null" }.encode_utf16().collect(), + specifier, + ' ' as JavaChar, + ) + .await; + } + if !jvm.is_instance(&**argument, "java/lang/Float") && !jvm.is_instance(&**argument, "java/lang/Double") { + return Err(Self::illegal_conversion(jvm, specifier, &argument).await?); + } + let value: f64 = jvm.invoke_virtual(&argument, "doubleValue", "()D", ()).await?; + let negative = value.is_sign_negative() && !value.is_nan(); + let magnitude = libm::fabs(value); + let precision = specifier.precision.unwrap_or(6); + let mut digits = if value.is_nan() { + "NaN".into() + } else if value.is_infinite() { + "Infinity".into() + } else { + match conversion { + 'f' => Self::decimal(jvm, magnitude, precision).await?, + 'e' => Self::scientific(jvm, magnitude, precision).await?, + 'g' => { + let precision = precision.max(1); + let exponent = if magnitude == 0.0 { + 0 + } else { + libm::floor(libm::log10(magnitude)) as i32 + }; + if exponent < -4 || exponent >= precision as i32 { + Self::scientific(jvm, magnitude, precision - 1).await? + } else { + let fractional = (precision as i32 - exponent - 1).max(0) as usize; + let rounded = Self::round_fraction(magnitude, fractional); + let rounded_exponent = if rounded == 0.0 { 0 } else { libm::floor(libm::log10(rounded)) as i32 }; + if rounded_exponent >= precision as i32 { + Self::scientific(jvm, magnitude, precision - 1).await? + } else { + let fractional = (precision as i32 - rounded_exponent - 1).max(0) as usize; + Self::decimal(jvm, magnitude, fractional).await? + } + } + } + _ => RustString::new(), + } + }; + if value.is_finite() && specifier.flags.contains(&'#') && !digits.contains('.') { + if let Some(exponent) = digits.find('e') { + digits.insert(exponent, '.'); + } else { + digits.push('.'); + } + } + if uppercase { + digits = digits.to_uppercase(); + } + let kind = if value.is_nan() { + NumericKind::Nan + } else if value.is_infinite() { + NumericKind::Infinite + } else { + NumericKind::Finite + }; + Self::finish_numeric( + jvm, + digits, + negative, + RustString::new(), + specifier, + specifier.flags.contains(&',') && conversion != 'e', + kind, + ) + .await + } + + async fn decimal(jvm: &Jvm, value: f64, precision: usize) -> Result { + let Some(capacity) = precision.checked_add(400) else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + let mut output = RustString::new(); + if output.try_reserve_exact(capacity).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + let value = Self::round_fraction(value, precision); + if write!(&mut output, "{value:.precision$}").is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + Ok(output) + } + + fn round_fraction(value: f64, precision: usize) -> f64 { + if precision > 308 { + return value; + } + let factor = libm::pow(10.0, precision as f64); + let scaled = value * factor; + if !scaled.is_finite() { + return value; + } + if value == 0.0 { + return value; + } + let exponent = libm::floor(libm::log10(value)) as i32; + let discarded_index = exponent + precision as i32 + 1; + let canonical = format!("{value:e}"); + let round_up = discarded_index >= 0 + && canonical + .bytes() + .take_while(|byte| *byte != b'e') + .filter(|byte| byte.is_ascii_digit()) + .nth(discarded_index as usize) + .is_some_and(|digit| digit >= b'5'); + (libm::floor(scaled) + if round_up { 1.0 } else { 0.0 }) / factor + } + + async fn scientific(jvm: &Jvm, value: f64, precision: usize) -> Result { + let mut exponent = if value == 0.0 { 0 } else { libm::floor(libm::log10(value)) as i32 }; + let scale = libm::pow(10.0, exponent as f64); + let mut mantissa = if value == 0.0 { + 0.0 + } else if value.to_bits() == 1 { + // Java's canonical decimal representation of Double.MIN_VALUE is 4.9E-324. + 4.9 + } else if scale == 0.0 { + value * 1.0e308 / libm::pow(10.0, (exponent + 308) as f64) + } else { + value / scale + }; + mantissa = Self::round_fraction(mantissa, precision); + if mantissa >= 10.0 { + mantissa /= 10.0; + exponent += 1; + } + let mantissa = Self::decimal(jvm, mantissa, precision).await?; + let exponent_magnitude = exponent.unsigned_abs().to_string(); + let Some(capacity) = mantissa + .len() + .checked_add(exponent_magnitude.len()) + .and_then(|length| length.checked_add(3)) + else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + let mut output = RustString::new(); + if output.try_reserve_exact(capacity).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + output.push_str(&mantissa); + output.push('e'); + output.push(if exponent < 0 { '-' } else { '+' }); + if exponent_magnitude.len() < 2 { + output.push('0'); + } + output.push_str(&exponent_magnitude); + Ok(output) + } + + async fn finish_numeric( + jvm: &Jvm, + mut digits: RustString, + negative: bool, + prefix: RustString, + specifier: &FormatSpecifier, + grouping: bool, + kind: NumericKind, + ) -> Result> { + if grouping && matches!(kind, NumericKind::Finite) { + let separator = digits.find(['.', 'e', 'E']).unwrap_or(digits.len()); + let integer = &digits[..separator]; + let suffix = &digits[separator..]; + let Some(capacity) = digits.len().checked_add(integer.len().saturating_sub(1) / 3) else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + let mut grouped = RustString::new(); + if grouped.try_reserve_exact(capacity).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + for (index, character) in integer.chars().enumerate() { + if index > 0 && (integer.len() - index).is_multiple_of(3) { + grouped.push(','); + } + grouped.push(character); + } + grouped.push_str(suffix); + digits = grouped; + } + + let parentheses = negative && specifier.flags.contains(&'('); + let sign = if parentheses { + "" + } else if negative { + "-" + } else if !matches!(kind, NumericKind::Nan) && specifier.flags.contains(&'+') { + "+" + } else if !matches!(kind, NumericKind::Nan) && specifier.flags.contains(&' ') { + " " + } else { + "" + }; + let Some(fixed) = sign + .len() + .checked_add(prefix.len()) + .and_then(|length| length.checked_add(usize::from(parentheses) * 2)) + else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + if specifier.flags.contains(&'0') && !specifier.flags.contains(&'-') && matches!(kind, NumericKind::Finite) { + let width = specifier.width.unwrap_or(0); + let Some(current) = fixed.checked_add(digits.len()) else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + if current < width { + let padding_length = width - current; + let Some(capacity) = padding_length.checked_add(digits.len()) else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + let mut padded = RustString::new(); + if padded.try_reserve_exact(capacity).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + for _ in 0..padding_length { + padded.push('0'); + } + padded.push_str(&digits); + digits = padded; + } + } + + let Some(capacity) = fixed.checked_add(digits.len()) else { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + }; + let mut result = RustString::new(); + if result.try_reserve_exact(capacity).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + if parentheses { + result.push('('); + } else { + result.push_str(sign); + } + result.push_str(&prefix); + result.push_str(&digits); + if parentheses { + result.push(')'); + } + let mut characters = Vec::new(); + if characters.try_reserve_exact(result.len()).is_err() { + return Err(jvm.exception("java/lang/OutOfMemoryError", "formatted output is too large").await); + } + characters.extend(result.encode_utf16()); + Self::apply_width(jvm, characters, specifier, ' ' as JavaChar).await + } +} diff --git a/java_runtime/src/classes/java/util/formatter_big_decimal_layout_form.rs b/java_runtime/src/classes/java/util/formatter_big_decimal_layout_form.rs new file mode 100644 index 00000000..990bb722 --- /dev/null +++ b/java_runtime/src/classes/java/util/formatter_big_decimal_layout_form.rs @@ -0,0 +1,128 @@ +use alloc::{vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Enum, String}, +}; + +// public enum java.util.Formatter.BigDecimalLayoutForm +pub struct FormatterBigDecimalLayoutForm; + +impl FormatterBigDecimalLayoutForm { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/Formatter$BigDecimalLayoutForm", + parent_class: Some("java/lang/Enum"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(Ljava/lang/String;I)V", Self::init, MethodAccessFlags::PRIVATE), + JavaMethodProto::new( + "values", + "()[Ljava/util/Formatter$BigDecimalLayoutForm;", + Self::values, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/util/Formatter$BigDecimalLayoutForm;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + ], + fields: vec![ + JavaFieldProto::new( + "SCIENTIFIC", + "Ljava/util/Formatter$BigDecimalLayoutForm;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL | FieldAccessFlags::ENUM, + ), + JavaFieldProto::new( + "DECIMAL_FLOAT", + "Ljava/util/Formatter$BigDecimalLayoutForm;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL | FieldAccessFlags::ENUM, + ), + JavaFieldProto::new( + "$VALUES", + "[Ljava/util/Formatter$BigDecimalLayoutForm;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL | FieldAccessFlags::SYNTHETIC, + ), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL | ClassAccessFlags::ENUM, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + let scientific_name = JavaLangString::from_rust_string(jvm, "SCIENTIFIC").await?; + let scientific = jvm + .new_class("java/util/Formatter$BigDecimalLayoutForm", "(Ljava/lang/String;I)V", (scientific_name, 0)) + .await?; + let decimal_float_name = JavaLangString::from_rust_string(jvm, "DECIMAL_FLOAT").await?; + let decimal_float = jvm + .new_class( + "java/util/Formatter$BigDecimalLayoutForm", + "(Ljava/lang/String;I)V", + (decimal_float_name, 1), + ) + .await?; + jvm.put_static_field( + "java/util/Formatter$BigDecimalLayoutForm", + "SCIENTIFIC", + "Ljava/util/Formatter$BigDecimalLayoutForm;", + scientific.clone(), + ) + .await?; + jvm.put_static_field( + "java/util/Formatter$BigDecimalLayoutForm", + "DECIMAL_FLOAT", + "Ljava/util/Formatter$BigDecimalLayoutForm;", + decimal_float.clone(), + ) + .await?; + let mut values = jvm.instantiate_array("Ljava/util/Formatter$BigDecimalLayoutForm;", 2).await?; + jvm.store_array(&mut values, 0, [scientific, decimal_float]).await?; + jvm.put_static_field( + "java/util/Formatter$BigDecimalLayoutForm", + "$VALUES", + "[Ljava/util/Formatter$BigDecimalLayoutForm;", + values, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, name: ClassInstanceRef, ordinal: i32) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Enum", "", "(Ljava/lang/String;I)V", (name, ordinal)) + .await + } + + async fn values(jvm: &Jvm, _: &mut RuntimeContext) -> Result>> { + let values: ClassInstanceRef> = jvm + .get_static_field( + "java/util/Formatter$BigDecimalLayoutForm", + "$VALUES", + "[Ljava/util/Formatter$BigDecimalLayoutForm;", + ) + .await?; + let length = jvm.array_length(&values).await?; + let contents: Vec> = jvm.load_array(&values, 0, length).await?; + let mut copy = jvm.instantiate_array("Ljava/util/Formatter$BigDecimalLayoutForm;", length).await?; + jvm.store_array(&mut copy, 0, contents).await?; + Ok(copy.into()) + } + + async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, name: ClassInstanceRef) -> Result> { + let class = jvm.resolve_class("java/util/Formatter$BigDecimalLayoutForm").await?.java_class(); + let value: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Enum", + "valueOf", + "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", + (class, name), + ) + .await?; + Ok(value.instance.into()) + } +} diff --git a/java_runtime/src/classes/java/util/formatter_closed_exception.rs b/java_runtime/src/classes/java/util/formatter_closed_exception.rs new file mode 100644 index 00000000..4f1d693b --- /dev/null +++ b/java_runtime/src/classes/java/util/formatter_closed_exception.rs @@ -0,0 +1,27 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext}; + +// public class java.util.FormatterClosedException +pub struct FormatterClosedException; + +impl FormatterClosedException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/FormatterClosedException", + parent_class: Some("java/lang/IllegalStateException"), + 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<()> { + jvm.invoke_special(&this, "java/lang/IllegalStateException", "", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/illegal_format_code_point_exception.rs b/java_runtime/src/classes/java/util/illegal_format_code_point_exception.rs new file mode 100644 index 00000000..59c3a643 --- /dev/null +++ b/java_runtime/src/classes/java/util/illegal_format_code_point_exception.rs @@ -0,0 +1,43 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.IllegalFormatCodePointException +pub struct IllegalFormatCodePointException; + +impl IllegalFormatCodePointException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/IllegalFormatCodePointException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getCodePoint", "()I", Self::get_code_point, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("codePoint", "I", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, code_point: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "codePoint", "I", code_point).await + } + + async fn get_code_point(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "codePoint", "I").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let code_point: i32 = jvm.get_field(&this, "codePoint", "I").await?; + Ok(JavaLangString::from_rust_string(jvm, &format!("Code point = 0x{code_point:x}")) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/illegal_format_conversion_exception.rs b/java_runtime/src/classes/java/util/illegal_format_conversion_exception.rs new file mode 100644 index 00000000..ed264573 --- /dev/null +++ b/java_runtime/src/classes/java/util/illegal_format_conversion_exception.rs @@ -0,0 +1,78 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Class, String}, +}; + +// public class java.util.IllegalFormatConversionException +pub struct IllegalFormatConversionException; + +impl IllegalFormatConversionException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/IllegalFormatConversionException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(CLjava/lang/Class;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getConversion", "()C", Self::get_conversion, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getArgumentClass", + "()Ljava/lang/Class;", + Self::get_argument_class, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("conversion", "C", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("argumentClass", "Ljava/lang/Class;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + conversion: JavaChar, + argument_class: ClassInstanceRef, + ) -> Result<()> { + if argument_class.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argumentClass is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "conversion", "C", conversion).await?; + jvm.put_field(&mut this, "argumentClass", "Ljava/lang/Class;", argument_class).await + } + + async fn get_conversion(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "conversion", "C").await + } + + async fn get_argument_class(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "argumentClass", "Ljava/lang/Class;").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let conversion: JavaChar = jvm.get_field(&this, "conversion", "C").await?; + let argument_class: ClassInstanceRef = jvm.get_field(&this, "argumentClass", "Ljava/lang/Class;").await?; + let class_name: ClassInstanceRef = jvm.invoke_virtual(&argument_class, "getName", "()Ljava/lang/String;", ()).await?; + Ok(JavaLangString::from_rust_string( + jvm, + &format!( + "{} != {}", + char::from_u32(conversion as u32).unwrap_or('\u{fffd}'), + JavaLangString::to_rust_string(jvm, &class_name).await? + ), + ) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/illegal_format_exception.rs b/java_runtime/src/classes/java/util/illegal_format_exception.rs new file mode 100644 index 00000000..f4cc4d54 --- /dev/null +++ b/java_runtime/src/classes/java/util/illegal_format_exception.rs @@ -0,0 +1,27 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::ClassAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext}; + +// public class java.util.IllegalFormatException +pub struct IllegalFormatException; + +impl IllegalFormatException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/IllegalFormatException", + parent_class: Some("java/lang/IllegalArgumentException"), + interfaces: vec![], + methods: vec![JavaMethodProto::new("", "()V", Self::init, Default::default())], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/IllegalArgumentException", "", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/illegal_format_flags_exception.rs b/java_runtime/src/classes/java/util/illegal_format_flags_exception.rs new file mode 100644 index 00000000..9aed9c82 --- /dev/null +++ b/java_runtime/src/classes/java/util/illegal_format_flags_exception.rs @@ -0,0 +1,46 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.IllegalFormatFlagsException +pub struct IllegalFormatFlagsException; + +impl IllegalFormatFlagsException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/IllegalFormatFlagsException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFlags", "()Ljava/lang/String;", Self::get_flags, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("flags", "Ljava/lang/String;", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, flags: ClassInstanceRef) -> Result<()> { + if flags.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "flags is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "flags", "Ljava/lang/String;", flags).await + } + + async fn get_flags(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "flags", "Ljava/lang/String;").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let flags: ClassInstanceRef = jvm.get_field(&this, "flags", "Ljava/lang/String;").await?; + JavaLangString::from_rust_string(jvm, &alloc::format!("Flags = '{}'", JavaLangString::to_rust_string(jvm, &flags).await?)) + .await + .map(Into::into) + } +} diff --git a/java_runtime/src/classes/java/util/illegal_format_precision_exception.rs b/java_runtime/src/classes/java/util/illegal_format_precision_exception.rs new file mode 100644 index 00000000..934a014e --- /dev/null +++ b/java_runtime/src/classes/java/util/illegal_format_precision_exception.rs @@ -0,0 +1,41 @@ +use alloc::{string::ToString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.IllegalFormatPrecisionException +pub struct IllegalFormatPrecisionException; + +impl IllegalFormatPrecisionException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/IllegalFormatPrecisionException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getPrecision", "()I", Self::get_precision, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("precision", "I", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, precision: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "precision", "I", precision).await + } + + async fn get_precision(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "precision", "I").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let precision: i32 = jvm.get_field(&this, "precision", "I").await?; + Ok(JavaLangString::from_rust_string(jvm, &precision.to_string()).await?.into()) + } +} diff --git a/java_runtime/src/classes/java/util/illegal_format_width_exception.rs b/java_runtime/src/classes/java/util/illegal_format_width_exception.rs new file mode 100644 index 00000000..601eca27 --- /dev/null +++ b/java_runtime/src/classes/java/util/illegal_format_width_exception.rs @@ -0,0 +1,41 @@ +use alloc::{string::ToString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.IllegalFormatWidthException +pub struct IllegalFormatWidthException; + +impl IllegalFormatWidthException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/IllegalFormatWidthException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getWidth", "()I", Self::get_width, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("width", "I", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, width: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "width", "I", width).await + } + + async fn get_width(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "width", "I").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let width: i32 = jvm.get_field(&this, "width", "I").await?; + Ok(JavaLangString::from_rust_string(jvm, &width.to_string()).await?.into()) + } +} diff --git a/java_runtime/src/classes/java/util/missing_format_argument_exception.rs b/java_runtime/src/classes/java/util/missing_format_argument_exception.rs new file mode 100644 index 00000000..34ef71fa --- /dev/null +++ b/java_runtime/src/classes/java/util/missing_format_argument_exception.rs @@ -0,0 +1,54 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.MissingFormatArgumentException +pub struct MissingFormatArgumentException; + +impl MissingFormatArgumentException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/MissingFormatArgumentException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getFormatSpecifier", + "()Ljava/lang/String;", + Self::get_format_specifier, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("specifier", "Ljava/lang/String;", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, specifier: ClassInstanceRef) -> Result<()> { + if specifier.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "specifier is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "specifier", "Ljava/lang/String;", specifier).await + } + + async fn get_format_specifier(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "specifier", "Ljava/lang/String;").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let specifier: ClassInstanceRef = jvm.get_field(&this, "specifier", "Ljava/lang/String;").await?; + JavaLangString::from_rust_string( + jvm, + &alloc::format!("Format specifier '{}'", JavaLangString::to_rust_string(jvm, &specifier).await?), + ) + .await + .map(Into::into) + } +} diff --git a/java_runtime/src/classes/java/util/missing_format_width_exception.rs b/java_runtime/src/classes/java/util/missing_format_width_exception.rs new file mode 100644 index 00000000..6deca47e --- /dev/null +++ b/java_runtime/src/classes/java/util/missing_format_width_exception.rs @@ -0,0 +1,49 @@ +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}; + +// public class java.util.MissingFormatWidthException +pub struct MissingFormatWidthException; + +impl MissingFormatWidthException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/MissingFormatWidthException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getFormatSpecifier", + "()Ljava/lang/String;", + Self::get_format_specifier, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getMessage", + "()Ljava/lang/String;", + Self::get_format_specifier, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![JavaFieldProto::new("specifier", "Ljava/lang/String;", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, specifier: ClassInstanceRef) -> Result<()> { + if specifier.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "specifier is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "specifier", "Ljava/lang/String;", specifier).await + } + + async fn get_format_specifier(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "specifier", "Ljava/lang/String;").await + } +} diff --git a/java_runtime/src/classes/java/util/unknown_format_conversion_exception.rs b/java_runtime/src/classes/java/util/unknown_format_conversion_exception.rs new file mode 100644 index 00000000..353f85a2 --- /dev/null +++ b/java_runtime/src/classes/java/util/unknown_format_conversion_exception.rs @@ -0,0 +1,49 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.UnknownFormatConversionException +pub struct UnknownFormatConversionException; + +impl UnknownFormatConversionException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/UnknownFormatConversionException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getConversion", "()Ljava/lang/String;", Self::get_conversion, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("conversion", "Ljava/lang/String;", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, conversion: ClassInstanceRef) -> Result<()> { + if conversion.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "conversion is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "conversion", "Ljava/lang/String;", conversion).await + } + + async fn get_conversion(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "conversion", "Ljava/lang/String;").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let conversion: ClassInstanceRef = jvm.get_field(&this, "conversion", "Ljava/lang/String;").await?; + JavaLangString::from_rust_string( + jvm, + &alloc::format!("Conversion = '{}'", JavaLangString::to_rust_string(jvm, &conversion).await?), + ) + .await + .map(Into::into) + } +} diff --git a/java_runtime/src/classes/java/util/unknown_format_flags_exception.rs b/java_runtime/src/classes/java/util/unknown_format_flags_exception.rs new file mode 100644 index 00000000..4c6037b6 --- /dev/null +++ b/java_runtime/src/classes/java/util/unknown_format_flags_exception.rs @@ -0,0 +1,46 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// public class java.util.UnknownFormatFlagsException +pub struct UnknownFormatFlagsException; + +impl UnknownFormatFlagsException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/UnknownFormatFlagsException", + parent_class: Some("java/util/IllegalFormatException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFlags", "()Ljava/lang/String;", Self::get_flags, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + ], + fields: vec![JavaFieldProto::new("flags", "Ljava/lang/String;", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, flags: ClassInstanceRef) -> Result<()> { + if flags.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "flags is null").await); + } + let _: () = jvm.invoke_special(&this, "java/util/IllegalFormatException", "", "()V", ()).await?; + jvm.put_field(&mut this, "flags", "Ljava/lang/String;", flags).await + } + + async fn get_flags(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "flags", "Ljava/lang/String;").await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let flags: ClassInstanceRef = jvm.get_field(&this, "flags", "Ljava/lang/String;").await?; + JavaLangString::from_rust_string(jvm, &alloc::format!("Flags = {}", JavaLangString::to_rust_string(jvm, &flags).await?)) + .await + .map(Into::into) + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index 690c3193..c3948f0f 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -14,6 +14,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { 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::Closeable::as_proto(), crate::classes::java::io::DataInput::as_proto(), crate::classes::java::io::DataInputStream::as_proto(), crate::classes::java::io::DataOutput::as_proto(), @@ -28,6 +29,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { 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::Flushable::as_proto(), crate::classes::java::io::InputStream::as_proto(), crate::classes::java::io::InputStreamReader::as_proto(), crate::classes::java::io::InterruptedIOException::as_proto(), @@ -45,6 +47,8 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::io::UTFDataFormatException::as_proto(), crate::classes::java::io::Writer::as_proto(), crate::classes::java::lang::AbstractMethodError::as_proto(), + crate::classes::java::lang::AbstractStringBuilder::as_proto(), + crate::classes::java::lang::Appendable::as_proto(), crate::classes::java::lang::ArithmeticException::as_proto(), crate::classes::java::lang::ArrayIndexOutOfBoundsException::as_proto(), crate::classes::java::lang::ArrayStoreException::as_proto(), @@ -62,6 +66,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::CloneNotSupportedException::as_proto(), crate::classes::java::lang::Comparable::as_proto(), crate::classes::java::lang::Double::as_proto(), + crate::classes::java::lang::Enum::as_proto(), crate::classes::java::lang::Error::as_proto(), crate::classes::java::lang::Exception::as_proto(), crate::classes::java::lang::ExceptionInInitializerError::as_proto(), @@ -101,6 +106,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { 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::StringBuilder::as_proto(), crate::classes::java::lang::StringIndexOutOfBoundsException::as_proto(), crate::classes::java::lang::System::as_proto(), crate::classes::java::lang::Thread::as_proto(), @@ -161,8 +167,15 @@ pub fn get_runtime_class_proto(name: &str) -> Option { 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::DuplicateFormatFlagsException::as_proto(), crate::classes::java::util::EmptyStackException::as_proto(), crate::classes::java::util::Enumeration::as_proto(), + crate::classes::java::util::FormatFlagsConversionMismatchException::as_proto(), + crate::classes::java::util::Formattable::as_proto(), + crate::classes::java::util::FormattableFlags::as_proto(), + crate::classes::java::util::Formatter::as_proto(), + crate::classes::java::util::FormatterBigDecimalLayoutForm::as_proto(), + crate::classes::java::util::FormatterClosedException::as_proto(), crate::classes::java::util::GregorianCalendar::as_proto(), crate::classes::java::util::HashMap::as_proto(), crate::classes::java::util::HashMapEntry::as_proto(), @@ -180,6 +193,12 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::HashtableEntrySet::as_proto(), crate::classes::java::util::HashtableKeySet::as_proto(), crate::classes::java::util::HashtableValues::as_proto(), + crate::classes::java::util::IllegalFormatCodePointException::as_proto(), + crate::classes::java::util::IllegalFormatConversionException::as_proto(), + crate::classes::java::util::IllegalFormatException::as_proto(), + crate::classes::java::util::IllegalFormatFlagsException::as_proto(), + crate::classes::java::util::IllegalFormatPrecisionException::as_proto(), + crate::classes::java::util::IllegalFormatWidthException::as_proto(), crate::classes::java::util::Iterator::as_proto(), crate::classes::java::util::List::as_proto(), crate::classes::java::util::ListIterator::as_proto(), @@ -189,6 +208,8 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::Locale::as_proto(), crate::classes::java::util::Map::as_proto(), crate::classes::java::util::MapEntry::as_proto(), + crate::classes::java::util::MissingFormatArgumentException::as_proto(), + crate::classes::java::util::MissingFormatWidthException::as_proto(), crate::classes::java::util::NoSuchElementException::as_proto(), crate::classes::java::util::Properties::as_proto(), crate::classes::java::util::regex::Matcher::as_proto(), @@ -217,6 +238,8 @@ pub fn get_runtime_class_proto(name: &str) -> Option { 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::UnknownFormatConversionException::as_proto(), + crate::classes::java::util::UnknownFormatFlagsException::as_proto(), crate::classes::java::util::Vector::as_proto(), crate::classes::java::util::VectorItr::as_proto(), crate::classes::java::util::VectorItr::list_proto(), 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 index a0e62348..432ec1f3 100644 --- a/java_runtime/tests/classes/java/io/test_file_reader_writer.rs +++ b/java_runtime/tests/classes/java/io/test_file_reader_writer.rs @@ -7,9 +7,9 @@ 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, + classes::java::lang::Object, get_runtime_class_proto, }; -use jvm::{ClassDefinition, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm::{Array, ClassDefinition, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; use jvm_rust::ClassDefinitionImpl; use test_utils::{TestRuntime, create_test_jvm, test_jvm_filesystem}; @@ -614,6 +614,38 @@ async fn file_writer_uses_only_opened_handle_write_semantics() -> Result<()> { Ok(()) } +#[tokio::test] +async fn formatter_file_constructor_truncates_before_writing() -> Result<()> { + let runtime = MemoryRuntime::new([("formatter.txt".into(), b"old content".to_vec())].into_iter().collect()); + let jvm = create_test_jvm(runtime.clone()).await?; + let path = JavaLangString::from_rust_string(&jvm, "formatter.txt").await?; + let formatter = jvm.new_class("java/util/Formatter", "(Ljava/lang/String;)V", (path,)).await?; + let format = JavaLangString::from_rust_string(&jvm, "new").await?; + let arguments = ClassInstanceRef::>::new(None); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let _: () = jvm.invoke_virtual(&formatter, "close", "()V", ()).await?; + + assert_eq!(&*runtime.files.lock().unwrap()["formatter.txt"].lock().unwrap(), b"new"); + assert_eq!( + runtime.open_calls.lock().unwrap()[0].1, + FileOpenOptions { + write: true, + truncate: true, + create: true, + ..Default::default() + } + ); + + 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()); 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 b045f425..b28a5f66 100644 --- a/java_runtime/tests/classes/java/io/test_print_stream.rs +++ b/java_runtime/tests/classes/java/io/test_print_stream.rs @@ -8,7 +8,7 @@ use java_runtime::classes::java::{ lang::{Object, String}, }; use java_runtime::{RuntimeClassProto, RuntimeContext}; -use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, JavaValue, Jvm, Result, runtime::JavaLangString}; use jvm_rust::ClassDefinitionImpl; use test_utils::{TestRuntime, create_test_jvm, test_jvm}; @@ -330,9 +330,18 @@ async fn assert_monitor_released(jvm: &Jvm, stream: &ClassInstanceRef Result<()> { let proto = PrintStream::as_proto(); assert_eq!(proto.parent_class, Some("java/io/FilterOutputStream")); + assert_eq!(proto.interfaces, vec!["java/lang/Appendable", "java/io/Closeable"]); assert!(proto.access_flags.contains(ClassAccessFlags::PUBLIC)); - for descriptor in ["(Ljava/io/OutputStream;)V", "(Ljava/io/OutputStream;Z)V"] { + for descriptor in [ + "(Ljava/io/OutputStream;)V", + "(Ljava/io/OutputStream;Z)V", + "(Ljava/io/OutputStream;ZLjava/lang/String;)V", + "(Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;)V", + "(Ljava/io/File;)V", + "(Ljava/io/File;Ljava/lang/String;)V", + ] { let constructors = proto .methods .iter() @@ -341,6 +350,19 @@ async fn ps_01_constructor_state_descriptors_and_access_flags() -> Result<()> { assert_eq!(constructors.len(), 1, "missing or duplicate constructor {descriptor}"); assert!(constructors[0].access_flags.contains(MethodAccessFlags::PUBLIC)); } + let set_error = proto + .methods + .iter() + .find(|method| method.name == "setError" && method.descriptor == "()V") + .expect("missing setError()V"); + assert_eq!(set_error.access_flags, MethodAccessFlags::PROTECTED); + assert!(!proto.methods.iter().any(|method| method.name == "clearError")); + assert!( + !proto + .methods + .iter() + .any(|method| method.name == "println" && matches!(method.descriptor.as_str(), "(B)V" | "(S)V")) + ); let auto_flush = proto.fields.iter().find(|field| field.name == "autoFlush").expect("autoFlush field"); assert_eq!(auto_flush.descriptor, "Z"); @@ -394,6 +416,27 @@ async fn ps_02_auto_flush_matches_public_write_and_println_contracts() -> Result let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", ('x' as i32,)).await?; assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 0); + + let formatted_output = jvm.new_class("ProbeOutputStream", "(III)V", (0, 0, 0)).await?; + let formatted_stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;Z)V", (formatted_output.clone(), true)) + .await?; + let format = JavaLangString::from_rust_string(&jvm, "%s").await?; + let value = JavaLangString::from_rust_string(&jvm, "value").await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(value))]).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatted_stream, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + (format, arguments), + ) + .await?; + let format_writes: i32 = jvm.get_field(&formatted_output, "writeCount", "I").await?; + assert!(format_writes > 0); + assert_eq!(jvm.get_field::(&formatted_output, "flushCount", "I").await?, 1); + let _: () = jvm.invoke_virtual(&stream, "write", "(I)V", ('\n' as i32,)).await?; assert_eq!(jvm.get_field::(&output, "flushCount", "I").await?, 1); @@ -413,8 +456,8 @@ async fn ps_02_auto_flush_matches_public_write_and_println_contracts() -> Result 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", "(I)V", (3,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(I)V", (4,)).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?; @@ -887,8 +930,8 @@ async fn test_print_stream_remaining_overloads_and_close() -> Result<()> { let mut chars = jvm.instantiate_array("C", 2).await?; jvm.store_array(&mut chars, 0, ['B' as JavaChar, 'C' 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", "(I)V", (-3,)).await?; + let _: () = jvm.invoke_virtual(&stream, "println", "(I)V", (4,)).await?; let _: () = jvm.invoke_virtual(&stream, "println", "(Z)V", (false,)).await?; let _: () = jvm.invoke_virtual(&stream, "println", "(F)V", (2.5f32,)).await?; let _: () = jvm.invoke_virtual(&stream, "close", "()V", ()).await?; 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 027a928d..56fa3d0c 100644 --- a/java_runtime/tests/classes/java/io/test_print_writer.rs +++ b/java_runtime/tests/classes/java/io/test_print_writer.rs @@ -320,6 +320,10 @@ async fn pw_01_constructors_fields_descriptors_and_access_flags() -> Result<()> ("", "(Ljava/io/Writer;Z)V"), ("", "(Ljava/io/OutputStream;)V"), ("", "(Ljava/io/OutputStream;Z)V"), + ("", "(Ljava/lang/String;)V"), + ("", "(Ljava/lang/String;Ljava/lang/String;)V"), + ("", "(Ljava/io/File;)V"), + ("", "(Ljava/io/File;Ljava/lang/String;)V"), ("write", "(I)V"), ("write", "([C)V"), ("write", "([CII)V"), @@ -347,8 +351,21 @@ async fn pw_01_constructors_fields_descriptors_and_access_flags() -> Result<()> ("flush", "()V"), ("close", "()V"), ("checkError", "()Z"), + ("printf", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;"), + ("printf", "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;"), + ("format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;"), + ("format", "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;"), + ("append", "(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;"), + ("append", "(Ljava/lang/CharSequence;II)Ljava/io/PrintWriter;"), + ("append", "(C)Ljava/io/PrintWriter;"), + ("append", "(Ljava/lang/CharSequence;)Ljava/io/Writer;"), + ("append", "(Ljava/lang/CharSequence;II)Ljava/io/Writer;"), + ("append", "(C)Ljava/io/Writer;"), + ("append", "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;"), + ("append", "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;"), + ("append", "(C)Ljava/lang/Appendable;"), ]; - assert_eq!(proto.methods.len(), expected_methods.len()); + assert_eq!(proto.methods.len(), expected_methods.len() + 1); for (name, descriptor) in expected_methods { let methods = proto .methods @@ -358,6 +375,12 @@ async fn pw_01_constructors_fields_descriptors_and_access_flags() -> Result<()> assert_eq!(methods.len(), 1, "missing or duplicated {name}{descriptor}"); assert!(methods[0].access_flags.contains(MethodAccessFlags::PUBLIC)); } + let set_error = proto + .methods + .iter() + .find(|method| method.name == "setError" && method.descriptor == "()V") + .expect("missing setError()V"); + assert_eq!(set_error.access_flags, MethodAccessFlags::PROTECTED); assert_eq!(proto.fields.len(), 3); let out = proto.fields.iter().find(|field| field.name == "out").expect("out field"); diff --git a/java_runtime/tests/classes/java/lang/mod.rs b/java_runtime/tests/classes/java/lang/mod.rs index 5de9e808..8059a2ec 100644 --- a/java_runtime/tests/classes/java/lang/mod.rs +++ b/java_runtime/tests/classes/java/lang/mod.rs @@ -13,6 +13,7 @@ mod test_object; mod test_short; mod test_string; mod test_string_buffer; +mod test_string_builder; mod test_system; mod test_thread; mod test_throwable; diff --git a/java_runtime/tests/classes/java/lang/test_boolean.rs b/java_runtime/tests/classes/java/lang/test_boolean.rs index eda529ef..7b7c3e83 100644 --- a/java_runtime/tests/classes/java/lang/test_boolean.rs +++ b/java_runtime/tests/classes/java/lang/test_boolean.rs @@ -70,7 +70,7 @@ async fn test_boolean_string_constants_and_type() -> Result<()> { } #[tokio::test] -async fn test_boolean_property_and_primitive_value_of_exclusion() -> Result<()> { +async fn test_boolean_property_and_java_5_primitive_value_of() -> Result<()> { let jvm = test_jvm().await?; let key = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.test").await?; @@ -113,11 +113,11 @@ async fn test_boolean_property_and_primitive_value_of_exclusion() -> Result<()> .await? ); - let result: Result> = jvm.invoke_static("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", (true,)).await; - let Err(JavaError::JavaException(exception)) = result else { - panic!("Boolean.valueOf(boolean) must remain outside the Java 1.2 API"); - }; - assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + let result: ClassInstanceRef = jvm + .invoke_static("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", (true,)) + .await?; + let true_constant: ClassInstanceRef = jvm.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;").await?; + assert_eq!(result.identity(), true_constant.identity()); let left = jvm.new_class("java/lang/Boolean", "(Z)V", (false,)).await?; let right = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?; diff --git a/java_runtime/tests/classes/java/lang/test_character.rs b/java_runtime/tests/classes/java/lang/test_character.rs index adc5d2e0..15270af2 100644 --- a/java_runtime/tests/classes/java/lang/test_character.rs +++ b/java_runtime/tests/classes/java/lang/test_character.rs @@ -59,13 +59,10 @@ async fn test_character_value_constants_and_type() -> Result<()> { assert!(jvm.is_instance(&**value, "java/lang/Comparable")); assert!(jvm.is_instance(&**value, "java/io/Serializable")); - let result: Result> = jvm + let result: ClassInstanceRef = jvm .invoke_static("java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", ('A' as JavaChar,)) - .await; - let Err(JavaError::JavaException(exception)) = result else { - panic!("Character.valueOf(char) must remain outside the Java 1.2 API"); - }; - assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + .await?; + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&result, "charValue", "()C", ()).await?, 'A' as JavaChar); 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 e21e1b26..64e7d8fa 100644 --- a/java_runtime/tests/classes/java/lang/test_string_buffer.rs +++ b/java_runtime/tests/classes/java/lang/test_string_buffer.rs @@ -1,4 +1,4 @@ -use java_constants::MethodAccessFlags; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use java_runtime::{ classes::java::lang::{CharSequence, String as JavaString, StringBuffer}, get_runtime_class_proto, @@ -10,7 +10,10 @@ use test_utils::test_jvm; #[tokio::test] async fn string_buffer_char_sequence_sub_sequence_is_a_synchronized_snapshot() -> Result<()> { let proto = get_runtime_class_proto("java/lang/StringBuffer").expect("StringBuffer must be registered"); - assert_eq!(proto.interfaces, vec!["java/lang/CharSequence"]); + assert_eq!(proto.parent_class, Some("java/lang/AbstractStringBuilder")); + assert_eq!(proto.interfaces, vec!["java/io/Serializable", "java/lang/CharSequence"]); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL); + assert!(proto.fields.is_empty()); let method = proto .methods .iter() @@ -142,12 +145,7 @@ async fn test_sb_03_length_char_access_and_synchronized_flags() -> Result<()> { ("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;"), @@ -174,6 +172,25 @@ async fn test_sb_03_length_char_access_and_synchronized_flags() -> Result<()> { assert!(flags.contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED)); } + for descriptor in [ + "(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer;", + "(IZ)Ljava/lang/StringBuffer;", + "(II)Ljava/lang/StringBuffer;", + "(IJ)Ljava/lang/StringBuffer;", + "(IF)Ljava/lang/StringBuffer;", + "(ID)Ljava/lang/StringBuffer;", + ] { + let flags = jvm + .get_class("java/lang/StringBuffer") + .expect("StringBuffer must be loaded") + .definition + .method("insert", descriptor, false) + .unwrap_or_else(|| panic!("missing insert{descriptor}")) + .access_flags(); + assert!(flags.contains(MethodAccessFlags::PUBLIC)); + assert!(!flags.contains(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 { @@ -320,6 +337,45 @@ async fn test_sb_06_insert_overloads_and_boundaries() -> Result<()> { 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 sequence = JavaLangString::from_rust_string(&jvm, "string").await?; + let sequence: ClassInstanceRef = sequence.into(); + let length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &buffer, + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer;", + (length, sequence), + ) + .await?; + let sequence = JavaLangString::from_rust_string(&jvm, "buffer").await?; + let sequence = jvm.new_class("java/lang/StringBuffer", "(Ljava/lang/String;)V", (sequence,)).await?; + let sequence: ClassInstanceRef = sequence.into(); + let length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &buffer, + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer;", + (length, sequence), + ) + .await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "SOtrue!12341.02.0abCstringbuffer"); + + let sequence: ClassInstanceRef = None.into(); + let length: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &buffer, + "insert", + "(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer;", + (length, sequence), + ) + .await?; + let text = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "SOtrue!12341.02.0abCstringbuffernull"); + let null_string: ClassInstanceRef = None.into(); let _: ClassInstanceRef = jvm .invoke_virtual(&buffer, "insert", "(ILjava/lang/String;)Ljava/lang/StringBuffer;", (0, null_string)) diff --git a/java_runtime/tests/classes/java/lang/test_string_builder.rs b/java_runtime/tests/classes/java/lang/test_string_builder.rs new file mode 100644 index 00000000..32ebf22d --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_string_builder.rs @@ -0,0 +1,160 @@ +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::{classes::java::lang::Object, get_runtime_class_proto}; +use jvm::{ClassInstanceRef, JavaChar, Result, runtime::JavaLangString}; +use test_utils::test_jvm; + +#[test] +fn java_5_builder_hierarchy_and_appendable_descriptors_are_registered() { + let appendable = get_runtime_class_proto("java/lang/Appendable").expect("Appendable must be registered"); + assert_eq!(appendable.parent_class, None); + assert_eq!( + appendable.access_flags, + ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT + ); + for descriptor in [ + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + "(C)Ljava/lang/Appendable;", + ] { + let method = appendable + .methods + .iter() + .find(|method| method.name == "append" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Appendable.append{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT); + } + + let base = get_runtime_class_proto("java/lang/AbstractStringBuilder").expect("AbstractStringBuilder must be registered"); + assert_eq!(base.parent_class, Some("java/lang/Object")); + assert_eq!(base.interfaces, vec!["java/lang/Appendable", "java/lang/CharSequence"]); + assert_eq!(base.access_flags, ClassAccessFlags::ABSTRACT); + + let builder = get_runtime_class_proto("java/lang/StringBuilder").expect("StringBuilder must be registered"); + assert_eq!(builder.parent_class, Some("java/lang/AbstractStringBuilder")); + assert_eq!(builder.interfaces, vec!["java/io/Serializable", "java/lang/CharSequence"]); + assert_eq!(builder.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL); + assert_eq!(builder.methods.len(), 72); + + for (name, descriptor) in [ + ("", "()V"), + ("", "(I)V"), + ("", "(Ljava/lang/String;)V"), + ("", "(Ljava/lang/CharSequence;)V"), + ("append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"), + ("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"), + ("append", "(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;"), + ("append", "(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;"), + ("append", "(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;"), + ("append", "([C)Ljava/lang/StringBuilder;"), + ("append", "([CII)Ljava/lang/StringBuilder;"), + ("append", "(Z)Ljava/lang/StringBuilder;"), + ("append", "(C)Ljava/lang/StringBuilder;"), + ("append", "(I)Ljava/lang/StringBuilder;"), + ("append", "(J)Ljava/lang/StringBuilder;"), + ("append", "(F)Ljava/lang/StringBuilder;"), + ("append", "(D)Ljava/lang/StringBuilder;"), + ("appendCodePoint", "(I)Ljava/lang/StringBuilder;"), + ("delete", "(II)Ljava/lang/StringBuilder;"), + ("deleteCharAt", "(I)Ljava/lang/StringBuilder;"), + ("replace", "(IILjava/lang/String;)Ljava/lang/StringBuilder;"), + ("insert", "(I[CII)Ljava/lang/StringBuilder;"), + ("insert", "(ILjava/lang/Object;)Ljava/lang/StringBuilder;"), + ("insert", "(ILjava/lang/String;)Ljava/lang/StringBuilder;"), + ("insert", "(I[C)Ljava/lang/StringBuilder;"), + ("insert", "(ILjava/lang/CharSequence;)Ljava/lang/StringBuilder;"), + ("insert", "(ILjava/lang/CharSequence;II)Ljava/lang/StringBuilder;"), + ("insert", "(IZ)Ljava/lang/StringBuilder;"), + ("insert", "(IC)Ljava/lang/StringBuilder;"), + ("insert", "(II)Ljava/lang/StringBuilder;"), + ("insert", "(IJ)Ljava/lang/StringBuilder;"), + ("insert", "(IF)Ljava/lang/StringBuilder;"), + ("insert", "(ID)Ljava/lang/StringBuilder;"), + ("indexOf", "(Ljava/lang/String;)I"), + ("indexOf", "(Ljava/lang/String;I)I"), + ("lastIndexOf", "(Ljava/lang/String;)I"), + ("lastIndexOf", "(Ljava/lang/String;I)I"), + ("reverse", "()Ljava/lang/StringBuilder;"), + ("toString", "()Ljava/lang/String;"), + ] { + let method = builder + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing StringBuilder.{name}{descriptor}")); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + let abstract_string_builder_bridges = builder + .methods + .iter() + .filter(|method| method.descriptor.ends_with("Ljava/lang/AbstractStringBuilder;")) + .collect::>(); + assert_eq!(abstract_string_builder_bridges.len(), 30); + for method in abstract_string_builder_bridges { + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC + ); + } + let appendable_bridges = builder + .methods + .iter() + .filter(|method| method.name == "append" && method.descriptor.ends_with("Ljava/lang/Appendable;")) + .collect::>(); + assert_eq!(appendable_bridges.len(), 3); + for method in appendable_bridges { + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC + ); + } + + let buffer = get_runtime_class_proto("java/lang/StringBuffer").expect("StringBuffer must be registered"); + assert_eq!(buffer.parent_class, Some("java/lang/AbstractStringBuilder")); + assert_eq!(buffer.interfaces, vec!["java/io/Serializable", "java/lang/CharSequence"]); + assert_eq!(buffer.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL); + assert_eq!(buffer.methods.len(), 87); + assert_eq!( + buffer + .methods + .iter() + .filter(|method| method.descriptor.ends_with("Ljava/lang/AbstractStringBuilder;")) + .count(), + 30 + ); + assert_eq!( + buffer + .methods + .iter() + .filter(|method| method.name == "append" && method.descriptor.ends_with("Ljava/lang/Appendable;")) + .count(), + 3 + ); +} + +#[tokio::test] +async fn string_builder_mutation_queries_and_appendable_bridge_use_utf16() -> Result<()> { + let jvm = test_jvm().await?; + let builder = jvm.new_class("java/lang/StringBuilder", "()V", ()).await?; + + let text = JavaLangString::from_utf16(&jvm, vec!['A' as JavaChar, 0xd83d, 0xde00]).await?; + let text: ClassInstanceRef = text.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&builder, "append", "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", (text,)) + .await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&builder, "append", "(I)Ljava/lang/StringBuilder;", (42,)).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&builder, "insert", "(IC)Ljava/lang/StringBuilder;", (1, '-' as JavaChar)) + .await?; + + assert_eq!(jvm.invoke_virtual::<_, i32>(&builder, "length", "()I", ()).await?, 6); + assert_eq!(jvm.invoke_virtual::<_, i32>(&builder, "codePointAt", "(I)I", (2,)).await?, 0x1f600); + + let result = jvm.invoke_virtual(&builder, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_utf16(&jvm, &result).await?, + vec![0x41, 0x2d, 0xd83d, 0xde00, 0x34, 0x32] + ); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/mod.rs b/java_runtime/tests/classes/java/util/mod.rs index 87b9b77a..0e75ddc7 100644 --- a/java_runtime/tests/classes/java/util/mod.rs +++ b/java_runtime/tests/classes/java/util/mod.rs @@ -5,6 +5,7 @@ mod test_arrays; mod test_collection_contracts; mod test_collections; mod test_date; +mod test_formatter; mod test_gregorian_calendar; mod test_hash_map; mod test_hash_set; diff --git a/java_runtime/tests/classes/java/util/test_formatter.rs b/java_runtime/tests/classes/java/util/test_formatter.rs new file mode 100644 index 00000000..a2a2f419 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_formatter.rs @@ -0,0 +1,1160 @@ +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + classes::java::{ + io::{File, OutputStream, PrintStream}, + lang::{Appendable, Class as JavaClass, Object, String as JavaString}, + util::{FormatterBigDecimalLayoutForm, Locale}, + }, + get_runtime_class_proto, +}; +use jvm::{Array, ClassInstanceRef, JavaError, JavaValue, Result, runtime::JavaLangString}; +use test_utils::test_jvm; + +#[test] +fn java_5_formatter_public_api_is_registered() { + let formatter = get_runtime_class_proto("java/util/Formatter").expect("Formatter must be registered"); + assert_eq!(formatter.parent_class, Some("java/lang/Object")); + assert_eq!(formatter.interfaces, vec!["java/io/Closeable", "java/io/Flushable"]); + assert_eq!(formatter.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL); + assert_eq!(formatter.methods.len(), 22); + assert!(!formatter.fields.iter().any(|field| field.access_flags.contains(FieldAccessFlags::PUBLIC))); + + for descriptor in [ + "()V", + "(Ljava/lang/Appendable;)V", + "(Ljava/util/Locale;)V", + "(Ljava/lang/Appendable;Ljava/util/Locale;)V", + "(Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Locale;)V", + "(Ljava/io/File;)V", + "(Ljava/io/File;Ljava/lang/String;)V", + "(Ljava/io/File;Ljava/lang/String;Ljava/util/Locale;)V", + "(Ljava/io/PrintStream;)V", + "(Ljava/io/OutputStream;)V", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + "(Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Locale;)V", + ] { + let constructor = formatter + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Formatter{descriptor}")); + assert_eq!(constructor.access_flags, MethodAccessFlags::PUBLIC); + } + + for (name, descriptor) in [ + ("locale", "()Ljava/util/Locale;"), + ("out", "()Ljava/lang/Appendable;"), + ("toString", "()Ljava/lang/String;"), + ("flush", "()V"), + ("close", "()V"), + ("ioException", "()Ljava/io/IOException;"), + ("format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;"), + ("format", "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;"), + ] { + let method = formatter + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Formatter.{name}{descriptor}")); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + if name == "format" { + assert!(method.access_flags.contains(MethodAccessFlags::VARARGS)); + } + } + + let formattable = get_runtime_class_proto("java/util/Formattable").expect("Formattable must be registered"); + assert_eq!(formattable.parent_class, None); + assert_eq!( + formattable.access_flags, + ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT + ); + assert!(formattable.interfaces.is_empty()); + assert!(formattable.fields.is_empty()); + assert_eq!(formattable.methods.len(), 1); + let format_to = formattable + .methods + .iter() + .find(|method| method.name == "formatTo" && method.descriptor == "(Ljava/util/Formatter;III)V") + .expect("Formattable.formatTo must be registered"); + assert_eq!(format_to.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT); + + let flags = get_runtime_class_proto("java/util/FormattableFlags").expect("FormattableFlags must be registered"); + assert_eq!(flags.parent_class, Some("java/lang/Object")); + assert!(flags.interfaces.is_empty()); + assert_eq!(flags.access_flags, ClassAccessFlags::PUBLIC); + assert_eq!(flags.fields.len(), 3); + assert!(!flags.methods.iter().any(|method| method.access_flags.contains(MethodAccessFlags::PUBLIC))); + for name in ["LEFT_JUSTIFY", "UPPERCASE", "ALTERNATE"] { + let field = flags.fields.iter().find(|field| field.name == name && field.descriptor == "I").unwrap(); + assert_eq!( + field.access_flags, + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL + ); + } + + let enum_base = get_runtime_class_proto("java/lang/Enum").expect("Enum must be registered"); + assert_eq!(enum_base.parent_class, Some("java/lang/Object")); + assert_eq!(enum_base.interfaces, vec!["java/lang/Comparable", "java/io/Serializable"]); + assert_eq!(enum_base.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT); + assert_eq!(enum_base.methods.len(), 12); + assert_eq!(enum_base.fields.len(), 2); + for (name, descriptor, flags) in [ + ("", "(Ljava/lang/String;I)V", MethodAccessFlags::PROTECTED), + ("name", "()Ljava/lang/String;", MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ("ordinal", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ("toString", "()Ljava/lang/String;", MethodAccessFlags::PUBLIC), + ("equals", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ("hashCode", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ("clone", "()Ljava/lang/Object;", MethodAccessFlags::PROTECTED | MethodAccessFlags::FINAL), + ("compareTo", "(Ljava/lang/Enum;)I", MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + ( + "getDeclaringClass", + "()Ljava/lang/Class;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + ( + "valueOf", + "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + ("finalize", "()V", MethodAccessFlags::PROTECTED | MethodAccessFlags::FINAL), + ] { + let method = enum_base + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Enum.{name}{descriptor}")); + assert_eq!(method.access_flags, flags); + } + let compare_bridge = enum_base + .methods + .iter() + .find(|method| method.name == "compareTo" && method.descriptor == "(Ljava/lang/Object;)I") + .expect("Enum.compareTo bridge must be registered"); + assert_eq!( + compare_bridge.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC + ); + + let layout = get_runtime_class_proto("java/util/Formatter$BigDecimalLayoutForm").expect("Formatter.BigDecimalLayoutForm must be registered"); + assert_eq!(layout.parent_class, Some("java/lang/Enum")); + assert!(layout.interfaces.is_empty()); + assert_eq!( + layout.access_flags, + ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL | ClassAccessFlags::ENUM + ); + assert_eq!(layout.methods.len(), 4); + assert_eq!(layout.fields.len(), 3); + for name in ["SCIENTIFIC", "DECIMAL_FLOAT"] { + let field = layout + .fields + .iter() + .find(|field| field.name == name && field.descriptor == "Ljava/util/Formatter$BigDecimalLayoutForm;") + .unwrap_or_else(|| panic!("missing Formatter.BigDecimalLayoutForm.{name}")); + assert_eq!( + field.access_flags, + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL | FieldAccessFlags::ENUM + ); + } + for (name, descriptor) in [ + ("values", "()[Ljava/util/Formatter$BigDecimalLayoutForm;"), + ("valueOf", "(Ljava/lang/String;)Ljava/util/Formatter$BigDecimalLayoutForm;"), + ] { + let method = layout + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Formatter.BigDecimalLayoutForm.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC); + } + + for (class, return_type) in [("java/io/PrintStream", "PrintStream"), ("java/io/PrintWriter", "PrintWriter")] { + let proto = get_runtime_class_proto(class).unwrap_or_else(|| panic!("{class} must be registered")); + for name in ["printf", "format"] { + for descriptor in [ + format!("(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/{return_type};"), + format!("(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/{return_type};"), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing {class}.{name}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::VARARGS); + } + } + } + + let string = get_runtime_class_proto("java/lang/String").expect("String must be registered"); + for descriptor in [ + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + ] { + let method = string + .methods + .iter() + .find(|method| method.name == "format" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing String.format{descriptor}")); + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::VARARGS + ); + } +} + +#[test] +fn java_5_formatter_io_types_use_covariant_methods_and_compiler_bridges() { + let appendable = get_runtime_class_proto("java/lang/Appendable").expect("Appendable must be registered"); + assert_eq!(appendable.methods.len(), 3); + assert!(appendable.fields.is_empty()); + + for class in ["java/io/Closeable", "java/io/Flushable"] { + let proto = get_runtime_class_proto(class).unwrap_or_else(|| panic!("{class} must be registered")); + assert_eq!(proto.parent_class, None); + assert_eq!( + proto.access_flags, + ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT + ); + assert_eq!(proto.methods.len(), 1); + assert_eq!(proto.methods[0].name, if class == "java/io/Closeable" { "close" } else { "flush" }); + assert_eq!(proto.methods[0].descriptor, "()V"); + assert_eq!(proto.methods[0].access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT); + } + + let output_stream = get_runtime_class_proto("java/io/OutputStream").expect("OutputStream must be registered"); + assert_eq!(output_stream.interfaces, vec!["java/io/Closeable", "java/io/Flushable"]); + assert_eq!(output_stream.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT); + assert_eq!(output_stream.methods.len(), 6); + assert!(output_stream.fields.is_empty()); + + let writer = get_runtime_class_proto("java/io/Writer").expect("Writer must be registered"); + assert_eq!(writer.interfaces, vec!["java/lang/Appendable", "java/io/Closeable", "java/io/Flushable"]); + assert_eq!(writer.access_flags, ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT); + assert_eq!(writer.methods.len(), 15); + for descriptor in [ + "(Ljava/lang/CharSequence;)Ljava/io/Writer;", + "(Ljava/lang/CharSequence;II)Ljava/io/Writer;", + "(C)Ljava/io/Writer;", + ] { + let method = writer + .methods + .iter() + .find(|method| method.name == "append" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Writer.append{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + for descriptor in [ + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + "(C)Ljava/lang/Appendable;", + ] { + let method = writer + .methods + .iter() + .find(|method| method.name == "append" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Writer.append bridge {descriptor}")); + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC + ); + } + + let print_stream = get_runtime_class_proto("java/io/PrintStream").expect("PrintStream must be registered"); + assert_eq!(print_stream.methods.len(), 42); + assert_eq!(print_stream.interfaces, vec!["java/lang/Appendable", "java/io/Closeable"]); + for (name, descriptor) in [ + ("", "(Ljava/io/OutputStream;)V"), + ("", "(Ljava/io/OutputStream;Z)V"), + ("", "(Ljava/io/OutputStream;ZLjava/lang/String;)V"), + ("", "(Ljava/lang/String;)V"), + ("", "(Ljava/lang/String;Ljava/lang/String;)V"), + ("", "(Ljava/io/File;)V"), + ("", "(Ljava/io/File;Ljava/lang/String;)V"), + ("flush", "()V"), + ("close", "()V"), + ("checkError", "()Z"), + ("write", "(I)V"), + ("write", "([BII)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"), + ("printf", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;"), + ("printf", "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;"), + ("format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;"), + ("format", "(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;"), + ("append", "(Ljava/lang/CharSequence;)Ljava/io/PrintStream;"), + ("append", "(Ljava/lang/CharSequence;II)Ljava/io/PrintStream;"), + ("append", "(C)Ljava/io/PrintStream;"), + ("append", "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;"), + ("append", "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;"), + ("append", "(C)Ljava/lang/Appendable;"), + ] { + assert_eq!( + print_stream + .methods + .iter() + .filter(|method| method.name == name && method.descriptor == descriptor) + .count(), + 1, + "missing or duplicated PrintStream.{name}{descriptor}" + ); + } + let print_writer = get_runtime_class_proto("java/io/PrintWriter").expect("PrintWriter must be registered"); + assert_eq!(print_writer.methods.len(), 49); + for proto in [&print_stream, &print_writer] { + let set_error = proto + .methods + .iter() + .find(|method| method.name == "setError" && method.descriptor == "()V") + .expect("setError must be registered"); + assert_eq!(set_error.access_flags, MethodAccessFlags::PROTECTED); + assert!(!proto.methods.iter().any(|method| method.name == "clearError")); + } + for descriptor in [ + "(Ljava/lang/CharSequence;)Ljava/io/Writer;", + "(Ljava/lang/CharSequence;II)Ljava/io/Writer;", + "(C)Ljava/io/Writer;", + "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;", + "(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;", + "(C)Ljava/lang/Appendable;", + ] { + let method = print_writer + .methods + .iter() + .find(|method| method.name == "append" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing PrintWriter.append bridge {descriptor}")); + assert_eq!( + method.access_flags, + MethodAccessFlags::PUBLIC | MethodAccessFlags::BRIDGE | MethodAccessFlags::SYNTHETIC + ); + } +} + +#[test] +fn java_5_format_exception_hierarchy_and_public_methods_are_registered() { + for (class, parent, methods) in [ + ( + "java/util/DuplicateFormatFlagsException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;)V"), + ("getFlags", "()Ljava/lang/String;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/FormatFlagsConversionMismatchException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;C)V"), + ("getFlags", "()Ljava/lang/String;"), + ("getConversion", "()C"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/IllegalFormatCodePointException", + "java/util/IllegalFormatException", + vec![("", "(I)V"), ("getCodePoint", "()I"), ("getMessage", "()Ljava/lang/String;")], + ), + ( + "java/util/IllegalFormatConversionException", + "java/util/IllegalFormatException", + vec![ + ("", "(CLjava/lang/Class;)V"), + ("getConversion", "()C"), + ("getArgumentClass", "()Ljava/lang/Class;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/IllegalFormatFlagsException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;)V"), + ("getFlags", "()Ljava/lang/String;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/IllegalFormatPrecisionException", + "java/util/IllegalFormatException", + vec![("", "(I)V"), ("getPrecision", "()I"), ("getMessage", "()Ljava/lang/String;")], + ), + ( + "java/util/IllegalFormatWidthException", + "java/util/IllegalFormatException", + vec![("", "(I)V"), ("getWidth", "()I"), ("getMessage", "()Ljava/lang/String;")], + ), + ( + "java/util/MissingFormatArgumentException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;)V"), + ("getFormatSpecifier", "()Ljava/lang/String;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/MissingFormatWidthException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;)V"), + ("getFormatSpecifier", "()Ljava/lang/String;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/UnknownFormatConversionException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;)V"), + ("getConversion", "()Ljava/lang/String;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ( + "java/util/UnknownFormatFlagsException", + "java/util/IllegalFormatException", + vec![ + ("", "(Ljava/lang/String;)V"), + ("getFlags", "()Ljava/lang/String;"), + ("getMessage", "()Ljava/lang/String;"), + ], + ), + ] { + let proto = get_runtime_class_proto(class).unwrap_or_else(|| panic!("{class} must be registered")); + assert_eq!(proto.parent_class, Some(parent)); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC); + assert_eq!(proto.methods.len(), methods.len()); + 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}{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + } + + let illegal_format = get_runtime_class_proto("java/util/IllegalFormatException").expect("IllegalFormatException must be registered"); + assert_eq!(illegal_format.parent_class, Some("java/lang/IllegalArgumentException")); + assert_eq!(illegal_format.access_flags, ClassAccessFlags::PUBLIC); + assert_eq!(illegal_format.methods.len(), 1); + assert_eq!(illegal_format.methods[0].name, ""); + assert_eq!(illegal_format.methods[0].descriptor, "()V"); + assert_eq!(illegal_format.methods[0].access_flags, MethodAccessFlags::empty()); + + let closed = get_runtime_class_proto("java/util/FormatterClosedException").expect("FormatterClosedException must be registered"); + assert_eq!(closed.parent_class, Some("java/lang/IllegalStateException")); + assert_eq!(closed.access_flags, ClassAccessFlags::PUBLIC); + assert_eq!(closed.methods.len(), 1); + assert_eq!(closed.methods[0].name, ""); + assert_eq!(closed.methods[0].descriptor, "()V"); + assert_eq!(closed.methods[0].access_flags, MethodAccessFlags::PUBLIC); +} + +#[test] +fn java_5_autoboxing_value_of_overloads_are_registered() { + for (class, descriptor) in [ + ("java/lang/Boolean", "(Z)Ljava/lang/Boolean;"), + ("java/lang/Byte", "(B)Ljava/lang/Byte;"), + ("java/lang/Short", "(S)Ljava/lang/Short;"), + ("java/lang/Integer", "(I)Ljava/lang/Integer;"), + ("java/lang/Long", "(J)Ljava/lang/Long;"), + ("java/lang/Float", "(F)Ljava/lang/Float;"), + ("java/lang/Double", "(D)Ljava/lang/Double;"), + ("java/lang/Character", "(C)Ljava/lang/Character;"), + ] { + let proto = get_runtime_class_proto(class).unwrap_or_else(|| panic!("{class} must be registered")); + let method = proto + .methods + .iter() + .find(|method| method.name == "valueOf" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing {class}.valueOf{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC); + } +} + +#[tokio::test] +async fn format_exception_state_messages_and_null_contracts_match_java_5() -> Result<()> { + let jvm = test_jvm().await?; + + for (class, getter, value, expected_message) in [ + ("java/util/DuplicateFormatFlagsException", "getFlags", "-", "Flags = '-'"), + ("java/util/IllegalFormatFlagsException", "getFlags", "+ ", "Flags = '+ '"), + ( + "java/util/MissingFormatArgumentException", + "getFormatSpecifier", + "%2$s", + "Format specifier '%2$s'", + ), + ("java/util/MissingFormatWidthException", "getFormatSpecifier", "%-s", "%-s"), + ("java/util/UnknownFormatConversionException", "getConversion", "q", "Conversion = 'q'"), + ("java/util/UnknownFormatFlagsException", "getFlags", "!", "Flags = !"), + ] { + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let exception = jvm.new_class(class, "(Ljava/lang/String;)V", (value.clone(),)).await?; + let stored: ClassInstanceRef = jvm.invoke_virtual(&exception, getter, "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_rust_string(&jvm, &stored).await?, + JavaLangString::to_rust_string(&jvm, &value).await? + ); + let message: ClassInstanceRef = jvm.invoke_virtual(&exception, "getMessage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, expected_message); + + let null = ClassInstanceRef::::new(None); + let result = jvm.new_class(class, "(Ljava/lang/String;)V", (null,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class} must reject a null constructor argument"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + + let flags = JavaLangString::from_rust_string(&jvm, "#").await?; + let mismatch = jvm + .new_class( + "java/util/FormatFlagsConversionMismatchException", + "(Ljava/lang/String;C)V", + (flags, 'b' as u16), + ) + .await?; + let stored_flags: ClassInstanceRef = jvm.invoke_virtual(&mismatch, "getFlags", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &stored_flags).await?, "#"); + assert_eq!(jvm.invoke_virtual::<_, u16>(&mismatch, "getConversion", "()C", ()).await?, 'b' as u16); + let message: ClassInstanceRef = jvm.invoke_virtual(&mismatch, "getMessage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, "Conversion = b, Flags = #"); + let null_flags = ClassInstanceRef::::new(None); + let result = jvm + .new_class( + "java/util/FormatFlagsConversionMismatchException", + "(Ljava/lang/String;C)V", + (null_flags, 'b' as u16), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("FormatFlagsConversionMismatchException must reject null flags"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let code_point = jvm.new_class("java/util/IllegalFormatCodePointException", "(I)V", (-1,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&code_point, "getCodePoint", "()I", ()).await?, -1); + let message: ClassInstanceRef = jvm.invoke_virtual(&code_point, "getMessage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, "Code point = 0xffffffff"); + + let string_class: ClassInstanceRef = jvm.resolve_class("java/lang/String").await?.java_class().into(); + let conversion = jvm + .new_class( + "java/util/IllegalFormatConversionException", + "(CLjava/lang/Class;)V", + ('d' as u16, string_class.clone()), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, u16>(&conversion, "getConversion", "()C", ()).await?, 'd' as u16); + let stored_class: ClassInstanceRef = jvm.invoke_virtual(&conversion, "getArgumentClass", "()Ljava/lang/Class;", ()).await?; + assert_eq!(stored_class.identity(), string_class.identity()); + let message: ClassInstanceRef = jvm.invoke_virtual(&conversion, "getMessage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, "d != java.lang.String"); + let null_class = ClassInstanceRef::::new(None); + let result = jvm + .new_class( + "java/util/IllegalFormatConversionException", + "(CLjava/lang/Class;)V", + ('d' as u16, null_class), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("IllegalFormatConversionException must reject a null argument class"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (class, getter, value) in [ + ("java/util/IllegalFormatPrecisionException", "getPrecision", -2), + ("java/util/IllegalFormatWidthException", "getWidth", -3), + ] { + let exception = jvm.new_class(class, "(I)V", (value,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&exception, getter, "()I", ()).await?, value); + let message: ClassInstanceRef = jvm.invoke_virtual(&exception, "getMessage", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, value.to_string()); + } + + Ok(()) +} + +#[tokio::test] +async fn formatter_big_decimal_layout_form_has_java_5_enum_identity_and_order() -> Result<()> { + let jvm = test_jvm().await?; + let scientific: ClassInstanceRef = jvm + .get_static_field( + "java/util/Formatter$BigDecimalLayoutForm", + "SCIENTIFIC", + "Ljava/util/Formatter$BigDecimalLayoutForm;", + ) + .await?; + let name = jvm.invoke_virtual(&scientific, "name", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "SCIENTIFIC"); + assert_eq!(jvm.invoke_virtual::<_, i32>(&scientific, "ordinal", "()I", ()).await?, 0); + + let values: ClassInstanceRef> = jvm + .invoke_static( + "java/util/Formatter$BigDecimalLayoutForm", + "values", + "()[Ljava/util/Formatter$BigDecimalLayoutForm;", + (), + ) + .await?; + assert_eq!(jvm.array_length(&values).await?, 2); + let requested = JavaLangString::from_rust_string(&jvm, "DECIMAL_FLOAT").await?; + let decimal: ClassInstanceRef = jvm + .invoke_static( + "java/util/Formatter$BigDecimalLayoutForm", + "valueOf", + "(Ljava/lang/String;)Ljava/util/Formatter$BigDecimalLayoutForm;", + (requested,), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&decimal, "ordinal", "()I", ()).await?, 1); + + Ok(()) +} + +#[tokio::test] +async fn formatter_formats_common_java_5_conversions() -> Result<()> { + let jvm = test_jvm().await?; + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%2$-6s %1$04d %3$.2f %% %n").await?; + let integer = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?; + let text = JavaLangString::from_rust_string(&jvm, "ok").await?; + let double = jvm.new_class("java/lang/Double", "(D)V", (12.5,)).await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 3).await?.into(); + jvm.store_array( + &mut arguments, + 0, + vec![JavaValue::from(integer), JavaValue::Object(Some(text)), JavaValue::from(double)], + ) + .await?; + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "ok 0007 12.50 % \n"); + + Ok(()) +} + +#[tokio::test] +async fn formatter_tracks_explicit_previous_and_ordinary_arguments_independently() -> Result<()> { + let jvm = test_jvm().await?; + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%2$s|%1$d|%> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(number)), JavaValue::Object(Some(text))]) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "two|26|1a|26"); + + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%2$s|%>::new(None); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "null|false"); + + let format = JavaLangString::from_rust_string(&jvm, "|%+x").await?; + let arguments = ClassInstanceRef::>::new(None); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "null|false|null"); + + Ok(()) +} + +#[tokio::test] +async fn formatter_uppercase_preserves_unpaired_utf16_surrogates() -> Result<()> { + let jvm = test_jvm().await?; + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%S|%C").await?; + let string = JavaLangString::from_utf16(&jvm, vec!['a' as u16, 0xd800, 'b' as u16]).await?; + let character = jvm.new_class("java/lang/Character", "(C)V", (0xd800u16,)).await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(string)), JavaValue::Object(Some(character))]) + .await?; + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_utf16(&jvm, &result).await?, + vec!['A' as u16, 0xd800, 'B' as u16, '|' as u16, 0xd800] + ); + + Ok(()) +} + +#[tokio::test] +async fn print_stream_and_print_writer_use_formatter_for_varargs_output() -> Result<()> { + let jvm = test_jvm().await?; + let format = JavaLangString::from_rust_string(&jvm, "%s=%03d").await?; + let label = JavaLangString::from_rust_string(&jvm, "n").await?; + let number = jvm.new_class("java/lang/Integer", "(I)V", (7,)).await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 2).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(label)), JavaValue::Object(Some(number))]) + .await?; + + let bytes = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let stream = jvm + .new_class("java/io/PrintStream", "(Ljava/io/OutputStream;)V", (bytes.clone(),)) + .await?; + let returned: ClassInstanceRef = jvm + .invoke_virtual( + &stream, + "printf", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;", + (format.clone(), arguments.clone()), + ) + .await?; + assert_eq!(returned.identity(), stream.identity()); + let stream_text = jvm.invoke_virtual(&bytes, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &stream_text).await?, "n=007"); + + 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 returned: ClassInstanceRef = jvm + .invoke_virtual( + &writer, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;", + (format, arguments), + ) + .await?; + assert_eq!(returned.identity(), writer.identity()); + let writer_text = jvm.invoke_virtual(&string_writer, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &writer_text).await?, "n=007"); + + Ok(()) +} + +#[tokio::test] +async fn charset_file_constructors_validate_encoding_before_opening_the_file() -> Result<()> { + let jvm = test_jvm().await?; + let path = JavaLangString::from_rust_string(&jvm, "existing.txt").await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?; + let encoding = JavaLangString::from_rust_string(&jvm, "not-a-charset").await?; + let locale: ClassInstanceRef = jvm.invoke_static("java/util/Locale", "getDefault", "()Ljava/util/Locale;", ()).await?; + + let formatter = jvm + .new_class( + "java/util/Formatter", + "(Ljava/io/File;Ljava/lang/String;Ljava/util/Locale;)V", + (file.clone(), encoding.clone(), locale), + ) + .await; + let Err(JavaError::JavaException(exception)) = formatter else { + panic!("Formatter must reject the charset before opening the file"); + }; + assert!(jvm.is_instance(&*exception, "java/io/UnsupportedEncodingException")); + + let print_stream = jvm + .new_class( + "java/io/PrintStream", + "(Ljava/io/File;Ljava/lang/String;)V", + (file.clone(), encoding.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = print_stream else { + panic!("PrintStream must reject the charset before opening the file"); + }; + assert!(jvm.is_instance(&*exception, "java/io/UnsupportedEncodingException")); + + let print_writer = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/File;Ljava/lang/String;)V", (file, encoding)) + .await; + let Err(JavaError::JavaException(exception)) = print_writer else { + panic!("PrintWriter must reject the charset before opening the file"); + }; + assert!(jvm.is_instance(&*exception, "java/io/UnsupportedEncodingException")); + + Ok(()) +} + +#[tokio::test] +async fn formatter_and_print_file_constructors_enforce_java_5_null_contracts() -> Result<()> { + let jvm = test_jvm().await?; + + let null_appendable = ClassInstanceRef::::new(None); + let formatter = jvm + .new_class("java/util/Formatter", "(Ljava/lang/Appendable;)V", (null_appendable,)) + .await?; + let out: ClassInstanceRef = jvm.invoke_virtual(&formatter, "out", "()Ljava/lang/Appendable;", ()).await?; + assert!(jvm.is_instance(&**out, "java/lang/StringBuilder")); + + let null_locale = ClassInstanceRef::::new(None); + let formatter = jvm.new_class("java/util/Formatter", "(Ljava/util/Locale;)V", (null_locale,)).await?; + let locale: ClassInstanceRef = jvm.invoke_virtual(&formatter, "locale", "()Ljava/util/Locale;", ()).await?; + assert!(locale.is_null()); + + let null_path = ClassInstanceRef::::new(None); + let result = jvm.new_class("java/util/Formatter", "(Ljava/lang/String;)V", (null_path.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Formatter(String) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_file = ClassInstanceRef::::new(None); + let result = jvm.new_class("java/util/Formatter", "(Ljava/io/File;)V", (null_file.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Formatter(File) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_stream = ClassInstanceRef::::new(None); + let result = jvm + .new_class("java/util/Formatter", "(Ljava/io/OutputStream;)V", (null_stream.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Formatter(OutputStream) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_print_stream = ClassInstanceRef::::new(None); + let result = jvm + .new_class("java/util/Formatter", "(Ljava/io/PrintStream;)V", (null_print_stream,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Formatter(PrintStream) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let null_encoding = ClassInstanceRef::::new(None); + let result = jvm + .new_class( + "java/util/Formatter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (output, null_encoding.clone()), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Formatter(OutputStream, String) must reject a null encoding"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (class, descriptor) in [ + ("java/io/PrintStream", "(Ljava/lang/String;)V"), + ("java/io/PrintWriter", "(Ljava/lang/String;)V"), + ] { + let result = jvm.new_class(class, descriptor, (null_path.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}{descriptor} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + + for (class, descriptor) in [("java/io/PrintStream", "(Ljava/io/File;)V"), ("java/io/PrintWriter", "(Ljava/io/File;)V")] { + let result = jvm.new_class(class, descriptor, (null_file.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}{descriptor} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + + Ok(()) +} + +#[tokio::test] +async fn formatter_handles_numeric_radix_grouping_and_uppercase_conversions() -> Result<()> { + let jvm = test_jvm().await?; + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%1$,(d|%2$x|%2$o|%3$.2e|%3$.4g|%4$C|%5$B").await?; + let negative = jvm.new_class("java/lang/Integer", "(I)V", (-1234,)).await?; + let minus_one = jvm.new_class("java/lang/Integer", "(I)V", (-1,)).await?; + let floating = jvm.new_class("java/lang/Double", "(D)V", (1234.0,)).await?; + let character = jvm.new_class("java/lang/Character", "(C)V", ('a' as u16,)).await?; + let boolean = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 5).await?.into(); + jvm.store_array( + &mut arguments, + 0, + vec![ + JavaValue::from(negative), + JavaValue::from(minus_one), + JavaValue::from(floating), + JavaValue::from(character), + JavaValue::from(boolean), + ], + ) + .await?; + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_rust_string(&jvm, &result).await?, + "(1,234)|ffffffff|37777777777|1.23e+03|1234|A|TRUE" + ); + + Ok(()) +} + +#[tokio::test] +async fn formatter_matches_java_5_radix_and_non_finite_number_rules() -> Result<()> { + let jvm = test_jvm().await?; + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%1$#o|%2$#05x|%3$#.0e|%4$+f|%5$010f").await?; + let zero = jvm.new_class("java/lang/Integer", "(I)V", (0,)).await?; + let fifteen = jvm.new_class("java/lang/Integer", "(I)V", (15,)).await?; + let one = jvm.new_class("java/lang/Double", "(D)V", (1.0,)).await?; + let nan = jvm.new_class("java/lang/Double", "(D)V", (f64::NAN,)).await?; + let infinity = jvm.new_class("java/lang/Double", "(D)V", (f64::INFINITY,)).await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 5).await?.into(); + jvm.store_array( + &mut arguments, + 0, + [ + JavaValue::Object(Some(zero)), + JavaValue::Object(Some(fifteen)), + JavaValue::Object(Some(one)), + JavaValue::Object(Some(nan)), + JavaValue::Object(Some(infinity)), + ], + ) + .await?; + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &result).await?, "00|0x00f|1.e+00|NaN| Infinity"); + + for (format, expected) in [ + ("%+x", "java/util/FormatFlagsConversionMismatchException"), + ("%+ x", "java/util/IllegalFormatFlagsException"), + ] { + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, format).await?; + let value = jvm.new_class("java/lang/Integer", "(I)V", (15,)).await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(value))]).await?; + let result: Result> = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{expected} must be thrown"); + }; + assert!(jvm.is_instance(&*exception, expected)); + } + + Ok(()) +} + +#[tokio::test] +async fn formatter_rounds_fixed_scientific_and_general_values_like_java_5() -> Result<()> { + let jvm = test_jvm().await?; + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "%.2f|%.3f|%.0f|%.2e|%.6g|%.6g|%.6g|%.6g|%.4g|%.4g|%g|%g|%.2e|%.3g|%.2e|%.0f|%.2f").await?; + let values = [ + 2.675, + 9.9995, + 0.5, + 9.995, + 0.0, + 0.0000999999, + 0.00001, + 999999.9, + 9999.5, + 9.9995, + -0.0, + 1.2, + f64::MIN_POSITIVE * f64::EPSILON, + f64::MIN_POSITIVE * f64::EPSILON, + f64::MAX, + 2.4999999999999996, + 1.005, + ]; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", values.len()).await?.into(); + for (index, value) in values.into_iter().enumerate() { + let value = jvm.new_class("java/lang/Double", "(D)V", (value,)).await?; + jvm.store_array(&mut arguments, index, [JavaValue::Object(Some(value))]).await?; + } + + let _: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await?; + let result = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!( + JavaLangString::to_rust_string(&jvm, &result).await?, + "2.68|10.000|1|1.00e+01|0.00000|9.99999e-05|1.00000e-05|1.00000e+06|1.000e+04|10.00|-0.00000|1.20000|4.90e-324|4.90e-324|1.80e+308|2|1.01" + ); + + Ok(()) +} + +#[tokio::test] +async fn formatter_throws_java_5_format_exceptions_and_rejects_use_after_close() -> Result<()> { + let jvm = test_jvm().await?; + + for (format, expected) in [ + ("%2$s", "java/util/MissingFormatArgumentException"), + ("%q", "java/util/UnknownFormatConversionException"), + ("%D", "java/util/UnknownFormatConversionException"), + ("%O", "java/util/UnknownFormatConversionException"), + ("%F", "java/util/UnknownFormatConversionException"), + ("%-0d", "java/util/MissingFormatWidthException"), + ("%-n", "java/util/IllegalFormatFlagsException"), + ("%2$#b", "java/util/FormatFlagsConversionMismatchException"), + ("%2$.2c", "java/util/IllegalFormatPrecisionException"), + ("%2$+x", "java/util/MissingFormatArgumentException"), + ("%2147483648s", "java/util/IllegalFormatWidthException"), + ("%.2147483648s", "java/util/IllegalFormatPrecisionException"), + ] { + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, format).await?; + let arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + let result: Result> = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{expected} must be thrown"); + }; + assert!(jvm.is_instance(&*exception, expected)); + } + + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&formatter, "close", "()V", ()).await?; + let result: Result> = jvm.invoke_virtual(&formatter, "out", "()Ljava/lang/Appendable;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("FormatterClosedException must be thrown"); + }; + assert!(jvm.is_instance(&*exception, "java/util/FormatterClosedException")); + + Ok(()) +} + +#[tokio::test] +async fn formatter_validates_all_syntax_before_writing_but_preserves_runtime_partial_output() -> Result<()> { + let jvm = test_jvm().await?; + + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "before %s after %q").await?; + let value = JavaLangString::from_rust_string(&jvm, "value").await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(value))]).await?; + let result: Result> = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("the trailing unknown conversion must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/UnknownFormatConversionException")); + let text = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, ""); + + let formatter = jvm.new_class("java/util/Formatter", "()V", ()).await?; + let format = JavaLangString::from_rust_string(&jvm, "before %d").await?; + let value = JavaLangString::from_rust_string(&jvm, "value").await?; + let mut arguments: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut arguments, 0, [JavaValue::Object(Some(value))]).await?; + let result: Result> = jvm + .invoke_virtual( + &formatter, + "format", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;", + (format, arguments), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("the incompatible runtime argument must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/IllegalFormatConversionException")); + let text = jvm.invoke_virtual(&formatter, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "before "); + + Ok(()) +} diff --git a/test_data/FormatterIntegration$1.class b/test_data/FormatterIntegration$1.class new file mode 100644 index 00000000..bc45692c Binary files /dev/null and b/test_data/FormatterIntegration$1.class differ diff --git a/test_data/FormatterIntegration$FailingAppendable.class b/test_data/FormatterIntegration$FailingAppendable.class new file mode 100644 index 00000000..64b2cc34 Binary files /dev/null and b/test_data/FormatterIntegration$FailingAppendable.class differ diff --git a/test_data/FormatterIntegration$SnapshotSequence.class b/test_data/FormatterIntegration$SnapshotSequence.class new file mode 100644 index 00000000..1285fa2c Binary files /dev/null and b/test_data/FormatterIntegration$SnapshotSequence.class differ diff --git a/test_data/FormatterIntegration.class b/test_data/FormatterIntegration.class new file mode 100644 index 00000000..4761a4fd Binary files /dev/null and b/test_data/FormatterIntegration.class differ diff --git a/test_data/FormatterIntegration.txt b/test_data/FormatterIntegration.txt new file mode 100644 index 00000000..d50a46a6 --- /dev/null +++ b/test_data/FormatterIntegration.txt @@ -0,0 +1,5 @@ +FORM:7:12:3|00ff|1.50 +ABC|5|io-4 +1|1|close +snapshot|slice +snapshot|slice diff --git a/test_data/src/FormatterIntegration.java b/test_data/src/FormatterIntegration.java new file mode 100644 index 00000000..6c7ab606 --- /dev/null +++ b/test_data/src/FormatterIntegration.java @@ -0,0 +1,96 @@ +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Formattable; +import java.util.FormattableFlags; +import java.util.Formatter; + +public class FormatterIntegration implements Formattable { + private static class SnapshotSequence implements CharSequence { + public int length() { + return 1; + } + + public char charAt(int index) { + if (index != 0) { + throw new IndexOutOfBoundsException(); + } + return 'x'; + } + + public CharSequence subSequence(int start, int end) { + if (start != 0 || end != 1) { + throw new IndexOutOfBoundsException(); + } + return "slice"; + } + + public String toString() { + return "snapshot"; + } + } + + private static class FailingAppendable implements Appendable, Flushable, Closeable { + private final StringBuilder output = new StringBuilder(); + private int calls; + private int flushes; + private int closes; + + public Appendable append(CharSequence value) throws IOException { + calls++; + if (calls == 2 || calls == 4) { + throw new IOException("io-" + calls); + } + output.append(value); + return this; + } + + public Appendable append(CharSequence value, int start, int end) throws IOException { + return append(value.subSequence(start, end)); + } + + public Appendable append(char value) throws IOException { + return append(String.valueOf(value)); + } + + public void flush() throws IOException { + flushes++; + throw new IOException("flush"); + } + + public void close() throws IOException { + closes++; + throw new IOException("close"); + } + + public String toString() { + return output.toString(); + } + } + + public void formatTo(Formatter formatter, int flags, int width, int precision) { + String value = (flags & FormattableFlags.UPPERCASE) != 0 ? "FORM" : "form"; + formatter.format("%s:%d:%d:%d", value, flags, width, precision); + } + + public static void main(String[] args) { + System.out.println(String.format("%#-12.3S|%04x|%.2f", new FormatterIntegration(), 255, 1.5)); + + FailingAppendable output = new FailingAppendable(); + Formatter formatter = new Formatter(output); + formatter.format("A%sB%sC", "x", "y"); + System.out.println(output + "|" + output.calls + "|" + formatter.ioException().getMessage()); + formatter.flush(); + formatter.close(); + formatter.close(); + System.out.println(output.flushes + "|" + output.closes + "|" + formatter.ioException().getMessage()); + + SnapshotSequence sequence = new SnapshotSequence(); + System.out.append(sequence).append('|').append(sequence, 0, 1).println(); + StringWriter destination = new StringWriter(); + new PrintWriter(destination).append(sequence).append('|').append(sequence, 0, 1).flush(); + System.out.println(destination); + } +}