001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.io;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020
021import java.io.*;
022
023/**
024 * A {@link Writer} implementation that writes to a {@link StringBuilder} instead of a {@link StringBuffer}.
025 *
026 * <p>
027 * This class is similar to {@link StringWriter}, but uses a {@link StringBuilder} instead of a
028 * {@link StringBuffer} to avoid synchronization overhead. This makes it more efficient for
029 * single-threaded use cases where thread-safety is not required.
030 *
031 * <h5 class='section'>Features:</h5>
032 * <ul class='spaced-list'>
033 *    <li>No synchronization overhead - uses {@link StringBuilder} instead of {@link StringBuffer}
034 *    <li>Efficient string building - optimized for single-threaded string construction
035 *    <li>Configurable initial capacity - can specify initial buffer size
036 *    <li>Wraps existing StringBuilder - can wrap an existing StringBuilder instance
037 *    <li>No-op close/flush - close and flush operations do nothing
038 * </ul>
039 *
040 * <h5 class='section'>Use Cases:</h5>
041 * <ul class='spaced-list'>
042 *    <li>Building strings efficiently in single-threaded contexts
043 *    <li>Capturing output from APIs that require a Writer
044 *    <li>Converting Writer-based APIs to StringBuilder output
045 *    <li>Performance-critical string building where synchronization is not needed
046 * </ul>
047 *
048 * <h5 class='section'>Usage:</h5>
049 * <p class='bjava'>
050 *    <jc>// Basic usage</jc>
051 *    StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter();
052 *    <jv>writer</jv>.write(<js>"Hello"</js>);
053 *    <jv>writer</jv>.write(<js>" World"</js>);
054 *    String <jv>result</jv> = <jv>writer</jv>.toString();  <jc>// Returns "Hello World"</jc>
055 *
056 *    <jc>// With initial capacity</jc>
057 *    StringBuilderWriter <jv>writer2</jv> = <jk>new</jk> StringBuilderWriter(1000);
058 *
059 *    <jc>// Wrap existing StringBuilder</jc>
060 *    StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder();
061 *    StringBuilderWriter <jv>writer3</jv> = <jk>new</jk> StringBuilderWriter(<jv>sb</jv>);
062 *    <jv>writer3</jv>.write(<js>"test"</js>);
063 *    <jc>// sb now contains "test"</jc>
064 * </p>
065 *
066 * <h5 class='section'>Comparison with StringWriter:</h5>
067 * <ul class='spaced-list'>
068 *    <li><b>StringWriter:</b> Uses {@link StringBuffer} (thread-safe, synchronized)
069 *    <li><b>StringBuilderWriter:</b> Uses {@link StringBuilder} (not thread-safe, faster)
070 *    <li><b>StringWriter:</b> Suitable for multi-threaded scenarios
071 *    <li><b>StringBuilderWriter:</b> Suitable for single-threaded scenarios where performance matters
072 * </ul>
073 *
074 * <h5 class='section'>Thread Safety:</h5>
075 * <p>
076 * This class is <b>not thread-safe</b>. It uses a {@link StringBuilder} internally, which is not
077 * synchronized. If multiple threads need to write to the same StringBuilderWriter instance,
078 * external synchronization is required.
079 *
080 * <h5 class='section'>See Also:</h5><ul>
081 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a>
082 * </ul>
083 */
084public class StringBuilderWriter extends Writer {
085
086   private StringBuilder sb;
087
088   /**
089    * Constructor.
090    *
091    * <p>
092    * Creates a new StringBuilderWriter with the default initial capacity (16 characters).
093    *
094    * <h5 class='section'>Example:</h5>
095    * <p class='bjava'>
096    *    StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter();
097    *    <jv>writer</jv>.write(<js>"Hello"</js>);
098    * </p>
099    */
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}