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.commons.io.output;
018
019import java.io.File;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.OutputStreamWriter;
024import java.io.Writer;
025import java.nio.charset.Charset;
026import java.nio.charset.CharsetEncoder;
027import java.util.Objects;
028
029import org.apache.commons.io.FileUtils;
030import org.apache.commons.io.IOUtils;
031
032/**
033 * Writer of files that allows the encoding to be set.
034 * <p>
035 * This class provides a simple alternative to <code>FileWriter</code>
036 * that allows an encoding to be set. Unfortunately, it cannot subclass
037 * <code>FileWriter</code>.
038 * <p>
039 * By default, the file will be overwritten, but this may be changed to append.
040 * <p>
041 * The encoding must be specified using either the name of the {@link Charset},
042 * the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding
043 * is required then use the {@link java.io.FileWriter} directly, rather than
044 * this implementation.
045 * <p>
046 *
047 *
048 * @since 1.4
049 *
050 */
051public class FileWriterWithEncoding extends Writer {
052    // Cannot extend ProxyWriter, as requires writer to be
053    // known when super() is called
054
055    /** The writer to decorate. */
056    private final Writer out;
057
058    /**
059     * Constructs a FileWriterWithEncoding with a file encoding.
060     *
061     * @param fileName  the name of the file to write to, not null
062     * @param charsetName  the name of the requested charset, not null
063     * @throws NullPointerException if the file name or encoding is null
064     * @throws IOException in case of an I/O error
065     */
066    public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException {
067        this(new File(fileName), charsetName, false);
068    }
069
070    /**
071     * Constructs a FileWriterWithEncoding with a file encoding.
072     *
073     * @param fileName  the name of the file to write to, not null
074     * @param charsetName  the name of the requested charset, not null
075     * @param append  true if content should be appended, false to overwrite
076     * @throws NullPointerException if the file name or encoding is null
077     * @throws IOException in case of an I/O error
078     */
079    public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append)
080            throws IOException {
081        this(new File(fileName), charsetName, append);
082    }
083
084    /**
085     * Constructs a FileWriterWithEncoding with a file encoding.
086     *
087     * @param fileName  the name of the file to write to, not null
088     * @param charset  the charset to use, not null
089     * @throws NullPointerException if the file name or encoding is null
090     * @throws IOException in case of an I/O error
091     */
092    public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException {
093        this(new File(fileName), charset, false);
094    }
095
096    /**
097     * Constructs a FileWriterWithEncoding with a file encoding.
098     *
099     * @param fileName  the name of the file to write to, not null
100     * @param charset  the encoding to use, not null
101     * @param append  true if content should be appended, false to overwrite
102     * @throws NullPointerException if the file name or encoding is null
103     * @throws IOException in case of an I/O error
104     */
105    public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append)
106            throws IOException {
107        this(new File(fileName), charset, append);
108    }
109
110    /**
111     * Constructs a FileWriterWithEncoding with a file encoding.
112     *
113     * @param fileName  the name of the file to write to, not null
114     * @param encoding  the encoding to use, not null
115     * @throws NullPointerException if the file name or encoding is null
116     * @throws IOException in case of an I/O error
117     */
118    public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException {
119        this(new File(fileName), encoding, false);
120    }
121
122    /**
123     * Constructs a FileWriterWithEncoding with a file encoding.
124     *
125     * @param fileName  the name of the file to write to, not null
126     * @param charsetEncoder  the encoding to use, not null
127     * @param append  true if content should be appended, false to overwrite
128     * @throws NullPointerException if the file name or encoding is null
129     * @throws IOException in case of an I/O error
130     */
131    public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append)
132            throws IOException {
133        this(new File(fileName), charsetEncoder, append);
134    }
135
136    /**
137     * Constructs a FileWriterWithEncoding with a file encoding.
138     *
139     * @param file  the file to write to, not null
140     * @param charsetName  the name of the requested charset, not null
141     * @throws NullPointerException if the file or encoding is null
142     * @throws IOException in case of an I/O error
143     */
144    public FileWriterWithEncoding(final File file, final String charsetName) throws IOException {
145        this(file, charsetName, false);
146    }
147
148    /**
149     * Constructs a FileWriterWithEncoding with a file encoding.
150     *
151     * @param file  the file to write to, not null
152     * @param charsetName  the name of the requested charset, not null
153     * @param append  true if content should be appended, false to overwrite
154     * @throws NullPointerException if the file or encoding is null
155     * @throws IOException in case of an I/O error
156     */
157    public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException {
158        super();
159        this.out = initWriter(file, charsetName, append);
160    }
161
162    /**
163     * Constructs a FileWriterWithEncoding with a file encoding.
164     *
165     * @param file  the file to write to, not null
166     * @param charset  the encoding to use, not null
167     * @throws NullPointerException if the file or encoding is null
168     * @throws IOException in case of an I/O error
169     */
170    public FileWriterWithEncoding(final File file, final Charset charset) throws IOException {
171        this(file, charset, false);
172    }
173
174    /**
175     * Constructs a FileWriterWithEncoding with a file encoding.
176     *
177     * @param file  the file to write to, not null
178     * @param encoding  the name of the requested charset, not null
179     * @param append  true if content should be appended, false to overwrite
180     * @throws NullPointerException if the file or encoding is null
181     * @throws IOException in case of an I/O error
182     */
183    public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException {
184        super();
185        this.out = initWriter(file, encoding, append);
186    }
187
188    /**
189     * Constructs a FileWriterWithEncoding with a file encoding.
190     *
191     * @param file  the file to write to, not null
192     * @param charsetEncoder  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     */
196    public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException {
197        this(file, charsetEncoder, false);
198    }
199
200    /**
201     * Constructs a FileWriterWithEncoding with a file encoding.
202     *
203     * @param file  the file to write to, not null
204     * @param charsetEncoder  the encoding to use, not null
205     * @param append  true if content should be appended, false to overwrite
206     * @throws NullPointerException if the file or encoding is null
207     * @throws IOException in case of an I/O error
208     */
209    public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append)
210            throws IOException {
211        super();
212        this.out = initWriter(file, charsetEncoder, append);
213    }
214
215    //-----------------------------------------------------------------------
216    /**
217     * Initialise the wrapped file writer.
218     * Ensure that a cleanup occurs if the writer creation fails.
219     *
220     * @param file  the file to be accessed
221     * @param encoding  the encoding to use - may be Charset, CharsetEncoder or String
222     * @param append  true to append
223     * @return the initialised writer
224     * @throws NullPointerException if the file or encoding is null
225     * @throws IOException if an error occurs
226     */
227     private static Writer initWriter(final File file, final Object encoding, final boolean append) throws IOException {
228        Objects.requireNonNull(file, "file");
229        Objects.requireNonNull(encoding, "encoding");
230        OutputStream stream = null;
231        final boolean fileExistedAlready = file.exists();
232        try {
233            stream = new FileOutputStream(file, append);
234            if (encoding instanceof Charset) {
235                return new OutputStreamWriter(stream, (Charset)encoding);
236            } else if (encoding instanceof CharsetEncoder) {
237                return new OutputStreamWriter(stream, (CharsetEncoder)encoding);
238            } else {
239                return new OutputStreamWriter(stream, (String)encoding);
240            }
241        } catch (final IOException | RuntimeException ex) {
242            try {
243                IOUtils.close(stream);
244            } catch (final IOException e) {
245                ex.addSuppressed(e);
246            }
247            if (fileExistedAlready == false) {
248                FileUtils.deleteQuietly(file);
249            }
250            throw ex;
251        }
252    }
253
254    //-----------------------------------------------------------------------
255    /**
256     * Write a character.
257     * @param idx the character to write
258     * @throws IOException if an I/O error occurs
259     */
260     @Override
261    public void write(final int idx) throws IOException {
262        out.write(idx);
263    }
264
265    /**
266     * Write the characters from an array.
267     * @param chr the characters to write
268     * @throws IOException if an I/O error occurs
269     */
270     @Override
271    public void write(final char[] chr) throws IOException {
272        out.write(chr);
273    }
274
275    /**
276     * Write the specified characters from an array.
277     * @param chr the characters to write
278     * @param st The start offset
279     * @param end The number of characters to write
280     * @throws IOException if an I/O error occurs
281     */
282     @Override
283    public void write(final char[] chr, final int st, final int end) throws IOException {
284        out.write(chr, st, end);
285    }
286
287    /**
288     * Write the characters from a string.
289     * @param str the string to write
290     * @throws IOException if an I/O error occurs
291     */
292     @Override
293    public void write(final String str) throws IOException {
294        out.write(str);
295    }
296
297    /**
298     * Write the specified characters from a string.
299     * @param str the string to write
300     * @param st The start offset
301     * @param end The number of characters to write
302     * @throws IOException if an I/O error occurs
303     */
304     @Override
305    public void write(final String str, final int st, final int end) throws IOException {
306        out.write(str, st, end);
307    }
308
309    /**
310     * Flush the stream.
311     * @throws IOException if an I/O error occurs
312     */
313     @Override
314    public void flush() throws IOException {
315        out.flush();
316    }
317
318    /**
319     * Close the stream.
320     * @throws IOException if an I/O error occurs
321     */
322     @Override
323    public void close() throws IOException {
324        out.close();
325    }
326}