From bbae625850d42bcb2763935be042f1387fd42cf4 Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Sun, 25 May 2025 14:18:25 +0900 Subject: [PATCH 1/2] Add Javadoc since for gh-34745 Closes gh-34940 Signed-off-by: Johnny Lim --- .../converter/xml/AbstractJaxb2HttpMessageConverter.java | 1 + .../xml/Jaxb2RootElementHttpMessageConverter.java | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java index da74365382d..ab5f6dfa335 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java @@ -124,6 +124,7 @@ public abstract class AbstractJaxb2HttpMessageConverter extends AbstractXmlHt * Detect the charset from the given {@link HttpHeaders#getContentType()}. * @param httpHeaders the current HTTP headers * @return the charset defined in the content type header, or {@code null} if not found + * @since 6.2.8 */ @Nullable protected Charset detectCharset(HttpHeaders httpHeaders) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java index 5417b28500c..d81a15f9ff9 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java @@ -161,6 +161,13 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } } + /** + * Process {@code source} with {@code charset}. + * @param source source to process + * @param charset charset to use + * @return source + * @since 6.2.8 + */ protected Source processSource(Source source, @Nullable Charset charset) { if (source instanceof StreamSource streamSource) { InputSource inputSource = new InputSource(streamSource.getInputStream()); From 182d654fa870e849371bc8841320241bd92a3b80 Mon Sep 17 00:00:00 2001 From: Patrick Strawderman Date: Tue, 22 Apr 2025 17:59:08 -0500 Subject: [PATCH 2/2] Add optimized DataBufferInputStream overrides Add optimized DataBufferInputStream overrides for readNBytes, skip, and transferTo; all of them allocate byte buffers which we can either avoid (in the case of skip) or size more precisely since the number of remaining bytes is known. Closes gh-34799 Signed-off-by: Patrick Strawderman --- .../core/io/buffer/DataBufferInputStream.java | 38 +++++++++++++++- .../core/io/buffer/DataBufferTests.java | 43 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java index 33135285ae9..669394369c2 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java @@ -18,6 +18,8 @@ package org.springframework.core.io.buffer; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.Objects; import org.springframework.util.Assert; @@ -103,10 +105,44 @@ final class DataBufferInputStream extends InputStream { this.closed = true; } + @Override + public byte[] readNBytes(int len) throws IOException { + if (len < 0) { + throw new IllegalArgumentException("len < 0"); + } + checkClosed(); + int size = Math.min(available(), len); + byte[] out = new byte[size]; + this.dataBuffer.read(out); + return out; + } + + @Override + public long skip(long n) throws IOException { + checkClosed(); + if (n <= 0) { + return 0L; + } + int skipped = Math.min(available(), n > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) n); + this.dataBuffer.readPosition(Math.min(this.end, this.dataBuffer.readPosition() + skipped)); + return skipped; + } + + @Override + public long transferTo(OutputStream out) throws IOException { + Objects.requireNonNull(out, "out"); + checkClosed(); + if (available() == 0) { + return 0L; + } + byte[] buf = readAllBytes(); + out.write(buf); + return buf.length; + } + private void checkClosed() throws IOException { if (this.closed) { throw new IOException("DataBufferInputStream is closed"); } } - } diff --git a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java index a425f389b0d..5522f664af3 100644 --- a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java @@ -16,6 +16,7 @@ package org.springframework.core.io.buffer; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; @@ -342,6 +343,48 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { assertThat(len).isEqualTo(3); assertThat(bytes).containsExactly('c', 'd', 'e'); + buffer.readPosition(0); + inputStream = buffer.asInputStream(); + assertThat(inputStream.readAllBytes()).asString().isEqualTo("abcde"); + assertThat(inputStream.available()).isEqualTo(0); + assertThat(inputStream.readAllBytes()).isEmpty(); + + buffer.readPosition(0); + inputStream = buffer.asInputStream(); + inputStream.mark(5); + assertThat(inputStream.readNBytes(0)).isEmpty(); + assertThat(inputStream.readNBytes(1000)).asString().isEqualTo("abcde"); + inputStream.reset(); + assertThat(inputStream.readNBytes(3)).asString().isEqualTo("abc"); + assertThat(inputStream.readNBytes(2)).asString().isEqualTo("de"); + assertThat(inputStream.readNBytes(10)).isEmpty(); + + buffer.readPosition(0); + inputStream = buffer.asInputStream(); + inputStream.mark(5); + assertThat(inputStream.skip(1)).isEqualTo(1); + assertThat(inputStream.readAllBytes()).asString().isEqualTo("bcde"); + assertThat(inputStream.skip(10)).isEqualTo(0); + assertThat(inputStream.available()).isEqualTo(0); + inputStream.reset(); + assertThat(inputStream.skip(100)).isEqualTo(5); + assertThat(inputStream.available()).isEqualTo(0); + + buffer.readPosition(0); + inputStream = buffer.asInputStream(); + inputStream.mark(5); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + assertThat(inputStream.transferTo(out)).isEqualTo(5); + assertThat(out.toByteArray()).asString().isEqualTo("abcde"); + assertThat(inputStream.available()).isEqualTo(0); + out.reset(); + inputStream.reset(); + assertThat(inputStream.read()).isEqualTo('a'); + assertThat(inputStream.transferTo(out)).isEqualTo(4); + assertThat(out.toByteArray()).asString().isEqualTo("bcde"); + assertThat(inputStream.available()).isEqualTo(0); + assertThat(inputStream.transferTo(OutputStream.nullOutputStream())).isEqualTo(0); + release(buffer); }