diff --git a/java_runtime/src/classes/java/util.rs b/java_runtime/src/classes/java/util.rs index 7ccf408d..9b23eafa 100644 --- a/java_runtime/src/classes/java/util.rs +++ b/java_runtime/src/classes/java/util.rs @@ -1,4 +1,5 @@ pub mod jar; +pub mod logging; pub mod regex; pub mod zip; diff --git a/java_runtime/src/classes/java/util/logging.rs b/java_runtime/src/classes/java/util/logging.rs new file mode 100644 index 00000000..6ac0ee2f --- /dev/null +++ b/java_runtime/src/classes/java/util/logging.rs @@ -0,0 +1,15 @@ +mod console_handler; +mod filter; +mod formatter; +mod handler; +mod level; +mod log_manager; +mod log_record; +mod logger; +mod simple_formatter; +mod stream_handler; + +pub use self::{ + console_handler::ConsoleHandler, filter::Filter, formatter::Formatter, handler::Handler, level::Level, log_manager::LogManager, + log_record::LogRecord, logger::Logger, simple_formatter::SimpleFormatter, stream_handler::StreamHandler, +}; diff --git a/java_runtime/src/classes/java/util/logging/console_handler.rs b/java_runtime/src/classes/java/util/logging/console_handler.rs new file mode 100644 index 00000000..77af50a7 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/console_handler.rs @@ -0,0 +1,65 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::io::OutputStream}; + +use super::LogRecord; + +// public class java.util.logging.ConsoleHandler +pub struct ConsoleHandler; + +impl ConsoleHandler { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/ConsoleHandler", + parent_class: Some("java/util/logging/StreamHandler"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("publish", "(Ljava/util/logging/LogRecord;)V", Self::publish, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.ConsoleHandler::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/util/logging/StreamHandler", "", "()V", ()).await?; + let error: ClassInstanceRef = jvm.get_static_field("java/lang/System", "err", "Ljava/io/PrintStream;").await?; + jvm.invoke_special( + &this, + "java/util/logging/StreamHandler", + "setOutputStream", + "(Ljava/io/OutputStream;)V", + (error,), + ) + .await + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.ConsoleHandler::close({this:?})"); + + jvm.invoke_special(&this, "java/util/logging/StreamHandler", "flush", "()V", ()).await + } + + async fn publish(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, record: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.ConsoleHandler::publish({this:?}, {record:?})"); + + let _: () = jvm + .invoke_special( + &this, + "java/util/logging/StreamHandler", + "publish", + "(Ljava/util/logging/LogRecord;)V", + (record,), + ) + .await?; + jvm.invoke_special(&this, "java/util/logging/StreamHandler", "flush", "()V", ()).await + } +} diff --git a/java_runtime/src/classes/java/util/logging/filter.rs b/java_runtime/src/classes/java/util/logging/filter.rs new file mode 100644 index 00000000..f98baeea --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/filter.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.logging.Filter +pub struct Filter; + +impl Filter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/Filter", + parent_class: None, + interfaces: vec![], + methods: vec![JavaMethodProto::new_abstract( + "isLoggable", + "(Ljava/util/logging/LogRecord;)Z", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + )], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + } + } +} diff --git a/java_runtime/src/classes/java/util/logging/formatter.rs b/java_runtime/src/classes/java/util/logging/formatter.rs new file mode 100644 index 00000000..9342987c --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/formatter.rs @@ -0,0 +1,155 @@ +use alloc::{string::String as RustString, vec, vec::Vec}; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +use super::LogRecord; + +// public abstract class java.util.logging.Formatter +pub struct Formatter; + +impl Formatter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/Formatter", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new_abstract( + "format", + "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new( + "formatMessage", + "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", + Self::format_message, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getHead", + "(Ljava/util/logging/Handler;)Ljava/lang/String;", + Self::get_head, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getTail", + "(Ljava/util/logging/Handler;)Ljava/lang/String;", + Self::get_tail, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Formatter::({this:?})"); + + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn format_message( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + record: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.Formatter::formatMessage({this:?}, {record:?})"); + + if record.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "record").await); + } + + let message: ClassInstanceRef = jvm.invoke_virtual(&record, "getMessage", "()Ljava/lang/String;", ()).await?; + if message.is_null() { + return Ok(message); + } + + let parameters: ClassInstanceRef> = jvm.invoke_virtual(&record, "getParameters", "()[Ljava/lang/Object;", ()).await?; + if parameters.is_null() { + return Ok(message); + } + + let parameter_count = jvm.array_length(¶meters).await?; + if parameter_count == 0 { + return Ok(message); + } + + let parameters: Vec> = jvm.load_array(¶meters, 0, parameter_count).await?; + let mut replacements = Vec::with_capacity(parameter_count); + for parameter in parameters { + replacements.push(if parameter.is_null() { + RustString::from("null") + } else { + let value: ClassInstanceRef = jvm.invoke_virtual(¶meter, "toString", "()Ljava/lang/String;", ()).await?; + if value.is_null() { + RustString::from("null") + } else { + JavaLangString::to_rust_string(jvm, &value).await? + } + }); + } + + let message = JavaLangString::to_rust_string(jvm, &message).await?; + let characters: Vec = message.chars().collect(); + let mut formatted = RustString::new(); + let mut index = 0; + while index < characters.len() { + if characters[index] == '{' { + let mut cursor = index + 1; + let mut parameter_index = Some(0usize); + let mut has_digit = false; + while cursor < characters.len() && characters[cursor].is_ascii_digit() { + has_digit = true; + let digit = (characters[cursor] as u8 - b'0') as usize; + parameter_index = parameter_index.and_then(|value| value.checked_mul(10)?.checked_add(digit)); + cursor += 1; + } + if has_digit && cursor < characters.len() && characters[cursor] == '}' { + if let Some(replacement) = parameter_index.and_then(|parameter_index| replacements.get(parameter_index)) { + formatted.push_str(replacement); + } else { + formatted.extend(&characters[index..=cursor]); + } + index = cursor + 1; + continue; + } + } + formatted.push(characters[index]); + index += 1; + } + + Ok(JavaLangString::from_rust_string(jvm, &formatted).await?.into()) + } + + async fn get_head( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + _: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.Formatter::getHead({this:?})"); + + Ok(JavaLangString::from_rust_string(jvm, "").await?.into()) + } + + async fn get_tail( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + _: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.Formatter::getTail({this:?})"); + + Ok(JavaLangString::from_rust_string(jvm, "").await?.into()) + } +} diff --git a/java_runtime/src/classes/java/util/logging/handler.rs b/java_runtime/src/classes/java/util/logging/handler.rs new file mode 100644 index 00000000..f469c293 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/handler.rs @@ -0,0 +1,184 @@ +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::{ + io::OutputStreamWriter, + lang::{Exception, String}, + }, +}; + +use super::{Filter, Formatter, Level, LogRecord}; + +// public abstract class java.util.logging.Handler +pub struct Handler; + +impl Handler { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/Handler", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new_abstract("close", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("flush", "()V", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract( + "publish", + "(Ljava/util/logging/LogRecord;)V", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + ), + JavaMethodProto::new("getEncoding", "()Ljava/lang/String;", Self::get_encoding, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFilter", "()Ljava/util/logging/Filter;", Self::get_filter, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getFormatter", + "()Ljava/util/logging/Formatter;", + Self::get_formatter, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("getLevel", "()Ljava/util/logging/Level;", Self::get_level, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isLoggable", + "(Ljava/util/logging/LogRecord;)Z", + Self::is_loggable, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "reportError", + "(Ljava/lang/String;Ljava/lang/Exception;I)V", + Self::report_error, + MethodAccessFlags::PROTECTED, + ), + JavaMethodProto::new("setEncoding", "(Ljava/lang/String;)V", Self::set_encoding, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setFilter", "(Ljava/util/logging/Filter;)V", Self::set_filter, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setFormatter", + "(Ljava/util/logging/Formatter;)V", + Self::set_formatter, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("setLevel", "(Ljava/util/logging/Level;)V", Self::set_level, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("level", "Ljava/util/logging/Level;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("filter", "Ljava/util/logging/Filter;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("formatter", "Ljava/util/logging/Formatter;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("encoding", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Handler::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let level: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "ALL", "Ljava/util/logging/Level;") + .await?; + jvm.put_field(&mut this, "level", "Ljava/util/logging/Level;", level).await + } + + async fn get_encoding(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Handler::getEncoding({this:?})"); + + jvm.get_field(&this, "encoding", "Ljava/lang/String;").await + } + + async fn get_filter(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Handler::getFilter({this:?})"); + + jvm.get_field(&this, "filter", "Ljava/util/logging/Filter;").await + } + + async fn get_formatter(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Handler::getFormatter({this:?})"); + + jvm.get_field(&this, "formatter", "Ljava/util/logging/Formatter;").await + } + + async fn get_level(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Handler::getLevel({this:?})"); + + jvm.get_field(&this, "level", "Ljava/util/logging/Level;").await + } + + async fn is_loggable(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, record: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.Handler::isLoggable({this:?}, {record:?})"); + + if record.is_null() { + return Ok(false); + } + + let record_level: ClassInstanceRef = jvm.invoke_virtual(&record, "getLevel", "()Ljava/util/logging/Level;", ()).await?; + let handler_level: ClassInstanceRef = jvm.get_field(&this, "level", "Ljava/util/logging/Level;").await?; + let record_value: i32 = jvm.invoke_virtual(&record_level, "intValue", "()I", ()).await?; + let handler_value: i32 = jvm.invoke_virtual(&handler_level, "intValue", "()I", ()).await?; + if handler_value == i32::MAX || record_value < handler_value { + return Ok(false); + } + + let filter: ClassInstanceRef = jvm.get_field(&this, "filter", "Ljava/util/logging/Filter;").await?; + if filter.is_null() { + return Ok(true); + } + + jvm.invoke_virtual(&filter, "isLoggable", "(Ljava/util/logging/LogRecord;)Z", (record,)) + .await + } + + async fn report_error( + _: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + _: ClassInstanceRef, + _: ClassInstanceRef, + _: i32, + ) -> Result<()> { + tracing::debug!("java.util.logging.Handler::reportError({this:?})"); + + Ok(()) + } + + async fn set_encoding(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, encoding: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Handler::setEncoding({this:?}, {encoding:?})"); + + if !encoding.is_null() { + OutputStreamWriter::validate_encoding(jvm, &encoding).await?; + } + jvm.put_field(&mut this, "encoding", "Ljava/lang/String;", encoding).await + } + + async fn set_filter(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, filter: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Handler::setFilter({this:?}, {filter:?})"); + + jvm.put_field(&mut this, "filter", "Ljava/util/logging/Filter;", filter).await + } + + async fn set_formatter( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + formatter: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.logging.Handler::setFormatter({this:?}, {formatter:?})"); + + if formatter.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "formatter").await); + } + jvm.put_field(&mut this, "formatter", "Ljava/util/logging/Formatter;", formatter).await + } + + async fn set_level(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, level: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Handler::setLevel({this:?}, {level:?})"); + + if level.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "level").await); + } + jvm.put_field(&mut this, "level", "Ljava/util/logging/Level;", level).await + } +} diff --git a/java_runtime/src/classes/java/util/logging/level.rs b/java_runtime/src/classes/java/util/logging/level.rs new file mode 100644 index 00000000..5b2b3352 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/level.rs @@ -0,0 +1,298 @@ +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::{Object, String}, +}; + +// public class java.util.logging.Level +pub struct Level; + +impl Level { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/Level", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(Ljava/lang/String;I)V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new( + "parse", + "(Ljava/lang/String;)Ljava/util/logging/Level;", + Self::parse, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("getName", "()Ljava/lang/String;", Self::get_name, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "()Ljava/lang/String;", + Self::to_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL, + ), + ], + fields: vec![ + JavaFieldProto::new( + "OFF", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "SEVERE", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "WARNING", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "INFO", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CONFIG", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FINE", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FINER", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FINEST", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "ALL", + "Ljava/util/logging/Level;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("name", "Ljava/lang/String;", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + JavaFieldProto::new("value", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + tracing::debug!("java.util.logging.Level::()"); + + let off: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "OFF").await?, i32::MAX), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "OFF", "Ljava/util/logging/Level;", off) + .await?; + + let severe: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "SEVERE").await?, 1000i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "SEVERE", "Ljava/util/logging/Level;", severe) + .await?; + + let warning: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "WARNING").await?, 900i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "WARNING", "Ljava/util/logging/Level;", warning) + .await?; + + let info: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "INFO").await?, 800i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;", info) + .await?; + + let config: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "CONFIG").await?, 700i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "CONFIG", "Ljava/util/logging/Level;", config) + .await?; + + let fine: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "FINE").await?, 500i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "FINE", "Ljava/util/logging/Level;", fine) + .await?; + + let finer: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "FINER").await?, 400i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;", finer) + .await?; + + let finest: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "FINEST").await?, 300i32), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "FINEST", "Ljava/util/logging/Level;", finest) + .await?; + + let all: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Level", + "(Ljava/lang/String;I)V", + (JavaLangString::from_rust_string(jvm, "ALL").await?, i32::MIN), + ) + .await? + .into(); + jvm.put_static_field("java/util/logging/Level", "ALL", "Ljava/util/logging/Level;", all) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, name: ClassInstanceRef, value: i32) -> Result<()> { + tracing::debug!("java.util.logging.Level::({this:?}, {name:?}, {value})"); + + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + + 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, "value", "I", value).await + } + + async fn parse(jvm: &Jvm, _: &mut RuntimeContext, name: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Level::parse({name:?})"); + + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + + let parsed_name = JavaLangString::to_rust_string(jvm, &name).await?; + let field_name = match parsed_name.as_str() { + "OFF" => Some("OFF"), + "SEVERE" => Some("SEVERE"), + "WARNING" => Some("WARNING"), + "INFO" => Some("INFO"), + "CONFIG" => Some("CONFIG"), + "FINE" => Some("FINE"), + "FINER" => Some("FINER"), + "FINEST" => Some("FINEST"), + "ALL" => Some("ALL"), + _ => None, + }; + if let Some(field_name) = field_name { + return jvm + .get_static_field("java/util/logging/Level", field_name, "Ljava/util/logging/Level;") + .await; + } + + let Ok(value) = parsed_name.parse::() else { + return Err(jvm.exception("java/lang/IllegalArgumentException", &parsed_name).await); + }; + let field_name = match value { + i32::MAX => Some("OFF"), + 1000 => Some("SEVERE"), + 900 => Some("WARNING"), + 800 => Some("INFO"), + 700 => Some("CONFIG"), + 500 => Some("FINE"), + 400 => Some("FINER"), + 300 => Some("FINEST"), + i32::MIN => Some("ALL"), + _ => None, + }; + if let Some(field_name) = field_name { + return jvm + .get_static_field("java/util/logging/Level", field_name, "Ljava/util/logging/Level;") + .await; + } + + Ok(jvm + .new_class("java/util/logging/Level", "(Ljava/lang/String;I)V", (name, value)) + .await? + .into()) + } + + async fn get_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Level::getName({this:?})"); + + jvm.get_field(&this, "name", "Ljava/lang/String;").await + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.Level::intValue({this:?})"); + + jvm.get_field(&this, "value", "I").await + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.Level::equals({this:?}, {other:?})"); + + if other.is_null() || !jvm.is_instance(&**other, "java/util/logging/Level") { + return Ok(false); + } + + let other: ClassInstanceRef = ClassInstanceRef::new(other.instance); + let value: i32 = jvm.get_field(&this, "value", "I").await?; + let other_value: i32 = jvm.get_field(&other, "value", "I").await?; + Ok(value == other_value) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.Level::hashCode({this:?})"); + + jvm.get_field(&this, "value", "I").await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Level::toString({this:?})"); + + jvm.get_field(&this, "name", "Ljava/lang/String;").await + } +} diff --git a/java_runtime/src/classes/java/util/logging/log_manager.rs b/java_runtime/src/classes/java/util/logging/log_manager.rs new file mode 100644 index 00000000..3f4aee96 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/log_manager.rs @@ -0,0 +1,297 @@ +use alloc::{string::String as RustString, 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::{Object, String}, + util::{ + Hashtable, + logging::{ConsoleHandler, Handler, Level, Logger}, + }, + }, +}; + +// public class java.util.logging.LogManager +pub struct LogManager; + +impl LogManager { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/LogManager", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PROTECTED), + JavaMethodProto::new( + "getLogManager", + "()Ljava/util/logging/LogManager;", + Self::get_log_manager, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("addLogger", "(Ljava/util/logging/Logger;)Z", Self::add_logger, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "registerLogger", + "(Ljava/lang/String;Ljava/util/logging/Logger;)Z", + Self::register_logger, + MethodAccessFlags::PRIVATE | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + Self::get_logger, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "getLoggerNames", + "()Ljava/util/Enumeration;", + Self::get_logger_names, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("reset", "()V", Self::reset, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "snapshotLoggers", + "()[Ljava/util/logging/Logger;", + Self::snapshot_loggers, + MethodAccessFlags::PRIVATE | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("checkAccess", "()V", Self::check_access, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "manager", + "Ljava/util/logging/LogManager;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC, + ), + JavaFieldProto::new("loggers", "Ljava/util/Hashtable;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("root", "Ljava/util/logging/Logger;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + tracing::debug!("java.util.logging.LogManager::()"); + let manager = jvm.new_class("java/util/logging/LogManager", "()V", ()).await?; + jvm.put_static_field("java/util/logging/LogManager", "manager", "Ljava/util/logging/LogManager;", manager) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.LogManager::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let loggers: ClassInstanceRef = jvm.new_class("java/util/Hashtable", "()V", ()).await?.into(); + jvm.put_field(&mut this, "loggers", "Ljava/util/Hashtable;", loggers.clone()).await?; + + let root_name = JavaLangString::from_rust_string(jvm, "").await?; + let resource_bundle_name: ClassInstanceRef = None.into(); + let mut root: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Logger", + "(Ljava/lang/String;Ljava/lang/String;)V", + (root_name.clone(), resource_bundle_name), + ) + .await? + .into(); + let info: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + jvm.put_field(&mut root, "level", "Ljava/util/logging/Level;", info).await?; + jvm.put_field(&mut root, "useParentHandlers", "Z", false).await?; + let console: ClassInstanceRef = jvm.new_class("java/util/logging/ConsoleHandler", "()V", ()).await?.into(); + let _: () = jvm + .invoke_virtual(&root, "addHandler", "(Ljava/util/logging/Handler;)V", (console,)) + .await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &loggers, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (root_name, root.clone()), + ) + .await?; + jvm.put_field(&mut this, "root", "Ljava/util/logging/Logger;", root.clone()).await?; + + let global_name: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Logger", "GLOBAL_LOGGER_NAME", "Ljava/lang/String;") + .await?; + let mut global: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Logger", "global", "Ljava/util/logging/Logger;") + .await?; + jvm.put_field(&mut global, "parent", "Ljava/util/logging/Logger;", root).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &loggers, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (global_name, global), + ) + .await?; + Ok(()) + } + + async fn get_log_manager(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { + tracing::debug!("java.util.logging.LogManager::getLogManager()"); + jvm.get_static_field("java/util/logging/LogManager", "manager", "Ljava/util/logging/LogManager;") + .await + } + + async fn add_logger(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, logger: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.LogManager::addLogger({this:?}, {logger:?})"); + if logger.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "logger").await); + } + let name: ClassInstanceRef = jvm.invoke_virtual(&logger, "getName", "()Ljava/lang/String;", ()).await?; + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "logger name").await); + } + + jvm.invoke_special( + &this, + "java/util/logging/LogManager", + "registerLogger", + "(Ljava/lang/String;Ljava/util/logging/Logger;)Z", + (name, logger), + ) + .await + } + + async fn register_logger( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + name: ClassInstanceRef, + logger: ClassInstanceRef, + ) -> Result { + let loggers: ClassInstanceRef = jvm.get_field(&this, "loggers", "Ljava/util/Hashtable;").await?; + let existing: ClassInstanceRef = jvm + .invoke_virtual(&loggers, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (name.clone(),)) + .await?; + if !existing.is_null() { + return Ok(false); + } + let _: ClassInstanceRef = jvm + .invoke_virtual( + &loggers, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (name, logger), + ) + .await?; + Self::reconnect_parents(jvm, &this, &loggers).await?; + Ok(true) + } + + async fn get_logger( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + name: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.LogManager::getLogger({this:?}, {name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + let loggers: ClassInstanceRef = jvm.get_field(&this, "loggers", "Ljava/util/Hashtable;").await?; + jvm.invoke_virtual(&loggers, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (name,)) + .await + } + + async fn get_logger_names(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.LogManager::getLoggerNames({this:?})"); + let loggers: ClassInstanceRef = jvm.get_field(&this, "loggers", "Ljava/util/Hashtable;").await?; + jvm.invoke_virtual(&loggers, "keys", "()Ljava/util/Enumeration;", ()).await + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.LogManager::reset({this:?})"); + let loggers: ClassInstanceRef> = jvm + .invoke_special( + &this, + "java/util/logging/LogManager", + "snapshotLoggers", + "()[Ljava/util/logging/Logger;", + (), + ) + .await?; + let root: ClassInstanceRef = jvm.get_field(&this, "root", "Ljava/util/logging/Logger;").await?; + let length = jvm.array_length(&loggers).await?; + let loggers: Vec> = jvm.load_array(&loggers, 0, length).await?; + for logger in loggers { + let handlers: ClassInstanceRef> = jvm + .invoke_special( + &logger, + "java/util/logging/Logger", + "reset", + "(Z)[Ljava/util/logging/Handler;", + (logger.identity() == root.identity(),), + ) + .await?; + let length = jvm.array_length(&handlers).await?; + let handlers: Vec> = jvm.load_array(&handlers, 0, length).await?; + for handler in handlers { + let _: () = jvm.invoke_virtual(&handler, "close", "()V", ()).await?; + } + } + Ok(()) + } + + async fn snapshot_loggers(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + let loggers: ClassInstanceRef = jvm.get_field(&this, "loggers", "Ljava/util/Hashtable;").await?; + let values: ClassInstanceRef = jvm.invoke_virtual(&loggers, "elements", "()Ljava/util/Enumeration;", ()).await?; + let mut snapshot: Vec> = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&values, "hasMoreElements", "()Z", ()).await? { + snapshot.push(jvm.invoke_virtual(&values, "nextElement", "()Ljava/lang/Object;", ()).await?); + } + + let mut result: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/logging/Logger;", snapshot.len()).await?.into(); + for (index, logger) in snapshot.into_iter().enumerate() { + jvm.store_array(&mut result, index, [logger]).await?; + } + Ok(result) + } + + async fn check_access(_: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.LogManager::checkAccess({this:?})"); + Ok(()) + } + + async fn reconnect_parents(jvm: &Jvm, manager: &ClassInstanceRef, loggers: &ClassInstanceRef) -> Result<()> { + let names: ClassInstanceRef = jvm.invoke_virtual(loggers, "keys", "()Ljava/util/Enumeration;", ()).await?; + let mut keys = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&names, "hasMoreElements", "()Z", ()).await? { + keys.push(jvm.invoke_virtual(&names, "nextElement", "()Ljava/lang/Object;", ()).await?); + } + + let root: ClassInstanceRef = jvm.get_field(manager, "root", "Ljava/util/logging/Logger;").await?; + for key in keys { + let name: RustString = JavaLangString::to_rust_string(jvm, &key).await?; + if name.is_empty() { + continue; + } + let mut logger: ClassInstanceRef = jvm + .invoke_virtual(loggers, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + let mut parent = root.clone(); + let mut prefix = name.as_str(); + while let Some(index) = prefix.rfind('.') { + prefix = &prefix[..index]; + let candidate_name = JavaLangString::from_rust_string(jvm, prefix).await?; + let candidate: ClassInstanceRef = jvm + .invoke_virtual(loggers, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (candidate_name,)) + .await?; + if !candidate.is_null() { + parent = candidate; + break; + } + } + jvm.put_field(&mut logger, "parent", "Ljava/util/logging/Logger;", parent).await?; + } + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/util/logging/log_record.rs b/java_runtime/src/classes/java/util/logging/log_record.rs new file mode 100644 index 00000000..24aeebe7 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/log_record.rs @@ -0,0 +1,279 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String, Throwable}, +}; + +use super::Level; + +// public class java.util.logging.LogRecord +pub struct LogRecord; + +impl LogRecord { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/LogRecord", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + Self::init, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "allocateSequenceNumber", + "()J", + Self::allocate_sequence_number, + MethodAccessFlags::PRIVATE | MethodAccessFlags::STATIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("getLoggerName", "()Ljava/lang/String;", Self::get_logger_name, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setLoggerName", "(Ljava/lang/String;)V", Self::set_logger_name, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getResourceBundle", + "()Ljava/util/ResourceBundle;", + Self::get_resource_bundle, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "setResourceBundle", + "(Ljava/util/ResourceBundle;)V", + Self::set_resource_bundle, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getResourceBundleName", + "()Ljava/lang/String;", + Self::get_resource_bundle_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "setResourceBundleName", + "(Ljava/lang/String;)V", + Self::set_resource_bundle_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("getLevel", "()Ljava/util/logging/Level;", Self::get_level, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setLevel", "(Ljava/util/logging/Level;)V", Self::set_level, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getSequenceNumber", "()J", Self::get_sequence_number, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setSequenceNumber", "(J)V", Self::set_sequence_number, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getSourceClassName", + "()Ljava/lang/String;", + Self::get_source_class_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "setSourceClassName", + "(Ljava/lang/String;)V", + Self::set_source_class_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getSourceMethodName", + "()Ljava/lang/String;", + Self::get_source_method_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "setSourceMethodName", + "(Ljava/lang/String;)V", + Self::set_source_method_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("getMessage", "()Ljava/lang/String;", Self::get_message, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setMessage", "(Ljava/lang/String;)V", Self::set_message, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getThreadID", "()I", Self::get_thread_id, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setThreadID", "(I)V", Self::set_thread_id, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getMillis", "()J", Self::get_millis, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setMillis", "(J)V", Self::set_millis, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getThrown", "()Ljava/lang/Throwable;", Self::get_thrown, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setThrown", "(Ljava/lang/Throwable;)V", Self::set_thrown, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getParameters", "()[Ljava/lang/Object;", Self::get_parameters, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("setParameters", "([Ljava/lang/Object;)V", Self::set_parameters, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new("nextSequenceNumber", "J", FieldAccessFlags::PRIVATE | FieldAccessFlags::STATIC), + JavaFieldProto::new("loggerName", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("resourceBundle", "Ljava/util/ResourceBundle;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("resourceBundleName", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("level", "Ljava/util/logging/Level;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("sequenceNumber", "J", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("sourceClassName", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("sourceMethodName", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("message", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("threadID", "I", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("millis", "J", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("thrown", "Ljava/lang/Throwable;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("parameters", "[Ljava/lang/Object;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init( + jvm: &Jvm, + context: &mut RuntimeContext, + mut this: ClassInstanceRef, + level: ClassInstanceRef, + message: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.logging.LogRecord::({this:?}, {level:?}, {message:?})"); + + if level.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "level").await); + } + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let sequence_number: i64 = jvm + .invoke_static("java/util/logging/LogRecord", "allocateSequenceNumber", "()J", ()) + .await?; + let current_thread = jvm.current_java_thread(); + let thread_id: i64 = jvm.get_field(¤t_thread, "id", "J").await?; + + jvm.put_field(&mut this, "level", "Ljava/util/logging/Level;", level).await?; + jvm.put_field(&mut this, "message", "Ljava/lang/String;", message).await?; + jvm.put_field(&mut this, "sequenceNumber", "J", sequence_number).await?; + jvm.put_field(&mut this, "millis", "J", context.now() as i64).await?; + jvm.put_field(&mut this, "threadID", "I", thread_id as i32).await + } + + async fn allocate_sequence_number(jvm: &Jvm, _: &mut RuntimeContext) -> Result { + let sequence_number: i64 = jvm.get_static_field("java/util/logging/LogRecord", "nextSequenceNumber", "J").await?; + jvm.put_static_field("java/util/logging/LogRecord", "nextSequenceNumber", "J", sequence_number.wrapping_add(1)) + .await?; + Ok(sequence_number) + } + + async fn get_logger_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "loggerName", "Ljava/lang/String;").await + } + + async fn set_logger_name(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, name: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "loggerName", "Ljava/lang/String;", name).await + } + + async fn get_resource_bundle(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "resourceBundle", "Ljava/util/ResourceBundle;").await + } + + async fn set_resource_bundle( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + bundle: ClassInstanceRef, + ) -> Result<()> { + jvm.put_field(&mut this, "resourceBundle", "Ljava/util/ResourceBundle;", bundle).await + } + + async fn get_resource_bundle_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "resourceBundleName", "Ljava/lang/String;").await + } + + async fn set_resource_bundle_name( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + name: ClassInstanceRef, + ) -> Result<()> { + jvm.put_field(&mut this, "resourceBundleName", "Ljava/lang/String;", name).await + } + + async fn get_level(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "level", "Ljava/util/logging/Level;").await + } + + async fn set_level(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, level: ClassInstanceRef) -> Result<()> { + if level.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "level").await); + } + + jvm.put_field(&mut this, "level", "Ljava/util/logging/Level;", level).await + } + + async fn get_sequence_number(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "sequenceNumber", "J").await + } + + async fn set_sequence_number(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, sequence_number: i64) -> Result<()> { + jvm.put_field(&mut this, "sequenceNumber", "J", sequence_number).await + } + + async fn get_source_class_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "sourceClassName", "Ljava/lang/String;").await + } + + async fn set_source_class_name( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + source_class_name: ClassInstanceRef, + ) -> Result<()> { + jvm.put_field(&mut this, "sourceClassName", "Ljava/lang/String;", source_class_name).await + } + + async fn get_source_method_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "sourceMethodName", "Ljava/lang/String;").await + } + + async fn set_source_method_name( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + source_method_name: ClassInstanceRef, + ) -> Result<()> { + jvm.put_field(&mut this, "sourceMethodName", "Ljava/lang/String;", source_method_name) + .await + } + + async fn get_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "message", "Ljava/lang/String;").await + } + + async fn set_message(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "message", "Ljava/lang/String;", message).await + } + + async fn get_thread_id(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "threadID", "I").await + } + + async fn set_thread_id(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, thread_id: i32) -> Result<()> { + jvm.put_field(&mut this, "threadID", "I", thread_id).await + } + + async fn get_millis(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "millis", "J").await + } + + async fn set_millis(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, millis: i64) -> Result<()> { + jvm.put_field(&mut this, "millis", "J", millis).await + } + + async fn get_thrown(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + jvm.get_field(&this, "thrown", "Ljava/lang/Throwable;").await + } + + async fn set_thrown(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, thrown: ClassInstanceRef) -> Result<()> { + jvm.put_field(&mut this, "thrown", "Ljava/lang/Throwable;", thrown).await + } + + async fn get_parameters(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + jvm.get_field(&this, "parameters", "[Ljava/lang/Object;").await + } + + async fn set_parameters( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + parameters: ClassInstanceRef>, + ) -> Result<()> { + jvm.put_field(&mut this, "parameters", "[Ljava/lang/Object;", parameters).await + } +} diff --git a/java_runtime/src/classes/java/util/logging/logger.rs b/java_runtime/src/classes/java/util/logging/logger.rs new file mode 100644 index 00000000..5cecb6e8 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/logger.rs @@ -0,0 +1,1210 @@ +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::{Object, String, Throwable}, + util::{ + Vector, + logging::{Filter, Handler, Level, LogManager, LogRecord}, + }, + }, +}; + +// public class java.util.logging.Logger +pub struct Logger; + +impl Logger { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/Logger", + parent_class: Some("java/lang/Object"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new( + "", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::init, + MethodAccessFlags::PROTECTED, + ), + JavaMethodProto::new( + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + Self::get_logger, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLogger", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/util/logging/Logger;", + Self::get_logger_with_resource_bundle, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "configureResourceBundle", + "(Ljava/lang/String;)V", + Self::configure_resource_bundle, + MethodAccessFlags::PRIVATE | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "getAnonymousLogger", + "()Ljava/util/logging/Logger;", + Self::get_anonymous_logger, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getAnonymousLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + Self::get_anonymous_logger_with_resource_bundle, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getGlobal", + "()Ljava/util/logging/Logger;", + Self::get_global, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC | MethodAccessFlags::FINAL, + ), + JavaMethodProto::new("getName", "()Ljava/lang/String;", Self::get_name, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getFilter", "()Ljava/util/logging/Filter;", Self::get_filter, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setFilter", + "(Ljava/util/logging/Filter;)V", + Self::set_filter, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("getLevel", "()Ljava/util/logging/Level;", Self::get_level, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setLevel", + "(Ljava/util/logging/Level;)V", + Self::set_level, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("getParent", "()Ljava/util/logging/Logger;", Self::get_parent, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setParent", + "(Ljava/util/logging/Logger;)V", + Self::set_parent, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("getUseParentHandlers", "()Z", Self::get_use_parent_handlers, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setUseParentHandlers", + "(Z)V", + Self::set_use_parent_handlers, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "addHandler", + "(Ljava/util/logging/Handler;)V", + Self::add_handler, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "removeHandler", + "(Ljava/util/logging/Handler;)V", + Self::remove_handler, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "getHandlers", + "()[Ljava/util/logging/Handler;", + Self::get_handlers, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new("isLoggable", "(Ljava/util/logging/Level;)Z", Self::is_loggable, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("log", "(Ljava/util/logging/LogRecord;)V", Self::log_record, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "log", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + Self::log, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "log", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/Object;)V", + Self::log_with_parameter, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "log", + "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V", + Self::log_with_parameters, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "log", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/Throwable;)V", + Self::log_with_throwable, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logp", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + Self::logp, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logp", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", + Self::logp_with_parameter, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logp", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V", + Self::logp_with_parameters, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logp", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V", + Self::logp_with_throwable, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logrb", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + Self::logrb, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logrb", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", + Self::logrb_with_parameter, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logrb", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V", + Self::logrb_with_parameters, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "logrb", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V", + Self::logrb_with_throwable, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("severe", "(Ljava/lang/String;)V", Self::severe, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("warning", "(Ljava/lang/String;)V", Self::warning, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("info", "(Ljava/lang/String;)V", Self::info, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("config", "(Ljava/lang/String;)V", Self::config, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("fine", "(Ljava/lang/String;)V", Self::fine, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("finer", "(Ljava/lang/String;)V", Self::finer, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("finest", "(Ljava/lang/String;)V", Self::finest, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "entering", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::entering, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "entering", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", + Self::entering_with_parameter, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "entering", + "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V", + Self::entering_with_parameters, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "exiting", + "(Ljava/lang/String;Ljava/lang/String;)V", + Self::exiting, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "exiting", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", + Self::exiting_with_result, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "throwing", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V", + Self::throwing, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getResourceBundleName", + "()Ljava/lang/String;", + Self::get_resource_bundle_name, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new( + "getResourceBundle", + "()Ljava/util/ResourceBundle;", + Self::get_resource_bundle, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("reset", "(Z)[Ljava/util/logging/Handler;", Self::reset, MethodAccessFlags::SYNCHRONIZED), + ], + fields: vec![ + JavaFieldProto::new( + "global", + "Ljava/util/logging/Logger;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "GLOBAL_LOGGER_NAME", + "Ljava/lang/String;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("name", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("level", "Ljava/util/logging/Level;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("filter", "Ljava/util/logging/Filter;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("handlers", "Ljava/util/Vector;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("parent", "Ljava/util/logging/Logger;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("useParentHandlers", "Z", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("resourceBundleName", "Ljava/lang/String;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("resourceBundle", "Ljava/util/ResourceBundle;", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + tracing::debug!("java.util.logging.Logger::()"); + + let name = JavaLangString::from_rust_string(jvm, "global").await?; + jvm.put_static_field("java/util/logging/Logger", "GLOBAL_LOGGER_NAME", "Ljava/lang/String;", name.clone()) + .await?; + let resource_bundle_name: ClassInstanceRef = None.into(); + let global = jvm + .new_class( + "java/util/logging/Logger", + "(Ljava/lang/String;Ljava/lang/String;)V", + (name.clone(), resource_bundle_name), + ) + .await?; + jvm.put_static_field("java/util/logging/Logger", "global", "Ljava/util/logging/Logger;", global) + .await + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + name: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.logging.Logger::({this:?}, {name:?}, {resource_bundle_name:?})"); + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let handlers = jvm.new_class("java/util/Vector", "()V", ()).await?; + jvm.put_field(&mut this, "name", "Ljava/lang/String;", name).await?; + jvm.put_field(&mut this, "handlers", "Ljava/util/Vector;", handlers).await?; + jvm.put_field(&mut this, "useParentHandlers", "Z", true).await?; + jvm.put_field(&mut this, "resourceBundleName", "Ljava/lang/String;", resource_bundle_name) + .await + } + + async fn get_logger(jvm: &Jvm, _: &mut RuntimeContext, name: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getLogger({name:?})"); + + let resource_bundle_name: ClassInstanceRef = None.into(); + jvm.invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/util/logging/Logger;", + (name, resource_bundle_name), + ) + .await + } + + async fn get_logger_with_resource_bundle( + jvm: &Jvm, + _: &mut RuntimeContext, + name: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.Logger::getLogger({name:?}, {resource_bundle_name:?})"); + + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + + let manager: ClassInstanceRef = jvm + .invoke_static("java/util/logging/LogManager", "getLogManager", "()Ljava/util/logging/LogManager;", ()) + .await?; + let mut logger: ClassInstanceRef = jvm + .invoke_virtual(&manager, "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", (name.clone(),)) + .await?; + if logger.is_null() { + let candidate: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Logger", + "(Ljava/lang/String;Ljava/lang/String;)V", + (name.clone(), resource_bundle_name.clone()), + ) + .await? + .into(); + let added: bool = jvm + .invoke_virtual(&manager, "addLogger", "(Ljava/util/logging/Logger;)Z", (candidate.clone(),)) + .await?; + logger = if added { + candidate + } else { + jvm.invoke_virtual(&manager, "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", (name,)) + .await? + }; + } + + let _: () = jvm + .invoke_special( + &logger, + "java/util/logging/Logger", + "configureResourceBundle", + "(Ljava/lang/String;)V", + (resource_bundle_name,), + ) + .await?; + Ok(logger) + } + + async fn configure_resource_bundle( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + ) -> Result<()> { + if resource_bundle_name.is_null() { + return Ok(()); + } + + let current: ClassInstanceRef = jvm.get_field(&this, "resourceBundleName", "Ljava/lang/String;").await?; + if current.is_null() { + return jvm + .put_field(&mut this, "resourceBundleName", "Ljava/lang/String;", resource_bundle_name) + .await; + } + if JavaLangString::to_rust_string(jvm, ¤t).await? != JavaLangString::to_rust_string(jvm, &resource_bundle_name).await? { + return Err(jvm + .exception("java/lang/IllegalArgumentException", "logger already uses a different resource bundle") + .await); + } + Ok(()) + } + + async fn get_anonymous_logger(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { + tracing::debug!("java.util.logging.Logger::getAnonymousLogger()"); + + let resource_bundle_name: ClassInstanceRef = None.into(); + jvm.invoke_static( + "java/util/logging/Logger", + "getAnonymousLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (resource_bundle_name,), + ) + .await + } + + async fn get_anonymous_logger_with_resource_bundle( + jvm: &Jvm, + _: &mut RuntimeContext, + resource_bundle_name: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.Logger::getAnonymousLogger({resource_bundle_name:?})"); + + let name: ClassInstanceRef = None.into(); + let mut logger: ClassInstanceRef = jvm + .new_class( + "java/util/logging/Logger", + "(Ljava/lang/String;Ljava/lang/String;)V", + (name, resource_bundle_name), + ) + .await? + .into(); + let manager: ClassInstanceRef = jvm + .invoke_static("java/util/logging/LogManager", "getLogManager", "()Ljava/util/logging/LogManager;", ()) + .await?; + let root_name = JavaLangString::from_rust_string(jvm, "").await?; + let root: ClassInstanceRef = jvm + .invoke_virtual(&manager, "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", (root_name,)) + .await?; + jvm.put_field(&mut logger, "parent", "Ljava/util/logging/Logger;", root).await?; + Ok(logger) + } + + async fn get_global(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { + tracing::debug!("java.util.logging.Logger::getGlobal()"); + jvm.get_static_field("java/util/logging/Logger", "global", "Ljava/util/logging/Logger;") + .await + } + + async fn get_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getName({this:?})"); + jvm.get_field(&this, "name", "Ljava/lang/String;").await + } + + async fn get_filter(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getFilter({this:?})"); + jvm.get_field(&this, "filter", "Ljava/util/logging/Filter;").await + } + + async fn set_filter(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, filter: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Logger::setFilter({this:?}, {filter:?})"); + jvm.put_field(&mut this, "filter", "Ljava/util/logging/Filter;", filter).await + } + + async fn get_level(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getLevel({this:?})"); + jvm.get_field(&this, "level", "Ljava/util/logging/Level;").await + } + + async fn set_level(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, level: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Logger::setLevel({this:?}, {level:?})"); + jvm.put_field(&mut this, "level", "Ljava/util/logging/Level;", level).await + } + + async fn get_parent(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getParent({this:?})"); + jvm.get_field(&this, "parent", "Ljava/util/logging/Logger;").await + } + + async fn set_parent(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, parent: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Logger::setParent({this:?}, {parent:?})"); + if parent.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "parent").await); + } + jvm.put_field(&mut this, "parent", "Ljava/util/logging/Logger;", parent).await + } + + async fn get_use_parent_handlers(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.Logger::getUseParentHandlers({this:?})"); + jvm.get_field(&this, "useParentHandlers", "Z").await + } + + async fn set_use_parent_handlers(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: bool) -> Result<()> { + tracing::debug!("java.util.logging.Logger::setUseParentHandlers({this:?}, {value})"); + jvm.put_field(&mut this, "useParentHandlers", "Z", value).await + } + + async fn add_handler(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, handler: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Logger::addHandler({this:?}, {handler:?})"); + if handler.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "handler").await); + } + let handlers: ClassInstanceRef = jvm.get_field(&this, "handlers", "Ljava/util/Vector;").await?; + jvm.invoke_virtual(&handlers, "addElement", "(Ljava/lang/Object;)V", (handler,)).await + } + + async fn remove_handler(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, handler: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Logger::removeHandler({this:?}, {handler:?})"); + if handler.is_null() { + return Ok(()); + } + let handlers: ClassInstanceRef = jvm.get_field(&this, "handlers", "Ljava/util/Vector;").await?; + let _: bool = jvm + .invoke_virtual(&handlers, "removeElement", "(Ljava/lang/Object;)Z", (handler,)) + .await?; + Ok(()) + } + + async fn get_handlers(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result>> { + tracing::debug!("java.util.logging.Logger::getHandlers({this:?})"); + let handlers: ClassInstanceRef = jvm.get_field(&this, "handlers", "Ljava/util/Vector;").await?; + let size: i32 = jvm.invoke_virtual(&handlers, "size", "()I", ()).await?; + let mut result: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/logging/Handler;", size as usize).await?.into(); + for index in 0..size { + let handler: ClassInstanceRef = jvm.invoke_virtual(&handlers, "elementAt", "(I)Ljava/lang/Object;", (index,)).await?; + jvm.store_array(&mut result, index as usize, [handler]).await?; + } + Ok(result) + } + + async fn is_loggable(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, level: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.Logger::isLoggable({this:?}, {level:?})"); + if level.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "level").await); + } + + let requested: i32 = jvm.invoke_virtual(&level, "intValue", "()I", ()).await?; + let mut logger = this; + let effective = loop { + let configured: ClassInstanceRef = jvm.get_field(&logger, "level", "Ljava/util/logging/Level;").await?; + if !configured.is_null() { + break jvm.invoke_virtual(&configured, "intValue", "()I", ()).await?; + } + let parent: ClassInstanceRef = jvm.get_field(&logger, "parent", "Ljava/util/logging/Logger;").await?; + if parent.is_null() { + let info: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + break jvm.invoke_virtual(&info, "intValue", "()I", ()).await?; + } + logger = parent; + }; + + Ok(effective != i32::MAX && requested >= effective) + } + + async fn log_record(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, mut record: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.Logger::log({this:?}, {record:?})"); + if record.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "record").await); + } + let level: ClassInstanceRef = jvm.invoke_virtual(&record, "getLevel", "()Ljava/util/logging/Level;", ()).await?; + if !jvm + .invoke_virtual::<_, bool>(&this, "isLoggable", "(Ljava/util/logging/Level;)Z", (level,)) + .await? + { + return Ok(()); + } + + let filter: ClassInstanceRef = jvm.get_field(&this, "filter", "Ljava/util/logging/Filter;").await?; + if !filter.is_null() + && !jvm + .invoke_virtual::<_, bool>(&filter, "isLoggable", "(Ljava/util/logging/LogRecord;)Z", (record.clone(),)) + .await? + { + return Ok(()); + } + + let logger_name: ClassInstanceRef = jvm.invoke_virtual(&record, "getLoggerName", "()Ljava/lang/String;", ()).await?; + if logger_name.is_null() { + let name: ClassInstanceRef = jvm.get_field(&this, "name", "Ljava/lang/String;").await?; + jvm.put_field(&mut record, "loggerName", "Ljava/lang/String;", name).await?; + } + + let mut logger = this; + loop { + let handlers: ClassInstanceRef> = jvm.invoke_virtual(&logger, "getHandlers", "()[Ljava/util/logging/Handler;", ()).await?; + let length = jvm.array_length(&handlers).await?; + let handlers: Vec> = jvm.load_array(&handlers, 0, length).await?; + for handler in handlers { + let _: () = jvm + .invoke_virtual(&handler, "publish", "(Ljava/util/logging/LogRecord;)V", (record.clone(),)) + .await?; + } + + let use_parent: bool = jvm.get_field(&logger, "useParentHandlers", "Z").await?; + if !use_parent { + break; + } + let parent: ClassInstanceRef = jvm.get_field(&logger, "parent", "Ljava/util/logging/Logger;").await?; + if parent.is_null() { + break; + } + logger = parent; + } + Ok(()) + } + + async fn log( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + message: ClassInstanceRef, + ) -> Result<()> { + let parameters: ClassInstanceRef> = None.into(); + let thrown: ClassInstanceRef = None.into(); + let source_class: ClassInstanceRef = None.into(); + let source_method: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn log_with_parameter( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + message: ClassInstanceRef, + parameter: ClassInstanceRef, + ) -> Result<()> { + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut parameters, 0, [parameter]).await?; + let thrown: ClassInstanceRef = None.into(); + let source_class: ClassInstanceRef = None.into(); + let source_method: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn log_with_parameters( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + message: ClassInstanceRef, + parameters: ClassInstanceRef>, + ) -> Result<()> { + let thrown: ClassInstanceRef = None.into(); + let source_class: ClassInstanceRef = None.into(); + let source_method: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn log_with_throwable( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + message: ClassInstanceRef, + thrown: ClassInstanceRef, + ) -> Result<()> { + let parameters: ClassInstanceRef> = None.into(); + let source_class: ClassInstanceRef = None.into(); + let source_method: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn logp( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + message: ClassInstanceRef, + ) -> Result<()> { + let parameters: ClassInstanceRef> = None.into(); + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logp_with_parameter( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + message: ClassInstanceRef, + parameter: ClassInstanceRef, + ) -> Result<()> { + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut parameters, 0, [parameter]).await?; + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logp_with_parameters( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + message: ClassInstanceRef, + parameters: ClassInstanceRef>, + ) -> Result<()> { + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logp_with_throwable( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + message: ClassInstanceRef, + thrown: ClassInstanceRef, + ) -> Result<()> { + let parameters: ClassInstanceRef> = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logrb( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + message: ClassInstanceRef, + ) -> Result<()> { + let parameters: ClassInstanceRef> = None.into(); + let thrown: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logrb_with_parameter( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + message: ClassInstanceRef, + parameter: ClassInstanceRef, + ) -> Result<()> { + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut parameters, 0, [parameter]).await?; + let thrown: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logrb_with_parameters( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + message: ClassInstanceRef, + parameters: ClassInstanceRef>, + ) -> Result<()> { + let thrown: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn logrb_with_throwable( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + level: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + message: ClassInstanceRef, + thrown: ClassInstanceRef, + ) -> Result<()> { + let parameters: ClassInstanceRef> = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn severe(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "SEVERE", message).await + } + + async fn warning(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "WARNING", message).await + } + + async fn info(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "INFO", message).await + } + + async fn config(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "CONFIG", message).await + } + + async fn fine(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "FINE", message).await + } + + async fn finer(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "FINER", message).await + } + + async fn finest(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + Self::log_at(jvm, this, "FINEST", message).await + } + + async fn entering( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + ) -> Result<()> { + let level = jvm + .get_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;") + .await?; + let message: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "ENTRY").await?.into(); + let parameters: ClassInstanceRef> = None.into(); + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn entering_with_parameter( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + parameter: ClassInstanceRef, + ) -> Result<()> { + let level = jvm + .get_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;") + .await?; + let message: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "ENTRY {0}").await?.into(); + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut parameters, 0, [parameter]).await?; + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn entering_with_parameters( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + parameters: ClassInstanceRef>, + ) -> Result<()> { + let level = jvm + .get_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;") + .await?; + let message: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "ENTRY").await?.into(); + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn exiting( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + ) -> Result<()> { + let level = jvm + .get_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;") + .await?; + let message: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "RETURN").await?.into(); + let parameters: ClassInstanceRef> = None.into(); + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn exiting_with_result( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + result: ClassInstanceRef, + ) -> Result<()> { + let level = jvm + .get_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;") + .await?; + let message: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "RETURN {0}").await?.into(); + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + jvm.store_array(&mut parameters, 0, [result]).await?; + let thrown: ClassInstanceRef = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn throwing( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + thrown: ClassInstanceRef, + ) -> Result<()> { + let level = jvm + .get_static_field("java/util/logging/Level", "FINER", "Ljava/util/logging/Level;") + .await?; + let message: ClassInstanceRef = JavaLangString::from_rust_string(jvm, "THROW").await?.into(); + let parameters: ClassInstanceRef> = None.into(); + let resource_bundle_name: ClassInstanceRef = None.into(); + Self::log_values( + jvm, + this, + level, + message, + parameters, + thrown, + source_class, + source_method, + resource_bundle_name, + ) + .await + } + + async fn get_resource_bundle_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getResourceBundleName({this:?})"); + jvm.get_field(&this, "resourceBundleName", "Ljava/lang/String;").await + } + + async fn get_resource_bundle(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + tracing::debug!("java.util.logging.Logger::getResourceBundle({this:?})"); + jvm.get_field(&this, "resourceBundle", "Ljava/util/ResourceBundle;").await + } + + async fn reset(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, root: bool) -> Result>> { + let handlers: ClassInstanceRef = jvm.get_field(&this, "handlers", "Ljava/util/Vector;").await?; + let size: i32 = jvm.invoke_virtual(&handlers, "size", "()I", ()).await?; + let mut removed: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/logging/Handler;", size as usize).await?.into(); + for index in 0..size { + let handler: ClassInstanceRef = jvm.invoke_virtual(&handlers, "elementAt", "(I)Ljava/lang/Object;", (index,)).await?; + jvm.store_array(&mut removed, index as usize, [handler]).await?; + } + let _: () = jvm.invoke_virtual(&handlers, "removeAllElements", "()V", ()).await?; + + let level: ClassInstanceRef = if root { + jvm.get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await? + } else { + None.into() + }; + jvm.put_field(&mut this, "level", "Ljava/util/logging/Level;", level).await?; + Ok(removed) + } + + async fn log_at(jvm: &Jvm, this: ClassInstanceRef, field: &str, message: ClassInstanceRef) -> Result<()> { + let level: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", field, "Ljava/util/logging/Level;") + .await?; + jvm.invoke_virtual(&this, "log", "(Ljava/util/logging/Level;Ljava/lang/String;)V", (level, message)) + .await + } + + #[allow(clippy::too_many_arguments)] + async fn log_values( + jvm: &Jvm, + this: ClassInstanceRef, + level: ClassInstanceRef, + message: ClassInstanceRef, + parameters: ClassInstanceRef>, + thrown: ClassInstanceRef, + source_class: ClassInstanceRef, + source_method: ClassInstanceRef, + resource_bundle_name: ClassInstanceRef, + ) -> Result<()> { + if !jvm + .invoke_virtual::<_, bool>(&this, "isLoggable", "(Ljava/util/logging/Level;)Z", (level.clone(),)) + .await? + { + return Ok(()); + } + + let mut record: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (level, message), + ) + .await? + .into(); + if !parameters.is_null() { + jvm.put_field(&mut record, "parameters", "[Ljava/lang/Object;", parameters).await?; + } + if !thrown.is_null() { + jvm.put_field(&mut record, "thrown", "Ljava/lang/Throwable;", thrown).await?; + } + if !source_class.is_null() { + jvm.put_field(&mut record, "sourceClassName", "Ljava/lang/String;", source_class).await?; + } + if !source_method.is_null() { + jvm.put_field(&mut record, "sourceMethodName", "Ljava/lang/String;", source_method) + .await?; + } + if !resource_bundle_name.is_null() { + jvm.put_field(&mut record, "resourceBundleName", "Ljava/lang/String;", resource_bundle_name) + .await?; + } + jvm.invoke_virtual(&this, "log", "(Ljava/util/logging/LogRecord;)V", (record,)).await + } +} diff --git a/java_runtime/src/classes/java/util/logging/simple_formatter.rs b/java_runtime/src/classes/java/util/logging/simple_formatter.rs new file mode 100644 index 00000000..19fdf3a0 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/simple_formatter.rs @@ -0,0 +1,115 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{PrintWriter, StringWriter}, + lang::{String, Throwable}, + }, +}; + +use super::{Level, LogRecord}; + +// public class java.util.logging.SimpleFormatter +pub struct SimpleFormatter; + +impl SimpleFormatter { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/SimpleFormatter", + parent_class: Some("java/util/logging/Formatter"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "format", + "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", + Self::format, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.SimpleFormatter::({this:?})"); + + jvm.invoke_special(&this, "java/util/logging/Formatter", "", "()V", ()).await + } + + async fn format( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + record: ClassInstanceRef, + ) -> Result> { + tracing::debug!("java.util.logging.SimpleFormatter::format({this:?}, {record:?})"); + + if record.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "record").await); + } + + let source_class: ClassInstanceRef = jvm.invoke_virtual(&record, "getSourceClassName", "()Ljava/lang/String;", ()).await?; + let source_method: ClassInstanceRef = jvm.invoke_virtual(&record, "getSourceMethodName", "()Ljava/lang/String;", ()).await?; + let logger_name: ClassInstanceRef = jvm.invoke_virtual(&record, "getLoggerName", "()Ljava/lang/String;", ()).await?; + let mut source = if source_class.is_null() { + if logger_name.is_null() { + RustString::new() + } else { + JavaLangString::to_rust_string(jvm, &logger_name).await? + } + } else { + JavaLangString::to_rust_string(jvm, &source_class).await? + }; + if !source_method.is_null() { + if !source.is_empty() { + source.push(' '); + } + source.push_str(&JavaLangString::to_rust_string(jvm, &source_method).await?); + } + + let level: ClassInstanceRef = jvm.invoke_virtual(&record, "getLevel", "()Ljava/util/logging/Level;", ()).await?; + let level_name: ClassInstanceRef = jvm.invoke_virtual(&level, "getName", "()Ljava/lang/String;", ()).await?; + let level_name = JavaLangString::to_rust_string(jvm, &level_name).await?; + let message: ClassInstanceRef = jvm + .invoke_virtual( + &this, + "formatMessage", + "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", + (record.clone(),), + ) + .await?; + let message = if message.is_null() { + RustString::from("null") + } else { + JavaLangString::to_rust_string(jvm, &message).await? + }; + + let mut formatted = if source.is_empty() { + format!("{level_name}: {message}\n") + } else { + format!("{source} {level_name}: {message}\n") + }; + let thrown: ClassInstanceRef = jvm.invoke_virtual(&record, "getThrown", "()Ljava/lang/Throwable;", ()).await?; + if !thrown.is_null() { + let string_writer: ClassInstanceRef = jvm.new_class("java/io/StringWriter", "()V", ()).await?.into(); + let print_writer: ClassInstanceRef = jvm + .new_class("java/io/PrintWriter", "(Ljava/io/Writer;)V", (string_writer.clone(),)) + .await? + .into(); + let _: () = jvm + .invoke_virtual(&thrown, "printStackTrace", "(Ljava/io/PrintWriter;)V", (print_writer,)) + .await?; + let trace: ClassInstanceRef = jvm.invoke_virtual(&string_writer, "toString", "()Ljava/lang/String;", ()).await?; + formatted.push_str(&JavaLangString::to_rust_string(jvm, &trace).await?); + } + + Ok(JavaLangString::from_rust_string(jvm, &formatted).await?.into()) + } +} diff --git a/java_runtime/src/classes/java/util/logging/stream_handler.rs b/java_runtime/src/classes/java/util/logging/stream_handler.rs new file mode 100644 index 00000000..91f4c314 --- /dev/null +++ b/java_runtime/src/classes/java/util/logging/stream_handler.rs @@ -0,0 +1,341 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, JavaError, Jvm, Result}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{OutputStream, OutputStreamWriter}, + lang::{Exception, String}, + }, +}; + +use super::{Formatter, Level, LogRecord, SimpleFormatter}; + +// public class java.util.logging.StreamHandler +pub struct StreamHandler; + +impl StreamHandler { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/logging/StreamHandler", + parent_class: Some("java/util/logging/Handler"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(Ljava/io/OutputStream;Ljava/util/logging/Formatter;)V", + Self::init_with_output, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("close", "()V", Self::close, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new("flush", "()V", Self::flush, MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED), + JavaMethodProto::new( + "isLoggable", + "(Ljava/util/logging/LogRecord;)Z", + Self::is_loggable, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "publish", + "(Ljava/util/logging/LogRecord;)V", + Self::publish, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "setEncoding", + "(Ljava/lang/String;)V", + Self::set_encoding, + MethodAccessFlags::PUBLIC | MethodAccessFlags::SYNCHRONIZED, + ), + JavaMethodProto::new( + "setOutputStream", + "(Ljava/io/OutputStream;)V", + Self::set_output_stream, + MethodAccessFlags::PROTECTED | MethodAccessFlags::SYNCHRONIZED, + ), + ], + fields: vec![ + JavaFieldProto::new("output", "Ljava/io/OutputStream;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("writer", "Ljava/io/OutputStreamWriter;", FieldAccessFlags::PRIVATE), + JavaFieldProto::new("headerWritten", "Z", FieldAccessFlags::PRIVATE), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/util/logging/Handler", "", "()V", ()).await?; + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let level: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + let _: () = jvm + .invoke_special( + &this, + "java/util/logging/Handler", + "setFormatter", + "(Ljava/util/logging/Formatter;)V", + (formatter,), + ) + .await?; + let _: () = jvm + .invoke_special(&this, "java/util/logging/Handler", "setLevel", "(Ljava/util/logging/Level;)V", (level,)) + .await?; + jvm.put_field(&mut this, "headerWritten", "Z", false).await + } + + async fn init_with_output( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + output: ClassInstanceRef, + formatter: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::({this:?}, {output:?}, {formatter:?})"); + + if output.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output").await); + } + if formatter.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "formatter").await); + } + + let _: () = jvm.invoke_special(&this, "java/util/logging/StreamHandler", "", "()V", ()).await?; + let _: () = jvm + .invoke_special( + &this, + "java/util/logging/Handler", + "setFormatter", + "(Ljava/util/logging/Formatter;)V", + (formatter,), + ) + .await?; + jvm.invoke_special( + &this, + "java/util/logging/StreamHandler", + "setOutputStream", + "(Ljava/io/OutputStream;)V", + (output,), + ) + .await + } + + async fn close(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::close({this:?})"); + + let writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/OutputStreamWriter;").await?; + if writer.is_null() { + return Ok(()); + } + + Self::write_tail(jvm, &this).await?; + let _: () = jvm.invoke_virtual(&writer, "flush", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&writer, "close", "()V", ()).await?; + let output: ClassInstanceRef = None.into(); + let writer: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "output", "Ljava/io/OutputStream;", output).await?; + jvm.put_field(&mut this, "writer", "Ljava/io/OutputStreamWriter;", writer).await?; + jvm.put_field(&mut this, "headerWritten", "Z", false).await + } + + async fn flush(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::flush({this:?})"); + + let writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/OutputStreamWriter;").await?; + if writer.is_null() { + return Ok(()); + } + jvm.invoke_virtual(&writer, "flush", "()V", ()).await + } + + async fn is_loggable(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, record: ClassInstanceRef) -> Result { + tracing::debug!("java.util.logging.StreamHandler::isLoggable({this:?}, {record:?})"); + + let output: ClassInstanceRef = jvm.get_field(&this, "output", "Ljava/io/OutputStream;").await?; + if output.is_null() || record.is_null() { + return Ok(false); + } + + jvm.invoke_special( + &this, + "java/util/logging/Handler", + "isLoggable", + "(Ljava/util/logging/LogRecord;)Z", + (record,), + ) + .await + } + + async fn publish(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, record: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::publish({this:?}, {record:?})"); + + let loggable: bool = jvm + .invoke_virtual(&this, "isLoggable", "(Ljava/util/logging/LogRecord;)Z", (record.clone(),)) + .await?; + if !loggable { + return Ok(()); + } + + if let Err(JavaError::JavaException(exception)) = Self::write_head(jvm, &this).await { + if !jvm.is_instance(&*exception, "java/lang/Exception") { + return Err(JavaError::JavaException(exception)); + } + let message: ClassInstanceRef = None.into(); + let exception: ClassInstanceRef = exception.into(); + let _: () = jvm + .invoke_virtual( + &this, + "reportError", + "(Ljava/lang/String;Ljava/lang/Exception;I)V", + (message, exception, 1), + ) + .await?; + return Ok(()); + } + let formatter: ClassInstanceRef = jvm.get_field(&this, "formatter", "Ljava/util/logging/Formatter;").await?; + let formatted: ClassInstanceRef = match jvm + .invoke_virtual(&formatter, "format", "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", (record,)) + .await + { + Ok(formatted) => formatted, + Err(JavaError::JavaException(exception)) => { + if !jvm.is_instance(&*exception, "java/lang/Exception") { + return Err(JavaError::JavaException(exception)); + } + let message: ClassInstanceRef = None.into(); + let exception: ClassInstanceRef = exception.into(); + let _: () = jvm + .invoke_virtual( + &this, + "reportError", + "(Ljava/lang/String;Ljava/lang/Exception;I)V", + (message, exception, 5), + ) + .await?; + return Ok(()); + } + }; + let writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/OutputStreamWriter;").await?; + if let Err(JavaError::JavaException(exception)) = jvm.invoke_virtual::<_, ()>(&writer, "write", "(Ljava/lang/String;)V", (formatted,)).await { + if !jvm.is_instance(&*exception, "java/lang/Exception") { + return Err(JavaError::JavaException(exception)); + } + let message: ClassInstanceRef = None.into(); + let exception: ClassInstanceRef = exception.into(); + let _: () = jvm + .invoke_virtual( + &this, + "reportError", + "(Ljava/lang/String;Ljava/lang/Exception;I)V", + (message, exception, 1), + ) + .await?; + } + Ok(()) + } + + async fn set_encoding(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, encoding: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::setEncoding({this:?}, {encoding:?})"); + + let _: () = jvm + .invoke_special( + &this, + "java/util/logging/Handler", + "setEncoding", + "(Ljava/lang/String;)V", + (encoding.clone(),), + ) + .await?; + + let output: ClassInstanceRef = jvm.get_field(&this, "output", "Ljava/io/OutputStream;").await?; + if output.is_null() { + return Ok(()); + } + + let writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/OutputStreamWriter;").await?; + let _: () = jvm.invoke_virtual(&writer, "flush", "()V", ()).await?; + let writer: ClassInstanceRef = if encoding.is_null() { + jvm.new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (output,)) + .await? + .into() + } else { + jvm.new_class( + "java/io/OutputStreamWriter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (output, encoding), + ) + .await? + .into() + }; + jvm.put_field(&mut this, "writer", "Ljava/io/OutputStreamWriter;", writer).await + } + + async fn set_output_stream( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + output: ClassInstanceRef, + ) -> Result<()> { + tracing::debug!("java.util.logging.StreamHandler::setOutputStream({this:?}, {output:?})"); + + if output.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "output").await); + } + + let current_writer: ClassInstanceRef = jvm.get_field(&this, "writer", "Ljava/io/OutputStreamWriter;").await?; + if !current_writer.is_null() { + Self::write_tail(jvm, &this).await?; + let _: () = jvm.invoke_virtual(¤t_writer, "flush", "()V", ()).await?; + let _: () = jvm.invoke_virtual(¤t_writer, "close", "()V", ()).await?; + } + + let encoding: ClassInstanceRef = jvm.get_field(&this, "encoding", "Ljava/lang/String;").await?; + let writer: ClassInstanceRef = if encoding.is_null() { + jvm.new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (output.clone(),)) + .await? + .into() + } else { + jvm.new_class( + "java/io/OutputStreamWriter", + "(Ljava/io/OutputStream;Ljava/lang/String;)V", + (output.clone(), encoding), + ) + .await? + .into() + }; + jvm.put_field(&mut this, "output", "Ljava/io/OutputStream;", output).await?; + jvm.put_field(&mut this, "writer", "Ljava/io/OutputStreamWriter;", writer).await?; + jvm.put_field(&mut this, "headerWritten", "Z", false).await + } + + async fn write_head(jvm: &Jvm, this: &ClassInstanceRef) -> Result<()> { + if jvm.get_field::(this, "headerWritten", "Z").await? { + return Ok(()); + } + + let formatter: ClassInstanceRef = jvm.get_field(this, "formatter", "Ljava/util/logging/Formatter;").await?; + let head: ClassInstanceRef = jvm + .invoke_virtual(&formatter, "getHead", "(Ljava/util/logging/Handler;)Ljava/lang/String;", (this.clone(),)) + .await?; + let writer: ClassInstanceRef = jvm.get_field(this, "writer", "Ljava/io/OutputStreamWriter;").await?; + let _: () = jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (head,)).await?; + let mut this = this.clone(); + jvm.put_field(&mut this, "headerWritten", "Z", true).await + } + + async fn write_tail(jvm: &Jvm, this: &ClassInstanceRef) -> Result<()> { + Self::write_head(jvm, this).await?; + let formatter: ClassInstanceRef = jvm.get_field(this, "formatter", "Ljava/util/logging/Formatter;").await?; + let tail: ClassInstanceRef = jvm + .invoke_virtual(&formatter, "getTail", "(Ljava/util/logging/Handler;)Ljava/lang/String;", (this.clone(),)) + .await?; + let writer: ClassInstanceRef = jvm.get_field(this, "writer", "Ljava/io/OutputStreamWriter;").await?; + jvm.invoke_virtual(&writer, "write", "(Ljava/lang/String;)V", (tail,)).await + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index dbc78f5b..1e6c312f 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -212,6 +212,16 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::LinkedListEntry::as_proto(), crate::classes::java::util::LinkedListItr::as_proto(), crate::classes::java::util::Locale::as_proto(), + crate::classes::java::util::logging::ConsoleHandler::as_proto(), + crate::classes::java::util::logging::Filter::as_proto(), + crate::classes::java::util::logging::Formatter::as_proto(), + crate::classes::java::util::logging::Handler::as_proto(), + crate::classes::java::util::logging::Level::as_proto(), + crate::classes::java::util::logging::LogManager::as_proto(), + crate::classes::java::util::logging::LogRecord::as_proto(), + crate::classes::java::util::logging::Logger::as_proto(), + crate::classes::java::util::logging::SimpleFormatter::as_proto(), + crate::classes::java::util::logging::StreamHandler::as_proto(), crate::classes::java::util::Map::as_proto(), crate::classes::java::util::MapEntry::as_proto(), crate::classes::java::util::MissingFormatArgumentException::as_proto(), diff --git a/java_runtime/tests/classes/java/util/logging/mod.rs b/java_runtime/tests/classes/java/util/logging/mod.rs new file mode 100644 index 00000000..bf02d898 --- /dev/null +++ b/java_runtime/tests/classes/java/util/logging/mod.rs @@ -0,0 +1,4 @@ +mod test_handlers; +mod test_level; +mod test_log_record; +mod test_logger; diff --git a/java_runtime/tests/classes/java/util/logging/test_handlers.rs b/java_runtime/tests/classes/java/util/logging/test_handlers.rs new file mode 100644 index 00000000..977c4b93 --- /dev/null +++ b/java_runtime/tests/classes/java/util/logging/test_handlers.rs @@ -0,0 +1,265 @@ +use alloc::{boxed::Box, collections::btree_map::BTreeMap, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + RuntimeClassProto, RuntimeContext, + classes::java::{ + io::{ByteArrayOutputStream, OutputStream}, + lang::{String, Throwable}, + util::logging::{Filter, Formatter, Level, LogRecord, SimpleFormatter, StreamHandler}, + }, +}; +use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; +use test_utils::{TestRuntime, create_test_jvm}; + +struct ConfigurableFilter; + +impl ConfigurableFilter { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "ConfigurableLoggingFilter", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/logging/Filter"], + methods: vec![ + JavaMethodProto::new("", "(Z)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isLoggable", + "(Ljava/util/logging/LogRecord;)Z", + Self::is_loggable, + MethodAccessFlags::PUBLIC, + ), + ], + fields: vec![JavaFieldProto::new("allowed", "Z", FieldAccessFlags::PRIVATE)], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, allowed: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "allowed", "Z", allowed).await + } + + async fn is_loggable(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + jvm.get_field(&this, "allowed", "Z").await + } +} + +struct FailingOutputStream; + +impl FailingOutputStream { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "FailingLoggingOutputStream", + parent_class: Some("java/io/OutputStream"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("write", "(I)V", Self::write, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await + } + + async fn write(jvm: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: i32) -> Result<()> { + Err(jvm.exception("java/io/IOException", "write failed").await) + } +} + +async fn logging_jvm() -> Result { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + ConfigurableFilter::as_proto(), + Box::new(runtime.clone()) as Box<_>, + )), + None, + ) + .await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + FailingOutputStream::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + Ok(jvm) +} + +#[tokio::test] +async fn formatter_substitutes_message_parameters() -> Result<()> { + let jvm = logging_jvm().await?; + let info: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + let message = JavaLangString::from_rust_string(&jvm, "hello {0} from {10}").await?; + let record: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (info, message), + ) + .await? + .into(); + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 11).await?.into(); + jvm.store_array(&mut parameters, 0, [JavaLangString::from_rust_string(&jvm, "world").await?]) + .await?; + jvm.store_array(&mut parameters, 10, [JavaLangString::from_rust_string(&jvm, "parameter ten").await?]) + .await?; + let _: () = jvm + .invoke_virtual(&record, "setParameters", "([Ljava/lang/Object;)V", (parameters,)) + .await?; + + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let formatted: ClassInstanceRef = jvm + .invoke_virtual( + &formatter, + "formatMessage", + "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", + (record,), + ) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &formatted).await?, "hello world from parameter ten"); + Ok(()) +} + +#[tokio::test] +async fn simple_formatter_includes_throwable_stack_trace() -> Result<()> { + let jvm = logging_jvm().await?; + let info: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + let record: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (info, JavaLangString::from_rust_string(&jvm, "failed").await?), + ) + .await? + .into(); + let mut thrown: ClassInstanceRef = jvm + .new_class( + "java/lang/RuntimeException", + "(Ljava/lang/String;)V", + (JavaLangString::from_rust_string(&jvm, "boom").await?,), + ) + .await? + .into(); + let mut stack_trace: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/String;", 1).await?.into(); + jvm.store_array( + &mut stack_trace, + 0, + [JavaLangString::from_rust_string(&jvm, "example.Test.run(Test.java:7)").await?], + ) + .await?; + jvm.put_field(&mut thrown, "stackTrace", "[Ljava/lang/String;", stack_trace).await?; + let _: () = jvm.invoke_virtual(&record, "setThrown", "(Ljava/lang/Throwable;)V", (thrown,)).await?; + + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let formatted: ClassInstanceRef = jvm + .invoke_virtual(&formatter, "format", "(Ljava/util/logging/LogRecord;)Ljava/lang/String;", (record,)) + .await?; + let formatted = JavaLangString::to_rust_string(&jvm, &formatted).await?; + assert!(formatted.contains("java.lang.RuntimeException: boom")); + assert!(formatted.contains("\tat example.Test.run(Test.java:7)")); + Ok(()) +} + +#[tokio::test] +async fn stream_handler_reports_output_failures_without_propagating_them() -> Result<()> { + let jvm = logging_jvm().await?; + let output: ClassInstanceRef = jvm.new_class("FailingLoggingOutputStream", "()V", ()).await?.into(); + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let handler: ClassInstanceRef = jvm + .new_class( + "java/util/logging/StreamHandler", + "(Ljava/io/OutputStream;Ljava/util/logging/Formatter;)V", + (output, formatter), + ) + .await? + .into(); + let info: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + let record: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (info, JavaLangString::from_rust_string(&jvm, "message").await?), + ) + .await? + .into(); + + let _: () = jvm + .invoke_virtual(&handler, "publish", "(Ljava/util/logging/LogRecord;)V", (record,)) + .await?; + Ok(()) +} + +#[tokio::test] +async fn stream_handler_applies_level_and_custom_filter_before_writing() -> Result<()> { + let jvm = logging_jvm().await?; + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let handler: ClassInstanceRef = jvm + .new_class( + "java/util/logging/StreamHandler", + "(Ljava/io/OutputStream;Ljava/util/logging/Formatter;)V", + (ClassInstanceRef::::new(output.instance.clone()), formatter), + ) + .await? + .into(); + let warning: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "WARNING", "Ljava/util/logging/Level;") + .await?; + let _: () = jvm + .invoke_virtual(&handler, "setLevel", "(Ljava/util/logging/Level;)V", (warning.clone(),)) + .await?; + + let denied: ClassInstanceRef = jvm.new_class("ConfigurableLoggingFilter", "(Z)V", (false,)).await?.into(); + let _: () = jvm + .invoke_virtual(&handler, "setFilter", "(Ljava/util/logging/Filter;)V", (denied,)) + .await?; + let denied_record: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (warning.clone(), JavaLangString::from_rust_string(&jvm, "denied").await?), + ) + .await? + .into(); + let _: () = jvm + .invoke_virtual(&handler, "publish", "(Ljava/util/logging/LogRecord;)V", (denied_record,)) + .await?; + + let allowed: ClassInstanceRef = jvm.new_class("ConfigurableLoggingFilter", "(Z)V", (true,)).await?.into(); + let _: () = jvm + .invoke_virtual(&handler, "setFilter", "(Ljava/util/logging/Filter;)V", (allowed,)) + .await?; + let allowed_record: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (warning, JavaLangString::from_rust_string(&jvm, "allowed").await?), + ) + .await? + .into(); + let _: () = jvm + .invoke_virtual(&handler, "publish", "(Ljava/util/logging/LogRecord;)V", (allowed_record,)) + .await?; + let _: () = jvm.invoke_virtual(&handler, "flush", "()V", ()).await?; + + let text: ClassInstanceRef = jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + let text = JavaLangString::to_rust_string(&jvm, &text).await?; + assert!(!text.contains("denied")); + assert!(text.contains("allowed")); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/logging/test_level.rs b/java_runtime/tests/classes/java/util/logging/test_level.rs new file mode 100644 index 00000000..1c21e1a5 --- /dev/null +++ b/java_runtime/tests/classes/java/util/logging/test_level.rs @@ -0,0 +1,139 @@ +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use java_runtime::{ + classes::java::{lang::String, util::logging::Level}, + get_runtime_class_proto, +}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use test_utils::test_jvm; + +#[test] +fn logging_core_public_types_are_registered() { + for (name, parent, flags) in [ + ( + "java/util/logging/Filter", + None, + ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, + ), + ( + "java/util/logging/Formatter", + Some("java/lang/Object"), + ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + ), + ( + "java/util/logging/Handler", + Some("java/lang/Object"), + ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + ), + ( + "java/util/logging/StreamHandler", + Some("java/util/logging/Handler"), + ClassAccessFlags::PUBLIC, + ), + ( + "java/util/logging/ConsoleHandler", + Some("java/util/logging/StreamHandler"), + ClassAccessFlags::PUBLIC, + ), + ( + "java/util/logging/SimpleFormatter", + Some("java/util/logging/Formatter"), + ClassAccessFlags::PUBLIC, + ), + ("java/util/logging/Level", Some("java/lang/Object"), ClassAccessFlags::PUBLIC), + ("java/util/logging/LogRecord", Some("java/lang/Object"), ClassAccessFlags::PUBLIC), + ("java/util/logging/Logger", Some("java/lang/Object"), ClassAccessFlags::PUBLIC), + ("java/util/logging/LogManager", Some("java/lang/Object"), ClassAccessFlags::PUBLIC), + ] { + let proto = get_runtime_class_proto(name).unwrap_or_else(|| panic!("{name} must be registered")); + assert_eq!(proto.parent_class, parent); + assert_eq!(proto.access_flags, flags); + } + + let logger = get_runtime_class_proto("java/util/logging/Logger").unwrap(); + for (name, descriptor) in [ + ("getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;"), + ("log", "(Ljava/util/logging/Level;Ljava/lang/String;)V"), + ( + "logp", + "(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + ), + ("info", "(Ljava/lang/String;)V"), + ("warning", "(Ljava/lang/String;)V"), + ("severe", "(Ljava/lang/String;)V"), + ] { + let method = logger + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing Logger.{name}{descriptor}")); + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + } + + let global = logger.fields.iter().find(|field| field.name == "global").unwrap(); + assert_eq!( + global.access_flags, + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL + ); +} + +#[tokio::test] +async fn standard_levels_have_java_values_and_parse_names_or_numbers() -> Result<()> { + let jvm = test_jvm().await?; + for (name, value) in [ + ("OFF", i32::MAX), + ("SEVERE", 1000), + ("WARNING", 900), + ("INFO", 800), + ("CONFIG", 700), + ("FINE", 500), + ("FINER", 400), + ("FINEST", 300), + ("ALL", i32::MIN), + ] { + let level: ClassInstanceRef = jvm.get_static_field("java/util/logging/Level", name, "Ljava/util/logging/Level;").await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&level, "intValue", "()I", ()).await?, value); + let parsed: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Level", + "parse", + "(Ljava/lang/String;)Ljava/util/logging/Level;", + (JavaLangString::from_rust_string(&jvm, name).await?,), + ) + .await?; + assert_eq!(parsed.identity(), level.identity()); + } + + let custom_name = JavaLangString::from_rust_string(&jvm, "42").await?; + let custom: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Level", + "parse", + "(Ljava/lang/String;)Ljava/util/logging/Level;", + (custom_name,), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&custom, "intValue", "()I", ()).await?, 42); + let name: ClassInstanceRef = jvm.invoke_virtual(&custom, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "42"); + + Ok(()) +} + +#[tokio::test] +async fn level_rejects_unknown_names() -> Result<()> { + let jvm = test_jvm().await?; + let name = JavaLangString::from_rust_string(&jvm, "TRACE").await?; + let result: Result> = jvm + .invoke_static( + "java/util/logging/Level", + "parse", + "(Ljava/lang/String;)Ljava/util/logging/Level;", + (name,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("unknown level must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/logging/test_log_record.rs b/java_runtime/tests/classes/java/util/logging/test_log_record.rs new file mode 100644 index 00000000..226e605f --- /dev/null +++ b/java_runtime/tests/classes/java/util/logging/test_log_record.rs @@ -0,0 +1,85 @@ +use java_runtime::classes::java::{ + lang::{Object, String}, + util::logging::{Level, LogRecord}, +}; +use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use test_utils::test_jvm; + +#[tokio::test] +async fn log_record_initializes_event_metadata_and_round_trips_properties() -> Result<()> { + let jvm = test_jvm().await?; + let info: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "INFO", "Ljava/util/logging/Level;") + .await?; + let message = JavaLangString::from_rust_string(&jvm, "message").await?; + let first: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (info.clone(), message), + ) + .await? + .into(); + let second: ClassInstanceRef = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (info, JavaLangString::from_rust_string(&jvm, "next").await?), + ) + .await? + .into(); + + let first_sequence: i64 = jvm.invoke_virtual(&first, "getSequenceNumber", "()J", ()).await?; + let second_sequence: i64 = jvm.invoke_virtual(&second, "getSequenceNumber", "()J", ()).await?; + assert_eq!(second_sequence, first_sequence + 1); + assert!(jvm.invoke_virtual::<_, i64>(&first, "getMillis", "()J", ()).await? > 0); + + let logger_name = JavaLangString::from_rust_string(&jvm, "app").await?; + let source_class = JavaLangString::from_rust_string(&jvm, "App").await?; + let source_method = JavaLangString::from_rust_string(&jvm, "run").await?; + let mut parameters: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + let parameter = JavaLangString::from_rust_string(&jvm, "value").await?; + jvm.store_array(&mut parameters, 0, [parameter]).await?; + let _: () = jvm + .invoke_virtual(&first, "setLoggerName", "(Ljava/lang/String;)V", (logger_name,)) + .await?; + let _: () = jvm + .invoke_virtual(&first, "setSourceClassName", "(Ljava/lang/String;)V", (source_class,)) + .await?; + let _: () = jvm + .invoke_virtual(&first, "setSourceMethodName", "(Ljava/lang/String;)V", (source_method,)) + .await?; + let _: () = jvm + .invoke_virtual(&first, "setParameters", "([Ljava/lang/Object;)V", (parameters,)) + .await?; + + let actual: ClassInstanceRef = jvm.invoke_virtual(&first, "getLoggerName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "app"); + let actual: ClassInstanceRef = jvm.invoke_virtual(&first, "getSourceClassName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "App"); + let actual: ClassInstanceRef = jvm.invoke_virtual(&first, "getSourceMethodName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "run"); + let actual: ClassInstanceRef> = jvm.invoke_virtual(&first, "getParameters", "()[Ljava/lang/Object;", ()).await?; + assert_eq!(jvm.array_length(&actual).await?, 1); + + Ok(()) +} + +#[tokio::test] +async fn log_record_rejects_null_level() -> Result<()> { + let jvm = test_jvm().await?; + let level: ClassInstanceRef = None.into(); + let message: ClassInstanceRef = None.into(); + let result: Result> = jvm + .new_class( + "java/util/logging/LogRecord", + "(Ljava/util/logging/Level;Ljava/lang/String;)V", + (level, message), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null level must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/logging/test_logger.rs b/java_runtime/tests/classes/java/util/logging/test_logger.rs new file mode 100644 index 00000000..0ea7f7f2 --- /dev/null +++ b/java_runtime/tests/classes/java/util/logging/test_logger.rs @@ -0,0 +1,296 @@ +use java_runtime::classes::java::{ + io::{ByteArrayOutputStream, OutputStream}, + lang::String, + util::logging::{Formatter, Handler, Level, LogManager, Logger}, +}; +use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use test_utils::test_jvm; + +#[tokio::test] +async fn named_loggers_are_reused_and_reparented_when_ancestor_is_added_late() -> Result<()> { + let jvm = test_jvm().await?; + let child_name = JavaLangString::from_rust_string(&jvm, "app.service.worker").await?; + let child: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (child_name.clone(),), + ) + .await?; + let same: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (child_name,), + ) + .await?; + assert_eq!(child.identity(), same.identity()); + + let parent_name = JavaLangString::from_rust_string(&jvm, "app.service").await?; + let parent: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (parent_name,), + ) + .await?; + let actual: ClassInstanceRef = jvm.invoke_virtual(&child, "getParent", "()Ljava/util/logging/Logger;", ()).await?; + assert_eq!(actual.identity(), parent.identity()); + Ok(()) +} + +#[tokio::test] +async fn existing_logger_preserves_and_validates_resource_bundle_name() -> Result<()> { + let jvm = test_jvm().await?; + let name = JavaLangString::from_rust_string(&jvm, "bundled.logger").await?; + let logger: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (name.clone(),), + ) + .await?; + let bundle = JavaLangString::from_rust_string(&jvm, "messages").await?; + let bundled: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/util/logging/Logger;", + (name.clone(), bundle.clone()), + ) + .await?; + assert_eq!(logger.identity(), bundled.identity()); + let actual: ClassInstanceRef = jvm.invoke_virtual(&logger, "getResourceBundleName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &actual).await?, "messages"); + + let same: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/util/logging/Logger;", + (name.clone(), bundle), + ) + .await?; + assert_eq!(logger.identity(), same.identity()); + + let result: Result> = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/util/logging/Logger;", + (name, JavaLangString::from_rust_string(&jvm, "other").await?), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("a different resource bundle name must throw IllegalArgumentException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + Ok(()) +} + +#[tokio::test] +async fn logger_inherits_levels_and_publishes_convenience_methods() -> Result<()> { + let jvm = test_jvm().await?; + let logger: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (JavaLangString::from_rust_string(&jvm, "output.test").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&logger, "setUseParentHandlers", "(Z)V", (false,)).await?; + + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let handler: ClassInstanceRef = jvm + .new_class( + "java/util/logging/StreamHandler", + "(Ljava/io/OutputStream;Ljava/util/logging/Formatter;)V", + (ClassInstanceRef::::new(output.instance.clone()), formatter), + ) + .await? + .into(); + let _: () = jvm + .invoke_virtual(&logger, "addHandler", "(Ljava/util/logging/Handler;)V", (handler.clone(),)) + .await?; + + let warning: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "WARNING", "Ljava/util/logging/Level;") + .await?; + let _: () = jvm + .invoke_virtual(&logger, "setLevel", "(Ljava/util/logging/Level;)V", (warning,)) + .await?; + let _: () = jvm + .invoke_virtual( + &logger, + "info", + "(Ljava/lang/String;)V", + (JavaLangString::from_rust_string(&jvm, "hidden").await?,), + ) + .await?; + let _: () = jvm + .invoke_virtual( + &logger, + "warning", + "(Ljava/lang/String;)V", + (JavaLangString::from_rust_string(&jvm, "visible").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&handler, "flush", "()V", ()).await?; + + let text: ClassInstanceRef = jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + let text = JavaLangString::to_rust_string(&jvm, &text).await?; + assert!(!text.contains("hidden")); + assert!(text.contains("visible")); + Ok(()) +} + +#[tokio::test] +async fn logger_publishes_to_parent_handlers_until_propagation_is_disabled() -> Result<()> { + let jvm = test_jvm().await?; + let parent: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (JavaLangString::from_rust_string(&jvm, "propagation").await?,), + ) + .await?; + let child: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (JavaLangString::from_rust_string(&jvm, "propagation.child").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&parent, "setUseParentHandlers", "(Z)V", (false,)).await?; + + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let handler: ClassInstanceRef = jvm + .new_class( + "java/util/logging/StreamHandler", + "(Ljava/io/OutputStream;Ljava/util/logging/Formatter;)V", + (ClassInstanceRef::::new(output.instance.clone()), formatter), + ) + .await? + .into(); + let _: () = jvm + .invoke_virtual(&parent, "addHandler", "(Ljava/util/logging/Handler;)V", (handler.clone(),)) + .await?; + + let _: () = jvm + .invoke_virtual( + &child, + "info", + "(Ljava/lang/String;)V", + (JavaLangString::from_rust_string(&jvm, "from child").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&handler, "flush", "()V", ()).await?; + let text: ClassInstanceRef = jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert!(JavaLangString::to_rust_string(&jvm, &text).await?.contains("from child")); + + let _: () = jvm.invoke_virtual(&child, "setUseParentHandlers", "(Z)V", (false,)).await?; + let _: () = jvm + .invoke_virtual( + &child, + "info", + "(Ljava/lang/String;)V", + (JavaLangString::from_rust_string(&jvm, "not propagated").await?,), + ) + .await?; + let _: () = jvm.invoke_virtual(&handler, "flush", "()V", ()).await?; + let text: ClassInstanceRef = jvm.invoke_virtual(&output, "toString", "()Ljava/lang/String;", ()).await?; + assert!(!JavaLangString::to_rust_string(&jvm, &text).await?.contains("not propagated")); + Ok(()) +} + +#[tokio::test] +async fn log_manager_reset_removes_handlers_and_restores_levels() -> Result<()> { + let jvm = test_jvm().await?; + let manager: ClassInstanceRef = jvm + .invoke_static("java/util/logging/LogManager", "getLogManager", "()Ljava/util/logging/LogManager;", ()) + .await?; + let logger: ClassInstanceRef = jvm + .invoke_static( + "java/util/logging/Logger", + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (JavaLangString::from_rust_string(&jvm, "reset.test").await?,), + ) + .await?; + let output: ClassInstanceRef = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?.into(); + let formatter: ClassInstanceRef = jvm.new_class("java/util/logging/SimpleFormatter", "()V", ()).await?.into(); + let handler: ClassInstanceRef = jvm + .new_class( + "java/util/logging/StreamHandler", + "(Ljava/io/OutputStream;Ljava/util/logging/Formatter;)V", + (ClassInstanceRef::::new(output.instance), formatter), + ) + .await? + .into(); + let severe: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Level", "SEVERE", "Ljava/util/logging/Level;") + .await?; + let _: () = jvm + .invoke_virtual(&logger, "addHandler", "(Ljava/util/logging/Handler;)V", (handler,)) + .await?; + let _: () = jvm.invoke_virtual(&logger, "setLevel", "(Ljava/util/logging/Level;)V", (severe,)).await?; + + let _: () = jvm.invoke_virtual(&manager, "reset", "()V", ()).await?; + + let handlers: ClassInstanceRef> = jvm.invoke_virtual(&logger, "getHandlers", "()[Ljava/util/logging/Handler;", ()).await?; + assert_eq!(jvm.array_length(&handlers).await?, 0); + let level: ClassInstanceRef = jvm.invoke_virtual(&logger, "getLevel", "()Ljava/util/logging/Level;", ()).await?; + assert!(level.is_null()); + + let root: ClassInstanceRef = jvm + .invoke_virtual( + &manager, + "getLogger", + "(Ljava/lang/String;)Ljava/util/logging/Logger;", + (JavaLangString::from_rust_string(&jvm, "").await?,), + ) + .await?; + let root_level: ClassInstanceRef = jvm.invoke_virtual(&root, "getLevel", "()Ljava/util/logging/Level;", ()).await?; + let value: i32 = jvm.invoke_virtual(&root_level, "intValue", "()I", ()).await?; + assert_eq!(value, 800); + Ok(()) +} + +#[tokio::test] +async fn global_logger_and_manager_initialize_in_either_entry_order() -> Result<()> { + let jvm = test_jvm().await?; + let global: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Logger", "global", "Ljava/util/logging/Logger;") + .await?; + let manager: ClassInstanceRef = jvm + .invoke_static("java/util/logging/LogManager", "getLogManager", "()Ljava/util/logging/LogManager;", ()) + .await?; + let name = JavaLangString::from_rust_string(&jvm, "global").await?; + let registered: ClassInstanceRef = jvm + .invoke_virtual(&manager, "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", (name,)) + .await?; + assert_eq!(global.identity(), registered.identity()); + + let jvm = test_jvm().await?; + let manager: ClassInstanceRef = jvm + .invoke_static("java/util/logging/LogManager", "getLogManager", "()Ljava/util/logging/LogManager;", ()) + .await?; + let global: ClassInstanceRef = jvm + .get_static_field("java/util/logging/Logger", "global", "Ljava/util/logging/Logger;") + .await?; + let name = JavaLangString::from_rust_string(&jvm, "global").await?; + let registered: ClassInstanceRef = jvm + .invoke_virtual(&manager, "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", (name,)) + .await?; + assert_eq!(global.identity(), registered.identity()); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/util/mod.rs b/java_runtime/tests/classes/java/util/mod.rs index adcd73ed..61933736 100644 --- a/java_runtime/tests/classes/java/util/mod.rs +++ b/java_runtime/tests/classes/java/util/mod.rs @@ -1,4 +1,5 @@ mod jar; +mod logging; mod regex; mod test_array_list; mod test_arrays;