Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions java_runtime/src/classes/java/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod jar;
pub mod logging;
pub mod regex;
pub mod zip;

Expand Down
15 changes: 15 additions & 0 deletions java_runtime/src/classes/java/util/logging.rs
Original file line number Diff line number Diff line change
@@ -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,
};
65 changes: 65 additions & 0 deletions java_runtime/src/classes/java/util/logging/console_handler.rs
Original file line number Diff line number Diff line change
@@ -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("<init>", "()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<Self>) -> Result<()> {
tracing::debug!("java.util.logging.ConsoleHandler::<init>({this:?})");

let _: () = jvm.invoke_special(&this, "java/util/logging/StreamHandler", "<init>", "()V", ()).await?;
let error: ClassInstanceRef<OutputStream> = 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<Self>) -> 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<Self>, record: ClassInstanceRef<LogRecord>) -> 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
}
}
26 changes: 26 additions & 0 deletions java_runtime/src/classes/java/util/logging/filter.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
155 changes: 155 additions & 0 deletions java_runtime/src/classes/java/util/logging/formatter.rs
Original file line number Diff line number Diff line change
@@ -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("<init>", "()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<Self>) -> Result<()> {
tracing::debug!("java.util.logging.Formatter::<init>({this:?})");

jvm.invoke_special(&this, "java/lang/Object", "<init>", "()V", ()).await
}

async fn format_message(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
record: ClassInstanceRef<LogRecord>,
) -> Result<ClassInstanceRef<String>> {
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<String> = jvm.invoke_virtual(&record, "getMessage", "()Ljava/lang/String;", ()).await?;
if message.is_null() {
return Ok(message);
}

let parameters: ClassInstanceRef<Array<Object>> = jvm.invoke_virtual(&record, "getParameters", "()[Ljava/lang/Object;", ()).await?;
if parameters.is_null() {
return Ok(message);
}

let parameter_count = jvm.array_length(&parameters).await?;
if parameter_count == 0 {
return Ok(message);
}

let parameters: Vec<ClassInstanceRef<Object>> = jvm.load_array(&parameters, 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<String> = jvm.invoke_virtual(&parameter, "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<char> = 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<Self>,
_: ClassInstanceRef<super::Handler>,
) -> Result<ClassInstanceRef<String>> {
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<Self>,
_: ClassInstanceRef<super::Handler>,
) -> Result<ClassInstanceRef<String>> {
tracing::debug!("java.util.logging.Formatter::getTail({this:?})");

Ok(JavaLangString::from_rust_string(jvm, "").await?.into())
}
}
Loading