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 1304052 2012-03-22 20:55:29Z ggregory $
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(String filename, 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(String filename, String encoding, boolean append) 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(String filename, 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(String filename, Charset encoding, boolean append) throws IOException {
104        this(new File(filename), encoding, append);
105    }
106
107    /**
108     * Constructs a FileWriterWithEncoding with a file encoding.
109     *
110     * @param filename  the name of the file to write to, not null
111     * @param encoding  the encoding to use, not null
112     * @throws NullPointerException if the file name or encoding is null
113     * @throws IOException in case of an I/O error
114     */
115    public FileWriterWithEncoding(String filename, CharsetEncoder encoding) throws IOException {
116        this(new File(filename), encoding, false);
117    }
118
119    /**
120     * Constructs a FileWriterWithEncoding with a file encoding.
121     *
122     * @param filename  the name of the file to write to, not null
123     * @param encoding  the encoding to use, not null
124     * @param append  true if content should be appended, false to overwrite
125     * @throws NullPointerException if the file name or encoding is null
126     * @throws IOException in case of an I/O error
127     */
128    public FileWriterWithEncoding(String filename, CharsetEncoder encoding, boolean append) throws IOException {
129        this(new File(filename), encoding, append);
130    }
131
132    /**
133     * Constructs a FileWriterWithEncoding with a file encoding.
134     *
135     * @param file  the file to write to, not null
136     * @param encoding  the encoding to use, not null
137     * @throws NullPointerException if the file or encoding is null
138     * @throws IOException in case of an I/O error
139     */
140    public FileWriterWithEncoding(File file, String encoding) throws IOException {
141        this(file, encoding, false);
142    }
143
144    /**
145     * Constructs a FileWriterWithEncoding with a file encoding.
146     *
147     * @param file  the file to write to, not null
148     * @param encoding  the encoding to use, not null
149     * @param append  true if content should be appended, false to overwrite
150     * @throws NullPointerException if the file or encoding is null
151     * @throws IOException in case of an I/O error
152     */
153    public FileWriterWithEncoding(File file, String encoding, boolean append) throws IOException {
154        super();
155        this.out = initWriter(file, encoding, append);
156    }
157
158    /**
159     * Constructs a FileWriterWithEncoding with a file encoding.
160     *
161     * @param file  the file to write to, not null
162     * @param encoding  the encoding to use, not null
163     * @throws NullPointerException if the file or encoding is null
164     * @throws IOException in case of an I/O error
165     */
166    public FileWriterWithEncoding(File file, Charset encoding) throws IOException {
167        this(file, encoding, false);
168    }
169
170    /**
171     * Constructs a FileWriterWithEncoding with a file encoding.
172     *
173     * @param file  the file to write to, not null
174     * @param encoding  the encoding to use, not null
175     * @param append  true if content should be appended, false to overwrite
176     * @throws NullPointerException if the file or encoding is null
177     * @throws IOException in case of an I/O error
178     */
179    public FileWriterWithEncoding(File file, Charset encoding, boolean append) throws IOException {
180        super();
181        this.out = initWriter(file, encoding, append);
182    }
183
184    /**
185     * Constructs a FileWriterWithEncoding with a file encoding.
186     *
187     * @param file  the file to write to, not null
188     * @param encoding  the encoding to use, not null
189     * @throws NullPointerException if the file or encoding is null
190     * @throws IOException in case of an I/O error
191     */
192    public FileWriterWithEncoding(File file, CharsetEncoder encoding) throws IOException {
193        this(file, encoding, false);
194    }
195
196    /**
197     * Constructs a FileWriterWithEncoding with a file encoding.
198     *
199     * @param file  the file to write to, not null
200     * @param encoding  the encoding to use, not null
201     * @param append  true if content should be appended, false to overwrite
202     * @throws NullPointerException if the file or encoding is null
203     * @throws IOException in case of an I/O error
204     */
205    public FileWriterWithEncoding(File file, CharsetEncoder encoding, boolean append) throws IOException {
206        super();
207        this.out = initWriter(file, encoding, append);
208    }
209
210    //-----------------------------------------------------------------------
211    /**
212     * Initialise the wrapped file writer.
213     * Ensure that a cleanup occurs if the writer creation fails.
214     *
215     * @param file  the file to be accessed
216     * @param encoding  the encoding to use - may be Charset, CharsetEncoder or String
217     * @param append  true to append
218     * @return the initialised writer
219     * @throws NullPointerException if the file or encoding is null
220     * @throws IOException if an error occurs
221     */
222     private static Writer initWriter(File file, Object encoding, boolean append) throws IOException {
223        if (file == null) {
224            throw new NullPointerException("File is missing");
225        }
226        if (encoding == null) {
227            throw new NullPointerException("Encoding is missing");
228        }
229        boolean fileExistedAlready = file.exists();
230        OutputStream stream = null;
231        Writer writer = null;
232        try {
233            stream = new FileOutputStream(file, append);
234            if (encoding instanceof Charset) {
235                writer = new OutputStreamWriter(stream, (Charset)encoding);
236            } else if (encoding instanceof CharsetEncoder) {
237                writer = new OutputStreamWriter(stream, (CharsetEncoder)encoding);
238            } else {
239                writer = new OutputStreamWriter(stream, (String)encoding);
240            }
241        } catch (IOException ex) {
242            IOUtils.closeQuietly(writer);
243            IOUtils.closeQuietly(stream);
244            if (fileExistedAlready == false) {
245                FileUtils.deleteQuietly(file);
246            }
247            throw ex;
248        } catch (RuntimeException ex) {
249            IOUtils.closeQuietly(writer);
250            IOUtils.closeQuietly(stream);
251            if (fileExistedAlready == false) {
252                FileUtils.deleteQuietly(file);
253            }
254            throw ex;
255        }
256        return writer;
257    }
258
259    //-----------------------------------------------------------------------
260    /**
261     * Write a character.
262     * @param idx the character to write
263     * @throws IOException if an I/O error occurs
264     */
265     @Override
266    public void write(int idx) throws IOException {
267        out.write(idx);
268    }
269
270    /**
271     * Write the characters from an array.
272     * @param chr the characters to write
273     * @throws IOException if an I/O error occurs
274     */
275     @Override
276    public void write(char[] chr) throws IOException {
277        out.write(chr);
278    }
279
280    /**
281     * Write the specified characters from an array.
282     * @param chr the characters to write
283     * @param st The start offset
284     * @param end The number of characters to write
285     * @throws IOException if an I/O error occurs
286     */
287     @Override
288    public void write(char[] chr, int st, int end) throws IOException {
289        out.write(chr, st, end);
290    }
291
292    /**
293     * Write the characters from a string.
294     * @param str the string to write
295     * @throws IOException if an I/O error occurs
296     */
297     @Override
298    public void write(String str) throws IOException {
299        out.write(str);
300    }
301
302    /**
303     * Write the specified characters from a string.
304     * @param str the string to write
305     * @param st The start offset
306     * @param end The number of characters to write
307     * @throws IOException if an I/O error occurs
308     */
309     @Override
310    public void write(String str, int st, int end) throws IOException {
311        out.write(str, st, end);
312    }
313
314    /**
315     * Flush the stream.
316     * @throws IOException if an I/O error occurs
317     */
318     @Override
319    public void flush() throws IOException {
320        out.flush();
321    }
322
323    /**
324     * Close the stream.
325     * @throws IOException if an I/O error occurs
326     */
327     @Override
328    public void close() throws IOException {
329        out.close();
330    }
331}