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.AbstractStreamBuilder; 032 033/** 034 * Writer of files that allows the encoding to be set. 035 * <p> 036 * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it cannot subclass {@link FileWriter}. 037 * </p> 038 * <p> 039 * By default, the file will be overwritten, but this may be changed to append. 040 * </p> 041 * <p> 042 * 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 043 * required then use the {@link java.io.FileWriter} directly, rather than this implementation. 044 * </p> 045 * 046 * @since 1.4 047 */ 048public class FileWriterWithEncoding extends ProxyWriter { 049 050 /** 051 * Builds a new {@link FileWriterWithEncoding} instance. 052 * <p> 053 * Using a CharsetEncoder: 054 * </p> 055 * <pre>{@code 056 * FileWriterWithEncoding s = FileWriterWithEncoding.builder() 057 * .setPath(path) 058 * .setAppend(false) 059 * .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder()) 060 * .get()} 061 * </pre> 062 * <p> 063 * Using a Charset: 064 * </p> 065 * <pre>{@code 066 * FileWriterWithEncoding s = FileWriterWithEncoding.builder() 067 * .setPath(path) 068 * .setAppend(false) 069 * .setCharsetEncoder(StandardCharsets.UTF_8) 070 * .get()} 071 * </pre> 072 * @since 2.12.0 073 */ 074 public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> { 075 076 private boolean append; 077 078 private CharsetEncoder charsetEncoder = super.getCharset().newEncoder(); 079 080 /** 081 * Constructs a new instance. 082 * 083 * @throws UnsupportedOperationException if the origin cannot be converted to a File. 084 */ 085 @SuppressWarnings("resource") 086 @Override 087 public FileWriterWithEncoding get() throws IOException { 088 if (charsetEncoder != null && getCharset() != null && !charsetEncoder.charset().equals(getCharset())) { 089 throw new IllegalStateException(String.format("Mismatched Charset(%s) and CharsetEncoder(%s)", getCharset(), charsetEncoder.charset())); 090 } 091 final Object encoder = charsetEncoder != null ? charsetEncoder : getCharset(); 092 return new FileWriterWithEncoding(FileWriterWithEncoding.initWriter(getOrigin().getFile(), encoder, append)); 093 } 094 095 /** 096 * Sets whether or not to append. 097 * 098 * @param append Whether or not to append. 099 * @return this 100 */ 101 public Builder setAppend(final boolean append) { 102 this.append = append; 103 return this; 104 } 105 106 /** 107 * Sets charsetEncoder to use for encoding. 108 * 109 * @param charsetEncoder The charsetEncoder to use for encoding. 110 * @return this 111 */ 112 public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) { 113 this.charsetEncoder = charsetEncoder; 114 return this; 115 } 116 117 } 118 119 /** 120 * Constructs a new {@link Builder}. 121 * 122 * @return Creates a new {@link Builder}. 123 * @since 2.12.0 124 */ 125 public static Builder builder() { 126 return new Builder(); 127 } 128 129 /** 130 * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails. 131 * 132 * @param file the file to be accessed 133 * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset. 134 * @param append true to append 135 * @return a new initialized OutputStreamWriter 136 * @throws IOException if an error occurs 137 */ 138 private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException { 139 Objects.requireNonNull(file, "file"); 140 OutputStream outputStream = null; 141 final boolean fileExistedAlready = file.exists(); 142 try { 143 outputStream = FileUtils.newOutputStream(file, append); 144 if (encoding == null || encoding instanceof Charset) { 145 return new OutputStreamWriter(outputStream, Charsets.toCharset((Charset) encoding)); 146 } 147 if (encoding instanceof CharsetEncoder) { 148 return new OutputStreamWriter(outputStream, (CharsetEncoder) encoding); 149 } 150 return new OutputStreamWriter(outputStream, (String) encoding); 151 } catch (final IOException | RuntimeException ex) { 152 try { 153 IOUtils.close(outputStream); 154 } catch (final IOException e) { 155 ex.addSuppressed(e); 156 } 157 if (!fileExistedAlready) { 158 FileUtils.deleteQuietly(file); 159 } 160 throw ex; 161 } 162 } 163 164 /** 165 * Constructs a FileWriterWithEncoding with a file encoding. 166 * 167 * @param file the file to write to, not null 168 * @param charset the encoding to use, not null 169 * @throws NullPointerException if the file or encoding is null 170 * @throws IOException in case of an I/O error 171 * @deprecated Use {@link #builder()} 172 */ 173 @Deprecated 174 public FileWriterWithEncoding(final File file, final Charset charset) throws IOException { 175 this(file, charset, false); 176 } 177 178 /** 179 * Constructs a FileWriterWithEncoding with a file encoding. 180 * 181 * @param file the file to write to, not null. 182 * @param encoding the name of the requested charset, null uses the default Charset. 183 * @param append true if content should be appended, false to overwrite. 184 * @throws NullPointerException if the file is null. 185 * @throws IOException in case of an I/O error. 186 * @deprecated Use {@link #builder()} 187 */ 188 @Deprecated 189 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 190 public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException { 191 this(initWriter(file, encoding, append)); 192 } 193 194 /** 195 * Constructs a FileWriterWithEncoding with a file encoding. 196 * 197 * @param file the file to write to, not null 198 * @param charsetEncoder the encoding to use, not null 199 * @throws NullPointerException if the file or encoding is null 200 * @throws IOException in case of an I/O error 201 * @deprecated Use {@link #builder()} 202 */ 203 @Deprecated 204 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException { 205 this(file, charsetEncoder, false); 206 } 207 208 /** 209 * Constructs a FileWriterWithEncoding with a file encoding. 210 * 211 * @param file the file to write to, not null. 212 * @param charsetEncoder the encoding to use, null uses the default Charset. 213 * @param append true if content should be appended, false to overwrite. 214 * @throws NullPointerException if the file is null. 215 * @throws IOException in case of an I/O error. 216 * @deprecated Use {@link #builder()} 217 */ 218 @Deprecated 219 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 220 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) throws IOException { 221 this(initWriter(file, charsetEncoder, append)); 222 } 223 224 /** 225 * Constructs a FileWriterWithEncoding with a file encoding. 226 * 227 * @param file the file to write to, not null 228 * @param charsetName the name of the requested charset, not null 229 * @throws NullPointerException if the file or encoding is null 230 * @throws IOException in case of an I/O error 231 * @deprecated Use {@link #builder()} 232 */ 233 @Deprecated 234 public FileWriterWithEncoding(final File file, final String charsetName) throws IOException { 235 this(file, charsetName, false); 236 } 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, null uses the default Charset. 243 * @param append true if content should be appended, false to overwrite. 244 * @throws NullPointerException if the file is null. 245 * @throws IOException in case of an I/O error. 246 * @deprecated Use {@link #builder()} 247 */ 248 @Deprecated 249 @SuppressWarnings("resource") // Call site is responsible for closing a new instance. 250 public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException { 251 this(initWriter(file, charsetName, append)); 252 } 253 254 private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) { 255 super(outputStreamWriter); 256 } 257 258 /** 259 * Constructs a FileWriterWithEncoding with a file encoding. 260 * 261 * @param fileName the name of the file to write to, not null 262 * @param charset the charset to use, not null 263 * @throws NullPointerException if the file name or encoding is null 264 * @throws IOException in case of an I/O error 265 * @deprecated Use {@link #builder()} 266 */ 267 @Deprecated 268 public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException { 269 this(new File(fileName), charset, false); 270 } 271 272 /** 273 * Constructs a FileWriterWithEncoding with a file encoding. 274 * 275 * @param fileName the name of the file to write to, not null 276 * @param charset the encoding to use, not null 277 * @param append true if content should be appended, false to overwrite 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()} 281 */ 282 @Deprecated 283 public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException { 284 this(new File(fileName), charset, append); 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 encoding the encoding to use, not null 292 * @throws NullPointerException if the file name or encoding is null 293 * @throws IOException in case of an I/O error 294 * @deprecated Use {@link #builder()} 295 */ 296 @Deprecated 297 public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException { 298 this(new File(fileName), encoding, false); 299 } 300 301 /** 302 * Constructs a FileWriterWithEncoding with a file encoding. 303 * 304 * @param fileName the name of the file to write to, not null 305 * @param charsetEncoder the encoding to use, not null 306 * @param append true if content should be appended, false to overwrite 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()} 310 */ 311 @Deprecated 312 public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException { 313 this(new File(fileName), charsetEncoder, append); 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 charsetName the name of the requested charset, not null 321 * @throws NullPointerException if the file name or encoding is null 322 * @throws IOException in case of an I/O error 323 * @deprecated Use {@link #builder()} 324 */ 325 @Deprecated 326 public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException { 327 this(new File(fileName), charsetName, false); 328 } 329 330 /** 331 * Constructs a FileWriterWithEncoding with a file encoding. 332 * 333 * @param fileName the name of the file to write to, not null 334 * @param charsetName the name of the requested charset, not null 335 * @param append true if content should be appended, false to overwrite 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()} 339 */ 340 @Deprecated 341 public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException { 342 this(new File(fileName), charsetName, append); 343 } 344}