1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.commons.io;
18
19 import static org.apache.juneau.commons.utils.AssertionUtils.*;
20
21 import java.io.*;
22
23 /**
24 * A {@link Writer} implementation that writes to a {@link StringBuilder} instead of a {@link StringBuffer}.
25 *
26 * <p>
27 * This class is similar to {@link StringWriter}, but uses a {@link StringBuilder} instead of a
28 * {@link StringBuffer} to avoid synchronization overhead. This makes it more efficient for
29 * single-threaded use cases where thread-safety is not required.
30 *
31 * <h5 class='section'>Features:</h5>
32 * <ul class='spaced-list'>
33 * <li>No synchronization overhead - uses {@link StringBuilder} instead of {@link StringBuffer}
34 * <li>Efficient string building - optimized for single-threaded string construction
35 * <li>Configurable initial capacity - can specify initial buffer size
36 * <li>Wraps existing StringBuilder - can wrap an existing StringBuilder instance
37 * <li>No-op close/flush - close and flush operations do nothing
38 * </ul>
39 *
40 * <h5 class='section'>Use Cases:</h5>
41 * <ul class='spaced-list'>
42 * <li>Building strings efficiently in single-threaded contexts
43 * <li>Capturing output from APIs that require a Writer
44 * <li>Converting Writer-based APIs to StringBuilder output
45 * <li>Performance-critical string building where synchronization is not needed
46 * </ul>
47 *
48 * <h5 class='section'>Usage:</h5>
49 * <p class='bjava'>
50 * <jc>// Basic usage</jc>
51 * StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter();
52 * <jv>writer</jv>.write(<js>"Hello"</js>);
53 * <jv>writer</jv>.write(<js>" World"</js>);
54 * String <jv>result</jv> = <jv>writer</jv>.toString(); <jc>// Returns "Hello World"</jc>
55 *
56 * <jc>// With initial capacity</jc>
57 * StringBuilderWriter <jv>writer2</jv> = <jk>new</jk> StringBuilderWriter(1000);
58 *
59 * <jc>// Wrap existing StringBuilder</jc>
60 * StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder();
61 * StringBuilderWriter <jv>writer3</jv> = <jk>new</jk> StringBuilderWriter(<jv>sb</jv>);
62 * <jv>writer3</jv>.write(<js>"test"</js>);
63 * <jc>// sb now contains "test"</jc>
64 * </p>
65 *
66 * <h5 class='section'>Comparison with StringWriter:</h5>
67 * <ul class='spaced-list'>
68 * <li><b>StringWriter:</b> Uses {@link StringBuffer} (thread-safe, synchronized)
69 * <li><b>StringBuilderWriter:</b> Uses {@link StringBuilder} (not thread-safe, faster)
70 * <li><b>StringWriter:</b> Suitable for multi-threaded scenarios
71 * <li><b>StringBuilderWriter:</b> Suitable for single-threaded scenarios where performance matters
72 * </ul>
73 *
74 * <h5 class='section'>Thread Safety:</h5>
75 * <p>
76 * This class is <b>not thread-safe</b>. It uses a {@link StringBuilder} internally, which is not
77 * synchronized. If multiple threads need to write to the same StringBuilderWriter instance,
78 * external synchronization is required.
79 *
80 * <h5 class='section'>See Also:</h5><ul>
81 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a>
82 * </ul>
83 */
84 public class StringBuilderWriter extends Writer {
85
86 private StringBuilder sb;
87
88 /**
89 * Constructor.
90 *
91 * <p>
92 * Creates a new StringBuilderWriter with the default initial capacity (16 characters).
93 *
94 * <h5 class='section'>Example:</h5>
95 * <p class='bjava'>
96 * StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter();
97 * <jv>writer</jv>.write(<js>"Hello"</js>);
98 * </p>
99 */
100 public StringBuilderWriter() {
101 sb = new StringBuilder();
102 lock = null;
103 }
104
105 /**
106 * Constructor.
107 *
108 * <p>
109 * Creates a new StringBuilderWriter with the specified initial capacity. This can improve
110 * performance if you know approximately how large the resulting string will be, avoiding
111 * multiple buffer reallocations.
112 *
113 * <h5 class='section'>Example:</h5>
114 * <p class='bjava'>
115 * <jc>// Pre-allocate buffer for known size</jc>
116 * StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter(1000);
117 * <jv>writer</jv>.write(<js>"Large content..."</js>);
118 * </p>
119 *
120 * @param initialSize The initial capacity of the internal StringBuilder in characters.
121 * Must be non-negative.
122 * @throws IllegalArgumentException If <tt>initialSize</tt> is negative.
123 */
124 public StringBuilderWriter(int initialSize) {
125 assertArg(initialSize >= 0, "Argument 'initialSize' cannot be negative.");
126 sb = new StringBuilder(initialSize);
127 lock = null;
128 }
129
130 /**
131 * Constructor.
132 *
133 * <p>
134 * Creates a new StringBuilderWriter that wraps an existing StringBuilder. All writes to
135 * this writer will be appended to the provided StringBuilder. This is useful when you
136 * want to write to a StringBuilder that you already have a reference to.
137 *
138 * <h5 class='section'>Example:</h5>
139 * <p class='bjava'>
140 * StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"Prefix: "</js>);
141 * StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter(<jv>sb</jv>);
142 * <jv>writer</jv>.write(<js>"Suffix"</js>);
143 * String <jv>result</jv> = <jv>sb</jv>.toString(); <jc>// Returns "Prefix: Suffix"</jc>
144 * </p>
145 *
146 * @param sb The StringBuilder to wrap. Must not be <jk>null</jk>.
147 */
148 public StringBuilderWriter(StringBuilder sb) {
149 this.sb = assertArgNotNull("sb", sb);
150 lock = null;
151 }
152
153 @Override /* Overridden from Writer */
154 public StringBuilderWriter append(char c) {
155 write(c);
156 return this;
157 }
158
159 @Override /* Overridden from Writer */
160 public StringBuilderWriter append(CharSequence csq) {
161 if (csq == null)
162 write("null");
163 else
164 write(csq.toString());
165 return this;
166 }
167
168 @Override /* Overridden from Writer */
169 public StringBuilderWriter append(CharSequence csq, int start, int end) {
170 CharSequence cs = (csq == null ? "null" : csq);
171 write(cs.subSequence(start, end).toString());
172 return this;
173 }
174
175 @Override /* Overridden from Writer */
176 public void close() throws IOException {}
177
178 @Override /* Overridden from Writer */
179 public void flush() {}
180
181 @Override /* Overridden from Object */
182 public String toString() {
183 return sb.toString();
184 }
185
186 @Override /* Overridden from Writer */
187 public void write(char cbuf[], int start, int length) {
188 assertArgNotNull("cbuf", cbuf);
189 sb.append(cbuf, start, length);
190 }
191
192 @Override /* Overridden from Writer */
193 public void write(int c) {
194 sb.appendCodePoint(c);
195 }
196
197 @Override /* Overridden from Writer */
198 public void write(String str) {
199 assertArgNotNull("str", str);
200 sb.append(str);
201 }
202
203 @Override /* Overridden from Writer */
204 public void write(String str, int off, int len) {
205 assertArgNotNull("str", str);
206 sb.append(str.substring(off, off + len));
207 }
208 }