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}