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