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 18 package java.io; 19 20 import java.nio.ByteBuffer; 21 import java.nio.CharBuffer; 22 import java.nio.charset.Charset; 23 import java.nio.charset.CharsetEncoder; 24 import java.nio.charset.CoderResult; 25 import java.nio.charset.CodingErrorAction; 26 import java.util.Arrays; 27 28 /** 29 * A class for turning a character stream into a byte stream. Data written to 30 * the target input stream is converted into bytes by either a default or a 31 * provided character converter. The default encoding is taken from the 32 * "file.encoding" system property. {@code OutputStreamWriter} contains a buffer 33 * of bytes to be written to target stream and converts these into characters as 34 * needed. The buffer size is 8K. 35 * 36 * @see InputStreamReader 37 */ 38 public class OutputStreamWriter extends Writer { 39 40 private final OutputStream out; 41 42 private CharsetEncoder encoder; 43 44 private ByteBuffer bytes = ByteBuffer.allocate(8192); 45 46 /** 47 * Constructs a new OutputStreamWriter using {@code out} as the target 48 * stream to write converted characters to. The default character encoding 49 * is used. 50 * 51 * @param out 52 * the non-null target stream to write converted bytes to. 53 */ OutputStreamWriter(OutputStream out)54 public OutputStreamWriter(OutputStream out) { 55 this(out, Charset.defaultCharset()); 56 } 57 58 /** 59 * Constructs a new OutputStreamWriter using {@code out} as the target 60 * stream to write converted characters to and {@code enc} as the character 61 * encoding. If the encoding cannot be found, an 62 * UnsupportedEncodingException error is thrown. 63 * 64 * @param out 65 * the target stream to write converted bytes to. 66 * @param enc 67 * the string describing the desired character encoding. 68 * @throws NullPointerException 69 * if {@code enc} is {@code null}. 70 * @throws UnsupportedEncodingException 71 * if the encoding specified by {@code enc} cannot be found. 72 */ OutputStreamWriter(OutputStream out, final String enc)73 public OutputStreamWriter(OutputStream out, final String enc) 74 throws UnsupportedEncodingException { 75 super(out); 76 if (enc == null) { 77 throw new NullPointerException(); 78 } 79 this.out = out; 80 try { 81 encoder = Charset.forName(enc).newEncoder(); 82 } catch (Exception e) { 83 throw new UnsupportedEncodingException(enc); 84 } 85 encoder.onMalformedInput(CodingErrorAction.REPLACE); 86 encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 87 } 88 89 /** 90 * Constructs a new OutputStreamWriter using {@code out} as the target 91 * stream to write converted characters to and {@code cs} as the character 92 * encoding. 93 * 94 * @param out 95 * the target stream to write converted bytes to. 96 * @param cs 97 * the {@code Charset} that specifies the character encoding. 98 */ OutputStreamWriter(OutputStream out, Charset cs)99 public OutputStreamWriter(OutputStream out, Charset cs) { 100 super(out); 101 this.out = out; 102 encoder = cs.newEncoder(); 103 encoder.onMalformedInput(CodingErrorAction.REPLACE); 104 encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 105 } 106 107 /** 108 * Constructs a new OutputStreamWriter using {@code out} as the target 109 * stream to write converted characters to and {@code enc} as the character 110 * encoder. 111 * 112 * @param out 113 * the target stream to write converted bytes to. 114 * @param enc 115 * the character encoder used for character conversion. 116 */ OutputStreamWriter(OutputStream out, CharsetEncoder enc)117 public OutputStreamWriter(OutputStream out, CharsetEncoder enc) { 118 super(out); 119 enc.charset(); 120 this.out = out; 121 encoder = enc; 122 } 123 124 /** 125 * Closes this writer. This implementation flushes the buffer as well as the 126 * target stream. The target stream is then closed and the resources for the 127 * buffer and converter are released. 128 * 129 * <p>Only the first invocation of this method has any effect. Subsequent calls 130 * do nothing. 131 * 132 * @throws IOException 133 * if an error occurs while closing this writer. 134 */ 135 @Override close()136 public void close() throws IOException { 137 synchronized (lock) { 138 if (encoder != null) { 139 drainEncoder(); 140 flushBytes(false); 141 out.close(); 142 encoder = null; 143 bytes = null; 144 } 145 } 146 } 147 148 /** 149 * Flushes this writer. This implementation ensures that all buffered bytes 150 * are written to the target stream. After writing the bytes, the target 151 * stream is flushed as well. 152 * 153 * @throws IOException 154 * if an error occurs while flushing this writer. 155 */ 156 @Override flush()157 public void flush() throws IOException { 158 flushBytes(true); 159 } 160 flushBytes(boolean flushUnderlyingStream)161 private void flushBytes(boolean flushUnderlyingStream) throws IOException { 162 synchronized (lock) { 163 checkStatus(); 164 int position = bytes.position(); 165 if (position > 0) { 166 bytes.flip(); 167 out.write(bytes.array(), bytes.arrayOffset(), position); 168 bytes.clear(); 169 } 170 if (flushUnderlyingStream) { 171 out.flush(); 172 } 173 } 174 } 175 convert(CharBuffer chars)176 private void convert(CharBuffer chars) throws IOException { 177 while (true) { 178 CoderResult result = encoder.encode(chars, bytes, false); 179 if (result.isOverflow()) { 180 // Make room and try again. 181 flushBytes(false); 182 continue; 183 } else if (result.isError()) { 184 result.throwException(); 185 } 186 break; 187 } 188 } 189 drainEncoder()190 private void drainEncoder() throws IOException { 191 // Strictly speaking, I think it's part of the CharsetEncoder contract that you call 192 // encode with endOfInput true before flushing. Our ICU-based implementations don't 193 // actually need this, and you'd hope that any reasonable implementation wouldn't either. 194 // CharsetEncoder.encode doesn't actually pass the boolean through to encodeLoop anyway! 195 CharBuffer chars = CharBuffer.allocate(0); 196 while (true) { 197 CoderResult result = encoder.encode(chars, bytes, true); 198 if (result.isError()) { 199 result.throwException(); 200 } else if (result.isOverflow()) { 201 flushBytes(false); 202 continue; 203 } 204 break; 205 } 206 207 // Some encoders (such as ISO-2022-JP) have stuff to write out after all the 208 // characters (such as shifting back into a default state). In our implementation, 209 // this is actually the first time ICU is told that we've run out of input. 210 CoderResult result = encoder.flush(bytes); 211 while (!result.isUnderflow()) { 212 if (result.isOverflow()) { 213 flushBytes(false); 214 result = encoder.flush(bytes); 215 } else { 216 result.throwException(); 217 } 218 } 219 } 220 checkStatus()221 private void checkStatus() throws IOException { 222 if (encoder == null) { 223 throw new IOException("OutputStreamWriter is closed"); 224 } 225 } 226 227 /** 228 * Returns the historical name of the encoding used by this writer to convert characters to 229 * bytes, or null if this writer has been closed. Most callers should probably keep 230 * track of the String or Charset they passed in; this method may not return the same 231 * name. 232 */ getEncoding()233 public String getEncoding() { 234 if (encoder == null) { 235 return null; 236 } 237 return HistoricalCharsetNames.get(encoder.charset()); 238 } 239 240 /** 241 * Writes {@code count} characters starting at {@code offset} in {@code buf} 242 * to this writer. The characters are immediately converted to bytes by the 243 * character converter and stored in a local buffer. If the buffer gets full 244 * as a result of the conversion, this writer is flushed. 245 * 246 * @param buffer 247 * the array containing characters to write. 248 * @param offset 249 * the index of the first character in {@code buf} to write. 250 * @param count 251 * the maximum number of characters to write. 252 * @throws IndexOutOfBoundsException 253 * if {@code offset < 0} or {@code count < 0}, or if 254 * {@code offset + count} is greater than the size of 255 * {@code buf}. 256 * @throws IOException 257 * if this writer has already been closed or another I/O error 258 * occurs. 259 */ 260 @Override write(char[] buffer, int offset, int count)261 public void write(char[] buffer, int offset, int count) throws IOException { 262 synchronized (lock) { 263 checkStatus(); 264 Arrays.checkOffsetAndCount(buffer.length, offset, count); 265 CharBuffer chars = CharBuffer.wrap(buffer, offset, count); 266 convert(chars); 267 } 268 } 269 270 /** 271 * Writes the character {@code oneChar} to this writer. The lowest two bytes 272 * of the integer {@code oneChar} are immediately converted to bytes by the 273 * character converter and stored in a local buffer. If the buffer gets full 274 * by converting this character, this writer is flushed. 275 * 276 * @param oneChar 277 * the character to write. 278 * @throws IOException 279 * if this writer is closed or another I/O error occurs. 280 */ 281 @Override write(int oneChar)282 public void write(int oneChar) throws IOException { 283 synchronized (lock) { 284 checkStatus(); 285 CharBuffer chars = CharBuffer.wrap(new char[] { (char) oneChar }); 286 convert(chars); 287 } 288 } 289 290 /** 291 * Writes {@code count} characters starting at {@code offset} in {@code str} 292 * to this writer. The characters are immediately converted to bytes by the 293 * character converter and stored in a local buffer. If the buffer gets full 294 * as a result of the conversion, this writer is flushed. 295 * 296 * @param str 297 * the string containing characters to write. 298 * @param offset 299 * the start position in {@code str} for retrieving characters. 300 * @param count 301 * the maximum number of characters to write. 302 * @throws IOException 303 * if this writer has already been closed or another I/O error 304 * occurs. 305 * @throws IndexOutOfBoundsException 306 * if {@code offset < 0} or {@code count < 0}, or if 307 * {@code offset + count} is bigger than the length of 308 * {@code str}. 309 */ 310 @Override write(String str, int offset, int count)311 public void write(String str, int offset, int count) throws IOException { 312 synchronized (lock) { 313 if (count < 0) { 314 throw new StringIndexOutOfBoundsException(str, offset, count); 315 } 316 if (str == null) { 317 throw new NullPointerException("str == null"); 318 } 319 if ((offset | count) < 0 || offset > str.length() - count) { 320 throw new StringIndexOutOfBoundsException(str, offset, count); 321 } 322 checkStatus(); 323 CharBuffer chars = CharBuffer.wrap(str, offset, count + offset); 324 convert(chars); 325 } 326 } 327 checkError()328 @Override boolean checkError() { 329 return out.checkError(); 330 } 331 } 332