View Javadoc
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 }