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, see {@link Builder}.
48   * </p>
49   *
50   * @since 1.4
51   */
52  public class FileWriterWithEncoding extends ProxyWriter {
53  
54      /**
55       * Builds a new {@link FileWriterWithEncoding} instance.
56       * <p>
57       * Using a CharsetEncoder:
58       * </p>
59       * <pre>{@code
60       * FileWriterWithEncoding s = FileWriterWithEncoding.builder()
61       *   .setPath(path)
62       *   .setAppend(false)
63       *   .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder())
64       *   .get();}
65       * </pre>
66       * <p>
67       * Using a Charset:
68       * </p>
69       * <pre>{@code
70       * FileWriterWithEncoding s = FileWriterWithEncoding.builder()
71       *   .setPath(path)
72       *   .setAppend(false)
73       *   .setCharsetEncoder(StandardCharsets.UTF_8)
74       *   .get();}
75       * </pre>
76       *
77       * @since 2.12.0
78       */
79      public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> {
80  
81          private boolean append;
82  
83          private CharsetEncoder charsetEncoder = super.getCharset().newEncoder();
84  
85          /**
86           * Constructs a new instance.
87           * <p>
88           * This builder use the aspects File, CharsetEncoder, and append.
89           * </p>
90           * <p>
91           * You must provide an origin that can be converted to a File by this builder, otherwise, this call will throw an
92           * {@link UnsupportedOperationException}.
93           * </p>
94           *
95           * @return a new instance.
96           * @throws UnsupportedOperationException if the origin cannot provide a File.
97           * @throws IllegalStateException if the {@code origin} is {@code null}.
98           * @see AbstractOrigin#getFile()
99           */
100         @SuppressWarnings("resource")
101         @Override
102         public FileWriterWithEncoding get() throws IOException {
103             if (charsetEncoder != null && getCharset() != null && !charsetEncoder.charset().equals(getCharset())) {
104                 throw new IllegalStateException(String.format("Mismatched Charset(%s) and CharsetEncoder(%s)", getCharset(), charsetEncoder.charset()));
105             }
106             final Object encoder = charsetEncoder != null ? charsetEncoder : getCharset();
107             return new FileWriterWithEncoding(FileWriterWithEncoding.initWriter(checkOrigin().getFile(), encoder, append));
108         }
109 
110         /**
111          * Sets whether or not to append.
112          *
113          * @param append Whether or not to append.
114          * @return this
115          */
116         public Builder setAppend(final boolean append) {
117             this.append = append;
118             return this;
119         }
120 
121         /**
122          * Sets charsetEncoder to use for encoding.
123          *
124          * @param charsetEncoder The charsetEncoder to use for encoding.
125          * @return this
126          */
127         public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) {
128             this.charsetEncoder = charsetEncoder;
129             return this;
130         }
131 
132     }
133 
134     /**
135      * Constructs a new {@link Builder}.
136      *
137      * @return Creates a new {@link Builder}.
138      * @since 2.12.0
139      */
140     public static Builder builder() {
141         return new Builder();
142     }
143 
144     /**
145      * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails.
146      *
147      * @param file     the file to be accessed
148      * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset.
149      * @param append   true to append
150      * @return a new initialized OutputStreamWriter
151      * @throws IOException if an error occurs
152      */
153     private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException {
154         Objects.requireNonNull(file, "file");
155         OutputStream outputStream = null;
156         final boolean fileExistedAlready = file.exists();
157         try {
158             outputStream = FileUtils.newOutputStream(file, append);
159             if (encoding == null || encoding instanceof Charset) {
160                 return new OutputStreamWriter(outputStream, Charsets.toCharset((Charset) encoding));
161             }
162             if (encoding instanceof CharsetEncoder) {
163                 return new OutputStreamWriter(outputStream, (CharsetEncoder) encoding);
164             }
165             return new OutputStreamWriter(outputStream, (String) encoding);
166         } catch (final IOException | RuntimeException ex) {
167             try {
168                 IOUtils.close(outputStream);
169             } catch (final IOException e) {
170                 ex.addSuppressed(e);
171             }
172             if (!fileExistedAlready) {
173                 FileUtils.deleteQuietly(file);
174             }
175             throw ex;
176         }
177     }
178 
179     /**
180      * Constructs a FileWriterWithEncoding with a file encoding.
181      *
182      * @param file    the file to write to, not null
183      * @param charset the encoding to use, not null
184      * @throws NullPointerException if the file or encoding is null
185      * @throws IOException          in case of an I/O error
186      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
187      */
188     @Deprecated
189     public FileWriterWithEncoding(final File file, final Charset charset) throws IOException {
190         this(file, charset, false);
191     }
192 
193     /**
194      * Constructs a FileWriterWithEncoding with a file encoding.
195      *
196      * @param file     the file to write to, not null.
197      * @param encoding the name of the requested charset, null uses the default Charset.
198      * @param append   true if content should be appended, false to overwrite.
199      * @throws NullPointerException if the file is null.
200      * @throws IOException          in case of an I/O error.
201      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
202      */
203     @Deprecated
204     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
205     public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException {
206         this(initWriter(file, encoding, append));
207     }
208 
209     /**
210      * Constructs a FileWriterWithEncoding with a file encoding.
211      *
212      * @param file           the file to write to, not null
213      * @param charsetEncoder the encoding to use, not null
214      * @throws NullPointerException if the file or encoding is null
215      * @throws IOException          in case of an I/O error
216      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
217      */
218     @Deprecated
219     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException {
220         this(file, charsetEncoder, false);
221     }
222 
223     /**
224      * Constructs a FileWriterWithEncoding with a file encoding.
225      *
226      * @param file           the file to write to, not null.
227      * @param charsetEncoder the encoding to use, null uses the default Charset.
228      * @param append         true if content should be appended, false to overwrite.
229      * @throws NullPointerException if the file is null.
230      * @throws IOException          in case of an I/O error.
231      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
232      */
233     @Deprecated
234     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
235     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
236         this(initWriter(file, charsetEncoder, append));
237     }
238 
239     /**
240      * Constructs a FileWriterWithEncoding with a file encoding.
241      *
242      * @param file        the file to write to, not null
243      * @param charsetName the name of the requested charset, not null
244      * @throws NullPointerException if the file or encoding is null
245      * @throws IOException          in case of an I/O error
246      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
247      */
248     @Deprecated
249     public FileWriterWithEncoding(final File file, final String charsetName) throws IOException {
250         this(file, charsetName, false);
251     }
252 
253     /**
254      * Constructs a FileWriterWithEncoding with a file encoding.
255      *
256      * @param file        the file to write to, not null.
257      * @param charsetName the name of the requested charset, null uses the default Charset.
258      * @param append      true if content should be appended, false to overwrite.
259      * @throws NullPointerException if the file is null.
260      * @throws IOException          in case of an I/O error.
261      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
262      */
263     @Deprecated
264     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
265     public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException {
266         this(initWriter(file, charsetName, append));
267     }
268 
269     private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) {
270         super(outputStreamWriter);
271     }
272 
273     /**
274      * Constructs a FileWriterWithEncoding with a file encoding.
275      *
276      * @param fileName the name of the file to write to, not null
277      * @param charset  the charset to use, not null
278      * @throws NullPointerException if the file name or encoding is null
279      * @throws IOException          in case of an I/O error
280      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
281      */
282     @Deprecated
283     public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException {
284         this(new File(fileName), charset, false);
285     }
286 
287     /**
288      * Constructs a FileWriterWithEncoding with a file encoding.
289      *
290      * @param fileName the name of the file to write to, not null
291      * @param charset  the encoding to use, not null
292      * @param append   true if content should be appended, false to overwrite
293      * @throws NullPointerException if the file name or encoding is null
294      * @throws IOException          in case of an I/O error
295      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
296      */
297     @Deprecated
298     public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException {
299         this(new File(fileName), charset, append);
300     }
301 
302     /**
303      * Constructs a FileWriterWithEncoding with a file encoding.
304      *
305      * @param fileName the name of the file to write to, not null
306      * @param encoding the encoding to use, not null
307      * @throws NullPointerException if the file name or encoding is null
308      * @throws IOException          in case of an I/O error
309      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
310      */
311     @Deprecated
312     public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException {
313         this(new File(fileName), encoding, false);
314     }
315 
316     /**
317      * Constructs a FileWriterWithEncoding with a file encoding.
318      *
319      * @param fileName       the name of the file to write to, not null
320      * @param charsetEncoder the encoding to use, not null
321      * @param append         true if content should be appended, false to overwrite
322      * @throws NullPointerException if the file name or encoding is null
323      * @throws IOException          in case of an I/O error
324      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
325      */
326     @Deprecated
327     public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
328         this(new File(fileName), charsetEncoder, append);
329     }
330 
331     /**
332      * Constructs a FileWriterWithEncoding with a file encoding.
333      *
334      * @param fileName    the name of the file to write to, not null
335      * @param charsetName the name of the requested charset, not null
336      * @throws NullPointerException if the file name or encoding is null
337      * @throws IOException          in case of an I/O error
338      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
339      */
340     @Deprecated
341     public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException {
342         this(new File(fileName), charsetName, false);
343     }
344 
345     /**
346      * Constructs a FileWriterWithEncoding with a file encoding.
347      *
348      * @param fileName    the name of the file to write to, not null
349      * @param charsetName the name of the requested charset, not null
350      * @param append      true if content should be appended, false to overwrite
351      * @throws NullPointerException if the file name or encoding is null
352      * @throws IOException          in case of an I/O error
353      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
354      */
355     @Deprecated
356     public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException {
357         this(new File(fileName), charsetName, append);
358     }
359 }