    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</code>
    036 * that allows an encoding to be set. Unfortunately, it cannot subclass
    037 * <code>FileWriter</code>.
    038 * <p>
    039 * By default, the file will be overwritten, but this may be changed to append.
    040 * <p>
    041 * The encoding must be specified using either the name of the {@link Charset},
    042 * the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding
    043 * is required then use the {@link java.io.FileWriter} directly, rather than
    044 * this implementation.
    045 * <p>
    046 *
    047 *
    048 * @since 1.4
    049 *
    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        super();
    159        this.out = initWriter(file, charsetName, append);
    160    }
    161
    162    /**
    163     * Constructs a FileWriterWithEncoding with a file encoding.
    164     *
    165     * @param file  the file to write to, not null
    166     * @param charset  the encoding to use, not null
    167     * @throws NullPointerException if the file or encoding is null
    168     * @throws IOException in case of an I/O error
    169     */
    170    public FileWriterWithEncoding(final File file, final Charset charset) throws IOException {
    171        this(file, charset, false);
    172    }
    173
    174    /**
    175     * Constructs a FileWriterWithEncoding with a file encoding.
    176     *
    177     * @param file  the file to write to, not null
    178     * @param encoding  the name of the requested charset, not null
    179     * @param append  true if content should be appended, false to overwrite
    180     * @throws NullPointerException if the file or encoding is null
    181     * @throws IOException in case of an I/O error
    182     */
    183    public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException {
    184        super();
    185        this.out = initWriter(file, encoding, append);
    186    }
    187
    188    /**
    189     * Constructs a FileWriterWithEncoding with a file encoding.
    190     *
    191     * @param file  the file to write to, not null
    192     * @param charsetEncoder  the encoding to use, not null
    193     * @throws NullPointerException if the file or encoding is null
    194     * @throws IOException in case of an I/O error
    195     */
    196    public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException {
    197        this(file, charsetEncoder, false);
    198    }
    199
    200    /**
    201     * Constructs a FileWriterWithEncoding with a file encoding.
    202     *
    203     * @param file  the file to write to, not null
    204     * @param charsetEncoder  the encoding to use, not null
    205     * @param append  true if content should be appended, false to overwrite
    206     * @throws NullPointerException if the file or encoding is null
    207     * @throws IOException in case of an I/O error
    208     */
    209    public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append)
    210            throws IOException {
    211        super();
    212        this.out = initWriter(file, charsetEncoder, append);
    213    }
    214
    215    //-----------------------------------------------------------------------
    216    /**
    217     * Initialise the wrapped file writer.
    218     * Ensure that a cleanup occurs if the writer creation fails.
    219     *
    220     * @param file  the file to be accessed
    221     * @param encoding  the encoding to use - may be Charset, CharsetEncoder or String
    222     * @param append  true to append
    223     * @return the initialised writer
    224     * @throws NullPointerException if the file or encoding is null
    225     * @throws IOException if an error occurs
    226     */
    227     private static Writer initWriter(final File file, final Object encoding, final boolean append) throws IOException {
    228        Objects.requireNonNull(file, "file");
    229        Objects.requireNonNull(encoding, "encoding");
    230        OutputStream stream = null;
    231        final boolean fileExistedAlready = file.exists();
    232        try {
    233            stream = new FileOutputStream(file, append);
    234            if (encoding instanceof Charset) {
    235                return new OutputStreamWriter(stream, (Charset)encoding);
    236            } else if (encoding instanceof CharsetEncoder) {
    237                return new OutputStreamWriter(stream, (CharsetEncoder)encoding);
    238            } else {
    239                return new OutputStreamWriter(stream, (String)encoding);
    240            }
    241        } catch (final IOException | RuntimeException ex) {
    242            try {
    243                IOUtils.close(stream);
    244            } catch (final IOException e) {
    245                ex.addSuppressed(e);
    246            }
    247            if (fileExistedAlready == false) {
    248                FileUtils.deleteQuietly(file);
    249            }
    250            throw ex;
    251        }
    252    }
    253
    254    //-----------------------------------------------------------------------
    255    /**
    256     * Write a character.
    257     * @param idx the character to write
    258     * @throws IOException if an I/O error occurs
    259     */
    260     @Override
    261    public void write(final int idx) throws IOException {
    262        out.write(idx);
    263    }
    264
    265    /**
    266     * Write the characters from an array.
    267     * @param chr the characters to write
    268     * @throws IOException if an I/O error occurs
    269     */
    270     @Override
    271    public void write(final char[] chr) throws IOException {
    272        out.write(chr);
    273    }
    274
    275    /**
    276     * Write the specified characters from an array.
    277     * @param chr the characters to write
    278     * @param st The start offset
    279     * @param end The number of characters to write
    280     * @throws IOException if an I/O error occurs
    281     */
    282     @Override
    283    public void write(final char[] chr, final int st, final int end) throws IOException {
    284        out.write(chr, st, end);
    285    }
    286
    287    /**
    288     * Write the characters from a string.
    289     * @param str the string to write
    290     * @throws IOException if an I/O error occurs
    291     */
    292     @Override
    293    public void write(final String str) throws IOException {
    294        out.write(str);
    295    }
    296
    297    /**
    298     * Write the specified characters from a string.
    299     * @param str the string to write
    300     * @param st The start offset
    301     * @param end The number of characters to write
    302     * @throws IOException if an I/O error occurs
    303     */
    304     @Override
    305    public void write(final String str, final int st, final 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     @Override
    314    public void flush() throws IOException {
    315        out.flush();
    316    }
    317
    318    /**
    319     * Close the stream.
    320     * @throws IOException if an I/O error occurs
    321     */
    322     @Override
    323    public void close() throws IOException {
    324        out.close();
    325    }
    326}