FileWriterWithEncoding.java

  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.  */
  17. package org.apache.commons.io.output;

  18. import java.io.File;
  19. import java.io.FileWriter;
  20. import java.io.IOException;
  21. import java.io.OutputStream;
  22. import java.io.OutputStreamWriter;
  23. import java.nio.charset.Charset;
  24. import java.nio.charset.CharsetEncoder;
  25. import java.util.Objects;

  26. import org.apache.commons.io.Charsets;
  27. import org.apache.commons.io.FileUtils;
  28. import org.apache.commons.io.IOUtils;
  29. import org.apache.commons.io.build.AbstractOrigin;
  30. import org.apache.commons.io.build.AbstractStreamBuilder;

  31. /**
  32.  * Writer of files that allows the encoding to be set.
  33.  * <p>
  34.  * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it cannot subclass {@link FileWriter}.
  35.  * </p>
  36.  * <p>
  37.  * By default, the file will be overwritten, but this may be changed to append.
  38.  * </p>
  39.  * <p>
  40.  * The encoding must be specified using either the name of the {@link Charset}, the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding is
  41.  * required then use the {@link FileWriter} directly, rather than this implementation.
  42.  * </p>
  43.  * <p>
  44.  * To build an instance, use {@link Builder}.
  45.  * </p>
  46.  *
  47.  * @see Builder
  48.  * @since 1.4
  49.  */
  50. public class FileWriterWithEncoding extends ProxyWriter {

  51.     // @formatter:off
  52.     /**
  53.      * Builds a new {@link FileWriterWithEncoding}.
  54.      *
  55.      * <p>
  56.      * Using a CharsetEncoder:
  57.      * </p>
  58.      * <pre>{@code
  59.      * FileWriterWithEncoding w = FileWriterWithEncoding.builder()
  60.      *   .setPath(path)
  61.      *   .setAppend(false)
  62.      *   .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder())
  63.      *   .get();}
  64.      * </pre>
  65.      * <p>
  66.      * Using a Charset:
  67.      * </p>
  68.      * <pre>{@code
  69.      * FileWriterWithEncoding w = FileWriterWithEncoding.builder()
  70.      *   .setPath(path)
  71.      *   .setAppend(false)
  72.      *   .setCharsetEncoder(StandardCharsets.UTF_8)
  73.      *   .get();}
  74.      * </pre>
  75.      *
  76.      * @see #get()
  77.      * @since 2.12.0
  78.      */
  79.     // @formatter:on
  80.     public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> {

  81.         private boolean append;

  82.         private CharsetEncoder charsetEncoder = super.getCharset().newEncoder();

  83.         /**
  84.          * Constructs a new builder of {@link FileWriterWithEncoding}.
  85.          */
  86.         public Builder() {
  87.             // empty
  88.         }

  89.         /**
  90.          * Builds a new {@link FileWriterWithEncoding}.
  91.          * <p>
  92.          * You must set an aspect that supports {@link File} on this builder, otherwise, this method throws an exception.
  93.          * </p>
  94.          * <p>
  95.          * This builder uses the following aspects:
  96.          * </p>
  97.          * <ul>
  98.          * <li>{@link File} is the target aspect.</li>
  99.          * <li>{@link CharsetEncoder}</li>
  100.          * <li>append</li>
  101.          * </ul>
  102.          *
  103.          * @return a new instance.
  104.          * @throws UnsupportedOperationException if the origin cannot provide a File.
  105.          * @throws IllegalStateException         if the {@code origin} is {@code null}.
  106.          * @throws IOException                   if an I/O error occurs converting to an {@link File} using {@link #getFile()}.
  107.          * @see AbstractOrigin#getFile()
  108.          * @see #getUnchecked()
  109.          */
  110.         @Override
  111.         public FileWriterWithEncoding get() throws IOException {
  112.             if (charsetEncoder != null && getCharset() != null && !charsetEncoder.charset().equals(getCharset())) {
  113.                 throw new IllegalStateException(String.format("Mismatched Charset(%s) and CharsetEncoder(%s)", getCharset(), charsetEncoder.charset()));
  114.             }
  115.             final Object encoder = charsetEncoder != null ? charsetEncoder : getCharset();
  116.             return new FileWriterWithEncoding(initWriter(checkOrigin().getFile(), encoder, append));
  117.         }

  118.         /**
  119.          * Sets whether or not to append.
  120.          *
  121.          * @param append Whether or not to append.
  122.          * @return {@code this} instance.
  123.          */
  124.         public Builder setAppend(final boolean append) {
  125.             this.append = append;
  126.             return this;
  127.         }

  128.         /**
  129.          * Sets charsetEncoder to use for encoding.
  130.          *
  131.          * @param charsetEncoder The charsetEncoder to use for encoding.
  132.          * @return {@code this} instance.
  133.          */
  134.         public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) {
  135.             this.charsetEncoder = charsetEncoder;
  136.             return this;
  137.         }

  138.     }

  139.     /**
  140.      * Constructs a new {@link Builder}.
  141.      *
  142.      * @return Creates a new {@link Builder}.
  143.      * @since 2.12.0
  144.      */
  145.     public static Builder builder() {
  146.         return new Builder();
  147.     }

  148.     /**
  149.      * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails.
  150.      *
  151.      * @param file     the file to be accessed.
  152.      * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset.
  153.      * @param append   true to append.
  154.      * @return a new initialized OutputStreamWriter.
  155.      * @throws IOException if an I/O error occurs.
  156.      */
  157.     private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException {
  158.         Objects.requireNonNull(file, "file");
  159.         OutputStream outputStream = null;
  160.         final boolean fileExistedAlready = file.exists();
  161.         try {
  162.             outputStream = FileUtils.newOutputStream(file, append);
  163.             if (encoding == null || encoding instanceof Charset) {
  164.                 return new OutputStreamWriter(outputStream, Charsets.toCharset((Charset) encoding));
  165.             }
  166.             if (encoding instanceof CharsetEncoder) {
  167.                 return new OutputStreamWriter(outputStream, (CharsetEncoder) encoding);
  168.             }
  169.             return new OutputStreamWriter(outputStream, (String) encoding);
  170.         } catch (final IOException | RuntimeException ex) {
  171.             try {
  172.                 IOUtils.close(outputStream);
  173.             } catch (final IOException e) {
  174.                 ex.addSuppressed(e);
  175.             }
  176.             if (!fileExistedAlready) {
  177.                 FileUtils.deleteQuietly(file);
  178.             }
  179.             throw ex;
  180.         }
  181.     }

  182.     /**
  183.      * Constructs a FileWriterWithEncoding with a file encoding.
  184.      *
  185.      * @param file    the file to write to, not null
  186.      * @param charset the encoding to use, not null
  187.      * @throws NullPointerException if the file or encoding is null
  188.      * @throws IOException          in case of an I/O error
  189.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  190.      */
  191.     @Deprecated
  192.     public FileWriterWithEncoding(final File file, final Charset charset) throws IOException {
  193.         this(file, charset, false);
  194.     }

  195.     /**
  196.      * Constructs a FileWriterWithEncoding with a file encoding.
  197.      *
  198.      * @param file     the file to write to, not null.
  199.      * @param encoding the name of the requested charset, null uses the default Charset.
  200.      * @param append   true if content should be appended, false to overwrite.
  201.      * @throws NullPointerException if the file is null.
  202.      * @throws IOException          in case of an I/O error.
  203.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  204.      */
  205.     @Deprecated
  206.     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
  207.     public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException {
  208.         this(initWriter(file, encoding, append));
  209.     }

  210.     /**
  211.      * Constructs a FileWriterWithEncoding with a file encoding.
  212.      *
  213.      * @param file           the file to write to, not null
  214.      * @param charsetEncoder the encoding to use, not null
  215.      * @throws NullPointerException if the file or encoding is null
  216.      * @throws IOException          in case of an I/O error
  217.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  218.      */
  219.     @Deprecated
  220.     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException {
  221.         this(file, charsetEncoder, false);
  222.     }

  223.     /**
  224.      * Constructs a FileWriterWithEncoding with a file encoding.
  225.      *
  226.      * @param file           the file to write to, not null.
  227.      * @param charsetEncoder the encoding to use, null uses the default Charset.
  228.      * @param append         true if content should be appended, false to overwrite.
  229.      * @throws NullPointerException if the file is null.
  230.      * @throws IOException          in case of an I/O error.
  231.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  232.      */
  233.     @Deprecated
  234.     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
  235.     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
  236.         this(initWriter(file, charsetEncoder, append));
  237.     }

  238.     /**
  239.      * Constructs a FileWriterWithEncoding with a file encoding.
  240.      *
  241.      * @param file        the file to write to, not null
  242.      * @param charsetName the name of the requested charset, not null
  243.      * @throws NullPointerException if the file or encoding is null
  244.      * @throws IOException          in case of an I/O error
  245.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  246.      */
  247.     @Deprecated
  248.     public FileWriterWithEncoding(final File file, final String charsetName) throws IOException {
  249.         this(file, charsetName, false);
  250.     }

  251.     /**
  252.      * Constructs a FileWriterWithEncoding with a file encoding.
  253.      *
  254.      * @param file        the file to write to, not null.
  255.      * @param charsetName the name of the requested charset, null uses the default Charset.
  256.      * @param append      true if content should be appended, false to overwrite.
  257.      * @throws NullPointerException if the file is null.
  258.      * @throws IOException          in case of an I/O error.
  259.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  260.      */
  261.     @Deprecated
  262.     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
  263.     public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException {
  264.         this(initWriter(file, charsetName, append));
  265.     }

  266.     private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) {
  267.         super(outputStreamWriter);
  268.     }

  269.     /**
  270.      * Constructs a FileWriterWithEncoding with a file encoding.
  271.      *
  272.      * @param fileName the name of the file to write to, not null
  273.      * @param charset  the charset to use, not null
  274.      * @throws NullPointerException if the file name or encoding is null
  275.      * @throws IOException          in case of an I/O error
  276.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  277.      */
  278.     @Deprecated
  279.     public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException {
  280.         this(new File(fileName), charset, false);
  281.     }

  282.     /**
  283.      * Constructs a FileWriterWithEncoding with a file encoding.
  284.      *
  285.      * @param fileName the name of the file to write to, not null
  286.      * @param charset  the encoding to use, not null
  287.      * @param append   true if content should be appended, false to overwrite
  288.      * @throws NullPointerException if the file name or encoding is null
  289.      * @throws IOException          in case of an I/O error
  290.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  291.      */
  292.     @Deprecated
  293.     public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException {
  294.         this(new File(fileName), charset, append);
  295.     }

  296.     /**
  297.      * Constructs a FileWriterWithEncoding with a file encoding.
  298.      *
  299.      * @param fileName the name of the file to write to, not null
  300.      * @param encoding the encoding to use, not null
  301.      * @throws NullPointerException if the file name or encoding is null
  302.      * @throws IOException          in case of an I/O error
  303.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  304.      */
  305.     @Deprecated
  306.     public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException {
  307.         this(new File(fileName), encoding, false);
  308.     }

  309.     /**
  310.      * Constructs a FileWriterWithEncoding with a file encoding.
  311.      *
  312.      * @param fileName       the name of the file to write to, not null
  313.      * @param charsetEncoder the encoding to use, not null
  314.      * @param append         true if content should be appended, false to overwrite
  315.      * @throws NullPointerException if the file name or encoding is null
  316.      * @throws IOException          in case of an I/O error
  317.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  318.      */
  319.     @Deprecated
  320.     public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
  321.         this(new File(fileName), charsetEncoder, append);
  322.     }

  323.     /**
  324.      * Constructs a FileWriterWithEncoding with a file encoding.
  325.      *
  326.      * @param fileName    the name of the file to write to, not null
  327.      * @param charsetName the name of the requested charset, not null
  328.      * @throws NullPointerException if the file name or encoding is null
  329.      * @throws IOException          in case of an I/O error
  330.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  331.      */
  332.     @Deprecated
  333.     public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException {
  334.         this(new File(fileName), charsetName, false);
  335.     }

  336.     /**
  337.      * Constructs a FileWriterWithEncoding with a file encoding.
  338.      *
  339.      * @param fileName    the name of the file to write to, not null
  340.      * @param charsetName the name of the requested charset, not null
  341.      * @param append      true if content should be appended, false to overwrite
  342.      * @throws NullPointerException if the file name or encoding is null
  343.      * @throws IOException          in case of an I/O error
  344.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  345.      */
  346.     @Deprecated
  347.     public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException {
  348.         this(new File(fileName), charsetName, append);
  349.     }
  350. }