Skip to content

Commit 84bf15c

Browse files
committed
Fix text format position handling
1 parent cab6756 commit 84bf15c

4 files changed

Lines changed: 224 additions & 23 deletions

File tree

java_runtime/src/classes/java/text/decimal_format.rs

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -341,16 +341,16 @@ impl DecimalFormat {
341341
let base: i32 = jvm.invoke_virtual(&buffer, "length", "()I", ()).await?;
342342
let field: i32 = jvm.invoke_virtual(&position, "getField", "()I", ()).await?;
343343
if field == 0 {
344-
let begin = base + prefix.chars().count() as i32;
344+
let begin = base + prefix.encode_utf16().count() as i32;
345345
let _: () = jvm.invoke_virtual(&position, "setBeginIndex", "(I)V", (begin,)).await?;
346346
let _: () = jvm
347-
.invoke_virtual(&position, "setEndIndex", "(I)V", (begin + integer.chars().count() as i32,))
347+
.invoke_virtual(&position, "setEndIndex", "(I)V", (begin + integer.encode_utf16().count() as i32,))
348348
.await?;
349349
} else if field == 1 && !fraction.is_empty() {
350-
let begin = base + prefix.chars().count() as i32 + integer.chars().count() as i32 + 1;
350+
let begin = base + prefix.encode_utf16().count() as i32 + integer.encode_utf16().count() as i32 + 1;
351351
let _: () = jvm.invoke_virtual(&position, "setBeginIndex", "(I)V", (begin,)).await?;
352352
let _: () = jvm
353-
.invoke_virtual(&position, "setEndIndex", "(I)V", (begin + fraction.chars().count() as i32,))
353+
.invoke_virtual(&position, "setEndIndex", "(I)V", (begin + fraction.encode_utf16().count() as i32,))
354354
.await?;
355355
}
356356

@@ -428,11 +428,22 @@ impl DecimalFormat {
428428
}
429429
let source = JavaLangString::to_rust_string(jvm, &source).await?;
430430
let characters: Vec<char> = source.chars().collect();
431+
let mut utf16_indices = Vec::with_capacity(characters.len() + 1);
432+
let mut utf16_index = 0;
433+
for character in &characters {
434+
utf16_indices.push(utf16_index);
435+
utf16_index += character.len_utf16();
436+
}
437+
utf16_indices.push(utf16_index);
431438
let start: i32 = jvm.invoke_virtual(&position, "getIndex", "()I", ()).await?;
432-
if start < 0 || start as usize > characters.len() {
439+
if start < 0 {
433440
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (start,)).await?;
434441
return Ok(ClassInstanceRef::new(None));
435442
}
443+
let Some(start_index) = utf16_indices.iter().position(|index| *index == start as usize) else {
444+
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (start,)).await?;
445+
return Ok(ClassInstanceRef::new(None));
446+
};
436447

