    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}