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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
318 changes: 311 additions & 7 deletions java_runtime/src/classes/java/lang/string.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use core::cmp::Ordering;

use alloc::{
str,
string::{String as RustString, ToString},
vec,
vec::Vec,
Expand Down Expand Up @@ -30,27 +29,56 @@ impl String {
parent_class: Some("java/lang/Object"),
interfaces: vec![],
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init_empty, Default::default()),
JavaMethodProto::new("<init>", "([B)V", Self::init_with_byte_array, Default::default()),
JavaMethodProto::new("<init>", "([C)V", Self::init_with_char_array, Default::default()),
JavaMethodProto::new("<init>", "([CII)V", Self::init_with_partial_char_array, Default::default()),
JavaMethodProto::new("<init>", "([BII)V", Self::init_with_partial_byte_array, Default::default()),
JavaMethodProto::new(
"<init>",
"([BLjava/lang/String;)V",
Self::init_with_byte_array_charset,
Default::default(),
),
JavaMethodProto::new(
"<init>",
"([BIILjava/lang/String;)V",
Self::init_with_partial_byte_array_charset,
Default::default(),
),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_string, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/StringBuffer;)V", Self::init_with_string_buffer, Default::default()),
JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, Default::default()),
JavaMethodProto::new("equalsIgnoreCase", "(Ljava/lang/String;)Z", Self::equals_ignore_case, Default::default()),
JavaMethodProto::new("compareTo", "(Ljava/lang/String;)I", Self::compare_to, Default::default()),
JavaMethodProto::new("hashCode", "()I", Self::hash_code, Default::default()),
JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()),
JavaMethodProto::new("charAt", "(I)C", Self::char_at, Default::default()),
JavaMethodProto::new("getBytes", "()[B", Self::get_bytes, Default::default()),
JavaMethodProto::new("getBytes", "(Ljava/lang/String;)[B", Self::get_bytes_charset, Default::default()),
JavaMethodProto::new("getChars", "(II[CI)V", Self::get_chars, Default::default()),
JavaMethodProto::new("toCharArray", "()[C", Self::to_char_array, Default::default()),
JavaMethodProto::new("toUpperCase", "()Ljava/lang/String;", Self::to_upper_case, Default::default()),
JavaMethodProto::new("toLowerCase", "()Ljava/lang/String;", Self::to_lower_case, Default::default()),
JavaMethodProto::new("length", "()I", Self::length, Default::default()),
JavaMethodProto::new("concat", "(Ljava/lang/String;)Ljava/lang/String;", Self::concat, Default::default()),
JavaMethodProto::new("substring", "(I)Ljava/lang/String;", Self::substring, Default::default()),
JavaMethodProto::new("substring", "(II)Ljava/lang/String;", Self::substring_with_end, Default::default()),
JavaMethodProto::new("replace", "(CC)Ljava/lang/String;", Self::replace, Default::default()),
JavaMethodProto::new("regionMatches", "(ZILjava/lang/String;II)Z", Self::region_matches, Default::default()),
JavaMethodProto::new("valueOf", "(Z)Ljava/lang/String;", Self::value_of_boolean, MethodAccessFlags::STATIC),
JavaMethodProto::new("valueOf", "(C)Ljava/lang/String;", Self::value_of_char, MethodAccessFlags::STATIC),
JavaMethodProto::new("valueOf", "(I)Ljava/lang/String;", Self::value_of_integer, MethodAccessFlags::STATIC),
JavaMethodProto::new("valueOf", "(J)Ljava/lang/String;", Self::value_of_long, MethodAccessFlags::STATIC),
JavaMethodProto::new("valueOf", "(F)Ljava/lang/String;", Self::value_of_float, MethodAccessFlags::STATIC),
JavaMethodProto::new("valueOf", "(D)Ljava/lang/String;", Self::value_of_double, MethodAccessFlags::STATIC),
JavaMethodProto::new("valueOf", "([C)Ljava/lang/String;", Self::value_of_char_array, MethodAccessFlags::STATIC),
JavaMethodProto::new(
"valueOf",
"([CII)Ljava/lang/String;",
Self::value_of_partial_char_array,
MethodAccessFlags::STATIC,
),
JavaMethodProto::new(
"valueOf",
"(Ljava/lang/Object;)Ljava/lang/String;",
Expand All @@ -62,9 +90,11 @@ impl String {
JavaMethodProto::new("indexOf", "(Ljava/lang/String;)I", Self::index_of_string, Default::default()),
JavaMethodProto::new("indexOf", "(Ljava/lang/String;I)I", Self::index_of_string_from, Default::default()),
JavaMethodProto::new("lastIndexOf", "(I)I", Self::last_index_of, Default::default()),
JavaMethodProto::new("lastIndexOf", "(II)I", Self::last_index_of_from, Default::default()),
JavaMethodProto::new("trim", "()Ljava/lang/String;", Self::trim, Default::default()),
JavaMethodProto::new("startsWith", "(Ljava/lang/String;)Z", Self::starts_with, Default::default()),
JavaMethodProto::new("startsWith", "(Ljava/lang/String;I)Z", Self::starts_with_offset, Default::default()),
JavaMethodProto::new("endsWith", "(Ljava/lang/String;)Z", Self::ends_with, Default::default()),
],
fields: vec![JavaFieldProto::new("value", "[C", Default::default())],
access_flags: Default::default(),
Expand Down Expand Up @@ -463,18 +493,292 @@ impl String {
Ok(this_string.starts_with(&prefix_string))
}

async fn init_empty(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>) -> Result<()> {
tracing::debug!("java.lang.String::<init>({:?})", &this);

let _: () = jvm.invoke_special(&this, "java/lang/Object", "<init>", "()V", ()).await?;

let array = jvm.instantiate_array("C", 0).await?;
jvm.put_field(&mut this, "value", "[C", array).await?;

Ok(())
}

async fn init_with_byte_array_charset(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
value: ClassInstanceRef<Array<i8>>,
charset_name: ClassInstanceRef<Self>,
) -> Result<()> {
tracing::debug!("java.lang.String::<init>({:?}, {:?}, {:?})", &this, &value, &charset_name);

let count = jvm.array_length(&value).await? as i32;

let _: () = jvm
.invoke_special(
&this,
"java/lang/String",
"<init>",
"([BIILjava/lang/String;)V",
(value, 0, count, charset_name),
)
.await?;

Ok(())
}

async fn init_with_partial_byte_array_charset(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
value: ClassInstanceRef<Array<i8>>,
offset: i32,
count: i32,
charset_name: ClassInstanceRef<Self>,
) -> Result<()> {
tracing::debug!(
"java.lang.String::<init>({:?}, {:?}, {}, {}, {:?})",
&this,
&value,
offset,
count,
&charset_name
);

if charset_name.is_null() {
return Err(jvm.exception("java/lang/NullPointerException", "charsetName is null").await);
}

let bytes: Vec<i8> = jvm.load_array(&value, offset as _, count as _).await?;

Comment thread
mirusu400 marked this conversation as resolved.
let charset = JavaLangString::to_rust_string(jvm, &charset_name).await?;
let string = Self::decode_str(&charset, cast_slice(&bytes));

let utf16 = string.encode_utf16().collect::<Vec<_>>();

let mut array = jvm.instantiate_array("C", utf16.len()).await?;
jvm.store_array(&mut array, 0, utf16).await?;

let _: () = jvm.invoke_special(&this, "java/lang/String", "<init>", "([C)V", [array.into()]).await?;

Ok(())
}

async fn equals_ignore_case(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, other: ClassInstanceRef<Self>) -> Result<bool> {
tracing::debug!("java.lang.String::equalsIgnoreCase({:?}, {:?})", &this, &other);

if other.is_null() {
return Ok(false);
}

let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
let other_string = JavaLangString::to_rust_string(jvm, &other).await?;

Ok(this_string.eq_ignore_ascii_case(&other_string) || this_string.to_lowercase() == other_string.to_lowercase())
}

async fn get_bytes_charset(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
charset_name: ClassInstanceRef<Self>,
) -> Result<ClassInstanceRef<Array<i8>>> {
tracing::debug!("java.lang.String::getBytes({:?}, {:?})", &this, &charset_name);

if charset_name.is_null() {
return Err(jvm.exception("java/lang/NullPointerException", "charsetName is null").await);
}

let string = JavaLangString::to_rust_string(jvm, &this).await?;
let charset = JavaLangString::to_rust_string(jvm, &charset_name).await?;

let bytes = cast_vec(Self::encode_str(&charset, &string));

let mut byte_array = jvm.instantiate_array("B", bytes.len()).await?;
jvm.array_raw_buffer_mut(&mut byte_array).await?.write(0, &bytes)?;

Ok(byte_array.into())
}

async fn to_lower_case(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::toLowerCase({:?})", &this);

let string = JavaLangString::to_rust_string(jvm, &this).await?;
let lower = string.to_lowercase();

Ok(JavaLangString::from_rust_string(jvm, &lower).await?.into())
}

async fn replace(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
old_char: JavaChar,
new_char: JavaChar,
) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::replace({:?}, {}, {})", &this, old_char, new_char);

let value = jvm.get_field(&this, "value", "[C").await?;
let length = jvm.array_length(&value).await?;
let chars: Vec<JavaChar> = jvm.load_array(&value, 0, length).await?;

let replaced: Vec<JavaChar> = chars.into_iter().map(|c| if c == old_char { new_char } else { c }).collect();

let mut array = jvm.instantiate_array("C", replaced.len()).await?;
jvm.store_array(&mut array, 0, replaced).await?;

let new_string = jvm.new_class("java/lang/String", "([C)V", (array,)).await?;

Ok(new_string.into())
}

