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}