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