#[allow(clippy::too_many_arguments)]
async fn region_matches(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
ignore_case: bool,
toffset: i32,
other: ClassInstanceRef<Self>,
ooffset: i32,
len: i32,
) -> Result<bool> {
tracing::debug!(
"java.lang.String::regionMatches({:?}, {}, {}, {:?}, {}, {})",
&this,
ignore_case,
toffset,
&other,
ooffset,
len
);

if other.is_null() {
return Err(jvm.exception("java/lang/NullPointerException", "other is null").await);
}

if toffset < 0 || ooffset < 0 || len < 0 {
return Ok(false);
}

let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
let other_string = JavaLangString::to_rust_string(jvm, &other).await?;

let this_chars: Vec<u16> = this_string.encode_utf16().collect();
let other_chars: Vec<u16> = other_string.encode_utf16().collect();

let end_t = toffset as usize + len as usize;
let end_o = ooffset as usize + len as usize;
if end_t > this_chars.len() || end_o > other_chars.len() {
return Ok(false);
}

let this_slice = &this_chars[toffset as usize..end_t];
let other_slice = &other_chars[ooffset as usize..end_o];

if ignore_case {
let to_lower = |c: u16| -> u16 {
char::from_u32(c as u32)
.map(|ch| ch.to_lowercase().next().unwrap_or(ch) as u32 as u16)
.unwrap_or(c)
};
Ok(this_slice.iter().copied().map(to_lower).eq(other_slice.iter().copied().map(to_lower)))
} else {
Ok(this_slice == other_slice)
}
}

