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 019 020import java.io.*; 021import java.nio.charset.*; 022import java.nio.file.*; 023import static org.apache.juneau.commons.utils.Shorts.*; 024 025/** 026 * A fluent builder for creating {@link Writer} instances for writing to files with configurable options. 027 * 028 * <p> 029 * This builder provides a convenient way to create file writers with custom character encodings, 030 * append mode, and buffering options. It's particularly useful when you need to write files with 031 * specific encodings or control whether to append to existing files. 032 * 033 * <h5 class='section'>Features:</h5> 034 * <ul class='spaced-list'> 035 * <li>Fluent API - all methods return <c>this</c> for method chaining 036 * <li>Character encoding support - specify custom charset for file writing 037 * <li>Append mode - optionally append to existing files instead of overwriting 038 * <li>Buffering support - optional buffering for improved performance 039 * <li>Multiple file specification methods - accept File or String path 040 * </ul> 041 * 042 * <h5 class='section'>Use Cases:</h5> 043 * <ul class='spaced-list'> 044 * <li>Writing files with specific character encodings (UTF-8, ISO-8859-1, etc.) 045 * <li>Appending to log files or data files 046 * <li>Creating writers with consistent encoding across an application 047 * <li>Writing files where buffering improves performance 048 * </ul> 049 * 050 * <h5 class='section'>Usage:</h5> 051 * <p class='bjava'> 052 * <jc>// Basic usage</jc> 053 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 054 * .file(<js>"/path/to/file.txt"</js>) 055 * .charset(<js>"UTF-8"</js>) 056 * .build(); 057 * 058 * <jc>// Append mode</jc> 059 * Writer <jv>logWriter</jv> = FileWriterBuilder.<jsm>create</jsm>() 060 * .file(<js>"app.log"</js>) 061 * .append() 062 * .build(); 063 * 064 * <jc>// With buffering</jc> 065 * Writer <jv>bufferedWriter</jv> = FileWriterBuilder.<jsm>create</jsm>() 066 * .file(<js>"output.txt"</js>) 067 * .buffered() 068 * .charset(StandardCharsets.UTF_8) 069 * .build(); 070 * </p> 071 * 072 * <h5 class='section'>Character Encoding:</h5> 073 * <p> 074 * By default, the builder uses the system's default charset ({@link Charset#defaultCharset()}). 075 * You can specify a custom charset using {@link #charset(Charset)} or {@link #charset(String)}. 076 * This is important when writing files that need to be read with a specific encoding. 077 * 078 * <h5 class='section'>See Also:</h5><ul> 079 * <li class='jc'>{@link FileReaderBuilder} - Builder for file readers 080 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a> 081 * </ul> 082 */ 083@SuppressWarnings({ 084 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 085 "resource" // Caller takes ownership of the returned Writer 086}) 087public class FileWriterBuilder { 088 089 /** 090 * Creates a new builder. 091 * 092 * <h5 class='section'>Example:</h5> 093 * <p class='bjava'> 094 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 095 * .file(<js>"output.txt"</js>) 096 * .build(); 097 * </p> 098 * 099 * @return A new builder instance. 100 */ 101 public static FileWriterBuilder create() { 102 return new FileWriterBuilder(); 103 } 104 105 /** 106 * Creates a new builder initialized with the specified file. 107 * 108 * <h5 class='section'>Example:</h5> 109 * <p class='bjava'> 110 * File <jv>file</jv> = <jk>new</jk> File(<js>"output.txt"</js>); 111 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>(<jv>file</jv>) 112 * .charset(<js>"UTF-8"</js>) 113 * .build(); 114 * </p> 115 * 116 * @param file The file to write to. 117 * @return A new builder instance initialized with the specified file. 118 */ 119 public static FileWriterBuilder create(File file) { 120 return new FileWriterBuilder().file(file); 121 } 122 123 /** 124 * Creates a new builder initialized with the specified file path. 125 * 126 * <h5 class='section'>Example:</h5> 127 * <p class='bjava'> 128 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>(<js>"/path/to/output.txt"</js>) 129 * .append() 130 * .build(); 131 * </p> 132 * 133 * @param path The file path to write to. Must not be <jk>null</jk>. 134 * @return A new builder instance initialized with the specified path. 135 */ 136 public static FileWriterBuilder create(String path) { 137 return new FileWriterBuilder().file(path); 138 } 139 140 private File file; 141 142 private Charset cs = Charset.defaultCharset(); 143 144 private boolean append; 145 private boolean buffered; 146 147 /** 148 * Enables append mode, which appends to the file instead of overwriting it. 149 * 150 * <p> 151 * When append mode is enabled, data written to the file will be appended to the end of the 152 * existing file content rather than overwriting it. This is useful for log files or data 153 * files where you want to preserve existing content. 154 * 155 * <h5 class='section'>Example:</h5> 156 * <p class='bjava'> 157 * <jc>// Append to log file</jc> 158 * Writer <jv>logWriter</jv> = FileWriterBuilder.<jsm>create</jsm>() 159 * .file(<js>"app.log"</js>) 160 * .append() 161 * .build(); 162 * <jv>logWriter</jv>.write(<js>"New log entry\n"</js>); <jc>// Appends to existing content</jc> 163 * </p> 164 * 165 * @return This object for method chaining. 166 */ 167 public FileWriterBuilder append() { 168 return append(true); 169 } 170 171 /** 172 * Enables or disables append mode, which appends to the file instead of overwriting it. 173 * 174 * <p> 175 * Boolean-arg form of {@link #append()}, useful when the setting is conditional. 176 * 177 * <h5 class='section'>Example:</h5> 178 * <p class='bjava'> 179 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 180 * .file(<js>"app.log"</js>) 181 * .append(<jv>shouldAppend</jv>) 182 * .build(); 183 * </p> 184 * 185 * @param value <jk>true</jk> to append to the file instead of overwriting it. 186 * @return This object for method chaining. 187 */ 188 public FileWriterBuilder append(boolean value) { 189 this.append = value; 190 return this; 191 } 192 193 /** 194 * Enables buffering for improved write performance. 195 * 196 * <p> 197 * When buffering is enabled, the writer wraps the underlying output stream with a 198 * {@link BufferedOutputStream}, which can significantly improve performance for multiple 199 * small writes by batching them together. 200 * 201 * <h5 class='section'>Example:</h5> 202 * <p class='bjava'> 203 * <jc>// Buffered writer for better performance</jc> 204 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 205 * .file(<js>"output.txt"</js>) 206 * .buffered() 207 * .build(); 208 * </p> 209 * 210 * @return This object for method chaining. 211 */ 212 public FileWriterBuilder buffered() { 213 return buffered(true); 214 } 215 216 /** 217 * Enables or disables buffering for improved write performance. 218 * 219 * <p> 220 * Boolean-arg form of {@link #buffered()}, useful when the setting is conditional. 221 * 222 * <h5 class='section'>Example:</h5> 223 * <p class='bjava'> 224 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 225 * .file(<js>"output.txt"</js>) 226 * .buffered(<jv>shouldBuffer</jv>) 227 * .build(); 228 * </p> 229 * 230 * @param value <jk>true</jk> to buffer writes for improved performance. 231 * @return This object for method chaining. 232 */ 233 public FileWriterBuilder buffered(boolean value) { 234 this.buffered = value; 235 return this; 236 } 237 238 /** 239 * Creates a new {@link Writer} for writing to the configured file. 240 * 241 * <p> 242 * The writer is created with the specified character encoding, append mode, and buffering 243 * options. The file will be created if it doesn't exist (unless in append mode, where the 244 * file must exist or be creatable). 245 * 246 * <h5 class='section'>Example:</h5> 247 * <p class='bjava'> 248 * <jk>try</jk> (Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 249 * .file(<js>"output.txt"</js>) 250 * .charset(<js>"UTF-8"</js>) 251 * .buffered() 252 * .build()) { 253 * <jv>writer</jv>.write(<js>"Hello World"</js>); 254 * } 255 * </p> 256 * 257 * @return A new {@link Writer} for writing to the file. 258 * @throws FileNotFoundException If the file could not be created or opened for writing. 259 */ 260 public Writer build() throws FileNotFoundException { 261 reqnn("file", file); 262 var os = (OutputStream)new FileOutputStream(file, append); 263 if (buffered) 264 os = new BufferedOutputStream(os); 265 return new OutputStreamWriter(os, cs != null ? cs : Charset.defaultCharset()); 266 } 267 268 /** 269 * Sets the character encoding for writing to the file. 270 * 271 * <p> 272 * If not specified, the system's default charset ({@link Charset#defaultCharset()}) is used. 273 * Specifying the encoding is important when writing files that need to be read with a specific 274 * character encoding. 275 * 276 * <h5 class='section'>Example:</h5> 277 * <p class='bjava'> 278 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 279 * .file(<js>"data.txt"</js>) 280 * .charset(StandardCharsets.UTF_8) 281 * .build(); 282 * </p> 283 * 284 * @param cs The character encoding to use. Can be <jk>null</jk> to use the default charset ({@link Charset#defaultCharset()}). 285 * @return This object for method chaining. 286 */ 287 public FileWriterBuilder charset(Charset cs) { 288 this.cs = cs; 289 return this; 290 } 291 292 /** 293 * Sets the character encoding for writing to the file by charset name. 294 * 295 * <p> 296 * This is a convenience method that accepts a charset name string and converts it to a 297 * {@link Charset} using {@link Charset#forName(String)}. 298 * 299 * <h5 class='section'>Example:</h5> 300 * <p class='bjava'> 301 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 302 * .file(<js>"data.txt"</js>) 303 * .charset(<js>"UTF-8"</js>) 304 * .build(); 305 * </p> 306 * 307 * @param cs The character encoding name (e.g., <js>"UTF-8"</js>, <js>"ISO-8859-1"</js>). 308 * The default is {@link Charset#defaultCharset()}. 309 * Must not be <jk>null</jk>. 310 * @return This object for method chaining. 311 */ 312 public FileWriterBuilder charset(String cs) { 313 this.cs = Charset.forName(reqnn("cs", cs)); 314 return this; 315 } 316 317 /** 318 * Sets the file to write to. 319 * 320 * <h5 class='section'>Example:</h5> 321 * <p class='bjava'> 322 * File <jv>f</jv> = <jk>new</jk> File(<js>"output.txt"</js>); 323 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 324 * .file(<jv>f</jv>) 325 * .build(); 326 * </p> 327 * 328 * @param value The file to write to. 329 * @return This object for method chaining. 330 */ 331 public FileWriterBuilder file(File value) { 332 file = value; 333 return this; 334 } 335 336 /** 337 * Sets the file path to write to. 338 * 339 * <h5 class='section'>Example:</h5> 340 * <p class='bjava'> 341 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 342 * .file(<js>"/path/to/output.txt"</js>) 343 * .build(); 344 * </p> 345 * 346 * @param path The file path to write to. Must not be <jk>null</jk>. 347 * @return This object for method chaining. 348 */ 349 public FileWriterBuilder file(String path) { 350 this.file = new File(path); 351 return this; 352 } 353 354 /** 355 * Sets the file to write to. 356 * 357 * <p> 358 * Convenience overload for callers working with the NIO {@link Path} API, consistent with 359 * {@link PathReaderBuilder#path(Path)}. 360 * 361 * <h5 class='section'>Example:</h5> 362 * <p class='bjava'> 363 * Writer <jv>writer</jv> = FileWriterBuilder.<jsm>create</jsm>() 364 * .file(Paths.<jsm>get</jsm>(<js>"/path/to/output.txt"</js>)) 365 * .build(); 366 * </p> 367 * 368 * @param value The file path to write to. Can be <jk>null</jk>. 369 * @return This object for method chaining. 370 */ 371 public FileWriterBuilder file(Path value) { 372 this.file = value == null ? null : value.toFile(); 373 return this; 374 } 375}