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.IOException; 021import java.io.OutputStream; 022import java.io.OutputStreamWriter; 023import java.io.Writer; 024import java.nio.charset.Charset; 025import java.nio.charset.CharsetEncoder; 026import java.nio.file.Files; 027import java.nio.file.StandardOpenOption; 028import java.util.Objects; 029 030import org.apache.commons.io.FileUtils; 031import org.apache.commons.io.IOUtils; 032 033/** 034 * Writer of files that allows the encoding to be set. 035 * <p> 036 * This class provides a simple alternative to {@code FileWriter} 037 * that allows an encoding to be set. Unfortunately, it cannot subclass 038 * {@code FileWriter}. 039 * </p> 040 * <p> 041 * By default, the file will be overwritten, but this may be changed to append. 042 * </p> 043 * <p> 044 * The encoding must be specified using either the name of the {@link Charset}, 045 * the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding 046 * is required then use the {@link java.io.FileWriter} directly, rather than 047 * this implementation. 048 * </p> 049 * 050 * @since 1.4 051 */ 052public class FileWriterWithEncoding extends Writer { 053 // Cannot extend ProxyWriter, as requires writer to be 054 // known when super() is called 055 056 /** The writer to decorate. */ 057 private final Writer out; 058 059 /** 060 * Constructs a FileWriterWithEncoding with a file encoding. 061 * 062 * @param fileName the name of the file to write to, not null 063 * @param charsetName the name of the requested charset, not null 064 * @throws NullPointerException if the file name or encoding is null 065 * @throws IOException in case of an I/O error 066 */ 067 public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException { 068 this(new File(fileName), charsetName, false); 069 } 070 071 /** 072 * Constructs a FileWriterWithEncoding with a file encoding. 073 * 074 * @param fileName the name of the file to write to, not null 075 * @param charsetName the name of the requested charset, not null 076 * @param append true if content should be appended, false to overwrite 077 * @throws NullPointerException if the file name or encoding is null 078 * @throws IOException in case of an I/O error 079 */ 080 public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) 081 throws IOException { 082 this(new File(fileName), charsetName, append); 083 } 084 085 /** 086 * Constructs a FileWriterWithEncoding with a file encoding. 087 * 088 * @param fileName the name of the file to write to, not null 089 * @param charset the charset to use, not null 090 * @throws NullPointerException if the file name or encoding is null 091 * @throws IOException in case of an I/O error 092 */ 093 public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException { 094 this(new File(fileName), charset, false); 095 } 096 097 /** 098 * Constructs a FileWriterWithEncoding with a file encoding. 099 * 100 * @param fileName the name of the file to write to, not null 101 * @param charset the encoding to use, not null 102 * @param append true if content should be appended, false to overwrite 103 * @throws NullPointerException if the file name or encoding is null 104 * @throws IOException in case of an I/O error 105 */ 106 public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) 107 throws IOException { 108 this(new File(fileName), charset, append); 109 } 110 111 /** 112 * Constructs a FileWriterWithEncoding with a file encoding. 113 * 114 * @param fileName the name of the file to write to, not null 115 * @param encoding the encoding to use, not null 116 * @throws NullPointerException if the file name or encoding is null 117 * @throws IOException in case of an I/O error 118 */ 119 public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException { 120 this(new File(fileName), encoding, false); 121 } 122 123 /** 124 * Constructs a FileWriterWithEncoding with a file encoding. 125 * 126 * @param fileName the name of the file to write to, not null 127 * @param charsetEncoder the encoding to use, not null 128 * @param append true if content should be appended, false to overwrite 129 * @throws NullPointerException if the file name or encoding is null 130 * @throws IOException in case of an I/O error 131 */ 132 public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) 133 throws IOException { 134 this(new File(fileName), charsetEncoder, append); 135 } 136 137 /** 138 * Constructs a FileWriterWithEncoding with a file encoding. 139 * 140 * @param file the file to write to, not null 141 * @param charsetName the name of the requested charset, not null 142 * @throws NullPointerException if the file or encoding is null 143 * @throws IOException in case of an I/O error 144 */ 145 public FileWriterWithEncoding(final File file, final String charsetName) throws IOException { 146 this(file, charsetName, false); 147 } 148 149 /** 150 * Constructs a FileWriterWithEncoding with a file encoding. 151 * 152 * @param file the file to write to, not null 153 * @param charsetName the name of the requested charset, not null 154 * @param append true if content should be appended, false to overwrite 155 * @throws NullPointerException if the file or encoding is null 156 * @throws IOException in case of an I/O error 157 */ 158 public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException { 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 this.out = initWriter(file, encoding, append); 185 } 186 187 /** 188 * Constructs a FileWriterWithEncoding with a file encoding. 189 * 190 * @param file the file to write to, not null 191 * @param charsetEncoder the encoding to use, not null 192 * @throws NullPointerException if the file or encoding is null 193 * @throws IOException in case of an I/O error 194 */ 195 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException { 196 this(file, charsetEncoder, false); 197 } 198 199 /** 200 * Constructs a FileWriterWithEncoding with a file encoding. 201 * 202 * @param file the file to write to, not null 203 * @param charsetEncoder the encoding to use, not null 204 * @param append true if content should be appended, false to overwrite 205 * @throws NullPointerException if the file or encoding is null 206 * @throws IOException in case of an I/O error 207 */ 208 public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) 209 throws IOException { 210 this.out = initWriter(file, charsetEncoder, append); 211 } 212 213 /** 214 * Initialize the wrapped file writer. 215 * Ensure that a cleanup occurs if the writer creation fails. 216 * 217 * @param file the file to be accessed 218 * @param encoding the encoding to use - may be Charset, CharsetEncoder or String 219 * @param append true to append 220 * @return the initialized writer 221 * @throws NullPointerException if the file or encoding is null 222 * @throws IOException if an error occurs 223 */ 224 private static Writer initWriter(final File file, final Object encoding, final boolean append) throws IOException { 225 Objects.requireNonNull(file, "file"); 226 Objects.requireNonNull(encoding, "encoding"); 227 OutputStream stream = null; 228 final boolean fileExistedAlready = file.exists(); 229 try { 230 stream = Files.newOutputStream(file.toPath(), append ? StandardOpenOption.APPEND : StandardOpenOption.CREATE); 231 if (encoding instanceof Charset) { 232 return new OutputStreamWriter(stream, (Charset)encoding); 233 } 234 if (encoding instanceof CharsetEncoder) { 235 return new OutputStreamWriter(stream, (CharsetEncoder)encoding); 236 } 237 return new OutputStreamWriter(stream, (String)encoding); 238 } catch (final IOException | RuntimeException ex) { 239 try { 240 IOUtils.close(stream); 241 } catch (final IOException e) { 242 ex.addSuppressed(e); 243 } 244 if (!fileExistedAlready) { 245 FileUtils.deleteQuietly(file); 246 } 247 throw ex; 248 } 249 } 250 251 /** 252 * Write a character. 253 * @param idx the character to write 254 * @throws IOException if an I/O error occurs. 255 */ 256 @Override 257 public void write(final int idx) throws IOException { 258 out.write(idx); 259 } 260 261 /** 262 * Write the characters from an array. 263 * @param chr the characters to write 264 * @throws IOException if an I/O error occurs. 265 */ 266 @Override 267 public void write(final char[] chr) throws IOException { 268 out.write(chr); 269 } 270 271 /** 272 * Write the specified characters from an array. 273 * @param chr the characters to write 274 * @param st The start offset 275 * @param end The number of characters to write 276 * @throws IOException if an I/O error occurs. 277 */ 278 @Override 279 public void write(final char[] chr, final int st, final int end) throws IOException { 280 out.write(chr, st, end); 281 } 282 283 /** 284 * Write the characters from a string. 285 * @param str the string to write 286 * @throws IOException if an I/O error occurs. 287 */ 288 @Override 289 public void write(final String str) throws IOException { 290 out.write(str); 291 } 292 293 /** 294 * Write the specified characters from a string. 295 * @param str the string to write 296 * @param st The start offset 297 * @param end The number of characters to write 298 * @throws IOException if an I/O error occurs. 299 */ 300 @Override 301 public void write(final String str, final int st, final int end) throws IOException { 302 out.write(str, st, end); 303 } 304 305 /** 306 * Flush the stream. 307 * @throws IOException if an I/O error occurs. 308 */ 309 @Override 310 public void flush() throws IOException { 311 out.flush(); 312 } 313 314 /** 315 * Close the stream. 316 * @throws IOException if an I/O error occurs. 317 */ 318 @Override 319 public void close() throws IOException { 320 out.close(); 321 } 322}