async fn last_index_of_from(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, ch: i32, from_index: i32) -> Result<i32> {
tracing::debug!("java.lang.String::lastIndexOf({:?}, {}, {})", &this, ch, from_index);

if from_index < 0 {
return Ok(-1);
}

let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
let chars: Vec<char> = this_string.chars().collect();
let end = (from_index as usize + 1).min(chars.len());

let index = chars[..end].iter().rposition(|&c| c as u32 == ch as u32).map(|x| x as i32);

Ok(index.unwrap_or(-1))
}

async fn ends_with(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, suffix: ClassInstanceRef<Self>) -> Result<bool> {
tracing::debug!("java.lang.String::endsWith({:?}, {:?})", &this, &suffix);

if suffix.is_null() {
return Err(jvm.exception("java/lang/NullPointerException", "suffix is null").await);
}

let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
let suffix_string = JavaLangString::to_rust_string(jvm, &suffix).await?;

Ok(this_string.ends_with(&suffix_string))
}

async fn value_of_boolean(jvm: &Jvm, _: &mut RuntimeContext, value: bool) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({})", value);

let string = if value { "true" } else { "false" };
Ok(JavaLangString::from_rust_string(jvm, string).await?.into())
}

async fn value_of_long(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({})", value);

Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into())
}

async fn value_of_float(jvm: &Jvm, _: &mut RuntimeContext, value: f32) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({})", value);

Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into())
}

async fn value_of_double(jvm: &Jvm, _: &mut RuntimeContext, value: f64) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({})", value);

Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into())
}

async fn value_of_char_array(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef<Array<JavaChar>>) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({:?})", &value);

let new_string = jvm.new_class("java/lang/String", "([C)V", (value,)).await?;

Ok(new_string.into())
}

async fn value_of_partial_char_array(
jvm: &Jvm,
_: &mut RuntimeContext,
value: ClassInstanceRef<Array<JavaChar>>,
offset: i32,
count: i32,
) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({:?}, {}, {})", &value, offset, count);

let new_string = jvm.new_class("java/lang/String", "([CII)V", (value, offset, count)).await?;

Ok(new_string.into())
}

fn decode_str(charset: &str, bytes: &[u8]) -> RustString {
match charset {
"UTF-8" => str::from_utf8(bytes).unwrap().to_string(),
"EUC-KR" => encoding_rs::EUC_KR.decode(bytes).0.to_string(),
match charset.to_ascii_uppercase().replace('_', "-").as_str() {
"UTF-8" | "UTF8" => RustString::from_utf8_lossy(bytes).into_owned(),
"EUC-KR" | "EUCKR" | "KS-C-5601-1987" | "MS949" | "CP949" => encoding_rs::EUC_KR.decode(bytes).0.to_string(),
"ISO-8859-1" | "LATIN1" | "US-ASCII" | "ASCII" => bytes.iter().map(|&b| b as char).collect(),
_ => unimplemented!("unsupported charset: {}", charset),
}
}

fn encode_str(charset: &str, string: &str) -> Vec<u8> {
match charset {
"UTF-8" => string.as_bytes().to_vec(),
"EUC-KR" => encoding_rs::EUC_KR.encode(string).0.to_vec(),
match charset.to_ascii_uppercase().replace('_', "-").as_str() {
"UTF-8" | "UTF8" => string.as_bytes().to_vec(),
"EUC-KR" | "EUCKR" | "KS-C-5601-1987" | "MS949" | "CP949" => encoding_rs::EUC_KR.encode(string).0.to_vec(),
"ISO-8859-1" | "LATIN1" | "US-ASCII" | "ASCII" => string.chars().map(|c| c as u8).collect(),
_ => unimplemented!("unsupported charset: {}", charset),
}
}
Expand Down
Loading