437448
let positive_prefix: ClassInstanceRef<String> = jvm.get_field(&this, "positivePrefix", "Ljava/lang/String;").await?;
438449
let positive_suffix: ClassInstanceRef<String> = jvm.get_field(&this, "positiveSuffix", "Ljava/lang/String;").await?;
@@ -443,8 +454,8 @@ impl DecimalFormat {
443454
let negative_prefix: Vec<char> = JavaLangString::to_rust_string(jvm, &negative_prefix).await?.chars().collect();
444455
let negative_suffix: Vec<char> = JavaLangString::to_rust_string(jvm, &negative_suffix).await?.chars().collect();
445456

446-
let mut index = start as usize;
447-
let negative = if characters[index..].starts_with(&negative_prefix) && negative_prefix != positive_prefix {
457+
let mut index = start_index;
458+
let prefix_negative = if characters[index..].starts_with(&negative_prefix) && negative_prefix != positive_prefix {
448459
index += negative_prefix.len();
449460
true
450461
} else if characters[index..].starts_with(&positive_prefix) {
@@ -457,9 +468,6 @@ impl DecimalFormat {
457468

458469
let parse_integer_only: bool = jvm.get_field(&this, "parseIntegerOnly", "Z").await?;
459470
let mut normalized = RustString::new();
460-
if negative {
461-
normalized.push('-');
462-
}
463471
let mut digits = 0;
464472
let mut decimal = false;
465473
while index < characters.len() {
@@ -478,12 +486,41 @@ impl DecimalFormat {
478486
_ => break,
479487
}
480488
}
481-
let suffix = if negative { &negative_suffix } else { &positive_suffix };
482-
if digits == 0 || !characters[index..].starts_with(suffix) {
483-
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (index as i32,)).await?;
489+
if digits == 0 {
490+
let _: () = jvm
491+
.invoke_virtual(&position, "setErrorIndex", "(I)V", (utf16_indices[index] as i32,))
492+
.await?;
484493
return Ok(ClassInstanceRef::new(None));
485494
}
486-
index += suffix.len();
495+
let negative = if negative_prefix == positive_prefix {
496+
let positive_matches = characters[index..].starts_with(&positive_suffix);
497+
let negative_matches = characters[index..].starts_with(&negative_suffix);
498+
if negative_matches && (!positive_matches || negative_suffix.len() > positive_suffix.len()) {
499+
index += negative_suffix.len();
500+
true
501+
} else if positive_matches {
502+
index += positive_suffix.len();
503+
false
504+
} else {
505+
let _: () = jvm
506+
.invoke_virtual(&position, "setErrorIndex", "(I)V", (utf16_indices[index] as i32,))
507+
.await?;
508+
return Ok(ClassInstanceRef::new(None));
509+
}
510+
} else {
511+
let suffix = if prefix_negative { &negative_suffix } else { &positive_suffix };
512+
if !characters[index..].starts_with(suffix) {
513+
let _: () = jvm
514+
.invoke_virtual(&position, "setErrorIndex", "(I)V", (utf16_indices[index] as i32,))
515+
.await?;
516+
return Ok(ClassInstanceRef::new(None));
517+
}
518+
index += suffix.len();
519+
prefix_negative
520+
};
521+
if negative {
522+
normalized.insert(0, '-');
523+
}
487524

488525
let Ok(mut value) = normalized.parse::<f64>() else {
489526
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (start,)).await?;
@@ -493,7 +530,7 @@ impl DecimalFormat {
493530
if multiplier != 0 {
494531
value /= f64::from(multiplier);
495532
}
496-
let _: () = jvm.invoke_virtual(&position, "setIndex", "(I)V", (index as i32,)).await?;
533+
let _: () = jvm.invoke_virtual(&position, "setIndex", "(I)V", (utf16_indices[index] as i32,)).await?;
497534
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (-1,)).await?;
498535

499536
if multiplier == 1

java_runtime/src/classes/java/text/simple_date_format.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ impl SimpleDateFormat {
463463
match token {
464464
DateToken::Literal(value) => formatted.push_str(&value),
465465
DateToken::Field(character, count) => {
466-
let begin = formatted.chars().count() as i32;
466+
let begin = formatted.encode_utf16().count() as i32;
467467
match character {
468468
'G' => formatted.push_str(if date_time.year() <= 0 { "BC" } else { "AD" }),
469469
'y' => {
@@ -512,7 +512,7 @@ impl SimpleDateFormat {
512512
if !field_position_set && Self::date_field(character) == Some(requested_field) {
513513
let _: () = jvm.invoke_virtual(&position, "setBeginIndex", "(I)V", (base + begin,)).await?;
514514
let _: () = jvm
515-
.invoke_virtual(&position, "setEndIndex", "(I)V", (base + formatted.chars().count() as i32,))
515+
.invoke_virtual(&position, "setEndIndex", "(I)V", (base + formatted.encode_utf16().count() as i32,))
516516
.await?;
517517
field_position_set = true;
518518
}
@@ -541,22 +541,35 @@ impl SimpleDateFormat {
541541
};
542542
let source = JavaLangString::to_rust_string(jvm, &source).await?;
543543
let characters: Vec<char> = source.chars().collect();
544+
let mut utf16_indices = Vec::with_capacity(characters.len() + 1);
545+
let mut utf16_index = 0;
546+
for character in &characters {
547+
utf16_indices.push(utf16_index);
548+
utf16_index += character.len_utf16();
549+
}
550+
utf16_indices.push(utf16_index);
544551
let start: i32 = jvm.invoke_virtual(&position, "getIndex", "()I", ()).await?;
545-
if start < 0 || start as usize > characters.len() {
552+
if start < 0 {
546553
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (start,)).await?;
547554
return Ok(ClassInstanceRef::new(None));
548555
}
556+
let Some(start_index) = utf16_indices.iter().position(|index| *index == start as usize) else {
557+
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (start,)).await?;
558+
return Ok(ClassInstanceRef::new(None));
559+
};
549560
let calendar: ClassInstanceRef<Calendar> = jvm.get_field(&this, "calendar", "Ljava/util/Calendar;").await?;
550561
let time_zone: ClassInstanceRef<TimeZone> = jvm.invoke_virtual(&calendar, "getTimeZone", "()Ljava/util/TimeZone;", ()).await?;
551562
let offset: i32 = jvm.invoke_virtual(&time_zone, "getRawOffset", "()I", ()).await?;
552-
match Self::parse_timestamp(&tokens, &characters, start as usize, offset) {
563+
match Self::parse_timestamp(&tokens, &characters, start_index, offset) {
553564
Ok((timestamp, index)) => {
554-
let _: () = jvm.invoke_virtual(&position, "setIndex", "(I)V", (index as i32,)).await?;
565+
let _: () = jvm.invoke_virtual(&position, "setIndex", "(I)V", (utf16_indices[index] as i32,)).await?;
555566
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (-1,)).await?;
556567
Ok(jvm.new_class("java/util/Date", "(J)V", (timestamp,)).await?.into())
557568
}
558569
Err(error_index) => {
559-
let _: () = jvm.invoke_virtual(&position, "setErrorIndex", "(I)V", (error_index as i32,)).await?;
570+
let _: () = jvm
571+
.invoke_virtual(&position, "setErrorIndex", "(I)V", (utf16_indices[error_index] as i32,))
572+
.await?;
560573
Ok(ClassInstanceRef::new(None))
561574
}
562575
}

java_runtime/tests/classes/java/text/test_date_format.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,61 @@ async fn test_simple_date_format_parse_and_positions() -> Result<()> {
151151
Ok(())
152152
}
153153

154+
#[tokio::test]
155+
async fn test_simple_date_format_uses_utf16_positions() -> Result<()> {
156+
let jvm = test_jvm().await?;
157+
let pattern = JavaLangString::from_rust_string(&jvm, "'\u{1f600}'yyyy").await?;
158+
let format: ClassInstanceRef<SimpleDateFormat> = jvm
159+
.new_class("java/text/SimpleDateFormat", "(Ljava/lang/String;)V", (pattern,))
160+
.await?
161+
.into();
162+
let date: ClassInstanceRef<Date> = jvm.new_class("java/util/Date", "(J)V", (0i64,)).await?.into();
163+
let buffer: ClassInstanceRef<StringBuffer> = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?.into();
164+
let position: ClassInstanceRef<FieldPosition> = jvm.new_class("java/text/FieldPosition", "(I)V", (1,)).await?.into();
165+
let _: ClassInstanceRef<StringBuffer> = jvm
166+
.invoke_virtual(
167+
&format,
168+
"format",
169+
"(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;",
170+
(date, buffer.clone(), position.clone()),
171+
)
172+
.await?;
173+
let text: ClassInstanceRef<String> = jvm.invoke_virtual(&buffer, "toString", "()Ljava/lang/String;", ()).await?;
174+
assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "\u{1f600}1970");
175+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getBeginIndex", "()I", ()).await?, 2);
176+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getEndIndex", "()I", ()).await?, 6);
177+
178+
let pattern = JavaLangString::from_rust_string(&jvm, "yyyy-MM-dd").await?;
179+
let _: () = jvm.invoke_virtual(&format, "applyPattern", "(Ljava/lang/String;)V", (pattern,)).await?;
180+
let source = JavaLangString::from_rust_string(&jvm, "\u{1f600}1970-01-02").await?;
181+
let position: ClassInstanceRef<ParsePosition> = jvm.new_class("java/text/ParsePosition", "(I)V", (2,)).await?.into();
182+
let date: ClassInstanceRef<Date> = jvm
183+
.invoke_virtual(
184+
&format,
185+
"parse",
186+
"(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/util/Date;",
187+
(source, position.clone()),
188+
)
189+
.await?;
190+
assert_eq!(jvm.invoke_virtual::<_, i64>(&date, "getTime", "()J", ()).await?, 86_400_000);
191+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getIndex", "()I", ()).await?, 12);
192+
193+
let source = JavaLangString::from_rust_string(&jvm, "\u{1f600}197x").await?;
194+
let position: ClassInstanceRef<ParsePosition> = jvm.new_class("java/text/ParsePosition", "(I)V", (2,)).await?.into();
195+
let date: ClassInstanceRef<Date> = jvm
196+
.invoke_virtual(
197+
&format,
198+
"parse",
199+
"(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/util/Date;",
200+
(source, position.clone()),
201+
)
202+
.await?;
203+
assert!(date.is_null());
204+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getErrorIndex", "()I", ()).await?, 5);
205+
206+
Ok(())
207+
}
208+
154209
#[tokio::test]
155210
async fn test_date_format_clone_and_calendar_leniency_are_isolated() -> Result<()> {
156211
let jvm = test_jvm().await?;

java_runtime/tests/classes/java/text/test_number_format.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,18 @@ async fn test_number_format_integer_factories() -> Result<()> {
5656
let text: ClassInstanceRef<String> = jvm.invoke_virtual(&integer, "format", "(D)Ljava/lang/String;", (1234.6f64,)).await?;
5757
assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "1,235");
5858

59-
let source = JavaLangString::from_rust_string(&jvm, "1,234.5").await?;
59+
let source = JavaLangString::from_rust_string(&jvm, "1,234.5rest").await?;
60+
let position: ClassInstanceRef<ParsePosition> = jvm.new_class("java/text/ParsePosition", "(I)V", (0,)).await?.into();
6061
let parsed: ClassInstanceRef<Long> = jvm
61-
.invoke_virtual(&integer, "parse", "(Ljava/lang/String;)Ljava/lang/Number;", (source,))
62+
.invoke_virtual(
63+
&integer,
64+
"parse",
65+
"(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
66+
(source, position.clone()),
67+
)
6268
.await?;
6369
assert_eq!(jvm.invoke_virtual::<_, i64>(&parsed, "longValue", "()J", ()).await?, 1234);
70+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getIndex", "()I", ()).await?, 5);
6471

6572
Ok(())
6673
}
@@ -201,3 +208,92 @@ async fn test_decimal_format_quoted_affixes_and_integer_boundaries() -> Result<(
201208

202209
Ok(())
203210
}
211+
212+
#[tokio::test]
213+
async fn test_decimal_format_uses_utf16_positions_and_negative_suffixes() -> Result<()> {
214+
let jvm = test_jvm().await?;
215+
let pattern = JavaLangString::from_rust_string(&jvm, "'\u{1f600}'0.0").await?;
216+
let format: ClassInstanceRef<DecimalFormat> = jvm
217+
.new_class("java/text/DecimalFormat", "(Ljava/lang/String;)V", (pattern,))
218+
.await?
219+
.into();
220+
221+
let buffer: ClassInstanceRef<StringBuffer> = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?.into();
222+
let integer_position: ClassInstanceRef<FieldPosition> = jvm.new_class("java/text/FieldPosition", "(I)V", (0,)).await?.into();
223+
let _: ClassInstanceRef<StringBuffer> = jvm
224+
.invoke_virtual(
225+
&format,
226+
"format",
227+
"(DLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;",
228+
(12.3f64, buffer, integer_position.clone()),
229+
)
230+
.await?;
231+
assert_eq!(jvm.invoke_virtual::<_, i32>(&integer_position, "getBeginIndex", "()I", ()).await?, 2);
232+
assert_eq!(jvm.invoke_virtual::<_, i32>(&integer_position, "getEndIndex", "()I", ()).await?, 4);
233+
234+
let buffer: ClassInstanceRef<StringBuffer> = jvm.new_class("java/lang/StringBuffer", "()V", ()).await?.into();
235+
let fraction_position: ClassInstanceRef<FieldPosition> = jvm.new_class("java/text/FieldPosition", "(I)V", (1,)).await?.into();
236+
let _: ClassInstanceRef<StringBuffer> = jvm
237+
.invoke_virtual(
238+
&format,
239+
"format",
240+
"(DLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;",
241+
(12.3f64, buffer, fraction_position.clone()),
242+
)
243+
.await?;
244+
assert_eq!(jvm.invoke_virtual::<_, i32>(&fraction_position, "getBeginIndex", "()I", ()).await?, 5);
245+
assert_eq!(jvm.invoke_virtual::<_, i32>(&fraction_position, "getEndIndex", "()I", ()).await?, 6);
246+
247+
let plain_pattern = JavaLangString::from_rust_string(&jvm, "0").await?;
248+
let _: () = jvm
249+
.invoke_virtual(&format, "applyPattern", "(Ljava/lang/String;)V", (plain_pattern,))
250+
.await?;
251+
let source = JavaLangString::from_rust_string(&jvm, "\u{1f600}12").await?;
252+
let position: ClassInstanceRef<ParsePosition> = jvm.new_class("java/text/ParsePosition", "(I)V", (2,)).await?.into();
253+
let parsed: ClassInstanceRef<Long> = jvm
254+
.invoke_virtual(
255+
&format,
256+
"parse",
257+
"(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
258+
(source, position.clone()),
259+
)
260+
.await?;
261+
assert_eq!(jvm.invoke_virtual::<_, i64>(&parsed, "longValue", "()J", ()).await?, 12);
262+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getIndex", "()I", ()).await?, 4);
263+
264+
let prefix_pattern = JavaLangString::from_rust_string(&jvm, "'\u{1f600}'0x").await?;
265+
let _: () = jvm
266+
.invoke_virtual(&format, "applyPattern", "(Ljava/lang/String;)V", (prefix_pattern,))
267+
.await?;
268+
let source = JavaLangString::from_rust_string(&jvm, "\u{1f600}12y").await?;
269+
let position: ClassInstanceRef<ParsePosition> = jvm.new_class("java/text/ParsePosition", "(I)V", (0,)).await?.into();
270+
let parsed: ClassInstanceRef<Number> = jvm
271+
.invoke_virtual(
272+
&format,
273+
"parse",
274+
"(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
275+
(source, position.clone()),
276+
)
277+
.await?;
278+
assert!(parsed.is_null());
279+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getErrorIndex", "()I", ()).await?, 4);
280+
281+
let suffix_pattern = JavaLangString::from_rust_string(&jvm, "0;0-").await?;
282+
let _: () = jvm
283+
.invoke_virtual(&format, "applyPattern", "(Ljava/lang/String;)V", (suffix_pattern,))
284+
.await?;
285+
let source = JavaLangString::from_rust_string(&jvm, "12-").await?;
286+
let position: ClassInstanceRef<ParsePosition> = jvm.new_class("java/text/ParsePosition", "(I)V", (0,)).await?.into();
287+
let parsed: ClassInstanceRef<Long> = jvm
288+
.invoke_virtual(
289+
&format,
290+
"parse",
291+
"(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
292+
(source, position.clone()),
293+
)
294+
.await?;
295+
assert_eq!(jvm.invoke_virtual::<_, i64>(&parsed, "longValue", "()J", ()).await?, -12);
296+
assert_eq!(jvm.invoke_virtual::<_, i32>(&position, "getIndex", "()I", ()).await?, 3);
297+
298+
Ok(())
299+
}

0 commit comments

Comments
 (0)