diff --git a/rustjava-runtime/src/classes/java/io/input_stream_reader.rs b/rustjava-runtime/src/classes/java/io/input_stream_reader.rs index 4e1d3600..129339a2 100644 --- a/rustjava-runtime/src/classes/java/io/input_stream_reader.rs +++ b/rustjava-runtime/src/classes/java/io/input_stream_reader.rs @@ -123,41 +123,65 @@ impl InputStreamReader { return Ok(0); } - let mut write_buf_size: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "writeBufSize", "I").await?; - - while write_buf_size < length { - let read_buf: ClassInstanceRef> = jvm.get_field(&this, "java/io/InputStreamReader", "readBuf", "[B").await?; - let read_buf_size: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "readBufSize", "I").await?; - - if read_buf_size < (BUF_SIZE / 2) as _ { - let bytes_to_read = BUF_SIZE as i32 - read_buf_size; - - let r#in = jvm.get_field(&this, "java/io/InputStreamReader", "in", "Ljava/io/InputStream;").await?; - - let temp = jvm.instantiate_array("B", bytes_to_read as _).await?; - let read: i32 = jvm - .invoke_virtual(&r#in, "java/io/InputStream", "read", "([BII)I", (temp.clone(), 0, bytes_to_read)) + let r#in = jvm.get_field(&this, "java/io/InputStreamReader", "in", "Ljava/io/InputStream;").await?; + let mut write_buf: ClassInstanceRef> = jvm.get_field(&this, "java/io/InputStreamReader", "writeBuf", "[C").await?; + let mut copied = 0; + + loop { + let write_buf_size: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "writeBufSize", "I").await?; + let to_copy = min(length - copied, write_buf_size); + if to_copy > 0 { + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (write_buf.clone(), 0, buf.clone(), offset + copied, to_copy), + ) .await?; - if read != -1 { - let _: () = jvm - .invoke_static( - "java/lang/System", - "arraycopy", - "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (temp, 0, read_buf.clone(), read_buf_size, read), - ) - .await?; - jvm.put_field(&mut this, "java/io/InputStreamReader", "readBufSize", "I", read_buf_size + read) - .await?; - } else { - jvm.put_field(&mut this, "java/io/InputStreamReader", "endOfInput", "Z", true).await?; - if read_buf_size == 0 && write_buf_size == 0 { - return Ok(-1); - } + let _: () = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (write_buf.clone(), to_copy, write_buf.clone(), 0, write_buf_size - to_copy), + ) + .await?; + jvm.put_field(&mut this, "java/io/InputStreamReader", "writeBufSize", "I", write_buf_size - to_copy) + .await?; + copied += to_copy; + } + + let mut end_of_input: bool = jvm.get_field(&this, "java/io/InputStreamReader", "endOfInput", "Z").await?; + if copied == length || end_of_input { + break; + } + // Once characters are available, do not wait for more input to fill the caller's buffer. + if copied > 0 { + let available: i32 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "available", "()I", ()).await?; + if available == 0 { + break; } } - let read_buf_size: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "readBufSize", "I").await?; + let read_buf: ClassInstanceRef> = jvm.get_field(&this, "java/io/InputStreamReader", "readBuf", "[B").await?; + let mut read_buf_size: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "readBufSize", "I").await?; + let read: i32 = jvm + .invoke_virtual( + &r#in, + "java/io/InputStream", + "read", + "([BII)I", + (read_buf.clone(), read_buf_size, BUF_SIZE as i32 - read_buf_size), + ) + .await?; + if read == -1 { + end_of_input = true; + jvm.put_field(&mut this, "java/io/InputStreamReader", "endOfInput", "Z", true).await?; + } else { + read_buf_size += read; + } + let mut read_buf_data = vec![0; read_buf_size as _]; jvm.array_raw_buffer(&read_buf).await?.read(0, &mut read_buf_data)?; @@ -172,7 +196,6 @@ impl InputStreamReader { }; let read_buf_data: Vec = cast_vec(read_buf_data); - let end_of_input: bool = jvm.get_field(&this, "java/io/InputStreamReader", "endOfInput", "Z").await?; let mut decode_length = read_buf_data.len(); if !end_of_input && charset == "UTF-8" && decode_length > 0 { let mut lead_index = decode_length - 1; @@ -188,8 +211,19 @@ impl InputStreamReader { if decode_length - lead_index < expected_length { decode_length = lead_index; } - } else if !end_of_input && charset == "EUC-KR" && read_buf_data.last().is_some_and(|value| *value >= 0x81) { - decode_length -= 1; + } else if !end_of_input && charset == "EUC-KR" { + let mut index = 0; + while index < decode_length { + if (0x81..=0xfe).contains(&read_buf_data[index]) { + if index + 1 == decode_length { + decode_length = index; + break; + } + index += 2; + } else { + index += 1; + } + } } let mut decoded = vec![0; BUF_SIZE * 3]; @@ -207,50 +241,14 @@ impl InputStreamReader { jvm.put_field(&mut this, "java/io/InputStreamReader", "readBufSize", "I", read_buf_size - read as i32) .await?; - // add to writeBuf - let mut write_buf = jvm.get_field(&this, "java/io/InputStreamReader", "writeBuf", "[C").await?; - let buffered_chars: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "writeBufSize", "I").await?; - jvm.store_array( - &mut write_buf, - buffered_chars as _, - cast_slice::(&decoded[..wrote]).to_vec(), - ) - .await?; - write_buf_size = buffered_chars + wrote as i32; - jvm.put_field(&mut this, "java/io/InputStreamReader", "writeBufSize", "I", write_buf_size) + // The previous output was drained before reading another chunk. + jvm.store_array(&mut write_buf, 0, cast_slice::(&decoded[..wrote]).to_vec()) + .await?; + jvm.put_field(&mut this, "java/io/InputStreamReader", "writeBufSize", "I", wrote as i32) .await?; - if write_buf_size > 0 { - break; - } } - let write_buf: ClassInstanceRef> = jvm.get_field(&this, "java/io/InputStreamReader", "writeBuf", "[C").await?; - let write_buf_size: i32 = jvm.get_field(&this, "java/io/InputStreamReader", "writeBufSize", "I").await?; - - let to_copy = min(length, write_buf_size); - - let _: () = jvm - .invoke_static( - "java/lang/System", - "arraycopy", - "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (write_buf.clone(), 0, buf, offset, to_copy), - ) - .await?; - - // advance writeBuf - let _: () = jvm - .invoke_static( - "java/lang/System", - "arraycopy", - "(Ljava/lang/Object;ILjava/lang/Object;II)V", - (write_buf.clone(), to_copy, write_buf, 0, write_buf_size - to_copy), - ) - .await?; - jvm.put_field(&mut this, "java/io/InputStreamReader", "writeBufSize", "I", write_buf_size - to_copy) - .await?; - - Ok(to_copy) + Ok(if copied == 0 { -1 } else { copied }) } async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { diff --git a/rustjava-runtime/tests/classes/java/io/test_input_stream_reader.rs b/rustjava-runtime/tests/classes/java/io/test_input_stream_reader.rs index 0668b673..30d0851e 100644 --- a/rustjava-runtime/tests/classes/java/io/test_input_stream_reader.rs +++ b/rustjava-runtime/tests/classes/java/io/test_input_stream_reader.rs @@ -103,6 +103,55 @@ async fn test_isr() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_input_stream_reader_drains_available_input() -> Result<()> { + let jvm = test_jvm().await?; + let value = "123456789한글\r\n가나다".repeat(20); + let expected: Vec = value.encode_utf16().collect(); + + for encoding in [encoding_rs::UTF_8, encoding_rs::EUC_KR] { + let (encoded, _, _) = encoding.encode(&value); + let mut bytes = jvm.instantiate_array("B", encoded.len()).await?; + jvm.store_array(&mut bytes, 0, encoded.iter().map(|byte| *byte as i8)).await?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?; + let charset = JavaLangString::from_rust_string(&jvm, encoding.name()).await?; + let reader = jvm + .new_class( + "java/io/InputStreamReader", + "(Ljava/io/InputStream;Ljava/lang/String;)V", + (input.clone(), charset), + ) + .await?; + let mut chars = jvm.instantiate_array("C", expected.len() + 4).await?; + jvm.store_array(&mut chars, 0, vec!['!' as JavaChar; expected.len() + 4]).await?; + + let read: i32 = jvm + .invoke_virtual( + &reader, + "java/io/Reader", + "read", + "([CII)I", + (chars.clone(), 2, (expected.len() + 2) as i32), + ) + .await?; + assert_eq!(read as usize, expected.len(), "{}", encoding.name()); + assert_eq!(jvm.load_array::(&chars, 2, expected.len()).await?, expected); + assert_eq!(jvm.load_array::(&chars, 0, 2).await?, ['!' as JavaChar; 2]); + assert_eq!(jvm.load_array::(&chars, expected.len() + 2, 2).await?, ['!' as JavaChar; 2]); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&input, "java/io/InputStream", "available", "()I", ()) + .await?, + 0 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&reader, "java/io/Reader", "read", "([C)I", (chars,)).await?, + -1 + ); + } + + Ok(()) +} + #[tokio::test] async fn test_input_stream_reader_preserves_split_multibyte_and_buffered_eof() -> Result<()> { let jvm = test_jvm().await?; @@ -138,7 +187,7 @@ async fn test_input_stream_reader_preserves_split_multibyte_and_buffered_eof() - } #[tokio::test] -async fn test_input_stream_reader_does_not_return_zero_for_split_multibyte_input() -> Result<()> { +async fn test_input_stream_reader_waits_only_until_a_character_is_available() -> Result<()> { let runtime = TestRuntime::new(BTreeMap::new()); let jvm = create_test_jvm(runtime.clone()).await?; jvm.register_class( @@ -150,24 +199,39 @@ async fn test_input_stream_reader_does_not_return_zero_for_split_multibyte_input ) .await?; - let value = "한"; - let mut bytes = jvm.instantiate_array("B", value.len()).await?; - jvm.store_array(&mut bytes, 0, value.as_bytes().iter().map(|byte| *byte as i8)).await?; - let input = jvm.new_class("OneByteInputStream", "([B)V", (bytes,)).await?; - let reader = jvm.new_class("java/io/InputStreamReader", "(Ljava/io/InputStream;)V", (input,)).await?; - let chars = jvm.instantiate_array("C", 1).await?; - - assert_eq!( - jvm.invoke_virtual::<_, i32>(&reader, &reader.class_definition().name(), "read", "([CII)I", (chars.clone(), 0, 1)) - .await?, - 1 - ); - assert_eq!(jvm.load_array::(&chars, 0, 1).await?, ['한' as JavaChar]); - assert_eq!( - jvm.invoke_virtual::<_, i32>(&reader, &reader.class_definition().name(), "read", "([CII)I", (chars, 0, 1)) - .await?, - -1 - ); + for (encoding, trailing_lead) in [(encoding_rs::UTF_8, 0xed), (encoding_rs::EUC_KR, 0xc7)] { + let (encoded, _, _) = encoding.encode("한글"); + let mut bytes = jvm.instantiate_array("B", encoded.len() + 1).await?; + jvm.store_array(&mut bytes, 0, encoded.iter().map(|byte| *byte as i8)).await?; + jvm.store_array(&mut bytes, encoded.len(), [trailing_lead as i8]).await?; + let input = jvm.new_class("OneByteInputStream", "([B)V", (bytes,)).await?; + let charset = JavaLangString::from_rust_string(&jvm, encoding.name()).await?; + let reader = jvm + .new_class( + "java/io/InputStreamReader", + "(Ljava/io/InputStream;Ljava/lang/String;)V", + (input.clone(), charset), + ) + .await?; + let chars = jvm.instantiate_array("C", 16).await?; + + for (expected, position) in [('한', encoded.len() / 2), ('글', encoded.len()), ('\u{fffd}', encoded.len() + 1)] { + assert_eq!( + jvm.invoke_virtual::<_, i32>(&reader, "java/io/Reader", "read", "([C)I", (chars.clone(),)) + .await?, + 1 + ); + assert_eq!(jvm.load_array::(&chars, 0, 1).await?, [expected as JavaChar]); + assert_eq!( + jvm.get_field::(&input, "OneByteInputStream", "position", "I").await?, + position as i32 + ); + } + assert_eq!( + jvm.invoke_virtual::<_, i32>(&reader, "java/io/Reader", "read", "([C)I", (chars,)).await?, + -1 + ); + } Ok(()) } @@ -215,7 +279,7 @@ async fn test_reader_default_contract_and_lifecycle() -> Result<()> { 'a' as i32 ); - let chars = jvm.instantiate_array("C", 2).await?; + let chars = jvm.instantiate_array("C", 4).await?; assert_eq!( jvm.invoke_virtual::<_, i32>(&reader, &reader.class_definition().name(), "read", "([C)I", (chars.clone(),)) .await?,