• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
get()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          */
setAppend(final boolean append)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          */
setCharsetEncoder(final CharsetEncoder charsetEncoder)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      */
builder()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      */
initWriter(final File file, final Object encoding, final boolean append)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
FileWriterWithEncoding(final File file, final Charset charset)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.
FileWriterWithEncoding(final File file, final Charset encoding, final boolean append)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
FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder)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.
FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append)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
FileWriterWithEncoding(final File file, final String charsetName)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.
FileWriterWithEncoding(final File file, final String charsetName, final boolean append)265     public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException {
266         this(initWriter(file, charsetName, append));
267     }
268 
FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter)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
FileWriterWithEncoding(final String fileName, final Charset charset)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
FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append)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
FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding)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
FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append)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
FileWriterWithEncoding(final String fileName, final String charsetName)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
FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append)356     public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException {
357         this(new File(fileName), charsetName, append);
358     }
359 }
360