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.Shorts.*;
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 */
084@SuppressWarnings({
085   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
086})
087public class StringBuilderWriter extends Writer {
088
089   private final StringBuilder sb;
090
091   /**
092    * Constructor.
093    *
094    * <p>
095    * Creates a new StringBuilderWriter with the default initial capacity (16 characters).
096    *
097    * <h5 class='section'>Example:</h5>
098    * <p class='bjava'>
099    *    StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter();
100    *    <jv>writer</jv>.write(<js>"Hello"</js>);
101    * </p>
102    */
103   public StringBuilderWriter() {
104      sb = new StringBuilder();
105      lock = null;
106   }
107
108   /**
109    * Constructor.
110    *
111    * <p>
112    * Creates a new StringBuilderWriter with the specified initial capacity. This can improve
113    * performance if you know approximately how large the resulting string will be, avoiding
114    * multiple buffer reallocations.
115    *
116    * <h5 class='section'>Example:</h5>
117    * <p class='bjava'>
118    *    <jc>// Pre-allocate buffer for known size</jc>
119    *    StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter(1000);
120    *    <jv>writer</jv>.write(<js>"Large content..."</js>);
121    * </p>
122    *
123    * @param initialSize The initial capacity of the internal StringBuilder in characters.
124    *                    Must be non-negative.
125    * @throws IllegalArgumentException If <tt>initialSize</tt> is negative.
126    */
127   public StringBuilderWriter(int initialSize) {
128      req(initialSize >= 0, "Argument 'initialSize' cannot be negative.");
129      sb = new StringBuilder(initialSize);
130      lock = null;
131   }
132
133   /**
134    * Constructor.
135    *
136    * <p>
137    * Creates a new StringBuilderWriter that wraps an existing StringBuilder. All writes to
138    * this writer will be appended to the provided StringBuilder. This is useful when you
139    * want to write to a StringBuilder that you already have a reference to.
140    *
141    * <h5 class='section'>Example:</h5>
142    * <p class='bjava'>
143    *    StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"Prefix: "</js>);
144    *    StringBuilderWriter <jv>writer</jv> = <jk>new</jk> StringBuilderWriter(<jv>sb</jv>);
145    *    <jv>writer</jv>.write(<js>"Suffix"</js>);
146    *    String <jv>result</jv> = <jv>sb</jv>.toString();  <jc>// Returns "Prefix: Suffix"</jc>
147    * </p>
148    *
149    * @param sb The StringBuilder to wrap. Must not be <jk>null</jk>.
150    */
151   public StringBuilderWriter(StringBuilder sb) {
152      this.sb = reqnn("sb", sb);
153      lock = null;
154   }
155
156   @Override /* Overridden from Writer */
157   public StringBuilderWriter append(char c) {
158      write(c);
159      return this;
160   }
161
162   @Override /* Overridden from Writer */
163   public StringBuilderWriter append(CharSequence csq) {
164      if (csq == null)
165         write("null");
166      else
167         write(csq.toString());
168      return this;
169   }
170
171   @Override /* Overridden from Writer */
172   public StringBuilderWriter append(CharSequence csq, int start, int end) {
173      write((csq == null ? "null" : csq).subSequence(start, end).toString());
174      return this;
175   }
176
177   @Override /* Overridden from Writer */
178   public void close() throws IOException {
179      // No-op: StringBuilder does not require closing
180   }
181
182   @Override /* Overridden from Writer */
183   public void flush() {
184      // No-op: StringBuilder does not require flushing
185   }
186
187   @Override /* Overridden from Object */
188   public String toString() {
189      return sb.toString();
190   }
191
192   @Override /* Overridden from Writer */
193   public void write(char[] cbuf, int start, int length) {
194      reqnn("cbuf", cbuf);
195      sb.append(cbuf, start, length);
196   }
197
198   @Override /* Overridden from Writer */
199   public void write(int c) {
200      sb.appendCodePoint(c);
201   }
202
203   @Override /* Overridden from Writer */
204   public void write(String str) {
205      reqnn("str", str);
206      sb.append(str);
207   }
208
209   @Override /* Overridden from Writer */
210   public void write(String str, int off, int len) {
211      reqnn("str", str);
212      sb.append(str.substring(off, off + len));
213   }
214}