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}