From d80e4cc9feb1df4f7f5beb3e9e535a3b08dfd281 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Thu, 16 Jul 2026 17:07:49 +0900 Subject: [PATCH 1/2] Add Java primitive wrapper classes --- java_runtime/src/classes/java/lang.rs | 30 +- java_runtime/src/classes/java/lang/boolean.rs | 151 ++++ java_runtime/src/classes/java/lang/byte.rs | 242 ++++++ .../src/classes/java/lang/character.rs | 782 ++++++++++++++++++ java_runtime/src/classes/java/lang/class.rs | 36 +- .../src/classes/java/lang/comparable.rs | 11 +- java_runtime/src/classes/java/lang/double.rs | 379 +++++++++ java_runtime/src/classes/java/lang/float.rs | 384 +++++++++ java_runtime/src/classes/java/lang/integer.rs | 447 ++++++++-- java_runtime/src/classes/java/lang/long.rs | 409 +++++++++ java_runtime/src/classes/java/lang/number.rs | 45 + java_runtime/src/classes/java/lang/short.rs | 225 +++++ java_runtime/src/loader.rs | 8 + java_runtime/tests/classes/java/lang/mod.rs | 8 + .../tests/classes/java/lang/test_boolean.rs | 136 +++ .../tests/classes/java/lang/test_byte.rs | 111 +++ .../tests/classes/java/lang/test_character.rs | 368 +++++++++ .../tests/classes/java/lang/test_class.rs | 112 ++- .../tests/classes/java/lang/test_double.rs | 243 ++++++ .../tests/classes/java/lang/test_float.rs | 233 ++++++ .../tests/classes/java/lang/test_integer.rs | 196 ++++- .../tests/classes/java/lang/test_long.rs | 163 ++++ .../tests/classes/java/lang/test_number.rs | 76 ++ .../tests/classes/java/lang/test_short.rs | 107 +++ jvm/src/runtime/java_lang_class.rs | 22 +- 25 files changed, 4858 insertions(+), 66 deletions(-) create mode 100644 java_runtime/src/classes/java/lang/boolean.rs create mode 100644 java_runtime/src/classes/java/lang/byte.rs create mode 100644 java_runtime/src/classes/java/lang/character.rs create mode 100644 java_runtime/src/classes/java/lang/double.rs create mode 100644 java_runtime/src/classes/java/lang/float.rs create mode 100644 java_runtime/src/classes/java/lang/long.rs create mode 100644 java_runtime/src/classes/java/lang/number.rs create mode 100644 java_runtime/src/classes/java/lang/short.rs create mode 100644 java_runtime/tests/classes/java/lang/test_boolean.rs create mode 100644 java_runtime/tests/classes/java/lang/test_byte.rs create mode 100644 java_runtime/tests/classes/java/lang/test_character.rs create mode 100644 java_runtime/tests/classes/java/lang/test_double.rs create mode 100644 java_runtime/tests/classes/java/lang/test_float.rs create mode 100644 java_runtime/tests/classes/java/lang/test_long.rs create mode 100644 java_runtime/tests/classes/java/lang/test_number.rs create mode 100644 java_runtime/tests/classes/java/lang/test_short.rs diff --git a/java_runtime/src/classes/java/lang.rs b/java_runtime/src/classes/java/lang.rs index 416ca554..5992124a 100644 --- a/java_runtime/src/classes/java/lang.rs +++ b/java_runtime/src/classes/java/lang.rs @@ -2,6 +2,9 @@ mod abstract_method_error; mod arithmetic_exception; mod array_index_out_of_bounds_exception; mod array_store_exception; +mod boolean; +mod byte; +mod character; mod class; mod class_cast_exception; mod class_loader; @@ -9,9 +12,11 @@ mod class_not_found_exception; mod clone_not_supported_exception; mod cloneable; mod comparable; +mod double; mod error; mod exception; mod exception_in_initializer_error; +mod float; mod illegal_argument_exception; mod incompatible_class_change_error; mod index_out_of_bounds_exception; @@ -19,18 +24,21 @@ mod instantiation_error; mod integer; mod interrupted_exception; mod linkage_error; +mod long; mod math; mod negative_array_size_exception; mod no_class_def_found_error; mod no_such_field_error; mod no_such_method_error; mod null_pointer_exception; +mod number; mod number_format_exception; mod object; mod runnable; mod runtime; mod runtime_exception; mod security_exception; +mod short; mod string; mod string_buffer; mod string_index_out_of_bounds_exception; @@ -41,15 +49,17 @@ mod unsupported_operation_exception; pub use self::{ abstract_method_error::AbstractMethodError, arithmetic_exception::ArithmeticException, - array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, class::Class, - class_cast_exception::ClassCastException, class_loader::ClassLoader, class_not_found_exception::ClassNotFoundException, - clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, comparable::Comparable, error::Error, exception::Exception, - exception_in_initializer_error::ExceptionInInitializerError, illegal_argument_exception::IllegalArgumentException, - incompatible_class_change_error::IncompatibleClassChangeError, index_out_of_bounds_exception::IndexOutOfBoundsException, - instantiation_error::InstantiationError, integer::Integer, interrupted_exception::InterruptedException, linkage_error::LinkageError, math::Math, + array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, boolean::Boolean, byte::Byte, + character::Character, class::Class, class_cast_exception::ClassCastException, class_loader::ClassLoader, + class_not_found_exception::ClassNotFoundException, clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, + comparable::Comparable, double::Double, error::Error, exception::Exception, exception_in_initializer_error::ExceptionInInitializerError, + float::Float, illegal_argument_exception::IllegalArgumentException, incompatible_class_change_error::IncompatibleClassChangeError, + index_out_of_bounds_exception::IndexOutOfBoundsException, instantiation_error::InstantiationError, integer::Integer, + interrupted_exception::InterruptedException, linkage_error::LinkageError, long::Long, math::Math, negative_array_size_exception::NegativeArraySizeException, no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, - no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number_format_exception::NumberFormatException, - object::Object, runnable::Runnable, runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, string::String, - string_buffer::StringBuffer, string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, - throwable::Throwable, unsupported_operation_exception::UnsupportedOperationException, + no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number::Number, + number_format_exception::NumberFormatException, object::Object, runnable::Runnable, runtime::Runtime, runtime_exception::RuntimeException, + security_exception::SecurityException, short::Short, string::String, string_buffer::StringBuffer, + string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, throwable::Throwable, + unsupported_operation_exception::UnsupportedOperationException, }; diff --git a/java_runtime/src/classes/java/lang/boolean.rs b/java_runtime/src/classes/java/lang/boolean.rs new file mode 100644 index 00000000..18648c32 --- /dev/null +++ b/java_runtime/src/classes/java/lang/boolean.rs @@ -0,0 +1,151 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Boolean +pub struct Boolean; + +impl Boolean { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Boolean", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(Z)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("booleanValue", "()Z", Self::boolean_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Boolean;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getBoolean", + "(Ljava/lang/String;)Z", + Self::get_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + ], + fields: vec![ + JavaFieldProto::new( + "TRUE", + "Ljava/lang/Boolean;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FALSE", + "Ljava/lang/Boolean;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "Z", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + let value = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?; + jvm.put_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;", value).await?; + + let value = jvm.new_class("java/lang/Boolean", "(Z)V", (false,)).await?; + jvm.put_static_field("java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;", value).await?; + + jvm.put_static_field( + "java/lang/Boolean", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "boolean").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "Z", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let parsed = if value.is_null() { + false + } else { + JavaLangString::to_rust_string(jvm, &value).await?.eq_ignore_ascii_case("true") + }; + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "Z", parsed).await + } + + async fn boolean_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "Z").await + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let parsed = if value.is_null() { + false + } else { + JavaLangString::to_rust_string(jvm, &value).await?.eq_ignore_ascii_case("true") + }; + + if parsed { + jvm.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;").await + } else { + jvm.get_static_field("java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;").await + } + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?.into()) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; + Ok(if value { 1231 } else { 1237 }) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Boolean") { + return Ok(false); + } + + let this_value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; + let other_value: bool = jvm.invoke_virtual(&other, "booleanValue", "()Z", ()).await?; + Ok(this_value == other_value) + } + + async fn get_boolean(jvm: &Jvm, _: &mut RuntimeContext, name: ClassInstanceRef) -> Result { + if name.is_null() { + return Ok(false); + } + + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (name,)) + .await?; + if value.is_null() { + return Ok(false); + } + + Ok(JavaLangString::to_rust_string(jvm, &value).await?.eq_ignore_ascii_case("true")) + } +} diff --git a/java_runtime/src/classes/java/lang/byte.rs b/java_runtime/src/classes/java/lang/byte.rs new file mode 100644 index 00000000..b0a28ecf --- /dev/null +++ b/java_runtime/src/classes/java/lang/byte.rs @@ -0,0 +1,242 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Byte +pub struct Byte; + +impl Byte { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Byte", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(B)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseByte", + "(Ljava/lang/String;)B", + Self::parse_byte, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseByte", + "(Ljava/lang/String;I)B", + Self::parse_byte_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Byte;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Byte;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Byte;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(B)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Byte;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "B", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Byte", "MIN_VALUE", "B", i8::MIN).await?; + jvm.put_static_field("java/lang/Byte", "MAX_VALUE", "B", i8::MAX).await?; + jvm.put_static_field( + "java/lang/Byte", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "byte").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i8) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "B", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", (value,)) + .await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "B", value).await + } + + async fn parse_byte(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", (value,)) + .await?; + if !(i32::from(i8::MIN)..=i32::from(i8::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i8) + } + + async fn parse_byte_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (value, radix)) + .await?; + if !(i32::from(i8::MIN)..=i32::from(i8::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i8) + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let value: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", (value,)) + .await?; + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value,)).await?.into()) + } + + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + let value: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;I)B", (value, radix)) + .await?; + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value,)).await?.into()) + } + + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let integer = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (value,)) + .await?; + let value: i32 = jvm.invoke_virtual(&integer, "intValue", "()I", ()).await?; + if !(i32::from(i8::MIN)..=i32::from(i8::MAX)).contains(&value) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value as i8,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "B").await + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i16) + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i32) + } + + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i64) + } + + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as f32) + } + + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as f64) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i8) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i32) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Byte") { + return Ok(false); + } + let left: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + let right: i8 = jvm.invoke_virtual(&other, "byteValue", "()B", ()).await?; + Ok(left == right) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + let right: i8 = jvm.invoke_virtual(&other, "byteValue", "()B", ()).await?; + Ok(left.cmp(&right) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Byte") { + return Err(jvm.exception("java/lang/ClassCastException", "not Byte").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + let right: i8 = jvm.invoke_virtual(&other, "byteValue", "()B", ()).await?; + Ok(left.cmp(&right) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/character.rs b/java_runtime/src/classes/java/lang/character.rs new file mode 100644 index 00000000..0d2543b9 --- /dev/null +++ b/java_runtime/src/classes/java/lang/character.rs @@ -0,0 +1,782 @@ +use core::char; + +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangClass}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Character +pub struct Character; + +impl Character { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Character", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Serializable", "java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(C)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("charValue", "()C", Self::char_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Character;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isLowerCase", + "(C)Z", + Self::is_lower_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isUpperCase", + "(C)Z", + Self::is_upper_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isTitleCase", + "(C)Z", + Self::is_title_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isDigit", "(C)Z", Self::is_digit, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "isDefined", + "(C)Z", + Self::is_defined, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isLetter", "(C)Z", Self::is_letter, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "isLetterOrDigit", + "(C)Z", + Self::is_letter_or_digit, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaLetter", + "(C)Z", + Self::is_java_identifier_start, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaLetterOrDigit", + "(C)Z", + Self::is_java_identifier_part, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaIdentifierStart", + "(C)Z", + Self::is_java_identifier_start, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaIdentifierPart", + "(C)Z", + Self::is_java_identifier_part, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isUnicodeIdentifierStart", + "(C)Z", + Self::is_unicode_identifier_start, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isUnicodeIdentifierPart", + "(C)Z", + Self::is_unicode_identifier_part, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isIdentifierIgnorable", + "(C)Z", + Self::is_identifier_ignorable, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isSpace", "(C)Z", Self::is_space, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "isSpaceChar", + "(C)Z", + Self::is_space_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isWhitespace", + "(C)Z", + Self::is_whitespace, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isISOControl", + "(C)Z", + Self::is_iso_control, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toLowerCase", + "(C)C", + Self::to_lower_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toUpperCase", + "(C)C", + Self::to_upper_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toTitleCase", + "(C)C", + Self::to_title_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("digit", "(CI)I", Self::digit, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "forDigit", + "(II)C", + Self::for_digit, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getNumericValue", + "(C)I", + Self::get_numeric_value, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("getType", "(C)I", Self::get_type, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "C", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "C", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MIN_RADIX", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_RADIX", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "UNASSIGNED", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "UPPERCASE_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "LOWERCASE_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TITLECASE_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MODIFIER_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "NON_SPACING_MARK", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "ENCLOSING_MARK", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "COMBINING_SPACING_MARK", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "DECIMAL_DIGIT_NUMBER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "LETTER_NUMBER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_NUMBER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "SPACE_SEPARATOR", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "LINE_SEPARATOR", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "PARAGRAPH_SEPARATOR", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CONTROL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FORMAT", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "PRIVATE_USE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "SURROGATE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "DASH_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "START_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "END_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CONNECTOR_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MATH_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CURRENCY_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MODIFIER_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "C", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Character", "MIN_VALUE", "C", JavaChar::MIN).await?; + jvm.put_static_field("java/lang/Character", "MAX_VALUE", "C", JavaChar::MAX).await?; + jvm.put_static_field("java/lang/Character", "MIN_RADIX", "I", 2i32).await?; + jvm.put_static_field("java/lang/Character", "MAX_RADIX", "I", 36i32).await?; + jvm.put_static_field( + "java/lang/Character", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "char").await?, + ) + .await?; + + for (name, value) in [ + ("UNASSIGNED", 0i8), + ("UPPERCASE_LETTER", 1), + ("LOWERCASE_LETTER", 2), + ("TITLECASE_LETTER", 3), + ("MODIFIER_LETTER", 4), + ("OTHER_LETTER", 5), + ("NON_SPACING_MARK", 6), + ("ENCLOSING_MARK", 7), + ("COMBINING_SPACING_MARK", 8), + ("DECIMAL_DIGIT_NUMBER", 9), + ("LETTER_NUMBER", 10), + ("OTHER_NUMBER", 11), + ("SPACE_SEPARATOR", 12), + ("LINE_SEPARATOR", 13), + ("PARAGRAPH_SEPARATOR", 14), + ("CONTROL", 15), + ("FORMAT", 16), + ("PRIVATE_USE", 18), + ("SURROGATE", 19), + ("DASH_PUNCTUATION", 20), + ("START_PUNCTUATION", 21), + ("END_PUNCTUATION", 22), + ("CONNECTOR_PUNCTUATION", 23), + ("OTHER_PUNCTUATION", 24), + ("MATH_SYMBOL", 25), + ("CURRENCY_SYMBOL", 26), + ("MODIFIER_SYMBOL", 27), + ("OTHER_SYMBOL", 28), + ] { + jvm.put_static_field("java/lang/Character", name, "B", value).await?; + } + + Ok(()) + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: JavaChar) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "C", value).await + } + + async fn char_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "C").await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + jvm.invoke_static("java/lang/String", "valueOf", "(C)Ljava/lang/String;", (value,)).await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + Ok(i32::from(value)) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Character") { + return Ok(false); + } + + let this_value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + let other_value: JavaChar = jvm.invoke_virtual(&other, "charValue", "()C", ()).await?; + Ok(this_value == other_value) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Character") { + return Err(jvm.exception("java/lang/ClassCastException", "not Character").await); + } + + let this_value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + let other_value: JavaChar = jvm.invoke_virtual(&other, "charValue", "()C", ()).await?; + Ok(this_value.cmp(&other_value) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Character") { + return Err(jvm.exception("java/lang/ClassCastException", "not Character").await); + } + + let other = ClassInstanceRef::::from(other.instance); + let this_value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + let other_value: JavaChar = jvm.invoke_virtual(&other, "charValue", "()C", ()).await?; + Ok(this_value.cmp(&other_value) as i32) + } + + async fn is_lower_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(char::from_u32(u32::from(value)).is_some_and(char::is_lowercase)) + } + + async fn is_upper_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(char::from_u32(u32::from(value)).is_some_and(char::is_uppercase)) + } + + async fn is_title_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!( + value, + 0x01c5 | 0x01c8 | 0x01cb | 0x01f2 | 0x1f88..=0x1f8f | 0x1f98..=0x1f9f | 0x1fa8..=0x1faf | 0x1fbc | 0x1fcc | 0x1ffc + )) + } + + async fn is_digit(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(Self::decimal_digit_value(value).is_some()) + } + + async fn is_defined(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok(category != 0) + } + + async fn is_letter(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category)) + } + + async fn is_letter_or_digit(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category) || category == 9) + } + + async fn is_java_identifier_start(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category) || matches!(category, 10 | 23 | 26)) + } + + async fn is_java_identifier_part(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=10).contains(&category) + || matches!(category, 23 | 26) + || matches!( + value, + 0x0000..=0x0008 | 0x000e..=0x001b | 0x007f..=0x009f | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff + )) + } + + async fn is_unicode_identifier_start(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category) || category == 10) + } + + async fn is_unicode_identifier_part(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=10).contains(&category) + || category == 23 + || matches!( + value, + 0x0000..=0x0008 | 0x000e..=0x001b | 0x007f..=0x009f | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff + )) + } + + async fn is_identifier_ignorable(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!( + value, + 0x0000..=0x0008 | 0x000e..=0x001b | 0x007f..=0x009f | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff + )) + } + + async fn is_space(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!(value, 0x0009 | 0x000a | 0x000c | 0x000d | 0x0020)) + } + + async fn is_space_char(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!( + value, + 0x0020 | 0x00a0 | 0x1680 | 0x2000..=0x200a | 0x2028..=0x2029 | 0x202f | 0x205f | 0x3000 + )) + } + + async fn is_whitespace(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let Some(value) = char::from_u32(u32::from(value)) else { + return Ok(false); + }; + Ok((value.is_whitespace() && !matches!(value, '\u{00a0}' | '\u{2007}' | '\u{202f}')) || matches!(value, '\u{001c}'..='\u{001f}')) + } + + async fn is_iso_control(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!(value, 0x0000..=0x001f | 0x007f..=0x009f)) + } + + async fn to_lower_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let Some(character) = char::from_u32(u32::from(value)) else { + return Ok(value); + }; + let mut mapped = character.to_lowercase(); + let Some(first) = mapped.next() else { + return Ok(value); + }; + if mapped.next().is_some() || u32::from(first) > u32::from(JavaChar::MAX) { + return Ok(value); + } + Ok(first as JavaChar) + } + + async fn to_upper_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let Some(character) = char::from_u32(u32::from(value)) else { + return Ok(value); + }; + let mut mapped = character.to_uppercase(); + let Some(first) = mapped.next() else { + return Ok(value); + }; + if mapped.next().is_some() || u32::from(first) > u32::from(JavaChar::MAX) { + return Ok(value); + } + Ok(first as JavaChar) + } + + async fn to_title_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let title = match value { + 0x01c4..=0x01c6 => 0x01c5, + 0x01c7..=0x01c9 => 0x01c8, + 0x01ca..=0x01cc => 0x01cb, + 0x01f1..=0x01f3 => 0x01f2, + _ => { + let Some(character) = char::from_u32(u32::from(value)) else { + return Ok(value); + }; + let mut mapped = character.to_uppercase(); + let Some(first) = mapped.next() else { + return Ok(value); + }; + if mapped.next().is_some() || u32::from(first) > u32::from(JavaChar::MAX) { + return Ok(value); + } + first as JavaChar + } + }; + Ok(title) + } + + async fn digit(_: &Jvm, _: &mut RuntimeContext, value: JavaChar, radix: i32) -> Result { + if !(2..=36).contains(&radix) { + return Ok(-1); + } + + let numeric = if let Some(value) = Self::decimal_digit_value(value) { + value + } else if (b'a' as JavaChar..=b'z' as JavaChar).contains(&value) { + i32::from(value - b'a' as JavaChar) + 10 + } else if (b'A' as JavaChar..=b'Z' as JavaChar).contains(&value) { + i32::from(value - b'A' as JavaChar) + 10 + } else if (0xff41..=0xff5a).contains(&value) { + i32::from(value - 0xff41) + 10 + } else if (0xff21..=0xff3a).contains(&value) { + i32::from(value - 0xff21) + 10 + } else { + return Ok(-1); + }; + + Ok(if numeric < radix { numeric } else { -1 }) + } + + async fn for_digit(_: &Jvm, _: &mut RuntimeContext, digit: i32, radix: i32) -> Result { + if !(2..=36).contains(&radix) || !(0..radix).contains(&digit) { + return Ok(0); + } + + Ok(if digit < 10 { + (i32::from(b'0') + digit) as JavaChar + } else { + (i32::from(b'a') + digit - 10) as JavaChar + }) + } + + async fn get_numeric_value(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + if let Some(value) = Self::decimal_digit_value(value) { + return Ok(value); + } + if (b'a' as JavaChar..=b'z' as JavaChar).contains(&value) { + return Ok(i32::from(value - b'a' as JavaChar) + 10); + } + if (b'A' as JavaChar..=b'Z' as JavaChar).contains(&value) { + return Ok(i32::from(value - b'A' as JavaChar) + 10); + } + if (0xff41..=0xff5a).contains(&value) { + return Ok(i32::from(value - 0xff41) + 10); + } + if (0xff21..=0xff3a).contains(&value) { + return Ok(i32::from(value - 0xff21) + 10); + } + + Ok(match value { + 0x00b2 => 2, + 0x00b3 => 3, + 0x00b9 => 1, + 0x2160..=0x216b => i32::from(value - 0x2160) + 1, + 0x2170..=0x217b => i32::from(value - 0x2170) + 1, + 0x00bc..=0x00be => -2, + _ => -1, + }) + } + + async fn get_type(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + if char::from_u32(u32::from(value)).is_none() || matches!(value, 0xfdd0..=0xfdef | 0xfffe | 0xffff) { + return Ok(0); + } + if matches!( + value, + 0x01c5 | 0x01c8 | 0x01cb | 0x01f2 | 0x1f88..=0x1f8f | 0x1f98..=0x1f9f | 0x1fa8..=0x1faf | 0x1fbc | 0x1fcc | 0x1ffc + ) { + return Ok(3); + } + + if matches!(value, 0x2160..=0x2188) { + return Ok(10); + } + if Self::decimal_digit_value(value).is_some() { + return Ok(9); + } + if matches!(value, 0x20dd..=0x20e0) { + return Ok(7); + } + if matches!(value, 0x0300..=0x036f | 0x1ab0..=0x1aff | 0x1dc0..=0x1dff | 0x20d0..=0x20ff | 0xfe00..=0xfe0f | 0xfe20..=0xfe2f) { + return Ok(6); + } + if matches!(value, 0x0903 | 0x093b | 0x093e..=0x0940 | 0x0949..=0x094c) { + return Ok(8); + } + + let character = char::from_u32(u32::from(value)); + if character.is_some_and(char::is_uppercase) { + return Ok(1); + } + if character.is_some_and(char::is_lowercase) { + return Ok(2); + } + if character.is_some_and(char::is_alphabetic) { + return Ok(5); + } + if matches!(value, 0x00b2..=0x00b3 | 0x00b9 | 0x00bc..=0x00be | 0x2070 | 0x2074..=0x2079 | 0x2080..=0x2089) { + return Ok(11); + } + if value == 0x2028 { + return Ok(13); + } + if value == 0x2029 { + return Ok(14); + } + if matches!(value, 0x0020 | 0x00a0 | 0x1680 | 0x2000..=0x200a | 0x202f | 0x205f | 0x3000) { + return Ok(12); + } + if matches!(value, 0x0000..=0x001f | 0x007f..=0x009f) { + return Ok(15); + } + if matches!(value, 0x00ad | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff) { + return Ok(16); + } + if matches!(value, 0xe000..=0xf8ff) { + return Ok(18); + } + if matches!(value, 0x002d | 0x058a | 0x2010..=0x2015 | 0x2e17 | 0x2e1a | 0x2e3a..=0x2e3b | 0x301c | 0x3030) { + return Ok(20); + } + if matches!(value, 0x0028 | 0x005b | 0x007b | 0x0f3a | 0x0f3c | 0x169b | 0x201a | 0x201e | 0x2045) { + return Ok(21); + } + if matches!(value, 0x0029 | 0x005d | 0x007d | 0x0f3b | 0x0f3d | 0x169c | 0x2046) { + return Ok(22); + } + if value == 0x00ab { + return Ok(21); + } + if value == 0x00bb { + return Ok(22); + } + if matches!(value, 0x005f | 0x203f..=0x2040 | 0x2054 | 0xfe33..=0xfe34 | 0xfe4d..=0xfe4f | 0xff3f) { + return Ok(23); + } + if matches!(value, 0x0024 | 0x00a2..=0x00a5 | 0x058f | 0x060b | 0x09f2..=0x09f3 | 0x0af1 | 0x0bf9 | 0x0e3f | 0x17db | 0x20a0..=0x20cf | 0xa838 | 0xfdfc | 0xfe69 | 0xff04 | 0xffe0..=0xffe6) + { + return Ok(26); + } + if matches!(value, 0x005e | 0x0060 | 0x00a8 | 0x00af | 0x00b4 | 0x00b8 | 0x02b0..=0x02ff) { + return Ok(27); + } + if matches!(value, 0x002b | 0x003c..=0x003e | 0x007c | 0x007e | 0x00ac | 0x00b1 | 0x00d7 | 0x00f7 | 0x2200..=0x22ff) { + return Ok(25); + } + if matches!(value, 0x00a6 | 0x00a9 | 0x00ae | 0x00b0 | 0x2300..=0x23ff | 0x2460..=0x24ff | 0x2600..=0x27bf) { + return Ok(28); + } + if matches!(value, 0x00a1 | 0x00a7 | 0x00b6..=0x00b7 | 0x00bf) { + return Ok(24); + } + if (value <= 0x007e && (value as u8).is_ascii_punctuation()) || matches!(value, 0x2000..=0x206f | 0x3001..=0x303f) { + return Ok(24); + } + + Ok(0) + } + + fn decimal_digit_value(value: JavaChar) -> Option { + let zero = match value { + 0x0030..=0x0039 => 0x0030, + 0x0660..=0x0669 => 0x0660, + 0x06f0..=0x06f9 => 0x06f0, + 0x07c0..=0x07c9 => 0x07c0, + 0x0966..=0x096f => 0x0966, + 0x09e6..=0x09ef => 0x09e6, + 0x0a66..=0x0a6f => 0x0a66, + 0x0ae6..=0x0aef => 0x0ae6, + 0x0b66..=0x0b6f => 0x0b66, + 0x0be6..=0x0bef => 0x0be6, + 0x0c66..=0x0c6f => 0x0c66, + 0x0ce6..=0x0cef => 0x0ce6, + 0x0d66..=0x0d6f => 0x0d66, + 0x0e50..=0x0e59 => 0x0e50, + 0x0ed0..=0x0ed9 => 0x0ed0, + 0x0f20..=0x0f29 => 0x0f20, + 0x1040..=0x1049 => 0x1040, + 0x1090..=0x1099 => 0x1090, + 0x17e0..=0x17e9 => 0x17e0, + 0x1810..=0x1819 => 0x1810, + 0x1946..=0x194f => 0x1946, + 0x19d0..=0x19d9 => 0x19d0, + 0x1a80..=0x1a89 => 0x1a80, + 0x1a90..=0x1a99 => 0x1a90, + 0x1b50..=0x1b59 => 0x1b50, + 0x1bb0..=0x1bb9 => 0x1bb0, + 0x1c40..=0x1c49 => 0x1c40, + 0x1c50..=0x1c59 => 0x1c50, + 0xa620..=0xa629 => 0xa620, + 0xa8d0..=0xa8d9 => 0xa8d0, + 0xa900..=0xa909 => 0xa900, + 0xa9d0..=0xa9d9 => 0xa9d0, + 0xa9f0..=0xa9f9 => 0xa9f0, + 0xaa50..=0xaa59 => 0xaa50, + 0xabf0..=0xabf9 => 0xabf0, + 0xff10..=0xff19 => 0xff10, + _ => return None, + }; + Some(i32::from(value - zero)) + } +} diff --git a/java_runtime/src/classes/java/lang/class.rs b/java_runtime/src/classes/java/lang/class.rs index a2f62d50..009cee29 100644 --- a/java_runtime/src/classes/java/lang/class.rs +++ b/java_runtime/src/classes/java/lang/class.rs @@ -27,6 +27,7 @@ impl Class { methods: vec![ JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("getName", "()Ljava/lang/String;", Self::get_name, Default::default()), + JavaMethodProto::new("isPrimitive", "()Z", Self::is_primitive, MethodAccessFlags::PUBLIC), JavaMethodProto::new("isAssignableFrom", "(Ljava/lang/Class;)Z", Self::is_assignable_from, Default::default()), JavaMethodProto::new( "getResourceAsStream", @@ -62,15 +63,42 @@ impl Class { async fn get_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.Class::getName({this:?})"); - let rust_class = JavaLangClass::to_rust_class(jvm, &this).await?; - let result = JavaLangString::from_rust_string(jvm, &rust_class.name().replace('/', ".")).await?; + let class_name = JavaLangClass::name(jvm, &this).await?; + let result = JavaLangString::from_rust_string(jvm, &class_name.replace('/', ".")).await?; Ok(result.into()) } + async fn is_primitive(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let name = JavaLangClass::name(jvm, &this).await?; + Ok(matches!( + name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" + )) + } + async fn is_assignable_from(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { tracing::debug!("java.lang.Class::isAssignableFrom({this:?}, {other:?})"); + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + + let class_name = JavaLangClass::name(jvm, &this).await?; + let other_name = JavaLangClass::name(jvm, &other).await?; + let class_is_primitive = matches!( + class_name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" + ); + let other_is_primitive = matches!( + other_name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" + ); + + if class_is_primitive || other_is_primitive { + return Ok(class_name == other_name); + } + let rust_class = JavaLangClass::to_rust_class(jvm, &this).await?; let other_rust_class = JavaLangClass::to_rust_class(jvm, &other).await?; @@ -101,6 +129,10 @@ impl Class { async fn for_name(jvm: &Jvm, _context: &mut RuntimeContext, name: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.Class::forName({name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + let rust_name = JavaLangString::to_rust_string(jvm, &name).await?; let qualified_name = rust_name.replace('.', "/"); diff --git a/java_runtime/src/classes/java/lang/comparable.rs b/java_runtime/src/classes/java/lang/comparable.rs index 5acd8d30..68361ee9 100644 --- a/java_runtime/src/classes/java/lang/comparable.rs +++ b/java_runtime/src/classes/java/lang/comparable.rs @@ -1,6 +1,7 @@ use alloc::vec; -use java_constants::ClassAccessFlags; +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -13,9 +14,13 @@ impl Comparable { name: "java/lang/Comparable", parent_class: None, interfaces: vec![], - methods: vec![], + methods: vec![JavaMethodProto::new_abstract( + "compareTo", + "(Ljava/lang/Object;)I", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + )], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/lang/double.rs b/java_runtime/src/classes/java/lang/double.rs new file mode 100644 index 00000000..89fc0fce --- /dev/null +++ b/java_runtime/src/classes/java/lang/double.rs @@ -0,0 +1,379 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Double +pub struct Double; + +impl Double { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Double", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(D)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseDouble", + "(Ljava/lang/String;)D", + Self::parse_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Double;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(D)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isNaN", "()Z", Self::is_nan, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isNaN", + "(D)Z", + Self::is_nan_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isInfinite", "()Z", Self::is_infinite, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isInfinite", + "(D)Z", + Self::is_infinite_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "doubleToLongBits", + "(D)J", + Self::double_to_long_bits, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "longBitsToDouble", + "(J)D", + Self::long_bits_to_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Double;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "POSITIVE_INFINITY", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "NEGATIVE_INFINITY", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("NaN", "D", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL), + JavaFieldProto::new( + "MAX_VALUE", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MIN_VALUE", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "D", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Double", "POSITIVE_INFINITY", "D", f64::INFINITY).await?; + jvm.put_static_field("java/lang/Double", "NEGATIVE_INFINITY", "D", f64::NEG_INFINITY) + .await?; + jvm.put_static_field("java/lang/Double", "NaN", "D", f64::NAN).await?; + jvm.put_static_field("java/lang/Double", "MAX_VALUE", "D", f64::MAX).await?; + jvm.put_static_field("java/lang/Double", "MIN_VALUE", "D", f64::from_bits(1)).await?; + jvm.put_static_field( + "java/lang/Double", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "double").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: f64) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "D", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "D", value).await + } + + async fn parse_value(jvm: &Jvm, value: &str) -> Result { + let trimmed = value.trim_matches(|character| character <= '\u{0020}'); + let parsed = match trimmed { + "NaN" | "+NaN" | "-NaN" => Some(f64::NAN), + "Infinity" | "+Infinity" => Some(f64::INFINITY), + "-Infinity" => Some(f64::NEG_INFINITY), + _ => { + let number = match trimmed.as_bytes().last() { + Some(b'f' | b'F' | b'd' | b'D') => &trimmed[..trimmed.len() - 1], + _ => trimmed, + }; + let bytes = number.as_bytes(); + let mut index = 0; + if matches!(bytes.first(), Some(b'+' | b'-')) { + index += 1; + } + + let mut digits = 0; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + if index < bytes.len() && bytes[index] == b'.' { + index += 1; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + } + + if digits == 0 { + None + } else { + if index < bytes.len() && matches!(bytes[index], b'e' | b'E') { + index += 1; + if index < bytes.len() && matches!(bytes[index], b'+' | b'-') { + index += 1; + } + let exponent_start = index; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + } + if exponent_start == index { + index = bytes.len() + 1; + } + } + if index == bytes.len() { number.parse::().ok() } else { None } + } + } + }; + + match parsed { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + + async fn parse_double(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value).await + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + Ok(jvm.new_class("java/lang/Double", "(D)V", (value,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok((value as i32) as i8) + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok((value as i32) as i16) + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value as i32) + } + + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value as i64) + } + + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value as f32) + } + + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "D").await + } + + fn format_value(value: f64) -> RustString { + if value.is_nan() { + return "NaN".into(); + } + if value == f64::INFINITY { + return "Infinity".into(); + } + if value == f64::NEG_INFINITY { + return "-Infinity".into(); + } + if value.to_bits() == 1 { + return "4.9E-324".into(); + } + if value.to_bits() == 0x8000_0000_0000_0001 { + return "-4.9E-324".into(); + } + + let magnitude = value.abs(); + let mut result = if value != 0.0 && !(0.001..10_000_000.0).contains(&magnitude) { + format!("{value:e}") + } else { + format!("{value}") + }; + if let Some(exponent_index) = result.find('e') { + let exponent = result.split_off(exponent_index + 1); + result.pop(); + if !result.contains('.') { + result.push_str(".0"); + } + result.push('E'); + result.push_str(exponent.strip_prefix('+').unwrap_or(&exponent)); + } else if !result.contains('.') { + result.push_str(".0"); + } + result + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: f64) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn is_nan(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value.is_nan()) + } + + async fn is_nan_static(_: &Jvm, _: &mut RuntimeContext, value: f64) -> Result { + Ok(value.is_nan()) + } + + async fn is_infinite(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value.is_infinite()) + } + + async fn is_infinite_static(_: &Jvm, _: &mut RuntimeContext, value: f64) -> Result { + Ok(value.is_infinite()) + } + + async fn double_to_long_bits(_: &Jvm, _: &mut RuntimeContext, value: f64) -> Result { + Ok(if value.is_nan() { 0x7ff8_0000_0000_0000 } else { value.to_bits() as i64 }) + } + + async fn long_bits_to_double(_: &Jvm, _: &mut RuntimeContext, bits: i64) -> Result { + Ok(f64::from_bits(bits as u64)) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let bits = if value.is_nan() { 0x7ff8_0000_0000_0000 } else { value.to_bits() }; + Ok((bits ^ (bits >> 32)) as i32) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Double") { + return Ok(false); + } + let left: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let right: f64 = jvm.invoke_virtual(&other, "doubleValue", "()D", ()).await?; + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() as i64 }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() as i64 }; + Ok(left_bits == right_bits) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let right: f64 = jvm.invoke_virtual(&other, "doubleValue", "()D", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() as i64 }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() as i64 }; + Ok(left_bits.cmp(&right_bits) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Double") { + return Err(jvm.exception("java/lang/ClassCastException", "not Double").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let right: f64 = jvm.invoke_virtual(&other, "doubleValue", "()D", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() as i64 }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() as i64 }; + Ok(left_bits.cmp(&right_bits) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/float.rs b/java_runtime/src/classes/java/lang/float.rs new file mode 100644 index 00000000..83d0c887 --- /dev/null +++ b/java_runtime/src/classes/java/lang/float.rs @@ -0,0 +1,384 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Float +pub struct Float; + +impl Float { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Float", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(F)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(D)V", Self::init_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseFloat", + "(Ljava/lang/String;)F", + Self::parse_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Float;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(F)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isNaN", "()Z", Self::is_nan, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isNaN", + "(F)Z", + Self::is_nan_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isInfinite", "()Z", Self::is_infinite, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isInfinite", + "(F)Z", + Self::is_infinite_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "floatToIntBits", + "(F)I", + Self::float_to_int_bits, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "intBitsToFloat", + "(I)F", + Self::int_bits_to_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Float;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "POSITIVE_INFINITY", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "NEGATIVE_INFINITY", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("NaN", "F", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL), + JavaFieldProto::new( + "MAX_VALUE", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MIN_VALUE", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "F", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Float", "POSITIVE_INFINITY", "F", f32::INFINITY).await?; + jvm.put_static_field("java/lang/Float", "NEGATIVE_INFINITY", "F", f32::NEG_INFINITY) + .await?; + jvm.put_static_field("java/lang/Float", "NaN", "F", f32::NAN).await?; + jvm.put_static_field("java/lang/Float", "MAX_VALUE", "F", f32::MAX).await?; + jvm.put_static_field("java/lang/Float", "MIN_VALUE", "F", f32::from_bits(1)).await?; + jvm.put_static_field( + "java/lang/Float", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "float").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: f32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "F", value).await + } + + async fn init_double(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: f64) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "F", value as f32).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "F", value).await + } + + async fn parse_value(jvm: &Jvm, value: &str) -> Result { + let trimmed = value.trim_matches(|character| character <= '\u{0020}'); + let parsed = match trimmed { + "NaN" | "+NaN" | "-NaN" => Some(f32::NAN), + "Infinity" | "+Infinity" => Some(f32::INFINITY), + "-Infinity" => Some(f32::NEG_INFINITY), + _ => { + let number = match trimmed.as_bytes().last() { + Some(b'f' | b'F' | b'd' | b'D') => &trimmed[..trimmed.len() - 1], + _ => trimmed, + }; + let bytes = number.as_bytes(); + let mut index = 0; + if matches!(bytes.first(), Some(b'+' | b'-')) { + index += 1; + } + + let mut digits = 0; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + if index < bytes.len() && bytes[index] == b'.' { + index += 1; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + } + + if digits == 0 { + None + } else { + if index < bytes.len() && matches!(bytes[index], b'e' | b'E') { + index += 1; + if index < bytes.len() && matches!(bytes[index], b'+' | b'-') { + index += 1; + } + let exponent_start = index; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + } + if exponent_start == index { + index = bytes.len() + 1; + } + } + if index == bytes.len() { number.parse::().ok() } else { None } + } + } + }; + + match parsed { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + + async fn parse_float(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value).await + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + Ok(jvm.new_class("java/lang/Float", "(F)V", (value,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok((value as i32) as i8) + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok((value as i32) as i16) + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value as i32) + } + + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value as i64) + } + + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "F").await + } + + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value as f64) + } + + fn format_value(value: f32) -> RustString { + if value.is_nan() { + return "NaN".into(); + } + if value == f32::INFINITY { + return "Infinity".into(); + } + if value == f32::NEG_INFINITY { + return "-Infinity".into(); + } + if value.to_bits() == 1 { + return "1.4E-45".into(); + } + if value.to_bits() == 0x8000_0001 { + return "-1.4E-45".into(); + } + + let magnitude = value.abs(); + let mut result = if value != 0.0 && !(0.001..10_000_000.0).contains(&magnitude) { + format!("{value:e}") + } else { + format!("{value}") + }; + if let Some(exponent_index) = result.find('e') { + let exponent = result.split_off(exponent_index + 1); + result.pop(); + if !result.contains('.') { + result.push_str(".0"); + } + result.push('E'); + result.push_str(exponent.strip_prefix('+').unwrap_or(&exponent)); + } else if !result.contains('.') { + result.push_str(".0"); + } + result + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: f32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn is_nan(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value.is_nan()) + } + + async fn is_nan_static(_: &Jvm, _: &mut RuntimeContext, value: f32) -> Result { + Ok(value.is_nan()) + } + + async fn is_infinite(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value.is_infinite()) + } + + async fn is_infinite_static(_: &Jvm, _: &mut RuntimeContext, value: f32) -> Result { + Ok(value.is_infinite()) + } + + async fn float_to_int_bits(_: &Jvm, _: &mut RuntimeContext, value: f32) -> Result { + Ok(if value.is_nan() { 0x7fc0_0000 } else { value.to_bits() as i32 }) + } + + async fn int_bits_to_float(_: &Jvm, _: &mut RuntimeContext, bits: i32) -> Result { + Ok(f32::from_bits(bits as u32)) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(if value.is_nan() { 0x7fc0_0000 } else { value.to_bits() as i32 }) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Float") { + return Ok(false); + } + let left: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + let right: f32 = jvm.invoke_virtual(&other, "floatValue", "()F", ()).await?; + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() as i32 }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() as i32 }; + Ok(left_bits == right_bits) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + let right: f32 = jvm.invoke_virtual(&other, "floatValue", "()F", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() as i32 }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() as i32 }; + Ok(left_bits.cmp(&right_bits) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Float") { + return Err(jvm.exception("java/lang/ClassCastException", "not Float").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + let right: f32 = jvm.invoke_virtual(&other, "floatValue", "()F", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() as i32 }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() as i32 }; + Ok(left_bits.cmp(&right_bits) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/integer.rs b/java_runtime/src/classes/java/lang/integer.rs index 0a0490f6..ceafdcb9 100644 --- a/java_runtime/src/classes/java/lang/integer.rs +++ b/java_runtime/src/classes/java/lang/integer.rs @@ -1,94 +1,449 @@ -use alloc::{format, string::ToString, vec}; +use alloc::{format, string::String as RustString, vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::MethodAccessFlags; -use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, JavaError, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; -use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; -// class java.lang.Integer +// public final class java.lang.Integer pub struct Integer; impl Integer { pub fn as_proto() -> RuntimeClassProto { RuntimeClassProto { name: "java/lang/Integer", - parent_class: Some("java/lang/Object"), + parent_class: Some("java/lang/Number"), interfaces: vec!["java/lang/Comparable"], methods: vec![ - JavaMethodProto::new("", "(I)V", Self::init, Default::default()), - JavaMethodProto::new("parseInt", "(Ljava/lang/String;)I", Self::parse_int, MethodAccessFlags::STATIC), - JavaMethodProto::new("valueOf", "(I)Ljava/lang/Integer;", Self::value_of, MethodAccessFlags::STATIC), - JavaMethodProto::new("intValue", "()I", Self::int_value, Default::default()), - JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), - JavaMethodProto::new("toString", "(I)Ljava/lang/String;", Self::to_string_static, MethodAccessFlags::STATIC), - JavaMethodProto::new("toHexString", "(I)Ljava/lang/String;", Self::to_hex_string, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseInt", + "(Ljava/lang/String;)I", + Self::parse_int, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseInt", + "(Ljava/lang/String;I)I", + Self::parse_int_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(I)Ljava/lang/Integer;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Integer;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Integer;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Integer;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getInteger", + "(Ljava/lang/String;)Ljava/lang/Integer;", + Self::get_integer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getInteger", + "(Ljava/lang/String;I)Ljava/lang/Integer;", + Self::get_integer_value_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + Self::get_integer_object_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(I)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toString", + "(II)Ljava/lang/String;", + Self::to_string_radix_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toBinaryString", + "(I)Ljava/lang/String;", + Self::to_binary_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toOctalString", + "(I)Ljava/lang/String;", + Self::to_octal_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toHexString", + "(I)Ljava/lang/String;", + Self::to_hex_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Integer;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), ], - fields: vec![JavaFieldProto::new("value", "I", Default::default())], - access_flags: Default::default(), + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, } } + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Integer", "MIN_VALUE", "I", i32::MIN).await?; + jvm.put_static_field("java/lang/Integer", "MAX_VALUE", "I", i32::MAX).await?; + jvm.put_static_field( + "java/lang/Integer", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "int").await?, + ) + .await + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result<()> { - tracing::debug!("java.lang.Integer::({this:?}, {value:?})"); + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "I", value).await + } - jvm.put_field(&mut this, "value", "I", value).await?; + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value, 10).await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "I", value).await + } - Ok(()) + fn parse_value_raw(value: &str, radix: u32) -> Option { + if !(2..=36).contains(&radix) || value.is_empty() { + return None; + } + let mut chars = value.chars(); + let negative = match chars.next() { + Some('-') => true, + Some('+') => false, + Some(first) => { + chars = value.chars(); + let _ = first; + false + } + None => return None, + }; + let limit = if negative { i64::from(i32::MIN) } else { -i64::from(i32::MAX) }; + let mut result = 0i64; + let mut count = 0; + for value in chars { + let digit = i64::from(value.to_digit(radix)?); + if result < (limit + digit) / i64::from(radix) { + return None; + } + result = result * i64::from(radix) - digit; + count += 1; + } + if count == 0 || (!negative && result == i64::from(i32::MIN)) { + return None; + } + Some(if negative { result as i32 } else { -result as i32 }) } - async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { - tracing::debug!("java.lang.Integer::intValue({this:?})"); + async fn parse_value(jvm: &Jvm, value: &str, radix: u32) -> Result { + match Self::parse_value_raw(value, radix) { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } - let value = jvm.get_field(&this, "value", "I").await?; + async fn parse_int(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value, 10).await + } - Ok(value) + async fn parse_int_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value, radix as u32).await } async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { - tracing::debug!("java.lang.Integer::valueOf({value:?})"); - - let instance = jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) + } - Ok(instance.into()) + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value, 10).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) } - async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { - tracing::debug!("java.lang.Integer::toString({this:?})"); + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value, radix as u32).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) + } - let value: i32 = jvm.get_field(&this, "value", "I").await?; + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let (sign, body) = if let Some(body) = value.strip_prefix('-') { + ("-", body) + } else if let Some(body) = value.strip_prefix('+') { + ("+", body) + } else { + ("", value.as_str()) + }; + let (radix, body) = if let Some(body) = body + .strip_prefix("0x") + .or_else(|| body.strip_prefix("0X")) + .or_else(|| body.strip_prefix('#')) + { + (16, body) + } else if body.starts_with('0') && body.len() > 1 { + (8, &body[1..]) + } else { + (10, body) + }; + if body.starts_with('-') || body.starts_with('+') { + return Err(jvm.exception("java/lang/NumberFormatException", "Sign character in wrong position").await); + } + let mut signed = RustString::from(sign); + signed.push_str(body); + let parsed = Self::parse_value(jvm, &signed, radix).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (parsed,)).await?.into()) + } - let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + async fn get_integer(jvm: &Jvm, _: &mut RuntimeContext, key: ClassInstanceRef) -> Result> { + let default: ClassInstanceRef = None.into(); + jvm.invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + (key, default), + ) + .await + } - Ok(string.into()) + async fn get_integer_value_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: i32, + ) -> Result> { + let default = jvm.new_class("java/lang/Integer", "(I)V", (default,)).await?; + jvm.invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + (key, default), + ) + .await } - async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { - tracing::debug!("java.lang.Integer::toString({value:?})"); + async fn get_integer_object_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: ClassInstanceRef, + ) -> Result> { + if key.is_null() { + return Ok(default); + } + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if value.is_null() { + return Ok(default); + } + match jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (value,)) + .await + { + Ok(value) => Ok(value), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/lang/NumberFormatException") => Ok(default), + Err(error) => Err(error), + } + } - let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "I").await + } - Ok(string.into()) + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as i64) } - async fn parse_int(jvm: &Jvm, _: &mut RuntimeContext, s: ClassInstanceRef) -> Result { - tracing::debug!("java.lang.Integer::parseInt({s:?})"); + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as f32) + } - let s = JavaLangString::to_rust_string(jvm, &s).await?; + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as f64) + } - match s.parse() { - Ok(x) => Ok(x), - Err(_) => Err(jvm - .exception("java/lang/NumberFormatException", &format!("For input string: \"{s}\"")) - .await), + fn format_value(value: i64, radix: u32) -> RustString { + let radix = if (2..=36).contains(&radix) { radix } else { 10 }; + if value == 0 { + return "0".into(); + } + let negative = value < 0; + let mut magnitude = value.unsigned_abs(); + let mut result = RustString::new(); + while magnitude != 0 { + let digit = (magnitude % u64::from(radix)) as u32; + result.push(char::from_digit(digit, radix).unwrap_or('0')); + magnitude /= u64::from(radix); + } + if negative { + result.push('-'); } + result.chars().rev().collect() + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value as i64, 10)).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value as i64, 10)).await?.into()) + } + + async fn to_string_radix_static(jvm: &Jvm, _: &mut RuntimeContext, value: i32, radix: i32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value as i64, radix as u32)) + .await? + .into()) + } + + async fn to_binary_string(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { + let text = if value < 0 { + format!("{:032b}", value as u32) + } else { + Self::format_value(value as i64, 2) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + + async fn to_octal_string(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { + let text = if value < 0 { + format!("{:011o}", value as u32) + } else { + Self::format_value(value as i64, 8) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) } async fn to_hex_string(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { - tracing::debug!("java.lang.Integer::toHexString({value:?})"); + let text = if value < 0 { + format!("{:08x}", value as u32) + } else { + Self::format_value(value as i64, 16) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } - let string = JavaLangString::from_rust_string(jvm, &format!("{value:x}")).await?; + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "I").await + } - Ok(string.into()) + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Integer") { + return Ok(false); + } + let this_value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + let other_value: i32 = jvm.invoke_virtual(&other, "intValue", "()I", ()).await?; + Ok(this_value == other_value) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let this_value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + let other_value: i32 = jvm.invoke_virtual(&other, "intValue", "()I", ()).await?; + Ok(this_value.cmp(&other_value) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Integer") { + return Err(jvm.exception("java/lang/ClassCastException", "java/lang/Object is not Integer").await); + } + let other = ClassInstanceRef::::from(other.instance); + let this_value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + let other_value: i32 = jvm.invoke_virtual(&other, "intValue", "()I", ()).await?; + Ok(this_value.cmp(&other_value) as i32) } } diff --git a/java_runtime/src/classes/java/lang/long.rs b/java_runtime/src/classes/java/lang/long.rs new file mode 100644 index 00000000..bd8d6959 --- /dev/null +++ b/java_runtime/src/classes/java/lang/long.rs @@ -0,0 +1,409 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, JavaError, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Long +pub struct Long; + +impl Long { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Long", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(J)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseLong", + "(Ljava/lang/String;)J", + Self::parse_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseLong", + "(Ljava/lang/String;I)J", + Self::parse_long_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Long;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Long;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Long;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLong", + "(Ljava/lang/String;)Ljava/lang/Long;", + Self::get_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLong", + "(Ljava/lang/String;J)Ljava/lang/Long;", + Self::get_long_value_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLong", + "(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;", + Self::get_long_object_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(J)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toString", + "(JI)Ljava/lang/String;", + Self::to_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toBinaryString", + "(J)Ljava/lang/String;", + Self::to_binary_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toOctalString", + "(J)Ljava/lang/String;", + Self::to_octal_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toHexString", + "(J)Ljava/lang/String;", + Self::to_hex_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Long;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "J", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "J", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "J", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Long", "MIN_VALUE", "J", i64::MIN).await?; + jvm.put_static_field("java/lang/Long", "MAX_VALUE", "J", i64::MAX).await?; + jvm.put_static_field( + "java/lang/Long", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "long").await?, + ) + .await + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i64) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "J", value).await + } + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (value,)) + .await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "J", value).await + } + fn parse_raw(value: &str, radix: u32) -> Option { + if !(2..=36).contains(&radix) || value.is_empty() { + return None; + } + let negative = value.starts_with('-'); + let body = value.strip_prefix('-').or_else(|| value.strip_prefix('+')).unwrap_or(value); + if body.is_empty() { + return None; + } + let limit = if negative { i128::from(i64::MIN) } else { -i128::from(i64::MAX) }; + let mut result = 0i128; + for value in body.chars() { + let digit = i128::from(value.to_digit(radix)?); + if result < (limit + digit) / i128::from(radix) { + return None; + } + result = result * i128::from(radix) - digit; + } + if !negative && result == i128::from(i64::MIN) { + return None; + } + Some(if negative { result as i64 } else { -result as i64 }) + } + async fn parse_long(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + match Self::parse_raw(&value, 10) { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + async fn parse_long_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + match Self::parse_raw(&value, radix as u32) { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let value: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (value,)) + .await?; + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + let value: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (value, radix)) + .await?; + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let (sign, body) = if let Some(body) = value.strip_prefix('-') { + ("-", body) + } else if let Some(body) = value.strip_prefix('+') { + ("+", body) + } else { + ("", value.as_str()) + }; + let (radix, body) = if let Some(body) = body + .strip_prefix("0x") + .or_else(|| body.strip_prefix("0X")) + .or_else(|| body.strip_prefix('#')) + { + (16, body) + } else if body.starts_with('0') && body.len() > 1 { + (8, &body[1..]) + } else { + (10, body) + }; + if body.starts_with('-') || body.starts_with('+') { + return Err(jvm.exception("java/lang/NumberFormatException", "Sign character in wrong position").await); + } + let mut signed = RustString::from(sign); + signed.push_str(body); + let value = match Self::parse_raw(&signed, radix) { + Some(value) => value, + None => return Err(jvm.exception("java/lang/NumberFormatException", "invalid long").await), + }; + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } + async fn get_long(jvm: &Jvm, _: &mut RuntimeContext, key: ClassInstanceRef) -> Result> { + let default: ClassInstanceRef = None.into(); + jvm.invoke_static( + "java/lang/Long", + "getLong", + "(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;", + (key, default), + ) + .await + } + async fn get_long_value_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: i64, + ) -> Result> { + let default = jvm.new_class("java/lang/Long", "(J)V", (default,)).await?; + jvm.invoke_static( + "java/lang/Long", + "getLong", + "(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;", + (key, default), + ) + .await + } + async fn get_long_object_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: ClassInstanceRef, + ) -> Result> { + if key.is_null() { + return Ok(default); + } + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if value.is_null() { + return Ok(default); + } + match jvm + .invoke_static("java/lang/Long", "decode", "(Ljava/lang/String;)Ljava/lang/Long;", (value,)) + .await + { + Ok(value) => Ok(value), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/lang/NumberFormatException") => Ok(default), + Err(error) => Err(error), + } + } + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as i8) + } + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as i16) + } + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as i32) + } + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "J").await + } + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as f32) + } + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as f64) + } + fn format_value(value: i64, radix: u32) -> RustString { + let radix = if (2..=36).contains(&radix) { radix } else { 10 }; + if value == 0 { + return "0".into(); + } + let negative = value < 0; + let mut magnitude = value.unsigned_abs(); + let mut result = RustString::new(); + while magnitude != 0 { + result.push(char::from_digit((magnitude % u64::from(radix)) as u32, radix).unwrap_or('0')); + magnitude /= u64::from(radix); + } + if negative { + result.push('-'); + } + result.chars().rev().collect() + } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value, 10)).await?.into()) + } + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value, 10)).await?.into()) + } + async fn to_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: i64, radix: i32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value, radix as u32)) + .await? + .into()) + } + async fn to_binary_string(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + let text = if value < 0 { + format!("{:064b}", value as u64) + } else { + Self::format_value(value, 2) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + async fn to_octal_string(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + let text = if value < 0 { + format!("{:022o}", value as u64) + } else { + Self::format_value(value, 8) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + async fn to_hex_string(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + let text = if value < 0 { + format!("{:016x}", value as u64) + } else { + Self::format_value(value, 16) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + Ok((value ^ (value >> 32)) as i32) + } + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Long") { + return Ok(false); + } + Ok( + jvm.invoke_virtual::<_, i64>(&this, "longValue", "()J", ()).await? + == jvm.invoke_virtual::<_, i64>(&other, "longValue", "()J", ()).await?, + ) + } + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + let right: i64 = jvm.invoke_virtual(&other, "longValue", "()J", ()).await?; + Ok(left.cmp(&right) as i32) + } + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Long") { + return Err(jvm.exception("java/lang/ClassCastException", "not Long").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + let right: i64 = jvm.invoke_virtual(&other, "longValue", "()J", ()).await?; + Ok(left.cmp(&right) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/number.rs b/java_runtime/src/classes/java/lang/number.rs new file mode 100644 index 00000000..28229567 --- /dev/null +++ b/java_runtime/src/classes/java/lang/number.rs @@ -0,0 +1,45 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext}; + +// abstract class java.lang.Number +pub struct Number; + +impl Number { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Number", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract("intValue", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("longValue", "()J", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("floatValue", "()F", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("doubleValue", "()D", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as i8) + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as i16) + } +} diff --git a/java_runtime/src/classes/java/lang/short.rs b/java_runtime/src/classes/java/lang/short.rs new file mode 100644 index 00000000..21637c8a --- /dev/null +++ b/java_runtime/src/classes/java/lang/short.rs @@ -0,0 +1,225 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Short +pub struct Short; + +impl Short { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Short", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(S)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseShort", + "(Ljava/lang/String;)S", + Self::parse_short, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseShort", + "(Ljava/lang/String;I)S", + Self::parse_short_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Short;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Short;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Short;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(S)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Short;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "S", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "S", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "S", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Short", "MIN_VALUE", "S", i16::MIN).await?; + jvm.put_static_field("java/lang/Short", "MAX_VALUE", "S", i16::MAX).await?; + jvm.put_static_field( + "java/lang/Short", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "short").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i16) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "S", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (value,)) + .await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "S", value).await + } + + async fn parse_short(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", (value,)) + .await?; + if !(i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i16) + } + + async fn parse_short_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (value, radix)) + .await?; + if !(i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i16) + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let value: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (value,)) + .await?; + Ok(jvm.new_class("java/lang/Short", "(S)V", (value,)).await?.into()) + } + + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + let value: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (value, radix)) + .await?; + Ok(jvm.new_class("java/lang/Short", "(S)V", (value,)).await?.into()) + } + + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let integer = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (value,)) + .await?; + let value: i32 = jvm.invoke_virtual(&integer, "intValue", "()I", ()).await?; + if !(i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&value) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(jvm.new_class("java/lang/Short", "(S)V", (value as i16,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok((jvm.get_field::(&this, "value", "S").await?) as i8) + } + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "S").await + } + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as i32) + } + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as i64) + } + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as f32) + } + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as f64) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i16 = jvm.invoke_virtual(&this, "shortValue", "()S", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i16) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as i32) + } + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Short") { + return Ok(false); + } + Ok(jvm.invoke_virtual::<_, i16>(&this, "shortValue", "()S", ()).await? + == jvm.invoke_virtual::<_, i16>(&other, "shortValue", "()S", ()).await?) + } + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: i16 = jvm.invoke_virtual(&this, "shortValue", "()S", ()).await?; + let right: i16 = jvm.invoke_virtual(&other, "shortValue", "()S", ()).await?; + Ok(left.cmp(&right) as i32) + } + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Short") { + return Err(jvm.exception("java/lang/ClassCastException", "not Short").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: i16 = jvm.invoke_virtual(&this, "shortValue", "()S", ()).await?; + let right: i16 = jvm.invoke_virtual(&other, "shortValue", "()S", ()).await?; + Ok(left.cmp(&right) as i32) + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index cbd2fced..0fe3fa8f 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -36,16 +36,21 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::ArithmeticException::as_proto(), crate::classes::java::lang::ArrayIndexOutOfBoundsException::as_proto(), crate::classes::java::lang::ArrayStoreException::as_proto(), + crate::classes::java::lang::Boolean::as_proto(), crate::classes::java::lang::Class::as_proto(), crate::classes::java::lang::ClassCastException::as_proto(), crate::classes::java::lang::ClassLoader::as_proto(), crate::classes::java::lang::ClassNotFoundException::as_proto(), + crate::classes::java::lang::Byte::as_proto(), + crate::classes::java::lang::Character::as_proto(), crate::classes::java::lang::Cloneable::as_proto(), crate::classes::java::lang::CloneNotSupportedException::as_proto(), crate::classes::java::lang::Comparable::as_proto(), + crate::classes::java::lang::Double::as_proto(), crate::classes::java::lang::Error::as_proto(), crate::classes::java::lang::Exception::as_proto(), crate::classes::java::lang::ExceptionInInitializerError::as_proto(), + crate::classes::java::lang::Float::as_proto(), crate::classes::java::lang::IllegalArgumentException::as_proto(), crate::classes::java::lang::InstantiationError::as_proto(), crate::classes::java::lang::IncompatibleClassChangeError::as_proto(), @@ -53,6 +58,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::Integer::as_proto(), crate::classes::java::lang::InterruptedException::as_proto(), crate::classes::java::lang::LinkageError::as_proto(), + crate::classes::java::lang::Long::as_proto(), crate::classes::java::lang::Math::as_proto(), crate::classes::java::lang::NegativeArraySizeException::as_proto(), crate::classes::java::lang::NoClassDefFoundError::as_proto(), @@ -60,11 +66,13 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::NoSuchMethodError::as_proto(), crate::classes::java::lang::NullPointerException::as_proto(), crate::classes::java::lang::NumberFormatException::as_proto(), + crate::classes::java::lang::Number::as_proto(), crate::classes::java::lang::Object::as_proto(), crate::classes::java::lang::Runnable::as_proto(), crate::classes::java::lang::Runtime::as_proto(), crate::classes::java::lang::RuntimeException::as_proto(), crate::classes::java::lang::SecurityException::as_proto(), + crate::classes::java::lang::Short::as_proto(), crate::classes::java::lang::String::as_proto(), crate::classes::java::lang::StringBuffer::as_proto(), crate::classes::java::lang::StringIndexOutOfBoundsException::as_proto(), diff --git a/java_runtime/tests/classes/java/lang/mod.rs b/java_runtime/tests/classes/java/lang/mod.rs index 0ccbbcec..9ab8df55 100644 --- a/java_runtime/tests/classes/java/lang/mod.rs +++ b/java_runtime/tests/classes/java/lang/mod.rs @@ -1,7 +1,15 @@ +mod test_boolean; +mod test_byte; +mod test_character; mod test_class; +mod test_double; +mod test_float; mod test_integer; +mod test_long; mod test_math; +mod test_number; mod test_object; +mod test_short; mod test_string; mod test_string_buffer; mod test_thread; diff --git a/java_runtime/tests/classes/java/lang/test_boolean.rs b/java_runtime/tests/classes/java/lang/test_boolean.rs new file mode 100644 index 00000000..eda529ef --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_boolean.rs @@ -0,0 +1,136 @@ +use java_runtime::classes::java::lang::{Boolean, Object, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_boolean_string_constants_and_type() -> Result<()> { + let jvm = test_jvm().await?; + + let true_value: ClassInstanceRef = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?.into(); + assert!(jvm.invoke_virtual::<_, bool>(&true_value, "booleanValue", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&true_value, "hashCode", "()I", ()).await?, 1231); + + let text: ClassInstanceRef = jvm.invoke_virtual(&true_value, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "true"); + + let mixed_case = JavaLangString::from_rust_string(&jvm, "TrUe").await?; + let parsed: ClassInstanceRef = jvm + .invoke_static("java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Ljava/lang/Boolean;", (mixed_case,)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&parsed, "booleanValue", "()Z", ()).await?); + + let padded = JavaLangString::from_rust_string(&jvm, " true").await?; + let parsed: ClassInstanceRef = jvm + .invoke_static("java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Ljava/lang/Boolean;", (padded,)) + .await?; + assert!(!jvm.invoke_virtual::<_, bool>(&parsed, "booleanValue", "()Z", ()).await?); + + let null_string: ClassInstanceRef = None.into(); + let parsed: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Boolean", + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Boolean;", + (null_string.clone(),), + ) + .await?; + assert!(!jvm.invoke_virtual::<_, bool>(&parsed, "booleanValue", "()Z", ()).await?); + + let from_null: ClassInstanceRef = jvm.new_class("java/lang/Boolean", "(Ljava/lang/String;)V", (null_string,)).await?.into(); + assert!(!jvm.invoke_virtual::<_, bool>(&from_null, "booleanValue", "()Z", ()).await?); + + let true_constant: ClassInstanceRef = jvm.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;").await?; + let false_constant: ClassInstanceRef = jvm.get_static_field("java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&true_constant, "booleanValue", "()Z", ()).await?); + assert!(!jvm.invoke_virtual::<_, bool>(&false_constant, "booleanValue", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&false_constant, "hashCode", "()I", ()).await?, 1237); + assert!( + jvm.invoke_virtual::<_, bool>(&true_constant, "equals", "(Ljava/lang/Object;)Z", (true_value,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&false_constant, "equals", "(Ljava/lang/Object;)Z", (None,)) + .await? + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&false_constant, "equals", "(Ljava/lang/Object;)Z", (object,)) + .await? + ); + assert!(!jvm.is_instance(&**true_constant, "java/lang/Comparable")); + assert!(jvm.is_instance(&**true_constant, "java/io/Serializable")); + + let typ = jvm.get_static_field("java/lang/Boolean", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "boolean"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + Ok(()) +} + +#[tokio::test] +async fn test_boolean_property_and_primitive_value_of_exclusion() -> Result<()> { + let jvm = test_jvm().await?; + + let key = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.test").await?; + let value = JavaLangString::from_rust_string(&jvm, "TRUE").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), value), + ) + .await?; + assert!( + jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (key,)) + .await? + ); + + let missing = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.missing").await?; + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (missing,)) + .await? + ); + + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (None,)) + .await? + ); + let false_key = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.false").await?; + let false_value = JavaLangString::from_rust_string(&jvm, "not-true").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (false_key.clone(), false_value), + ) + .await?; + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (false_key,)) + .await? + ); + + let result: Result> = jvm.invoke_static("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", (true,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Boolean.valueOf(boolean) must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + + let left = jvm.new_class("java/lang/Boolean", "(Z)V", (false,)).await?; + let right = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?; + let result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Boolean;)I", (right.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Boolean typed compareTo must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + let result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Object;)I", (right,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Boolean raw compareTo must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_byte.rs b/java_runtime/tests/classes/java/lang/test_byte.rs new file mode 100644 index 00000000..b49458bc --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_byte.rs @@ -0,0 +1,111 @@ +use java_runtime::classes::java::lang::{Byte, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_byte_api() -> Result<()> { + let jvm = test_jvm().await?; + let value = jvm.new_class("java/lang/Byte", "(B)V", (-2i8,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, -2.0); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, -2.0); + let text: jvm::ClassInstanceRef = + jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "-2"); + + let min = jvm.get_static_field::("java/lang/Byte", "MIN_VALUE", "B").await?; + let max = jvm.get_static_field::("java/lang/Byte", "MAX_VALUE", "B").await?; + assert_eq!((min, max), (i8::MIN, i8::MAX)); + let typ = jvm.get_static_field("java/lang/Byte", "TYPE", "Ljava/lang/Class;").await?; + let name: jvm::ClassInstanceRef = + jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "byte"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + let string = JavaLangString::from_rust_string(&jvm, "0x7f").await?; + let decoded = jvm + .invoke_static("java/lang/Byte", "decode", "(Ljava/lang/String;)Ljava/lang/Byte;", (string,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&decoded, "byteValue", "()B", ()).await?, 127); + + let invalid = JavaLangString::from_rust_string(&jvm, "128").await?; + let result: Result> = jvm + .invoke_static("java/lang/Byte", "valueOf", "(Ljava/lang/String;)Ljava/lang/Byte;", (invalid,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte.valueOf must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Byte;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, radix, expected) in [ + ("-10000000", 2, i8::MIN), + ("1111111", 2, i8::MAX), + ("-128", 10, i8::MIN), + ("127", 10, i8::MAX), + ("-80", 16, i8::MIN), + ("7f", 16, i8::MAX), + ("-3k", 36, i8::MIN), + ("3j", 36, i8::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;I)B", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + for text in ["-129", "128"] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm.invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", (text,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte.parseByte must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let equal = jvm.new_class("java/lang/Byte", "(B)V", (-2i8,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (equal.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, -2); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Object;)I", (equal,)) + .await?, + 0 + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte raw compare must reject another type"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, expected) in [("#7f", 127i8), ("0177", 127i8)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Byte", "decode", "(Ljava/lang/String;)Ljava/lang/Byte;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&decoded, "byteValue", "()B", ()).await?, expected); + } + + let typ = jvm.get_static_field("java/lang/Byte", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "byte"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_character.rs b/java_runtime/tests/classes/java/lang/test_character.rs new file mode 100644 index 00000000..3318a4cf --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_character.rs @@ -0,0 +1,368 @@ +use java_runtime::classes::java::lang::{Character, String}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_character_value_constants_and_type() -> Result<()> { + let jvm = test_jvm().await?; + + let value: ClassInstanceRef = jvm.new_class("java/lang/Character", "(C)V", ('A' as JavaChar,)).await?.into(); + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&value, "charValue", "()C", ()).await?, 'A' as JavaChar); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, 'A' as i32); + + let text: ClassInstanceRef = jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "A"); + + assert_eq!(jvm.get_static_field::("java/lang/Character", "MIN_VALUE", "C").await?, 0); + assert_eq!(jvm.get_static_field::("java/lang/Character", "MAX_VALUE", "C").await?, u16::MAX); + assert_eq!(jvm.get_static_field::("java/lang/Character", "MIN_RADIX", "I").await?, 2); + assert_eq!(jvm.get_static_field::("java/lang/Character", "MAX_RADIX", "I").await?, 36); + + for (name, expected) in [ + ("UNASSIGNED", 0i8), + ("UPPERCASE_LETTER", 1), + ("LOWERCASE_LETTER", 2), + ("TITLECASE_LETTER", 3), + ("MODIFIER_LETTER", 4), + ("OTHER_LETTER", 5), + ("NON_SPACING_MARK", 6), + ("ENCLOSING_MARK", 7), + ("COMBINING_SPACING_MARK", 8), + ("DECIMAL_DIGIT_NUMBER", 9), + ("LETTER_NUMBER", 10), + ("OTHER_NUMBER", 11), + ("SPACE_SEPARATOR", 12), + ("LINE_SEPARATOR", 13), + ("PARAGRAPH_SEPARATOR", 14), + ("CONTROL", 15), + ("FORMAT", 16), + ("PRIVATE_USE", 18), + ("SURROGATE", 19), + ("DASH_PUNCTUATION", 20), + ("START_PUNCTUATION", 21), + ("END_PUNCTUATION", 22), + ("CONNECTOR_PUNCTUATION", 23), + ("OTHER_PUNCTUATION", 24), + ("MATH_SYMBOL", 25), + ("CURRENCY_SYMBOL", 26), + ("MODIFIER_SYMBOL", 27), + ("OTHER_SYMBOL", 28), + ] { + assert_eq!(jvm.get_static_field::("java/lang/Character", name, "B").await?, expected); + } + + let typ = jvm.get_static_field("java/lang/Character", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "char"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + assert!(jvm.is_instance(&**value, "java/lang/Comparable")); + assert!(jvm.is_instance(&**value, "java/io/Serializable")); + + let result: Result> = jvm + .invoke_static("java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", ('A' as JavaChar,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Character.valueOf(char) must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + + Ok(()) +} + +#[tokio::test] +async fn test_character_ascii_non_ascii_and_radix() -> Result<()> { + let jvm = test_jvm().await?; + + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isLetter", "(C)Z", ('A' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isUpperCase", "(C)Z", ('A' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isLowerCase", "(C)Z", ('z' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isDigit", "(C)Z", ('7' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierStart", "(C)Z", ('_' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierStart", "(C)Z", ('$' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierPart", "(C)Z", ('7' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isWhitespace", "(C)Z", ('\n' as JavaChar,)) + .await? + ); + for value in ['\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}', '\u{0020}'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isSpace", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['\u{000b}', '\u{001c}'] { + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Character", "isSpace", "(C)Z", (value as JavaChar,)) + .await? + ); + } + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isSpaceChar", "(C)Z", ('\u{00a0}' as JavaChar,)) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Character", "isWhitespace", "(C)Z", ('\u{00a0}' as JavaChar,)) + .await? + ); + + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isLetter", "(C)Z", ('é' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isUpperCase", "(C)Z", ('Ω' as JavaChar,)) + .await? + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "toLowerCase", "(C)C", ('Ω' as JavaChar,)) + .await?, + 'ω' as JavaChar + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "toUpperCase", "(C)C", ('ß' as JavaChar,)) + .await?, + 'ß' as JavaChar + ); + + let arabic_three = '٣' as JavaChar; + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isDigit", "(C)Z", (arabic_three,)) + .await? + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", (arabic_three, 10)) + .await?, + 3 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getNumericValue", "(C)I", (arabic_three,)) + .await?, + 3 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", ('A' as JavaChar, 16)) + .await?, + 10 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", ('g' as JavaChar, 16)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "forDigit", "(II)C", (15, 16)) + .await?, + 'f' as JavaChar + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "forDigit", "(II)C", (16, 16)) + .await?, + 0 + ); + + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('A' as JavaChar,)) + .await?, + 1 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('é' as JavaChar,)) + .await?, + 2 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (arabic_three,)) + .await?, + 9 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('\u{0301}' as JavaChar,)) + .await?, + 6 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('Ⅷ' as JavaChar,)) + .await?, + 10 + ); + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Character", "isDefined", "(C)Z", (0x0378 as JavaChar,)) + .await? + ); + + for (value, expected_type) in [('¡', 24), ('«', 21), ('»', 22), ('§', 24)] { + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (value as JavaChar,)) + .await?, + expected_type + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isDefined", "(C)Z", (value as JavaChar,)) + .await? + ); + } + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (0xe000 as JavaChar,)) + .await?, + 18 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (0xfeff as JavaChar,)) + .await?, + 16 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (0xffff as JavaChar,)) + .await?, + 0 + ); + + for value in ['£', '‿', 'Ⅰ'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierStart", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['Ⅰ', '\u{0301}', '\u{0000}'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierPart", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['£', '‿', 'Ⅰ'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaLetter", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['7', 'Ⅰ', '\u{0301}', '\u{0000}'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaLetterOrDigit", "(C)Z", (value as JavaChar,)) + .await? + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_character_surrogate_and_compare_errors() -> Result<()> { + let jvm = test_jvm().await?; + let surrogate = 0xd800 as JavaChar; + + let value: ClassInstanceRef = jvm.new_class("java/lang/Character", "(C)V", (surrogate,)).await?.into(); + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&value, "charValue", "()C", ()).await?, surrogate); + let text: ClassInstanceRef = jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; + let chars: ClassInstanceRef> = jvm.get_field(&text, "value", "[C").await?; + assert_eq!(jvm.load_array::(&chars, 0, 1).await?, [surrogate]); + + for method in [ + "isLowerCase", + "isUpperCase", + "isTitleCase", + "isDigit", + "isDefined", + "isLetter", + "isLetterOrDigit", + "isJavaIdentifierStart", + "isJavaIdentifierPart", + "isUnicodeIdentifierStart", + "isUnicodeIdentifierPart", + "isIdentifierIgnorable", + "isSpace", + "isSpaceChar", + "isWhitespace", + "isISOControl", + ] { + assert!(!jvm.invoke_static::<_, bool>("java/lang/Character", method, "(C)Z", (surrogate,)).await?); + } + for method in ["toLowerCase", "toUpperCase", "toTitleCase"] { + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", method, "(C)C", (surrogate,)) + .await?, + surrogate + ); + } + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", (surrogate, 10)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getNumericValue", "(C)I", (surrogate,)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (surrogate,)) + .await?, + 0 + ); + + let other: ClassInstanceRef = jvm.new_class("java/lang/Character", "(C)V", ('Z' as JavaChar,)).await?.into(); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Character;)I", (other.clone(),)) + .await?, + 1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&other, "compareTo", "(Ljava/lang/Object;)I", (value.clone(),)) + .await?, + -1 + ); + + let null_character: ClassInstanceRef = None.into(); + let result: Result = jvm + .invoke_virtual(&value, "compareTo", "(Ljava/lang/Character;)I", (null_character,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Character.compareTo(Character) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_object: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (null_object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Character.compareTo(Object) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let wrong = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm + .invoke_virtual(&value, "compareTo", "(Ljava/lang/Character;)I", (wrong.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed Character.compareTo must reject a non-Character instance"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (wrong,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("raw Character.compareTo must reject a non-Character instance"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_class.rs b/java_runtime/tests/classes/java/lang/test_class.rs index 9e5cab51..6ad89df1 100644 --- a/java_runtime/tests/classes/java/lang/test_class.rs +++ b/java_runtime/tests/classes/java/lang/test_class.rs @@ -1,6 +1,6 @@ -use java_runtime::classes::java::lang::Class; +use java_runtime::classes::java::lang::{Class, String}; use jvm::{ - ClassInstanceRef, Result, + ClassInstanceRef, JavaError, Result, runtime::{JavaLangClass, JavaLangString}, }; @@ -56,5 +56,113 @@ async fn test_for_name() -> Result<()> { let rust_class = JavaLangClass::to_rust_class(&jvm, &class).await?; assert_eq!(rust_class.name(), "java/lang/String"); + let result: Result> = jvm + .invoke_static("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Class.forName(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_primitive_class_api() -> Result<()> { + let jvm = test_jvm().await?; + + for name in ["boolean", "byte", "char", "short", "int", "long", "float", "double"] { + let primitive = JavaLangClass::from_rust_primitive(&jvm, name).await?; + let primitive_name = JavaLangClass::name(&jvm, &primitive).await?; + assert_eq!(primitive_name, name); + + let virtual_name: ClassInstanceRef = jvm.invoke_virtual(&primitive, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &virtual_name).await?, name); + + let is_primitive: bool = jvm.invoke_virtual(&primitive, "isPrimitive", "()Z", ()).await?; + assert!(is_primitive); + + let class_name = JavaLangString::from_rust_string(&jvm, name).await?; + let result: Result> = jvm + .invoke_static("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", (class_name,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Class.forName must reject primitive source names"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassNotFoundException")); + } + + let object_class = jvm.resolve_class("java/lang/Object").await?.java_class(); + let string_class = jvm.resolve_class("java/lang/String").await?.java_class(); + let primitive = JavaLangClass::from_rust_primitive(&jvm, "int").await?; + let other_primitive = JavaLangClass::from_rust_primitive(&jvm, "long").await?; + + let result: bool = jvm + .invoke_virtual(&primitive, "isAssignableFrom", "(Ljava/lang/Class;)Z", (primitive.clone(),)) + .await?; + assert!(result); + + let result: bool = jvm + .invoke_virtual(&primitive, "isAssignableFrom", "(Ljava/lang/Class;)Z", (other_primitive,)) + .await?; + assert!(!result); + + let result: bool = jvm + .invoke_virtual(&primitive, "isAssignableFrom", "(Ljava/lang/Class;)Z", (string_class.clone(),)) + .await?; + assert!(!result); + + let result: bool = jvm + .invoke_virtual(&object_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (primitive,)) + .await?; + assert!(!result); + + let result: Result = jvm + .invoke_virtual(&object_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Class.isAssignableFrom(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_wrapper_type_fields_survive_gc() -> Result<()> { + let jvm = test_jvm().await?; + + for (wrapper, primitive_name) in [ + ("java/lang/Boolean", "boolean"), + ("java/lang/Byte", "byte"), + ("java/lang/Character", "char"), + ("java/lang/Short", "short"), + ("java/lang/Integer", "int"), + ("java/lang/Long", "long"), + ("java/lang/Float", "float"), + ("java/lang/Double", "double"), + ] { + let typ = jvm.get_static_field(wrapper, "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, primitive_name); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + } + + let _: () = jvm.invoke_static("java/lang/System", "gc", "()V", ()).await?; + + for wrapper in [ + "java/lang/Boolean", + "java/lang/Byte", + "java/lang/Character", + "java/lang/Short", + "java/lang/Integer", + "java/lang/Long", + "java/lang/Float", + "java/lang/Double", + ] { + let typ = jvm.get_static_field(wrapper, "TYPE", "Ljava/lang/Class;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + } + Ok(()) } diff --git a/java_runtime/tests/classes/java/lang/test_double.rs b/java_runtime/tests/classes/java/lang/test_double.rs new file mode 100644 index 00000000..cfe270c9 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_double.rs @@ -0,0 +1,243 @@ +use java_runtime::classes::java::lang::{Double, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_double_constructors_and_number_conversions() -> Result<()> { + let jvm = test_jvm().await?; + + let value = jvm.new_class("java/lang/Double", "(D)V", (65_537.75f64,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, 65_537.75); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, 65_537.75f32); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, 65_537); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, 65_537); + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, 1); + + let text = JavaLangString::from_rust_string(&jvm, "-3.5").await?; + let from_string = jvm.new_class("java/lang/Double", "(Ljava/lang/String;)V", (text,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f64>(&from_string, "doubleValue", "()D", ()).await?, -3.5); + + Ok(()) +} + +#[tokio::test] +async fn test_double_parse_value_of_and_format() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, expected) in [(" \t-1.25e2D\n", -125.0), (".5f", 0.5), ("+42.", 42.0)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: f64 = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (text,)) + .await?; + assert_eq!(parsed, expected); + } + for suffix in ['f', 'F', 'd', 'D'] { + let text = JavaLangString::from_rust_string(&jvm, &format!("1.5{suffix}")).await?; + let parsed: f64 = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (text,)) + .await?; + assert_eq!(parsed, 1.5); + } + + let infinity = JavaLangString::from_rust_string(&jvm, "-Infinity").await?; + let infinity: f64 = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (infinity,)) + .await?; + assert_eq!(infinity, f64::NEG_INFINITY); + + let nan = JavaLangString::from_rust_string(&jvm, "+NaN").await?; + let nan: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "valueOf", "(Ljava/lang/String;)Ljava/lang/Double;", (nan,)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&nan, "isNaN", "()Z", ()).await?); + + for (value, expected) in [(12.0f64, "12.0"), (-0.0, "-0.0"), (10_000_000.0, "1.0E7"), (0.000_125, "1.25E-4")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + for malformed in ["", "nan", "Infinityd", "1e", ".", "1_0", "0x1.0p0", "1.0 dd"] { + let malformed = JavaLangString::from_rust_string(&jvm, malformed).await?; + let result: Result = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (malformed,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Double.parseDouble must reject malformed input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let null_result: Result = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Double.parseDouble(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (value, expected) in [(f64::from_bits(1), "4.9E-324"), (f64::from_bits(0x8000_0000_0000_0001), "-4.9E-324")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + let min = JavaLangString::from_rust_string(&jvm, "4.9e-324").await?; + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (min,)) + .await? + .to_bits(), + 1 + ); + let overflow = JavaLangString::from_rust_string(&jvm, "1e10000").await?; + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (overflow,)) + .await?, + f64::INFINITY + ); + let underflow = JavaLangString::from_rust_string(&jvm, "-1e-10000").await?; + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (underflow,)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_double_bits_equality_hash_and_comparison() -> Result<()> { + let jvm = test_jvm().await?; + + let payload_nan_a: f64 = jvm + .invoke_static("java/lang/Double", "longBitsToDouble", "(J)D", (0x7ff8_0000_0000_0001i64,)) + .await?; + let payload_nan_b: f64 = jvm + .invoke_static("java/lang/Double", "longBitsToDouble", "(J)D", (0x7fff_ffff_ffff_ffffi64,)) + .await?; + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Double", "doubleToLongBits", "(D)J", (payload_nan_a,)) + .await?, + 0x7ff8_0000_0000_0000 + ); + + let raw = (-12.5f64).to_bits() as i64; + let round_trip: f64 = jvm.invoke_static("java/lang/Double", "longBitsToDouble", "(J)D", (raw,)).await?; + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Double", "doubleToLongBits", "(D)J", (round_trip,)) + .await?, + raw + ); + + let nan_a = jvm.new_class("java/lang/Double", "(D)V", (payload_nan_a,)).await?; + let nan_b = jvm.new_class("java/lang/Double", "(D)V", (payload_nan_b,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&nan_a, "equals", "(Ljava/lang/Object;)Z", (nan_b.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nan_a, "hashCode", "()I", ()).await?, 0x7ff8_0000); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Double;)I", (nan_b,)) + .await?, + 0 + ); + + let negative_zero = jvm.new_class("java/lang/Double", "(D)V", (-0.0f64,)).await?; + let positive_zero = jvm.new_class("java/lang/Double", "(D)V", (0.0f64,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&negative_zero, "equals", "(Ljava/lang/Object;)Z", (positive_zero.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Double;)I", (positive_zero.clone(),),) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (positive_zero,)) + .await?, + -1 + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&negative_zero, "hashCode", "()I", ()).await?, i32::MIN); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&jvm.new_class("java/lang/Double", "(D)V", (0.0f64,)).await?, "hashCode", "()I", (),) + .await?, + 0 + ); + + let infinity = jvm.new_class("java/lang/Double", "(D)V", (f64::INFINITY,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Double;)I", (infinity,)) + .await?, + 1 + ); + + let typed_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Double;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null else { + panic!("Double typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let raw_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = raw_null else { + panic!("Double raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let wrong_type: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = wrong_type else { + panic!("Double raw compare wrong type must throw CCE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + for (value, int_value, long_value) in [ + (f64::NAN, 0, 0i64), + (f64::INFINITY, i32::MAX, i64::MAX), + (f64::NEG_INFINITY, i32::MIN, i64::MIN), + ] { + let wrapper = jvm.new_class("java/lang/Double", "(D)V", (value,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&wrapper, "intValue", "()I", ()).await?, int_value); + assert_eq!(jvm.invoke_virtual::<_, i64>(&wrapper, "longValue", "()J", ()).await?, long_value); + } + let maximum = jvm.new_class("java/lang/Double", "(D)V", (f64::MAX,)).await?; + assert!(jvm.invoke_virtual::<_, f32>(&maximum, "floatValue", "()F", ()).await?.is_infinite()); + let minimum = jvm.new_class("java/lang/Double", "(D)V", (f64::from_bits(1),)).await?; + assert_eq!(jvm.invoke_virtual::<_, f32>(&minimum, "floatValue", "()F", ()).await?.to_bits(), 0); + + Ok(()) +} + +#[tokio::test] +async fn test_double_constants_type_and_predicates() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!( + jvm.get_static_field::("java/lang/Double", "POSITIVE_INFINITY", "D").await?, + f64::INFINITY + ); + assert_eq!( + jvm.get_static_field::("java/lang/Double", "NEGATIVE_INFINITY", "D").await?, + f64::NEG_INFINITY + ); + assert!(jvm.get_static_field::("java/lang/Double", "NaN", "D").await?.is_nan()); + assert_eq!(jvm.get_static_field::("java/lang/Double", "MAX_VALUE", "D").await?, f64::MAX); + assert_eq!(jvm.get_static_field::("java/lang/Double", "MIN_VALUE", "D").await?.to_bits(), 1); + assert!(jvm.invoke_static::<_, bool>("java/lang/Double", "isNaN", "(D)Z", (f64::NAN,)).await?); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Double", "isInfinite", "(D)Z", (f64::NEG_INFINITY,)) + .await? + ); + + let typ = jvm.get_static_field("java/lang/Double", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "double"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_float.rs b/java_runtime/tests/classes/java/lang/test_float.rs new file mode 100644 index 00000000..1d1ef3f7 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_float.rs @@ -0,0 +1,233 @@ +use java_runtime::classes::java::lang::{Float, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_float_constructors_and_number_conversions() -> Result<()> { + let jvm = test_jvm().await?; + + let value = jvm.new_class("java/lang/Float", "(D)V", (130.75f64,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, 130.75); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, 130.75); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, 130); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, 130); + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, -126); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, 130); + + let text = JavaLangString::from_rust_string(&jvm, "-3.5").await?; + let from_string = jvm.new_class("java/lang/Float", "(Ljava/lang/String;)V", (text,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f32>(&from_string, "floatValue", "()F", ()).await?, -3.5); + + Ok(()) +} + +#[tokio::test] +async fn test_float_parse_value_of_and_format() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, expected) in [(" \t-1.25e2F\n", -125.0), (".5d", 0.5), ("+42.", 42.0)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: f32 = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (text,)) + .await?; + assert_eq!(parsed, expected); + } + for suffix in ['f', 'F', 'd', 'D'] { + let text = JavaLangString::from_rust_string(&jvm, &format!("1.5{suffix}")).await?; + let parsed: f32 = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (text,)) + .await?; + assert_eq!(parsed, 1.5); + } + + let infinity = JavaLangString::from_rust_string(&jvm, "+Infinity").await?; + let infinity: f32 = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (infinity,)) + .await?; + assert_eq!(infinity, f32::INFINITY); + + let nan = JavaLangString::from_rust_string(&jvm, "-NaN").await?; + let nan: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "valueOf", "(Ljava/lang/String;)Ljava/lang/Float;", (nan,)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&nan, "isNaN", "()Z", ()).await?); + + for (value, expected) in [(12.0f32, "12.0"), (-0.0, "-0.0"), (10_000_000.0, "1.0E7"), (0.000_125, "1.25E-4")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + for malformed in ["", "nan", "Infinityf", "1e", ".", "1_0", "0x1.0p0", "1.0 ff"] { + let malformed = JavaLangString::from_rust_string(&jvm, malformed).await?; + let result: Result = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (malformed,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Float.parseFloat must reject malformed input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let null_result: Result = jvm.invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (None,)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Float.parseFloat(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (value, expected) in [(f32::from_bits(1), "1.4E-45"), (f32::from_bits(0x8000_0001), "-1.4E-45")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + let min = JavaLangString::from_rust_string(&jvm, "1.4e-45").await?; + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (min,)) + .await? + .to_bits(), + 1 + ); + let overflow = JavaLangString::from_rust_string(&jvm, "1e1000").await?; + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (overflow,)) + .await?, + f32::INFINITY + ); + let underflow = JavaLangString::from_rust_string(&jvm, "-1e-1000").await?; + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (underflow,)) + .await? + .to_bits(), + (-0.0f32).to_bits() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_float_bits_equality_hash_and_comparison() -> Result<()> { + let jvm = test_jvm().await?; + + let payload_nan_a: f32 = jvm.invoke_static("java/lang/Float", "intBitsToFloat", "(I)F", (0x7fc0_0001i32,)).await?; + let payload_nan_b: f32 = jvm.invoke_static("java/lang/Float", "intBitsToFloat", "(I)F", (0x7fff_ffffi32,)).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Float", "floatToIntBits", "(F)I", (payload_nan_a,)) + .await?, + 0x7fc0_0000 + ); + + let raw = (-12.5f32).to_bits() as i32; + let round_trip: f32 = jvm.invoke_static("java/lang/Float", "intBitsToFloat", "(I)F", (raw,)).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Float", "floatToIntBits", "(F)I", (round_trip,)) + .await?, + raw + ); + + let nan_a = jvm.new_class("java/lang/Float", "(F)V", (payload_nan_a,)).await?; + let nan_b = jvm.new_class("java/lang/Float", "(F)V", (payload_nan_b,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&nan_a, "equals", "(Ljava/lang/Object;)Z", (nan_b.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nan_a, "hashCode", "()I", ()).await?, 0x7fc0_0000); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Float;)I", (nan_b,)) + .await?, + 0 + ); + + let negative_zero = jvm.new_class("java/lang/Float", "(F)V", (-0.0f32,)).await?; + let positive_zero = jvm.new_class("java/lang/Float", "(F)V", (0.0f32,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&negative_zero, "equals", "(Ljava/lang/Object;)Z", (positive_zero.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Float;)I", (positive_zero.clone(),),) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (positive_zero,)) + .await?, + -1 + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&negative_zero, "hashCode", "()I", ()).await?, i32::MIN); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&jvm.new_class("java/lang/Float", "(F)V", (0.0f32,)).await?, "hashCode", "()I", (),) + .await?, + 0 + ); + + let infinity = jvm.new_class("java/lang/Float", "(F)V", (f32::INFINITY,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Float;)I", (infinity,)) + .await?, + 1 + ); + + let typed_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Float;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null else { + panic!("Float typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let raw_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = raw_null else { + panic!("Float raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let wrong_type: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = wrong_type else { + panic!("Float raw compare wrong type must throw CCE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + for (value, int_value, long_value) in [ + (f32::NAN, 0, 0i64), + (f32::INFINITY, i32::MAX, i64::MAX), + (f32::NEG_INFINITY, i32::MIN, i64::MIN), + ] { + let wrapper = jvm.new_class("java/lang/Float", "(F)V", (value,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&wrapper, "intValue", "()I", ()).await?, int_value); + assert_eq!(jvm.invoke_virtual::<_, i64>(&wrapper, "longValue", "()J", ()).await?, long_value); + } + + Ok(()) +} + +#[tokio::test] +async fn test_float_constants_type_and_predicates() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!( + jvm.get_static_field::("java/lang/Float", "POSITIVE_INFINITY", "F").await?, + f32::INFINITY + ); + assert_eq!( + jvm.get_static_field::("java/lang/Float", "NEGATIVE_INFINITY", "F").await?, + f32::NEG_INFINITY + ); + assert!(jvm.get_static_field::("java/lang/Float", "NaN", "F").await?.is_nan()); + assert_eq!(jvm.get_static_field::("java/lang/Float", "MAX_VALUE", "F").await?, f32::MAX); + assert_eq!(jvm.get_static_field::("java/lang/Float", "MIN_VALUE", "F").await?.to_bits(), 1); + assert!(jvm.invoke_static::<_, bool>("java/lang/Float", "isNaN", "(F)Z", (f32::NAN,)).await?); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Float", "isInfinite", "(F)Z", (f32::NEG_INFINITY,)) + .await? + ); + + let typ = jvm.get_static_field("java/lang/Float", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "float"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_integer.rs b/java_runtime/tests/classes/java/lang/test_integer.rs index fc1cf600..dcf318e6 100644 --- a/java_runtime/tests/classes/java/lang/test_integer.rs +++ b/java_runtime/tests/classes/java/lang/test_integer.rs @@ -1,4 +1,5 @@ -use jvm::{JavaError, Result, runtime::JavaLangString}; +use java_runtime::classes::java::lang::{Integer, Object, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -13,6 +14,9 @@ async fn test_parse_int() -> Result<()> { .await? ); + let boxed: ClassInstanceRef = jvm.invoke_static("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", (42,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&boxed, "intValue", "()I", ()).await?, 42); + Ok(()) } @@ -32,3 +36,193 @@ async fn test_parse_int_invalid() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_integer_strict_api() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, radix, expected) in [("7fffffff", 16, i32::MAX), ("-80000000", 16, i32::MIN), ("z", 36, 35)] { + let string = JavaLangString::from_rust_string(&jvm, text).await?; + let value: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (string, radix)) + .await?; + assert_eq!(value, expected); + } + for (text, radix, expected) in [ + ("-10000000000000000000000000000000", 2, i32::MIN), + ("1111111111111111111111111111111", 2, i32::MAX), + ("-2147483648", 10, i32::MIN), + ("2147483647", 10, i32::MAX), + ("-zik0zk", 36, i32::MIN), + ("zik0zj", 36, i32::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + + let decoded_name = JavaLangString::from_rust_string(&jvm, "-0x80000000").await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (decoded_name,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&decoded, "intValue", "()I", ()).await?, i32::MIN); + + for (method, expected) in [ + ("toBinaryString", "11111111111111111111111111111111"), + ("toOctalString", "37777777777"), + ("toHexString", "ffffffff"), + ] { + let formatted: ClassInstanceRef = jvm.invoke_static("java/lang/Integer", method, "(I)Ljava/lang/String;", (-1,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &formatted).await?, expected); + } + + let left = jvm.new_class("java/lang/Integer", "(I)V", (1,)).await?; + let right = jvm.new_class("java/lang/Integer", "(I)V", (2,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&left, "compareTo", "(Ljava/lang/Integer;)I", (right.clone(),)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&left, "compareTo", "(Ljava/lang/Object;)I", (right.clone(),)) + .await?, + -1 + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&left, "equals", "(Ljava/lang/Object;)Z", (right.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&left, "hashCode", "()I", ()).await?, 1); + + let typed_null_result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Integer;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null_result else { + panic!("Integer typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Integer raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let wrong_result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = wrong_result else { + panic!("Integer raw compare wrong type must throw CCE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + let min = jvm.get_static_field::("java/lang/Integer", "MIN_VALUE", "I").await?; + let max = jvm.get_static_field::("java/lang/Integer", "MAX_VALUE", "I").await?; + assert_eq!((min, max), (i32::MIN, i32::MAX)); + let typ = jvm.get_static_field("java/lang/Integer", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "int"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + let _: () = jvm.invoke_static("java/lang/System", "gc", "()V", ()).await?; + let typ = jvm.get_static_field("java/lang/Integer", "TYPE", "Ljava/lang/Class;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} + +#[tokio::test] +async fn test_integer_rejects_invalid_forms_and_uses_property_defaults() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, radix) in [ + ("", 10), + ("+", 10), + ("-", 10), + (" 1", 10), + ("2147483648", 10), + ("-2147483649", 10), + ("1", 1), + ("1", 37), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (text, radix)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Integer.parseInt must reject invalid input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let misplaced_sign = JavaLangString::from_rust_string(&jvm, "0x-1").await?; + let result: Result> = jvm + .invoke_static( + "java/lang/Integer", + "decode", + "(Ljava/lang/String;)Ljava/lang/Integer;", + (misplaced_sign,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Integer.decode must reject a sign after the radix prefix"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + for (text, expected) in [("#7f", 127i32), ("0177", 127i32)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&decoded, "intValue", "()I", ()).await?, expected); + } + + let key = JavaLangString::from_rust_string(&jvm, "rustjava.test.integer").await?; + let value = JavaLangString::from_rust_string(&jvm, "0x2a").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), value), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;)Ljava/lang/Integer;", + (key.clone(),), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&property, "intValue", "()I", ()).await?, 42); + + let invalid = JavaLangString::from_rust_string(&jvm, "not-an-integer").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), invalid), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;I)Ljava/lang/Integer;", + (key.clone(), 17), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&property, "intValue", "()I", ()).await?, 17); + + let default = jvm.new_class("java/lang/Integer", "(I)V", (23,)).await?; + let property: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + (key, default), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&property, "intValue", "()I", ()).await?, 23); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_long.rs b/java_runtime/tests/classes/java/lang/test_long.rs new file mode 100644 index 00000000..b1075ad5 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_long.rs @@ -0,0 +1,163 @@ +use java_runtime::classes::java::lang::{Long, Object, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_long_api() -> Result<()> { + let jvm = test_jvm().await?; + let value = jvm.new_class("java/lang/Long", "(J)V", (i64::MIN,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, i64::MIN); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, i64::MIN as f32); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, i64::MIN as f64); + + for (input, radix, expected) in [("7fffffffffffffff", 16, i64::MAX), ("-8000000000000000", 16, i64::MIN)] { + let string = JavaLangString::from_rust_string(&jvm, input).await?; + let parsed: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (string, radix)) + .await?; + assert_eq!(parsed, expected); + } + + let value = jvm + .invoke_static::<_, jvm::ClassInstanceRef>( + "java/lang/Long", + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Long;", + (JavaLangString::from_rust_string(&jvm, "-1").await?,), + ) + .await?; + let hex: jvm::ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "toHexString", "(J)Ljava/lang/String;", (-1i64,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &hex).await?, "ffffffffffffffff"); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, -1); + + let overflow = JavaLangString::from_rust_string(&jvm, "9223372036854775808").await?; + let result: Result = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (overflow,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long.parseLong must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let typed_null_result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Long;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null_result else { + panic!("Long typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let misplaced_sign = JavaLangString::from_rust_string(&jvm, "#-1").await?; + let result: Result> = jvm + .invoke_static("java/lang/Long", "decode", "(Ljava/lang/String;)Ljava/lang/Long;", (misplaced_sign,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long.decode must reject a sign after the radix prefix"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let key = JavaLangString::from_rust_string(&jvm, "rustjava.test.long").await?; + let property_value = JavaLangString::from_rust_string(&jvm, "077").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), property_value), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "getLong", "(Ljava/lang/String;)Ljava/lang/Long;", (key.clone(),)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&property, "longValue", "()J", ()).await?, 63); + + let invalid = JavaLangString::from_rust_string(&jvm, "invalid").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), invalid), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "getLong", "(Ljava/lang/String;J)Ljava/lang/Long;", (key, -9i64)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&property, "longValue", "()J", ()).await?, -9); + + for (text, radix, expected) in [ + ("-1000000000000000000000000000000000000000000000000000000000000000", 2, i64::MIN), + ("111111111111111111111111111111111111111111111111111111111111111", 2, i64::MAX), + ("-9223372036854775808", 10, i64::MIN), + ("9223372036854775807", 10, i64::MAX), + ("-8000000000000000", 16, i64::MIN), + ("7fffffffffffffff", 16, i64::MAX), + ("-1y2p0ij32e8e8", 36, i64::MIN), + ("1y2p0ij32e8e7", 36, i64::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + for text in ["-9223372036854775809", "9223372036854775808", "", "+", "-"] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm.invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (text,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long.parseLong must reject invalid input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + for (text, expected) in [("#7f", 127i64), ("077", 63i64), ("-0x8000000000000000", i64::MIN)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "decode", "(Ljava/lang/String;)Ljava/lang/Long;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&decoded, "longValue", "()J", ()).await?, expected); + } + for (method, expected) in [ + ("toBinaryString", "1111111111111111111111111111111111111111111111111111111111111111"), + ("toOctalString", "1777777777777777777777"), + ("toHexString", "ffffffffffffffff"), + ] { + let text: ClassInstanceRef = jvm.invoke_static("java/lang/Long", method, "(J)Ljava/lang/String;", (-1i64,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + let equal = jvm.new_class("java/lang/Long", "(J)V", (-1i64,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (equal.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, 0); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Object;)I", (equal,)) + .await?, + 0 + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long raw compare must reject another type"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + assert_eq!(jvm.get_static_field::("java/lang/Long", "MIN_VALUE", "J").await?, i64::MIN); + assert_eq!(jvm.get_static_field::("java/lang/Long", "MAX_VALUE", "J").await?, i64::MAX); + let typ = jvm.get_static_field("java/lang/Long", "TYPE", "Ljava/lang/Class;").await?; + let type_name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &type_name).await?, "long"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_number.rs b/java_runtime/tests/classes/java/lang/test_number.rs new file mode 100644 index 00000000..62f19932 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_number.rs @@ -0,0 +1,76 @@ +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::lang::{Comparable, Number}; +use jvm::{JavaError, Result}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_number_is_abstract() -> Result<()> { + let jvm = test_jvm().await?; + + let number_proto = Number::as_proto(); + assert!(number_proto.access_flags.contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT)); + for method in &number_proto.methods { + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + if method.name != "" && method.name != "byteValue" && method.name != "shortValue" { + assert!(method.access_flags.contains(MethodAccessFlags::ABSTRACT)); + } + } + + let comparable_proto = Comparable::as_proto(); + assert!( + comparable_proto + .access_flags + .contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT) + ); + assert!( + comparable_proto.methods[0] + .access_flags + .contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT) + ); + + let number_class = jvm.resolve_class("java/lang/Number").await?.java_class(); + let serializable_class = jvm.resolve_class("java/io/Serializable").await?.java_class(); + let is_serializable: bool = jvm + .invoke_virtual(&serializable_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (number_class.clone(),)) + .await?; + assert!(is_serializable); + let comparable_class = jvm.resolve_class("java/lang/Comparable").await?.java_class(); + + let integer = jvm.new_class("java/lang/Integer", "(I)V", (257,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&integer, "byteValue", "()B", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i16>(&integer, "shortValue", "()S", ()).await?, 257); + let negative = jvm.new_class("java/lang/Integer", "(I)V", (-129,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&negative, "byteValue", "()B", ()).await?, 127); + + for class_name in [ + "java/lang/Byte", + "java/lang/Short", + "java/lang/Integer", + "java/lang/Long", + "java/lang/Float", + "java/lang/Double", + ] { + let wrapper_class = jvm.resolve_class(class_name).await?.java_class(); + let is_number: bool = jvm + .invoke_virtual(&number_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class.clone(),)) + .await?; + assert!(is_number); + let is_serializable: bool = jvm + .invoke_virtual(&serializable_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class.clone(),)) + .await?; + assert!(is_serializable); + let is_comparable: bool = jvm + .invoke_virtual(&comparable_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class,)) + .await?; + assert!(is_comparable); + } + + let result = jvm.new_class("java/lang/Number", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Number must not be instantiable"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/InstantiationError")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_short.rs b/java_runtime/tests/classes/java/lang/test_short.rs new file mode 100644 index 00000000..91be27a0 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_short.rs @@ -0,0 +1,107 @@ +use java_runtime::classes::java::lang::String; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_short_api() -> Result<()> { + let jvm = test_jvm().await?; + let value = jvm.new_class("java/lang/Short", "(S)V", (-129i16,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, 127); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, -129); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, -129); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, -129); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, -129.0); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, -129.0); + + for (input, radix, expected) in [("7f", 16, 127i16), ("-100000", 2, -32i16)] { + let string = JavaLangString::from_rust_string(&jvm, input).await?; + let parsed: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (string, radix)) + .await?; + assert_eq!(parsed, expected); + } + + let min = jvm.get_static_field::("java/lang/Short", "MIN_VALUE", "S").await?; + let max = jvm.get_static_field::("java/lang/Short", "MAX_VALUE", "S").await?; + assert_eq!((min, max), (i16::MIN, i16::MAX)); + let name = JavaLangString::from_rust_string(&jvm, "077").await?; + let decoded = jvm + .invoke_static("java/lang/Short", "decode", "(Ljava/lang/String;)Ljava/lang/Short;", (name,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i16>(&decoded, "shortValue", "()S", ()).await?, 63); + + let null_result: Result = jvm.invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (None,)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Short.parseShort(null) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Short;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, radix, expected) in [ + ("-1000000000000000", 2, i16::MIN), + ("111111111111111", 2, i16::MAX), + ("-32768", 10, i16::MIN), + ("32767", 10, i16::MAX), + ("-8000", 16, i16::MIN), + ("7fff", 16, i16::MAX), + ("-pa8", 36, i16::MIN), + ("pa7", 36, i16::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + for text in ["-32769", "32768"] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm.invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (text,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short.parseShort must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let equal = jvm.new_class("java/lang/Short", "(S)V", (-129i16,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (equal.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, -129); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Object;)I", (equal,)) + .await?, + 0 + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short raw compare must reject another type"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, expected) in [("#7fff", i16::MAX), ("077777", i16::MAX)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded = jvm + .invoke_static("java/lang/Short", "decode", "(Ljava/lang/String;)Ljava/lang/Short;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i16>(&decoded, "shortValue", "()S", ()).await?, expected); + } + + let typ = jvm.get_static_field("java/lang/Short", "TYPE", "Ljava/lang/Class;").await?; + let type_name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &type_name).await?, "short"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} diff --git a/jvm/src/runtime/java_lang_class.rs b/jvm/src/runtime/java_lang_class.rs index 012f68c7..7d0da194 100644 --- a/jvm/src/runtime/java_lang_class.rs +++ b/jvm/src/runtime/java_lang_class.rs @@ -7,12 +7,30 @@ use crate::{Array, ClassInstanceRef, Result, class_definition::ClassDefinition, pub struct JavaLangClass; impl JavaLangClass { + pub async fn from_rust_primitive(jvm: &Jvm, name: &str) -> Result> { + let mut java_class = jvm.new_class("java/lang/Class", "()V", ()).await?; + let mut name_bytes = jvm.instantiate_array("B", name.len()).await?; + let bytes: Vec = cast_vec(name.as_bytes().to_vec()); + jvm.store_array(&mut name_bytes, 0, bytes).await?; + jvm.put_field(&mut java_class, "nameBytes", "[B", name_bytes).await?; + + Ok(java_class) + } + #[allow(clippy::borrowed_box)] - pub async fn to_rust_class(jvm: &Jvm, this: &Box) -> Result> { + pub async fn name(jvm: &Jvm, this: &Box) -> Result { let name_bytes: ClassInstanceRef> = jvm.get_field(this, "nameBytes", "[B").await?; let len = jvm.array_length(&name_bytes).await?; let name_bytes_vec: Vec = jvm.load_array(&name_bytes, 0, len).await?; - let class_name = String::from_utf8(cast_vec(name_bytes_vec)).unwrap_or_default(); + match String::from_utf8(cast_vec(name_bytes_vec)) { + Ok(name) => Ok(name), + Err(_) => Err(jvm.exception("java/lang/NoClassDefFoundError", "invalid class name").await), + } + } + + #[allow(clippy::borrowed_box)] + pub async fn to_rust_class(jvm: &Jvm, this: &Box) -> Result> { + let class_name = Self::name(jvm, this).await?; if let Some(class) = jvm.get_class(&class_name) { Ok(class.definition) } else { From 366ea344f3abce61883a5de481d74408c25d037d Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Thu, 16 Jul 2026 18:15:41 +0900 Subject: [PATCH 2/2] Use Character digit semantics for numeric parsing --- java_runtime/src/classes/java/lang/character.rs | 10 +++++++--- java_runtime/src/classes/java/lang/integer.rs | 10 +++++++--- java_runtime/src/classes/java/lang/long.rs | 10 +++++++--- java_runtime/tests/classes/java/lang/test_integer.rs | 8 +++++++- java_runtime/tests/classes/java/lang/test_long.rs | 7 ++++++- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/java_runtime/src/classes/java/lang/character.rs b/java_runtime/src/classes/java/lang/character.rs index 0d2543b9..56993d4f 100644 --- a/java_runtime/src/classes/java/lang/character.rs +++ b/java_runtime/src/classes/java/lang/character.rs @@ -577,8 +577,12 @@ impl Character { } async fn digit(_: &Jvm, _: &mut RuntimeContext, value: JavaChar, radix: i32) -> Result { + Ok(Self::digit_value(value, radix)) + } + + pub(crate) fn digit_value(value: JavaChar, radix: i32) -> i32 { if !(2..=36).contains(&radix) { - return Ok(-1); + return -1; } let numeric = if let Some(value) = Self::decimal_digit_value(value) { @@ -592,10 +596,10 @@ impl Character { } else if (0xff21..=0xff3a).contains(&value) { i32::from(value - 0xff21) + 10 } else { - return Ok(-1); + return -1; }; - Ok(if numeric < radix { numeric } else { -1 }) + if numeric < radix { numeric } else { -1 } } async fn for_digit(_: &Jvm, _: &mut RuntimeContext, digit: i32, radix: i32) -> Result { diff --git a/java_runtime/src/classes/java/lang/integer.rs b/java_runtime/src/classes/java/lang/integer.rs index ceafdcb9..c03ff503 100644 --- a/java_runtime/src/classes/java/lang/integer.rs +++ b/java_runtime/src/classes/java/lang/integer.rs @@ -3,13 +3,13 @@ use alloc::{format, string::String as RustString, vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{ - ClassInstanceRef, JavaError, Jvm, Result, + ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::{JavaLangClass, JavaLangString}, }; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::lang::{Object, String}, + classes::java::lang::{Character, Object, String}, }; // public final class java.lang.Integer @@ -188,7 +188,11 @@ impl Integer { let mut result = 0i64; let mut count = 0; for value in chars { - let digit = i64::from(value.to_digit(radix)?); + let value = JavaChar::try_from(u32::from(value)).ok()?; + let digit = i64::from(Character::digit_value(value, radix as i32)); + if digit < 0 { + return None; + } if result < (limit + digit) / i64::from(radix) { return None; } diff --git a/java_runtime/src/classes/java/lang/long.rs b/java_runtime/src/classes/java/lang/long.rs index bd8d6959..7c0b1539 100644 --- a/java_runtime/src/classes/java/lang/long.rs +++ b/java_runtime/src/classes/java/lang/long.rs @@ -3,13 +3,13 @@ use alloc::{format, string::String as RustString, vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; use jvm::{ - ClassInstanceRef, JavaError, Jvm, Result, + ClassInstanceRef, JavaChar, JavaError, Jvm, Result, runtime::{JavaLangClass, JavaLangString}, }; use crate::{ RuntimeClassProto, RuntimeContext, - classes::java::lang::{Object, String}, + classes::java::lang::{Character, Object, String}, }; // public final class java.lang.Long @@ -172,7 +172,11 @@ impl Long { let limit = if negative { i128::from(i64::MIN) } else { -i128::from(i64::MAX) }; let mut result = 0i128; for value in body.chars() { - let digit = i128::from(value.to_digit(radix)?); + let value = JavaChar::try_from(u32::from(value)).ok()?; + let digit = i128::from(Character::digit_value(value, radix as i32)); + if digit < 0 { + return None; + } if result < (limit + digit) / i128::from(radix) { return None; } diff --git a/java_runtime/tests/classes/java/lang/test_integer.rs b/java_runtime/tests/classes/java/lang/test_integer.rs index dcf318e6..fab0648d 100644 --- a/java_runtime/tests/classes/java/lang/test_integer.rs +++ b/java_runtime/tests/classes/java/lang/test_integer.rs @@ -41,7 +41,13 @@ async fn test_parse_int_invalid() -> Result<()> { async fn test_integer_strict_api() -> Result<()> { let jvm = test_jvm().await?; - for (text, radix, expected) in [("7fffffff", 16, i32::MAX), ("-80000000", 16, i32::MIN), ("z", 36, 35)] { + for (text, radix, expected) in [ + ("7fffffff", 16, i32::MAX), + ("-80000000", 16, i32::MIN), + ("z", 36, 35), + ("\u{ff21}", 16, 10), + ("\u{0661}\u{0662}\u{0663}", 10, 123), + ] { let string = JavaLangString::from_rust_string(&jvm, text).await?; let value: i32 = jvm .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (string, radix)) diff --git a/java_runtime/tests/classes/java/lang/test_long.rs b/java_runtime/tests/classes/java/lang/test_long.rs index b1075ad5..02eb73e9 100644 --- a/java_runtime/tests/classes/java/lang/test_long.rs +++ b/java_runtime/tests/classes/java/lang/test_long.rs @@ -14,7 +14,12 @@ async fn test_long_api() -> Result<()> { assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, i64::MIN as f32); assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, i64::MIN as f64); - for (input, radix, expected) in [("7fffffffffffffff", 16, i64::MAX), ("-8000000000000000", 16, i64::MIN)] { + for (input, radix, expected) in [ + ("7fffffffffffffff", 16, i64::MAX), + ("-8000000000000000", 16, i64::MIN), + ("\u{ff21}", 16, 10), + ("\u{0661}\u{0662}\u{0663}", 10, 123), + ] { let string = JavaLangString::from_rust_string(&jvm, input).await?; let parsed: i64 = jvm .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (string, radix))