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.FileWriter; 021import java.io.IOException; 022import java.io.OutputStream; 023import java.io.OutputStreamWriter; 024import java.nio.charset.Charset; 025import java.nio.charset.CharsetEncoder; 026import java.util.Objects; 027 028import org.apache.commons.io.Charsets; 029import org.apache.commons.io.FileUtils; 030import org.apache.commons.io.IOUtils; 031import org.apache.commons.io.build.AbstractOrigin; 032import org.apache.commons.io.build.AbstractStreamBuilder; 033 034/** 035 * Writer of files that allows the encoding to be set. 036 * <p> 037 * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it cannot subclass {@link 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}, the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding is 044 * required then use the {@link FileWriter} directly, rather than this implementation. 045 * </p> 046 * <p> 047 * To build an instance, use {@link Builder}. 048 * </p> 049 * 050 * @see Builder 051 * @since 1.4 052 */ 053public class FileWriterWithEncoding extends ProxyWriter { 054 055 // @formatter:off 056 /** 057 * Builds a new {@link FileWriterWithEncoding}. 058 * 059 * <p> 060 * Using a CharsetEncoder: 061 * </p> 062 * <pre>{@code 063 * FileWriterWithEncoding w = FileWriterWithEncoding.builder() 064 * .setPath(path) 065 * .setAppend(false) 066 * .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder()) 067 * .get();} 068 * </pre> 069 * <p> 070 * Using a Charset: 071 * </p> 072 * <pre>{@code 073 * FileWriterWithEncoding w = FileWriterWithEncoding.builder() 074 * .setPath(path) 075 * .setAppend(false) 076 * .setCharsetEncoder(StandardCharsets.UTF_8) 077 * .get();} 078 * </pre> 079 * 080 * @see #get() 081 * @since 2.12.0 082 */ 083 // @formatter:on 084 public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> { 085 086 private boolean append; 087 088 private CharsetEncoder charsetEncoder = super.getCharset().newEncoder(); 089 090 /** 091 * Builds a new {@link FileWriterWithEncoding}. 092 * <p> 093 * You must set input that supports {@link File} on this builder, otherwise, this method throws an exception. 094 * </p> 095 * <p> 096 * This builder use the following aspects: 097 * </p> 098 * <ul> 099 * <li>{@link File}</li> 100 * <li>{@link CharsetEncoder}</li> 101 * <li>append</li> 102 * </ul> 103 * 104 * @return a new instance. 105 * @throws UnsupportedOperationException if the origin cannot provide a File. 106 * @throws IllegalStateException if the {@code origin} is {@code null}. 107 * @see AbstractOrigin#getFile() 108 */ 109 @SuppressWarnings("resource") 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 /** 120 * Sets whether or not to append. 121 * 122 * @param append Whether or not to append. 123 * @return {@code this} instance. 124 */ 125 public Builder setAppend(final boolean append) { 126 this.append = append; 127 return this; 128 } 129 130 /** 131 * Sets charsetEncoder to use for encoding. 132 * 133 * @param charsetEncoder The charsetEncoder to use for encoding. 134 * @return {@code this} instance. 135 */ 136 public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) { 137 this.charsetEncoder = charsetEncoder; 138 return this; 139 } 140 141 } 142 143 /** 144 * Constructs a new {@link Builder}. 145 * 146 * @return Creates a new {@link Builder}. 147 * @since 2.12.0 148 */ 149 public static Builder builder() { 150 return new Builder(); 151 } 152 153 /** 154 * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails. 155 * 156 * @param file the file to be accessed 157 * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset. 158 * @param append true to append 159 * @return a new initialized OutputStreamWriter 160 * @throws IOException if an error occurs 161 */ 162 private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException { 163 Objects.requireNonNull(file, "file"); 164 OutputStream outputStream = null; 165 final boolean fileExistedAlready = file.exists(); 166 try { 167 outputStream = FileUtils.newOutputStream(file, append); 168 if (encoding == null || encoding instanceof Charset) { 169 return new OutputStreamWriter(outputStream, Charsets.toCharset((Charset) encoding)); 170 } 171 if (encoding instanceof CharsetEncoder) { 172 return new OutputStreamWriter(outputStream, (CharsetEncoder) encoding); 173 } 174 return new OutputStreamWriter(outputStream, (String) encoding); 175 } catch (final IOException | RuntimeException ex) { 176 try { 177 IOUtils.close(outputStream); 178 } catch (final IOException e) { 179 ex.addSuppressed(e); 180 } 181 if (!fileExistedAlready) { 182 FileUtils.deleteQuietly(file); 183 } 184 throw ex; 185 } 186 } 187 188 /** 189 * Constructs a FileWriterWithEncoding with a file encoding. 190 * 191 * @param file the file to write to, not null 192 * @param charset 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 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 196 */ 197 @Deprecated 198 public FileWriterWithEncoding(final File file, final Charset charset) throws IOException { 199 this(file, charset, false); 200 } 201 202 /** 203 * Constructs a FileWriterWithEncoding with a file encoding. 204 * 205 * @param file the file to write to, not null. 206 * @param encoding the name of the requested charset, null uses the default Charset. 207 * @param append true if content should be appended, false to overwrite. 208 * @throws NullPointerException if the file is null. 209 * @throws IOException in case of an I/O error. 210 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 211 */ 212 @Deprecated 213 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 214 public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException { 215 this(initWriter(file, encoding, append)); 216 } 217 218 /** 219 * Constructs a FileWriterWithEncoding with a file encoding. 220 * 221 * @param file the file to write to, not null 222 * @param charsetEncoder the encoding to use, not null 223 * @throws NullPointerException if the file or encoding is null 224 * @throws IOException in case of an I/O error 225 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 226 */ 227 @Deprecated 228 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException { 229 this(file, charsetEncoder, false); 230 } 231 232 /** 233 * Constructs a FileWriterWithEncoding with a file encoding. 234 * 235 * @param file the file to write to, not null. 236 * @param charsetEncoder the encoding to use, null uses the default Charset. 237 * @param append true if content should be appended, false to overwrite. 238 * @throws NullPointerException if the file is null. 239 * @throws IOException in case of an I/O error. 240 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 241 */ 242 @Deprecated 243 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 244 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) throws IOException { 245 this(initWriter(file, charsetEncoder, append)); 246 } 247 248 /** 249 * Constructs a FileWriterWithEncoding with a file encoding. 250 * 251 * @param file the file to write to, not null 252 * @param charsetName the name of the requested charset, not null 253 * @throws NullPointerException if the file or encoding is null 254 * @throws IOException in case of an I/O error 255 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 256 */ 257 @Deprecated 258 public FileWriterWithEncoding(final File file, final String charsetName) throws IOException { 259 this(file, charsetName, false); 260 } 261 262 /** 263 * Constructs a FileWriterWithEncoding with a file encoding. 264 * 265 * @param file the file to write to, not null. 266 * @param charsetName the name of the requested charset, null uses the default Charset. 267 * @param append true if content should be appended, false to overwrite. 268 * @throws NullPointerException if the file is null. 269 * @throws IOException in case of an I/O error. 270 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 271 */ 272 @Deprecated 273 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 274 public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException { 275 this(initWriter(file, charsetName, append)); 276 } 277 278 private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) { 279 super(outputStreamWriter); 280 } 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 charset to use, not null 287 * @throws NullPointerException if the file name or encoding is null 288 * @throws IOException in case of an I/O error 289 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 290 */ 291 @Deprecated 292 public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException { 293 this(new File(fileName), charset, false); 294 } 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 charset the encoding to use, not null 301 * @param append true if content should be appended, false to overwrite 302 * @throws NullPointerException if the file name or encoding is null 303 * @throws IOException in case of an I/O error 304 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 305 */ 306 @Deprecated 307 public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException { 308 this(new File(fileName), charset, append); 309 } 310 311 /** 312 * Constructs a FileWriterWithEncoding with a file encoding. 313 * 314 * @param fileName the name of the file to write to, not null 315 * @param encoding the encoding to use, not null 316 * @throws NullPointerException if the file name or encoding is null 317 * @throws IOException in case of an I/O error 318 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 319 */ 320 @Deprecated 321 public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException { 322 this(new File(fileName), encoding, false); 323 } 324 325 /** 326 * Constructs a FileWriterWithEncoding with a file encoding. 327 * 328 * @param fileName the name of the file to write to, not null 329 * @param charsetEncoder the encoding to use, not null 330 * @param append true if content should be appended, false to overwrite 331 * @throws NullPointerException if the file name or encoding is null 332 * @throws IOException in case of an I/O error 333 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 334 */ 335 @Deprecated 336 public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException { 337 this(new File(fileName), charsetEncoder, append); 338 } 339 340 /** 341 * Constructs a FileWriterWithEncoding with a file encoding. 342 * 343 * @param fileName the name of the file to write to, not null 344 * @param charsetName the name of the requested charset, not null 345 * @throws NullPointerException if the file name or encoding is null 346 * @throws IOException in case of an I/O error 347 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 348 */ 349 @Deprecated 350 public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException { 351 this(new File(fileName), charsetName, false); 352 } 353 354 /** 355 * Constructs a FileWriterWithEncoding with a file encoding. 356 * 357 * @param fileName the name of the file to write to, not null 358 * @param charsetName the name of the requested charset, not null 359 * @param append true if content should be appended, false to overwrite 360 * @throws NullPointerException if the file name or encoding is null 361 * @throws IOException in case of an I/O error 362 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 363 */ 364 @Deprecated 365 public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException { 366 this(new File(fileName), charsetName, append); 367 } 368}