Skip to content

Commit 58d885c

Browse files
committed
Add WIPI 1.2.1 String API: charset ctors, valueOf overloads, region/case ops
Add missing java.lang.String API as documented in the WIPI 1.2.1 spec (see https://mirusu400.github.io/wipi-wiki/java-api/java/lang/String.md). Korean J2ME apps that target this profile frequently fail with NoSuchMethodError on these signatures, especially the charset-aware byte[] constructors used to decode EUC-KR network payloads. Constructors added: - String() - String(byte[], String charsetName) - String(byte[], int, int, String charsetName) Instance methods added: - endsWith(String) - equalsIgnoreCase(String) - getBytes(String charsetName) - lastIndexOf(int, int) - regionMatches(boolean, int, String, int, int) - replace(char, char) - toLowerCase() Static methods added: - valueOf(boolean), valueOf(long), valueOf(float), valueOf(double) - valueOf(char[]), valueOf(char[], int, int)
1 parent 29d2a87 commit 58d885c

2 files changed

Lines changed: 439 additions & 0 deletions

File tree

java_runtime/src/classes/java/lang/string.rs

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,56 @@ impl String {
3030
parent_class: Some("java/lang/Object"),
3131
interfaces: vec![],
3232
methods: vec![
33+
JavaMethodProto::new("<init>", "()V", Self::init_empty, Default::default()),
3334
JavaMethodProto::new("<init>", "([B)V", Self::init_with_byte_array, Default::default()),
3435
JavaMethodProto::new("<init>", "([C)V", Self::init_with_char_array, Default::default()),
3536
JavaMethodProto::new("<init>", "([CII)V", Self::init_with_partial_char_array, Default::default()),
3637
JavaMethodProto::new("<init>", "([BII)V", Self::init_with_partial_byte_array, Default::default()),
38+
JavaMethodProto::new(
39+
"<init>",
40+
"([BLjava/lang/String;)V",
41+
Self::init_with_byte_array_charset,
42+
Default::default(),
43+
),
44+
JavaMethodProto::new(
45+
"<init>",
46+
"([BIILjava/lang/String;)V",
47+
Self::init_with_partial_byte_array_charset,
48+
Default::default(),
49+
),
3750
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_string, Default::default()),
3851
JavaMethodProto::new("<init>", "(Ljava/lang/StringBuffer;)V", Self::init_with_string_buffer, Default::default()),
3952
JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, Default::default()),
53+
JavaMethodProto::new("equalsIgnoreCase", "(Ljava/lang/String;)Z", Self::equals_ignore_case, Default::default()),
4054
JavaMethodProto::new("compareTo", "(Ljava/lang/String;)I", Self::compare_to, Default::default()),
4155
JavaMethodProto::new("hashCode", "()I", Self::hash_code, Default::default()),
4256
JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()),
4357
JavaMethodProto::new("charAt", "(I)C", Self::char_at, Default::default()),
4458
JavaMethodProto::new("getBytes", "()[B", Self::get_bytes, Default::default()),
59+
JavaMethodProto::new("getBytes", "(Ljava/lang/String;)[B", Self::get_bytes_charset, Default::default()),
4560
JavaMethodProto::new("getChars", "(II[CI)V", Self::get_chars, Default::default()),
4661
JavaMethodProto::new("toCharArray", "()[C", Self::to_char_array, Default::default()),
4762
JavaMethodProto::new("toUpperCase", "()Ljava/lang/String;", Self::to_upper_case, Default::default()),
63+
JavaMethodProto::new("toLowerCase", "()Ljava/lang/String;", Self::to_lower_case, Default::default()),
4864
JavaMethodProto::new("length", "()I", Self::length, Default::default()),
4965
JavaMethodProto::new("concat", "(Ljava/lang/String;)Ljava/lang/String;", Self::concat, Default::default()),
5066
JavaMethodProto::new("substring", "(I)Ljava/lang/String;", Self::substring, Default::default()),
5167
JavaMethodProto::new("substring", "(II)Ljava/lang/String;", Self::substring_with_end, Default::default()),
68+
JavaMethodProto::new("replace", "(CC)Ljava/lang/String;", Self::replace, Default::default()),
69+
JavaMethodProto::new("regionMatches", "(ZILjava/lang/String;II)Z", Self::region_matches, Default::default()),
70+
JavaMethodProto::new("valueOf", "(Z)Ljava/lang/String;", Self::value_of_boolean, MethodAccessFlags::STATIC),
5271
JavaMethodProto::new("valueOf", "(C)Ljava/lang/String;", Self::value_of_char, MethodAccessFlags::STATIC),
5372
JavaMethodProto::new("valueOf", "(I)Ljava/lang/String;", Self::value_of_integer, MethodAccessFlags::STATIC),
73+
JavaMethodProto::new("valueOf", "(J)Ljava/lang/String;", Self::value_of_long, MethodAccessFlags::STATIC),
74+
JavaMethodProto::new("valueOf", "(F)Ljava/lang/String;", Self::value_of_float, MethodAccessFlags::STATIC),
75+
JavaMethodProto::new("valueOf", "(D)Ljava/lang/String;", Self::value_of_double, MethodAccessFlags::STATIC),
76+
JavaMethodProto::new("valueOf", "([C)Ljava/lang/String;", Self::value_of_char_array, MethodAccessFlags::STATIC),
77+
JavaMethodProto::new(
78+
"valueOf",
79+
"([CII)Ljava/lang/String;",
80+
Self::value_of_partial_char_array,
81+
MethodAccessFlags::STATIC,
82+
),
5483
JavaMethodProto::new(
5584
"valueOf",
5685
"(Ljava/lang/Object;)Ljava/lang/String;",
@@ -62,9 +91,11 @@ impl String {
6291
JavaMethodProto::new("indexOf", "(Ljava/lang/String;)I", Self::index_of_string, Default::default()),
6392
JavaMethodProto::new("indexOf", "(Ljava/lang/String;I)I", Self::index_of_string_from, Default::default()),
6493
JavaMethodProto::new("lastIndexOf", "(I)I", Self::last_index_of, Default::default()),
94+
JavaMethodProto::new("lastIndexOf", "(II)I", Self::last_index_of_from, Default::default()),
6595
JavaMethodProto::new("trim", "()Ljava/lang/String;", Self::trim, Default::default()),
6696
JavaMethodProto::new("startsWith", "(Ljava/lang/String;)Z", Self::starts_with, Default::default()),
6797
JavaMethodProto::new("startsWith", "(Ljava/lang/String;I)Z", Self::starts_with_offset, Default::default()),
98+
JavaMethodProto::new("endsWith", "(Ljava/lang/String;)Z", Self::ends_with, Default::default()),
6899
],
69100
fields: vec![JavaFieldProto::new("value", "[C", Default::default())],
70101
access_flags: Default::default(),
@@ -463,6 +494,262 @@ impl String {
463494
Ok(this_string.starts_with(&prefix_string))
464495
}
465496

497+
async fn init_empty(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>) -> Result<()> {
498+
tracing::debug!("java.lang.String::<init>({:?})", &this);
499+
500+
let _: () = jvm.invoke_special(&this, "java/lang/Object", "<init>", "()V", ()).await?;
501+
502+
let array = jvm.instantiate_array("C", 0).await?;
503+
jvm.put_field(&mut this, "value", "[C", array).await?;
504+
505+
Ok(())
506+
}
507+
508+
async fn init_with_byte_array_charset(
509+
jvm: &Jvm,
510+
_: &mut RuntimeContext,
511+
this: ClassInstanceRef<Self>,
512+
value: ClassInstanceRef<Array<i8>>,
513+
charset_name: ClassInstanceRef<Self>,
514+
) -> Result<()> {
515+
tracing::debug!("java.lang.String::<init>({:?}, {:?}, {:?})", &this, &value, &charset_name);
516+
517+
let count = jvm.array_length(&value).await? as i32;
518+
519+
let _: () = jvm
520+
.invoke_special(
521+
&this,
522+
"java/lang/String",
523+
"<init>",
524+
"([BIILjava/lang/String;)V",
525+
(value, 0, count, charset_name),
526+
)
527+
.await?;
528+
529+
Ok(())
530+
}
531+
532+
async fn init_with_partial_byte_array_charset(
533+
jvm: &Jvm,
534+
_: &mut RuntimeContext,
535+
this: ClassInstanceRef<Self>,
536+
value: ClassInstanceRef<Array<i8>>,
537+
offset: i32,
538+
count: i32,
539+
charset_name: ClassInstanceRef<Self>,
540+
) -> Result<()> {
541+
tracing::debug!(
542+
"java.lang.String::<init>({:?}, {:?}, {}, {}, {:?})",
543+
&this,
544+
&value,
545+
offset,
546+
count,
547+
&charset_name
548+
);
549+
550+
let bytes: Vec<i8> = jvm.load_array(&value, offset as _, count as _).await?;
551+
552+
let charset = JavaLangString::to_rust_string(jvm, &charset_name).await?;
553+
let string = Self::decode_str(&charset, cast_slice(&bytes));
554+
555+
let utf16 = string.encode_utf16().collect::<Vec<_>>();
556+
557+
let mut array = jvm.instantiate_array("C", utf16.len()).await?;
558+
jvm.store_array(&mut array, 0, utf16).await?;
559+
560+
let _: () = jvm.invoke_special(&this, "java/lang/String", "<init>", "([C)V", [array.into()]).await?;
561+
562+
Ok(())
563+
}
564+
565+
async fn equals_ignore_case(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, other: ClassInstanceRef<Self>) -> Result<bool> {
566+
tracing::debug!("java.lang.String::equalsIgnoreCase({:?}, {:?})", &this, &other);
567+
568+
if other.is_null() {
569+
return Ok(false);
570+
}
571+
572+
let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
573+
let other_string = JavaLangString::to_rust_string(jvm, &other).await?;
574+
575+
Ok(this_string.eq_ignore_ascii_case(&other_string) || this_string.to_lowercase() == other_string.to_lowercase())
576+
}
577+
578+
async fn get_bytes_charset(
579+
jvm: &Jvm,
580+
_: &mut RuntimeContext,
581+
this: ClassInstanceRef<Self>,
582+
charset_name: ClassInstanceRef<Self>,
583+
) -> Result<ClassInstanceRef<Array<i8>>> {
584+
tracing::debug!("java.lang.String::getBytes({:?}, {:?})", &this, &charset_name);
585+
586+
let string = JavaLangString::to_rust_string(jvm, &this).await?;
587+
let charset = JavaLangString::to_rust_string(jvm, &charset_name).await?;
588+
589+
let bytes = cast_vec(Self::encode_str(&charset, &string));
590+
591+
let mut byte_array = jvm.instantiate_array("B", bytes.len()).await?;
592+
jvm.array_raw_buffer_mut(&mut byte_array).await?.write(0, &bytes)?;
593+
594+
Ok(byte_array.into())
595+
}
596+
597+
async fn to_lower_case(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<ClassInstanceRef<Self>> {
598+
tracing::debug!("java.lang.String::toLowerCase({:?})", &this);
599+
600+
let string = JavaLangString::to_rust_string(jvm, &this).await?;
601+
let lower = string.to_lowercase();
602+
603+
Ok(JavaLangString::from_rust_string(jvm, &lower).await?.into())
604+
}
605+
606+
async fn replace(
607+
jvm: &Jvm,
608+
_: &mut RuntimeContext,
609+
this: ClassInstanceRef<Self>,
610+
old_char: JavaChar,
611+
new_char: JavaChar,
612+
) -> Result<ClassInstanceRef<Self>> {
613+
tracing::debug!("java.lang.String::replace({:?}, {}, {})", &this, old_char, new_char);
614+
615+
let value = jvm.get_field(&this, "value", "[C").await?;
616+
let length = jvm.array_length(&value).await?;
617+
let chars: Vec<JavaChar> = jvm.load_array(&value, 0, length).await?;
618+
619+
let replaced: Vec<JavaChar> = chars.into_iter().map(|c| if c == old_char { new_char } else { c }).collect();
620+
621+
let mut array = jvm.instantiate_array("C", replaced.len()).await?;
622+
jvm.store_array(&mut array, 0, replaced).await?;
623+
624+
let new_string = jvm.new_class("java/lang/String", "([C)V", (array,)).await?;
625+
626+
Ok(new_string.into())
627+
}
628+
629+
#[allow(clippy::too_many_arguments)]
630+
async fn region_matches(
631+
jvm: &Jvm,
632+
_: &mut RuntimeContext,
633+
this: ClassInstanceRef<Self>,
634+
ignore_case: bool,
635+
toffset: i32,
636+
other: ClassInstanceRef<Self>,
637+
ooffset: i32,
638+
len: i32,
639+
) -> Result<bool> {
640+
tracing::debug!(
641+
"java.lang.String::regionMatches({:?}, {}, {}, {:?}, {}, {})",
642+
&this,
643+
ignore_case,
644+
toffset,
645+
&other,
646+
ooffset,
647+
len
648+
);
649+
650+
if toffset < 0 || ooffset < 0 || len < 0 {
651+
return Ok(false);
652+
}
653+
654+
let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
655+
let other_string = JavaLangString::to_rust_string(jvm, &other).await?;
656+
657+
let this_chars: Vec<u16> = this_string.encode_utf16().collect();
658+
let other_chars: Vec<u16> = other_string.encode_utf16().collect();
659+
660+
let end_t = toffset as usize + len as usize;
661+
let end_o = ooffset as usize + len as usize;
662+
if end_t > this_chars.len() || end_o > other_chars.len() {
663+
return Ok(false);
664+
}
665+
666+
let this_slice = &this_chars[toffset as usize..end_t];
667+
let other_slice = &other_chars[ooffset as usize..end_o];
668+
669+
if ignore_case {
670+
let to_lower = |c: u16| -> u16 {
671+
char::from_u32(c as u32)
672+
.map(|ch| ch.to_lowercase().next().unwrap_or(ch) as u32 as u16)
673+
.unwrap_or(c)
674+
};
675+
Ok(this_slice.iter().copied().map(to_lower).eq(other_slice.iter().copied().map(to_lower)))
676+
} else {
677+
Ok(this_slice == other_slice)
678+
}
679+
}
680+
681+
async fn last_index_of_from(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, ch: i32, from_index: i32) -> Result<i32> {
682+
tracing::debug!("java.lang.String::lastIndexOf({:?}, {}, {})", &this, ch, from_index);
683+
684+
if from_index < 0 {
685+
return Ok(-1);
686+
}
687+
688+
let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
689+
let chars: Vec<char> = this_string.chars().collect();
690+
let end = (from_index as usize + 1).min(chars.len());
691+
692+
let index = chars[..end].iter().rposition(|&c| c as u32 == ch as u32).map(|x| x as i32);
693+
694+
Ok(index.unwrap_or(-1))
695+
}
696+
697+
async fn ends_with(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, suffix: ClassInstanceRef<Self>) -> Result<bool> {
698+
tracing::debug!("java.lang.String::endsWith({:?}, {:?})", &this, &suffix);
699+
700+
let this_string = JavaLangString::to_rust_string(jvm, &this).await?;
701+
let suffix_string = JavaLangString::to_rust_string(jvm, &suffix).await?;
702+
703+
Ok(this_string.ends_with(&suffix_string))
704+
}
705+
706+
async fn value_of_boolean(jvm: &Jvm, _: &mut RuntimeContext, value: bool) -> Result<ClassInstanceRef<Self>> {
707+
tracing::debug!("java.lang.String::valueOf({})", value);
708+
709+
let string = if value { "true" } else { "false" };
710+
Ok(JavaLangString::from_rust_string(jvm, string).await?.into())
711+
}
712+
713+
async fn value_of_long(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result<ClassInstanceRef<Self>> {
714+
tracing::debug!("java.lang.String::valueOf({})", value);
715+
716+
Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into())
717+
}
718+
719+
async fn value_of_float(jvm: &Jvm, _: &mut RuntimeContext, value: f32) -> Result<ClassInstanceRef<Self>> {
720+
tracing::debug!("java.lang.String::valueOf({})", value);
721+
722+
Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into())
723+
}
724+
725+
async fn value_of_double(jvm: &Jvm, _: &mut RuntimeContext, value: f64) -> Result<ClassInstanceRef<Self>> {
726+
tracing::debug!("java.lang.String::valueOf({})", value);
727+
728+
Ok(JavaLangString::from_rust_string(jvm, &value.to_string()).await?.into())
729+
}
730+
731+
async fn value_of_char_array(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef<Array<JavaChar>>) -> Result<ClassInstanceRef<Self>> {
732+
tracing::debug!("java.lang.String::valueOf({:?})", &value);
733+
734+
let new_string = jvm.new_class("java/lang/String", "([C)V", (value,)).await?;
735+
736+
Ok(new_string.into())
737+
}
738+
739+
async fn value_of_partial_char_array(
740+
jvm: &Jvm,
741+
_: &mut RuntimeContext,
742+
value: ClassInstanceRef<Array<JavaChar>>,
743+
offset: i32,
744+
count: i32,
745+
) -> Result<ClassInstanceRef<Self>> {
746+
tracing::debug!("java.lang.String::valueOf({:?}, {}, {})", &value, offset, count);
747+
748+
let new_string = jvm.new_class("java/lang/String", "([CII)V", (value, offset, count)).await?;
749+
750+
Ok(new_string.into())
751+
}
752+
466753
fn decode_str(charset: &str, bytes: &[u8]) -> RustString {
467754
match charset {
468755
"UTF-8" => str::from_utf8(bytes).unwrap().to_string(),

0 commit comments

Comments
 (0)