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 19 import java.io.File; 20 import java.io.FileWriter; 21 import java.io.IOException; 22 import java.io.OutputStream; 23 import java.io.OutputStreamWriter; 24 import java.nio.charset.Charset; 25 import java.nio.charset.CharsetEncoder; 26 import java.util.Objects; 27 28 import org.apache.commons.io.Charsets; 29 import org.apache.commons.io.FileUtils; 30 import org.apache.commons.io.IOUtils; 31 import org.apache.commons.io.build.AbstractOrigin; 32 import org.apache.commons.io.build.AbstractStreamBuilder; 33 34 /** 35 * Writer of files that allows the encoding to be set. 36 * <p> 37 * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it cannot subclass {@link FileWriter}. 38 * </p> 39 * <p> 40 * By default, the file will be overwritten, but this may be changed to append. 41 * </p> 42 * <p> 43 * 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 44 * required then use the {@link java.io.FileWriter} directly, rather than this implementation. 45 * </p> 46 * <p> 47 * To build an instance, see {@link Builder}. 48 * </p> 49 * 50 * @since 1.4 51 */ 52 public class FileWriterWithEncoding extends ProxyWriter { 53 54 /** 55 * Builds a new {@link FileWriterWithEncoding} instance. 56 * <p> 57 * Using a CharsetEncoder: 58 * </p> 59 * <pre>{@code 60 * FileWriterWithEncoding s = FileWriterWithEncoding.builder() 61 * .setPath(path) 62 * .setAppend(false) 63 * .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder()) 64 * .get();} 65 * </pre> 66 * <p> 67 * Using a Charset: 68 * </p> 69 * <pre>{@code 70 * FileWriterWithEncoding s = FileWriterWithEncoding.builder() 71 * .setPath(path) 72 * .setAppend(false) 73 * .setCharsetEncoder(StandardCharsets.UTF_8) 74 * .get();} 75 * </pre> 76 * 77 * @since 2.12.0 78 */ 79 public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> { 80 81 private boolean append; 82 83 private CharsetEncoder charsetEncoder = super.getCharset().newEncoder(); 84 85 /** 86 * Constructs a new instance. 87 * <p> 88 * This builder use the aspects File, CharsetEncoder, and append. 89 * </p> 90 * <p> 91 * You must provide an origin that can be converted to a File by this builder, otherwise, this call will throw an 92 * {@link UnsupportedOperationException}. 93 * </p> 94 * 95 * @return a new instance. 96 * @throws UnsupportedOperationException if the origin cannot provide a File. 97 * @throws IllegalStateException if the {@code origin} is {@code null}. 98 * @see AbstractOrigin#getFile() 99 */ 100 @SuppressWarnings("resource") 101 @Override 102 public FileWriterWithEncoding get() throws IOException { 103 if (charsetEncoder != null && getCharset() != null && !charsetEncoder.charset().equals(getCharset())) { 104 throw new IllegalStateException(String.format("Mismatched Charset(%s) and CharsetEncoder(%s)", getCharset(), charsetEncoder.charset())); 105 } 106 final Object encoder = charsetEncoder != null ? charsetEncoder : getCharset(); 107 return new FileWriterWithEncoding(FileWriterWithEncoding.initWriter(checkOrigin().getFile(), encoder, append)); 108 } 109 110 /** 111 * Sets whether or not to append. 112 * 113 * @param append Whether or not to append. 114 * @return this 115 */ 116 public Builder setAppend(final boolean append) { 117 this.append = append; 118 return this; 119 } 120 121 /** 122 * Sets charsetEncoder to use for encoding. 123 * 124 * @param charsetEncoder The charsetEncoder to use for encoding. 125 * @return this 126 */ 127 public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) { 128 this.charsetEncoder = charsetEncoder; 129 return this; 130 } 131 132 } 133 134 /** 135 * Constructs a new {@link Builder}. 136 * 137 * @return Creates a new {@link Builder}. 138 * @since 2.12.0 139 */ 140 public static Builder builder() { 141 return new Builder(); 142 } 143 144 /** 145 * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails. 146 * 147 * @param file the file to be accessed 148 * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset. 149 * @param append true to append 150 * @return a new initialized OutputStreamWriter 151 * @throws IOException if an error occurs 152 */ 153 private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException { 154 Objects.requireNonNull(file, "file"); 155 OutputStream outputStream = null; 156 final boolean fileExistedAlready = file.exists(); 157 try { 158 outputStream = FileUtils.newOutputStream(file, append); 159 if (encoding == null || encoding instanceof Charset) { 160 return new OutputStreamWriter(outputStream, Charsets.toCharset((Charset) encoding)); 161 } 162 if (encoding instanceof CharsetEncoder) { 163 return new OutputStreamWriter(outputStream, (CharsetEncoder) encoding); 164 } 165 return new OutputStreamWriter(outputStream, (String) encoding); 166 } catch (final IOException | RuntimeException ex) { 167 try { 168 IOUtils.close(outputStream); 169 } catch (final IOException e) { 170 ex.addSuppressed(e); 171 } 172 if (!fileExistedAlready) { 173 FileUtils.deleteQuietly(file); 174 } 175 throw ex; 176 } 177 } 178 179 /** 180 * Constructs a FileWriterWithEncoding with a file encoding. 181 * 182 * @param file the file to write to, not null 183 * @param charset the encoding to use, not null 184 * @throws NullPointerException if the file or encoding is null 185 * @throws IOException in case of an I/O error 186 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 187 */ 188 @Deprecated 189 public FileWriterWithEncoding(final File file, final Charset charset) throws IOException { 190 this(file, charset, false); 191 } 192 193 /** 194 * Constructs a FileWriterWithEncoding with a file encoding. 195 * 196 * @param file the file to write to, not null. 197 * @param encoding the name of the requested charset, null uses the default Charset. 198 * @param append true if content should be appended, false to overwrite. 199 * @throws NullPointerException if the file is null. 200 * @throws IOException in case of an I/O error. 201 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 202 */ 203 @Deprecated 204 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 205 public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException { 206 this(initWriter(file, encoding, append)); 207 } 208 209 /** 210 * Constructs a FileWriterWithEncoding with a file encoding. 211 * 212 * @param file the file to write to, not null 213 * @param charsetEncoder the encoding to use, not null 214 * @throws NullPointerException if the file or encoding is null 215 * @throws IOException in case of an I/O error 216 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 217 */ 218 @Deprecated 219 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException { 220 this(file, charsetEncoder, false); 221 } 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 /** 240 * Constructs a FileWriterWithEncoding with a file encoding. 241 * 242 * @param file the file to write to, not null 243 * @param charsetName the name of the requested charset, not null 244 * @throws NullPointerException if the file or encoding is null 245 * @throws IOException in case of an I/O error 246 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 247 */ 248 @Deprecated 249 public FileWriterWithEncoding(final File file, final String charsetName) throws IOException { 250 this(file, charsetName, false); 251 } 252 253 /** 254 * Constructs a FileWriterWithEncoding with a file encoding. 255 * 256 * @param file the file to write to, not null. 257 * @param charsetName the name of the requested charset, null uses the default Charset. 258 * @param append true if content should be appended, false to overwrite. 259 * @throws NullPointerException if the file is null. 260 * @throws IOException in case of an I/O error. 261 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 262 */ 263 @Deprecated 264 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 265 public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException { 266 this(initWriter(file, charsetName, append)); 267 } 268 269 private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) { 270 super(outputStreamWriter); 271 } 272 273 /** 274 * Constructs a FileWriterWithEncoding with a file encoding. 275 * 276 * @param fileName the name of the file to write to, not null 277 * @param charset the charset to use, not null 278 * @throws NullPointerException if the file name or encoding is null 279 * @throws IOException in case of an I/O error 280 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 281 */ 282 @Deprecated 283 public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException { 284 this(new File(fileName), charset, false); 285 } 286 287 /** 288 * Constructs a FileWriterWithEncoding with a file encoding. 289 * 290 * @param fileName the name of the file to write to, not null 291 * @param charset the encoding to use, not null 292 * @param append true if content should be appended, false to overwrite 293 * @throws NullPointerException if the file name or encoding is null 294 * @throws IOException in case of an I/O error 295 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 296 */ 297 @Deprecated 298 public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException { 299 this(new File(fileName), charset, append); 300 } 301 302 /** 303 * Constructs a FileWriterWithEncoding with a file encoding. 304 * 305 * @param fileName the name of the file to write to, not null 306 * @param encoding the encoding to use, not null 307 * @throws NullPointerException if the file name or encoding is null 308 * @throws IOException in case of an I/O error 309 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 310 */ 311 @Deprecated 312 public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException { 313 this(new File(fileName), encoding, false); 314 } 315 316 /** 317 * Constructs a FileWriterWithEncoding with a file encoding. 318 * 319 * @param fileName the name of the file to write to, not null 320 * @param charsetEncoder the encoding to use, not null 321 * @param append true if content should be appended, false to overwrite 322 * @throws NullPointerException if the file name or encoding is null 323 * @throws IOException in case of an I/O error 324 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 325 */ 326 @Deprecated 327 public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException { 328 this(new File(fileName), charsetEncoder, append); 329 } 330 331 /** 332 * Constructs a FileWriterWithEncoding with a file encoding. 333 * 334 * @param fileName the name of the file to write to, not null 335 * @param charsetName the name of the requested charset, not null 336 * @throws NullPointerException if the file name or encoding is null 337 * @throws IOException in case of an I/O error 338 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 339 */ 340 @Deprecated 341 public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException { 342 this(new File(fileName), charsetName, false); 343 } 344 345 /** 346 * Constructs a FileWriterWithEncoding with a file encoding. 347 * 348 * @param fileName the name of the file to write to, not null 349 * @param charsetName the name of the requested charset, not null 350 * @param append true if content should be appended, false to overwrite 351 * @throws NullPointerException if the file name or encoding is null 352 * @throws IOException in case of an I/O error 353 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 354 */ 355 @Deprecated 356 public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException { 357 this(new File(fileName), charsetName, append); 358 } 359 }