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 */
17package org.apache.commons.io.output;
18
19import java.io.File;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.OutputStream;
23import java.io.OutputStreamWriter;
24import java.io.Writer;
25import java.nio.charset.Charset;
26import java.nio.charset.CharsetEncoder;
27
28import org.apache.commons.io.FileUtils;
29import org.apache.commons.io.IOUtils;
30
31/**
32 * Writer of files that allows the encoding to be set.
33 * <p>
34 * This class provides a simple alternative to <code>FileWriter</code>
35 * that allows an encoding to be set. Unfortunately, it cannot subclass
36 * <code>FileWriter</code>.
37 * <p>
38 * By default, the file will be overwritten, but this may be changed to append.
39 * <p>
40 * The encoding must be specified using either the name of the {@link Charset},
41 * the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding
42 * is required then use the {@link java.io.FileWriter} directly, rather than
43 * this implementation.
44 * <p>
45 *
46 *
47 * @since Commons IO 1.4
48 * @version $Id: FileWriterWithEncoding.java 611634 2008-01-13 20:35:00Z niallp $
49 */
50public class FileWriterWithEncoding extends Writer {
51    // Cannot extend ProxyWriter, as requires writer to be
52    // known when super() is called
53
54    /** The writer to decorate. */
55    private final Writer out;
56
57    /**
58     * Constructs a FileWriterWithEncoding with a file encoding.
59     *
60     * @param filename  the name of the file to write to, not null
61     * @param encoding  the encoding to use, not null
62     * @throws NullPointerException if the file name or encoding is null
63     * @throws IOException in case of an I/O error
64     */
65    public FileWriterWithEncoding(String filename, String encoding) throws IOException {
66        this(new File(filename), encoding, false);
67    }
68
69    /**
70     * Constructs a FileWriterWithEncoding with a file encoding.
71     *
72     * @param filename  the name of the file to write to, not null
73     * @param encoding  the encoding to use, not null
74     * @param append  true if content should be appended, false to overwrite
75     * @throws NullPointerException if the file name or encoding is null
76     * @throws IOException in case of an I/O error
77     */
78    public FileWriterWithEncoding(String filename, String encoding, boolean append) throws IOException {
79        this(new File(filename), encoding, append);
80    }
81
82    /**
83     * Constructs a FileWriterWithEncoding with a file encoding.
84     *
85     * @param filename  the name of the file to write to, not null
86     * @param encoding  the encoding to use, not null
87     * @throws NullPointerException if the file name or encoding is null
88     * @throws IOException in case of an I/O error
89     */
90    public FileWriterWithEncoding(String filename, Charset encoding) throws IOException {
91        this(new File(filename), encoding, false);
92    }
93
94    /**
95     * Constructs a FileWriterWithEncoding with a file encoding.
96     *
97     * @param filename  the name of the file to write to, not null
98     * @param encoding  the encoding to use, not null
99     * @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    public void write(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    public void write(char[] chr) throws IOException {
275        out.write(chr);
276    }
277
278    /**
279     * Write the specified characters from an array.
280     * @param chr the characters to write
281     * @param st The start offset
282     * @param end The number of characters to write
283     * @throws IOException if an I/O error occurs
284     */
285    public void write(char[] chr, int st, int end) throws IOException {
286        out.write(chr, st, end);
287    }
288
289    /**
290     * Write the characters from a string.
291     * @param str the string to write
292     * @throws IOException if an I/O error occurs
293     */
294    public void write(String str) throws IOException {
295        out.write(str);
296    }
297
298    /**
299     * Write the specified characters from a string.
300     * @param str the string to write
301     * @param st The start offset
302     * @param end The number of characters to write
303     * @throws IOException if an I/O error occurs
304     */
305    public void write(String str, int st, 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    public void flush() throws IOException {
314        out.flush();
315    }
316
317    /**
318     * Close the stream.
319     * @throws IOException if an I/O error occurs
320     */
321    public void close() throws IOException {
322        out.close();
323    }
324}
325