View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.output;
18  
19  import java.io.File;
20  import java.io.FileWriter;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.OutputStreamWriter;
24  import java.nio.charset.Charset;
25  import java.nio.charset.CharsetEncoder;
26  import java.util.Objects;
27  
28  import org.apache.commons.io.Charsets;
29  import org.apache.commons.io.FileUtils;
30  import org.apache.commons.io.IOUtils;
31  import org.apache.commons.io.build.AbstractOrigin;
32  import org.apache.commons.io.build.AbstractStreamBuilder;
33  
34  /**
35   * Writer of files that allows the encoding to be set.
36   * <p>
37   * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it cannot subclass {@link FileWriter}.
38   * </p>
39   * <p>
40   * By default, the file will be overwritten, but this may be changed to append.
41   * </p>
42   * <p>
43   * The encoding must be specified using either the name of the {@link Charset}, the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding is
44   * required then use the {@link java.io.FileWriter} directly, rather than this implementation.
45   * </p>
46   * <p>
47   * To build an instance, use {@link Builder}.
48   * </p>
49   *
50   * @see Builder
51   * @since 1.4
52   */
53  public class FileWriterWithEncoding extends ProxyWriter {
54  
55      // @formatter:off
56      /**
57       * Builds a new {@link FileWriterWithEncoding}.
58       *
59       * <p>
60       * Using a CharsetEncoder:
61       * </p>
62       * <pre>{@code
63       * FileWriterWithEncoding w = FileWriterWithEncoding.builder()
64       *   .setPath(path)
65       *   .setAppend(false)
66       *   .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder())
67       *   .get();}
68       * </pre>
69       * <p>
70       * Using a Charset:
71       * </p>
72       * <pre>{@code
73       * FileWriterWithEncoding w = FileWriterWithEncoding.builder()
74       *   .setPath(path)
75       *   .setAppend(false)
76       *   .setCharsetEncoder(StandardCharsets.UTF_8)
77       *   .get();}
78       * </pre>
79       *
80       * @see #get()
81       * @since 2.12.0
82       */
83      // @formatter:on
84      public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> {
85  
86          private boolean append;
87  
88          private CharsetEncoder charsetEncoder = super.getCharset().newEncoder();
89  
90          /**
91           * Builds a new {@link FileWriterWithEncoding}.
92           * <p>
93           * You must set input that supports {@link File} on this builder, otherwise, this method throws an exception.
94           * </p>
95           * <p>
96           * This builder use the following aspects:
97           * </p>
98           * <ul>
99           * <li>{@link File}</li>
100          * <li>{@link CharsetEncoder}</li>
101          * <li>append</li>
102          * </ul>
103          *
104          * @return a new instance.
105          * @throws UnsupportedOperationException if the origin cannot provide a File.
106          * @throws IllegalStateException if the {@code origin} is {@code null}.
107          * @see AbstractOrigin#getFile()
108          */
109         @SuppressWarnings("resource")
110         @Override
111         public FileWriterWithEncoding get() throws IOException {
112             if (charsetEncoder != null && getCharset() != null && !charsetEncoder.charset().equals(getCharset())) {
113                 throw new IllegalStateException(String.format("Mismatched Charset(%s) and CharsetEncoder(%s)", getCharset(), charsetEncoder.charset()));
114             }
115             final Object encoder = charsetEncoder != null ? charsetEncoder : getCharset();
116             return new FileWriterWithEncoding(FileWriterWithEncoding.initWriter(checkOrigin().getFile(), encoder, append));
117         }
118 
119         /**
120          * Sets whether or not to append.
121          *
122          * @param append Whether or not to append.
123          * @return this
124          */
125         public Builder setAppend(final boolean append) {
126             this.append = append;
127             return this;
128         }
129 
130         /**
131          * Sets charsetEncoder to use for encoding.
132          *
133          * @param charsetEncoder The charsetEncoder to use for encoding.
134          * @return this
135          */
136         public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) {
137             this.charsetEncoder = charsetEncoder;
138             return this;
139         }
140 
141     }
142 
143     /**
144      * Constructs a new {@link Builder}.
145      *
146      * @return Creates a new {@link Builder}.
147      * @since 2.12.0
148      */
149     public static Builder builder() {
150         return new Builder();
151     }
152 
153     /**
154      * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails.
155      *
156      * @param file     the file to be accessed
157      * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset.
158      * @param append   true to append
159      * @return a new initialized OutputStreamWriter
160      * @throws IOException if an error occurs
161      */
162     private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException {
163         Objects.requireNonNull(file, "file");
164         OutputStream outputStream = null;
165         final boolean fileExistedAlready = file.exists();
166         try {
167             outputStream = FileUtils.newOutputStream(file, append);
168             if (encoding == null || encoding instanceof Charset) {
169                 return new OutputStreamWriter(outputStream, Charsets.toCharset((Charset) encoding));
170             }
171             if (encoding instanceof CharsetEncoder) {
172                 return new OutputStreamWriter(outputStream, (CharsetEncoder) encoding);
173             }
174             return new OutputStreamWriter(outputStream, (String) encoding);
175         } catch (final IOException | RuntimeException ex) {
176             try {
177                 IOUtils.close(outputStream);
178             } catch (final IOException e) {
179                 ex.addSuppressed(e);
180             }
181             if (!fileExistedAlready) {
182                 FileUtils.deleteQuietly(file);
183             }
184             throw ex;
185         }
186     }
187 
188     /**
189      * Constructs a FileWriterWithEncoding with a file encoding.
190      *
191      * @param file    the file to write to, not null
192      * @param charset the encoding to use, not null
193      * @throws NullPointerException if the file or encoding is null
194      * @throws IOException          in case of an I/O error
195      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
196      */
197     @Deprecated
198     public FileWriterWithEncoding(final File file, final Charset charset) throws IOException {
199         this(file, charset, false);
200     }
201 
202     /**
203      * Constructs a FileWriterWithEncoding with a file encoding.
204      *
205      * @param file     the file to write to, not null.
206      * @param encoding the name of the requested charset, null uses the default Charset.
207      * @param append   true if content should be appended, false to overwrite.
208      * @throws NullPointerException if the file is null.
209      * @throws IOException          in case of an I/O error.
210      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
211      */
212     @Deprecated
213     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
214     public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException {
215         this(initWriter(file, encoding, append));
216     }
217 
218     /**
219      * Constructs a FileWriterWithEncoding with a file encoding.
220      *
221      * @param file           the file to write to, not null
222      * @param charsetEncoder the encoding to use, not null
223      * @throws NullPointerException if the file or encoding is null
224      * @throws IOException          in case of an I/O error
225      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
226      */
227     @Deprecated
228     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException {
229         this(file, charsetEncoder, false);
230     }
231 
232     /**
233      * Constructs a FileWriterWithEncoding with a file encoding.
234      *
235      * @param file           the file to write to, not null.
236      * @param charsetEncoder the encoding to use, null uses the default Charset.
237      * @param append         true if content should be appended, false to overwrite.
238      * @throws NullPointerException if the file is null.
239      * @throws IOException          in case of an I/O error.
240      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
241      */
242     @Deprecated
243     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
244     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
245         this(initWriter(file, charsetEncoder, append));
246     }
247 
248     /**
249      * Constructs a FileWriterWithEncoding with a file encoding.
250      *
251      * @param file        the file to write to, not null
252      * @param charsetName the name of the requested charset, not null
253      * @throws NullPointerException if the file or encoding is null
254      * @throws IOException          in case of an I/O error
255      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
256      */
257     @Deprecated
258     public FileWriterWithEncoding(final File file, final String charsetName) throws IOException {
259         this(file, charsetName, false);
260     }
261 
262     /**
263      * Constructs a FileWriterWithEncoding with a file encoding.
264      *
265      * @param file        the file to write to, not null.
266      * @param charsetName the name of the requested charset, null uses the default Charset.
267      * @param append      true if content should be appended, false to overwrite.
268      * @throws NullPointerException if the file is null.
269      * @throws IOException          in case of an I/O error.
270      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
271      */
272     @Deprecated
273     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
274     public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException {
275         this(initWriter(file, charsetName, append));
276     }
277 
278     private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) {
279         super(outputStreamWriter);
280     }
281 
282     /**
283      * Constructs a FileWriterWithEncoding with a file encoding.
284      *
285      * @param fileName the name of the file to write to, not null
286      * @param charset  the charset to use, not null
287      * @throws NullPointerException if the file name or encoding is null
288      * @throws IOException          in case of an I/O error
289      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
290      */
291     @Deprecated
292     public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException {
293         this(new File(fileName), charset, false);
294     }
295 
296     /**
297      * Constructs a FileWriterWithEncoding with a file encoding.
298      *
299      * @param fileName the name of the file to write to, not null
300      * @param charset  the encoding to use, not null
301      * @param append   true if content should be appended, false to overwrite
302      * @throws NullPointerException if the file name or encoding is null
303      * @throws IOException          in case of an I/O error
304      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
305      */
306     @Deprecated
307     public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException {
308         this(new File(fileName), charset, append);
309     }
310 
311     /**
312      * Constructs a FileWriterWithEncoding with a file encoding.
313      *
314      * @param fileName the name of the file to write to, not null
315      * @param encoding the encoding to use, not null
316      * @throws NullPointerException if the file name or encoding is null
317      * @throws IOException          in case of an I/O error
318      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
319      */
320     @Deprecated
321     public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException {
322         this(new File(fileName), encoding, false);
323     }
324 
325     /**
326      * Constructs a FileWriterWithEncoding with a file encoding.
327      *
328      * @param fileName       the name of the file to write to, not null
329      * @param charsetEncoder the encoding to use, not null
330      * @param append         true if content should be appended, false to overwrite
331      * @throws NullPointerException if the file name or encoding is null
332      * @throws IOException          in case of an I/O error
333      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
334      */
335     @Deprecated
336     public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
337         this(new File(fileName), charsetEncoder, append);
338     }
339 
340     /**
341      * Constructs a FileWriterWithEncoding with a file encoding.
342      *
343      * @param fileName    the name of the file to write to, not null
344      * @param charsetName the name of the requested charset, not null
345      * @throws NullPointerException if the file name or encoding is null
346      * @throws IOException          in case of an I/O error
347      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
348      */
349     @Deprecated
350     public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException {
351         this(new File(fileName), charsetName, false);
352     }
353 
354     /**
355      * Constructs a FileWriterWithEncoding with a file encoding.
356      *
357      * @param fileName    the name of the file to write to, not null
358      * @param charsetName the name of the requested charset, not null
359      * @param append      true if content should be appended, false to overwrite
360      * @throws NullPointerException if the file name or encoding is null
361      * @throws IOException          in case of an I/O error
362      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
363      */
364     @Deprecated
365     public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException {
366         this(new File(fileName), charsetName, append);
367     }
368 }