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     */
017    package org.apache.commons.io.output;
018    
019    import java.io.File;
020    import java.io.FileOutputStream;
021    import java.io.IOException;
022    import java.io.OutputStream;
023    import java.io.OutputStreamWriter;
024    import java.io.Writer;
025    import java.nio.charset.Charset;
026    import java.nio.charset.CharsetEncoder;
027    
028    import org.apache.commons.io.FileUtils;
029    import 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     */
050    public 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    }