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