From 7cdacf308348d11e2edc07d9c2ebc761bf999e36 Mon Sep 17 00:00:00 2001 From: Patrick Strawderman Date: Fri, 1 Dec 2023 12:50:19 -0800 Subject: [PATCH] Introduce toString(Charset) in FastByteArrayOutputStream This commit introduces a toString() overload in FastByteArrayOutputStream that accepts a Charset in order to mirror the method that was introduced in ByteArrayOutputStream in JDK 10, including a special case for when a single buffer is in use internally to avoid the need to resize. This commit also updates getContentAsString() in ContentCachingRequestWrapper to use this new toString(Charset) method. Closes gh-31737 --- .../util/FastByteArrayOutputStream.java | 20 ++++++++++++++++++- .../util/FastByteArrayOutputStreamTests.java | 18 +++++++++++++++++ .../util/ContentCachingRequestWrapper.java | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java index e2673124aa15..10b8798f2b91 100644 --- a/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java +++ b/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; import java.security.MessageDigest; import java.util.ArrayDeque; import java.util.Deque; @@ -162,9 +163,26 @@ public void close() { */ @Override public String toString() { - return new String(toByteArrayUnsafe()); + return toString(Charset.defaultCharset()); } + /** + * Converts the buffer's contents into a string by decoding the bytes using + * the specified {@link java.nio.charset.Charset charset}. + * + * @param charset the {@linkplain java.nio.charset.Charset charset} + * to be used to decode the {@code bytes} + * @return a String decoded from the buffer's contents + */ + public String toString(Charset charset) { + if (size() == 0) { + return ""; + } + if (buffers.size() == 1) { + return new String(buffers.getFirst(), 0, index, charset); + } + return new String(toByteArrayUnsafe(), charset); + } // Custom methods diff --git a/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java b/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java index 675810fe96e3..94e520d97559 100644 --- a/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java +++ b/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java @@ -57,6 +57,24 @@ void resize() throws Exception { assertThat(this.os.size()).isEqualTo(sizeBefore); } + @Test + void stringConversion() throws Exception { + this.os.write(this.helloBytes); + assertThat(this.os.toString()).isEqualTo("Hello World"); + assertThat(this.os.toString(StandardCharsets.UTF_8)).isEqualTo("Hello World"); + + FastByteArrayOutputStream empty = new FastByteArrayOutputStream(); + assertThat(empty.toString()).isEqualTo(""); + assertThat(empty.toString(StandardCharsets.US_ASCII)).isEqualTo(""); + + FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream(5); + // Add bytes in multiple writes to ensure we get more than one buffer internally + outputStream.write(this.helloBytes, 0, 5); + outputStream.write(this.helloBytes, 5, 6); + assertThat(outputStream.toString(StandardCharsets.UTF_8)).isEqualTo("Hello World"); + assertThat(outputStream.toString()).isEqualTo("Hello World"); + } + @Test void autoGrow() throws IOException { this.os.resize(1); diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java index a54475afeae7..5a53f64c39c8 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java @@ -205,7 +205,7 @@ public byte[] getContentAsByteArray() { * @see #getContentAsByteArray() */ public String getContentAsString() { - return new String(this.cachedContent.toByteArrayUnsafe(), Charset.forName(getCharacterEncoding())); + return this.cachedContent.toString(Charset.forName(getCharacterEncoding())); } /**