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