diff --git a/src/java.base/share/classes/java/io/StringWriter.java b/src/java.base/share/classes/java/io/StringWriter.java index ee58d741cc869..8edd0145b2478 100644 --- a/src/java.base/share/classes/java/io/StringWriter.java +++ b/src/java.base/share/classes/java/io/StringWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,10 @@ * can be called after the stream has been closed without generating an * {@code IOException}. * + * @apiNote + * {@link Writer#of(StringBuilder)} provides a method to write into an existing + * {@link StringBuilder} that may be more efficient than {@code StringWriter}. + * * @author Mark Reinhold * @since 1.1 */ diff --git a/src/java.base/share/classes/java/io/Writer.java b/src/java.base/share/classes/java/io/Writer.java index 433a116a4bbcd..54aaea60ab5d8 100644 --- a/src/java.base/share/classes/java/io/Writer.java +++ b/src/java.base/share/classes/java/io/Writer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -144,6 +144,97 @@ public void close() throws IOException { }; } + /** + * Returns a {@code Writer} that writes characters into a + * {@code StringBuilder}. The writer is initially open and writing appends + * after the last character in the string builder. + * + *
The resulting writer is not safe for use by multiple + * concurrent threads. If the writer is to be used by more than one + * thread it should be controlled by appropriate synchronization. + * + *
If the string builder changes while the writer is open, e.g. the length
+ * changes, the behavior is undefined.
+ *
+ * @param sb {@code StringBuilder} consuming the character stream.
+ * @return a {@code Writer} which writes characters into {@code sb}.
+ * @throws NullPointerException if {@code sb} is {@code null}
+ *
+ * @since 25
+ */
+ public static Writer of(final StringBuilder sb) {
+ Objects.requireNonNull(sb);
+
+ return new Writer() {
+ private boolean isClosed;
+
+ /** Check to make sure that the stream has not been closed */
+ private void ensureOpen() throws IOException {
+ if (isClosed)
+ throw new IOException("Stream closed");
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ ensureOpen();
+ sb.append((char) c);
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ ensureOpen();
+ Objects.checkFromIndexSize(off, len, cbuf.length);
+ if (len == 0) {
+ return;
+ }
+ sb.append(cbuf, off, len);
+ }
+
+ @Override
+ public void write(String str) throws IOException {
+ ensureOpen();
+ sb.append(str);
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException {
+ ensureOpen();
+ sb.append(str, off, off + len);
+ }
+
+ @Override
+ public Writer append(CharSequence csq) throws IOException {
+ ensureOpen();
+ sb.append(csq);
+ return this;
+ }
+
+ @Override
+ public Writer append(CharSequence csq, int start, int end) throws IOException {
+ ensureOpen();
+ sb.append(csq, start, end);
+ return this;
+ }
+
+ @Override
+ public Writer append(char c) throws IOException {
+ ensureOpen();
+ sb.append(c);
+ return this;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ ensureOpen();
+ }
+
+ @Override
+ public void close() throws IOException {
+ isClosed = true;
+ }
+ };
+ }
+
/**
* The object used to synchronize operations on this stream. For
* efficiency, a character-stream object may use an object other than
diff --git a/test/jdk/java/io/Writer/Of.java b/test/jdk/java/io/Writer/Of.java
new file mode 100644
index 0000000000000..03eb28f61b029
--- /dev/null
+++ b/test/jdk/java/io/Writer/Of.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.Writer;
+import java.io.StringWriter;
+import java.io.IOException;
+import java.util.function.Supplier;
+
+import org.testng.annotations.*;
+
+import static org.testng.Assert.*;
+
+/*
+ * @test
+ * @bug 8353795
+ * @summary Check for expected behavior of Writer.of().
+ * @run testng Of
+ */
+public class Of {
+ private static final String CONTENT = "Some Writer Test";
+
+ private static record Config(String id, Writer writer, Supplier