001/* OutputStreamWriter.java -- Writer that converts chars to bytes 002 Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package java.io; 040 041import gnu.java.nio.charset.EncodingHelper; 042 043import java.nio.ByteBuffer; 044import java.nio.CharBuffer; 045import java.nio.charset.CharacterCodingException; 046import java.nio.charset.Charset; 047import java.nio.charset.CharsetEncoder; 048import java.nio.charset.CodingErrorAction; 049import java.nio.charset.MalformedInputException; 050 051/** 052 * This class writes characters to an output stream that is byte oriented 053 * It converts the chars that are written to bytes using an encoding layer, 054 * which is specific to a particular encoding standard. The desired 055 * encoding can either be specified by name, or if no encoding is specified, 056 * the system default encoding will be used. The system default encoding 057 * name is determined from the system property <code>file.encoding</code>. 058 * The only encodings that are guaranteed to be available are "8859_1" 059 * (the Latin-1 character set) and "UTF8". Unfortunately, Java does not 060 * provide a mechanism for listing the encodings that are supported in 061 * a given implementation. 062 * <p> 063 * Here is a list of standard encoding names that may be available: 064 * <p> 065 * <ul> 066 * <li>8859_1 (ISO-8859-1/Latin-1) 067 * <li>8859_2 (ISO-8859-2/Latin-2) 068 * <li>8859_3 (ISO-8859-3/Latin-3) 069 * <li>8859_4 (ISO-8859-4/Latin-4) 070 * <li>8859_5 (ISO-8859-5/Latin-5) 071 * <li>8859_6 (ISO-8859-6/Latin-6) 072 * <li>8859_7 (ISO-8859-7/Latin-7) 073 * <li>8859_8 (ISO-8859-8/Latin-8) 074 * <li>8859_9 (ISO-8859-9/Latin-9) 075 * <li>ASCII (7-bit ASCII) 076 * <li>UTF8 (UCS Transformation Format-8) 077 * <li>More Later 078 * </ul> 079 * 080 * @author Aaron M. Renn (arenn@urbanophile.com) 081 * @author Per Bothner (bothner@cygnus.com) 082 * @date April 17, 1998. 083 */ 084public class OutputStreamWriter extends Writer 085{ 086 /** 087 * The output stream. 088 */ 089 private OutputStream out; 090 091 /** 092 * The charset encoder. 093 */ 094 private final CharsetEncoder encoder; 095 096 /** 097 * java.io canonical name of the encoding. 098 */ 099 private final String encodingName; 100 101 /** 102 * Buffer output before character conversion as it has costly overhead. 103 */ 104 private final CharBuffer outputBuffer; 105 private final static int BUFFER_SIZE = 1024; 106 107 /** 108 * This method initializes a new instance of <code>OutputStreamWriter</code> 109 * to write to the specified stream using a caller supplied character 110 * encoding scheme. Note that due to a deficiency in the Java language 111 * design, there is no way to determine which encodings are supported. 112 * 113 * @param out The <code>OutputStream</code> to write to 114 * @param encoding_scheme The name of the encoding scheme to use for 115 * character to byte translation 116 * 117 * @exception UnsupportedEncodingException If the named encoding is 118 * not available. 119 */ 120 public OutputStreamWriter (OutputStream out, String encoding_scheme) 121 throws UnsupportedEncodingException 122 { 123 CharsetEncoder encoder; 124 String encodingName; 125 this.out = out; 126 outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 127 128 try 129 { 130 // Don't use NIO if avoidable 131 if(EncodingHelper.isISOLatin1(encoding_scheme)) 132 { 133 encodingName = "ISO8859_1"; 134 encoder = null; 135 } 136 else 137 { 138 /* 139 * Workaround for encodings with a byte-order-mark. 140 * We only want to write it once per stream. 141 */ 142 try 143 { 144 if(encoding_scheme.equalsIgnoreCase("UnicodeBig") || 145 encoding_scheme.equalsIgnoreCase("UTF-16") || 146 encoding_scheme.equalsIgnoreCase("UTF16")) 147 { 148 encoding_scheme = "UTF-16BE"; 149 out.write((byte)0xFE); 150 out.write((byte)0xFF); 151 } 152 else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle")) 153 { 154 encoding_scheme = "UTF-16LE"; 155 out.write((byte)0xFF); 156 out.write((byte)0xFE); 157 } 158 } 159 catch(IOException ioe) 160 { 161 } 162 163 Charset cs = EncodingHelper.getCharset(encoding_scheme); 164 if(cs == null) 165 throw new UnsupportedEncodingException("Encoding "+encoding_scheme+ 166 " unknown"); 167 encoder = cs.newEncoder(); 168 encodingName = EncodingHelper.getOldCanonical(cs.name()); 169 170 encoder.onMalformedInput(CodingErrorAction.REPLACE); 171 encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 172 } 173 } 174 catch(RuntimeException e) 175 { 176 // Default to ISO Latin-1, will happen if this is called, for instance, 177 // before the NIO provider is loadable. 178 encoder = null; 179 encodingName = "ISO8859_1"; 180 } 181 this.encoder = encoder; 182 this.encodingName = encodingName; 183 } 184 185 /** 186 * This method initializes a new instance of <code>OutputStreamWriter</code> 187 * to write to the specified stream using the default encoding. 188 * 189 * @param out The <code>OutputStream</code> to write to 190 */ 191 public OutputStreamWriter (OutputStream out) 192 { 193 CharsetEncoder encoder; 194 String encodingName; 195 this.out = out; 196 outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 197 try 198 { 199 String encoding = System.getProperty("file.encoding"); 200 Charset cs = Charset.forName(encoding); 201 encoder = cs.newEncoder(); 202 encodingName = EncodingHelper.getOldCanonical(cs.name()); 203 } 204 catch(RuntimeException e) 205 { 206 encoder = null; 207 encodingName = "ISO8859_1"; 208 } 209 210 if(encoder != null) 211 { 212 encoder.onMalformedInput(CodingErrorAction.REPLACE); 213 encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 214 } 215 this.encoder = encoder; 216 this.encodingName = encodingName; 217 } 218 219 /** 220 * This method initializes a new instance of <code>OutputStreamWriter</code> 221 * to write to the specified stream using a given <code>Charset</code>. 222 * 223 * @param out The <code>OutputStream</code> to write to 224 * @param cs The <code>Charset</code> of the encoding to use 225 * 226 * @since 1.5 227 */ 228 public OutputStreamWriter(OutputStream out, Charset cs) 229 { 230 this.out = out; 231 encoder = cs.newEncoder(); 232 encoder.onMalformedInput(CodingErrorAction.REPLACE); 233 encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 234 outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 235 encodingName = EncodingHelper.getOldCanonical(cs.name()); 236 } 237 238 /** 239 * This method initializes a new instance of <code>OutputStreamWriter</code> 240 * to write to the specified stream using a given 241 * <code>CharsetEncoder</code>. 242 * 243 * @param out The <code>OutputStream</code> to write to 244 * @param enc The <code>CharsetEncoder</code> to encode the output with 245 * 246 * @since 1.5 247 */ 248 public OutputStreamWriter(OutputStream out, CharsetEncoder enc) 249 { 250 this.out = out; 251 encoder = enc; 252 outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 253 Charset cs = enc.charset(); 254 if (cs == null) 255 encodingName = "US-ASCII"; 256 else 257 encodingName = EncodingHelper.getOldCanonical(cs.name()); 258 } 259 260 /** 261 * This method closes this stream, and the underlying 262 * <code>OutputStream</code> 263 * 264 * @exception IOException If an error occurs 265 */ 266 public void close () throws IOException 267 { 268 if(out == null) 269 return; 270 flush(); 271 out.close (); 272 out = null; 273 } 274 275 /** 276 * This method returns the name of the character encoding scheme currently 277 * in use by this stream. If the stream has been closed, then this method 278 * may return <code>null</code>. 279 * 280 * @return The encoding scheme name 281 */ 282 public String getEncoding () 283 { 284 return out != null ? encodingName : null; 285 } 286 287 /** 288 * This method flushes any buffered bytes to the underlying output sink. 289 * 290 * @exception IOException If an error occurs 291 */ 292 public void flush () throws IOException 293 { 294 if(out != null){ 295 if(outputBuffer != null){ 296 char[] buf = new char[outputBuffer.position()]; 297 if(buf.length > 0){ 298 outputBuffer.flip(); 299 outputBuffer.get(buf); 300 writeConvert(buf, 0, buf.length); 301 outputBuffer.clear(); 302 } 303 } 304 out.flush (); 305 } 306 } 307 308 /** 309 * This method writes <code>count</code> characters from the specified 310 * array to the output stream starting at position <code>offset</code> 311 * into the array. 312 * 313 * @param buf The array of character to write from 314 * @param offset The offset into the array to start writing chars from 315 * @param count The number of chars to write. 316 * 317 * @exception IOException If an error occurs 318 */ 319 public void write (char[] buf, int offset, int count) throws IOException 320 { 321 if(out == null) 322 throw new IOException("Stream is closed."); 323 if(buf == null) 324 throw new IOException("Buffer is null."); 325 326 if(outputBuffer != null) 327 { 328 if(count >= outputBuffer.remaining()) 329 { 330 int r = outputBuffer.remaining(); 331 outputBuffer.put(buf, offset, r); 332 writeConvert(outputBuffer.array(), 0, BUFFER_SIZE); 333 outputBuffer.clear(); 334 offset += r; 335 count -= r; 336 // if the remaining bytes is larger than the whole buffer, 337 // just don't buffer. 338 if(count >= outputBuffer.remaining()){ 339 writeConvert(buf, offset, count); 340 return; 341 } 342 } 343 outputBuffer.put(buf, offset, count); 344 } else writeConvert(buf, offset, count); 345 } 346 347 /** 348 * Converts and writes characters. 349 */ 350 private void writeConvert (char[] buf, int offset, int count) 351 throws IOException 352 { 353 if(encoder == null) 354 { 355 byte[] b = new byte[count]; 356 for(int i=0;i<count;i++) 357 b[i] = nullConversion(buf[offset+i]); 358 out.write(b); 359 } else { 360 try { 361 ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count)); 362 encoder.reset(); 363 if(output.hasArray()) 364 out.write(output.array()); 365 else 366 { 367 byte[] outbytes = new byte[output.remaining()]; 368 output.get(outbytes); 369 out.write(outbytes); 370 } 371 } catch(IllegalStateException e) { 372 throw new IOException("Internal error."); 373 } catch(MalformedInputException e) { 374 throw new IOException("Invalid character sequence."); 375 } catch(CharacterCodingException e) { 376 throw new IOException("Unmappable character."); 377 } 378 } 379 } 380 381 private byte nullConversion(char c) { 382 return (byte)((c <= 0xFF)?c:'?'); 383 } 384 385 /** 386 * This method writes <code>count</code> bytes from the specified 387 * <code>String</code> starting at position <code>offset</code> into the 388 * <code>String</code>. 389 * 390 * @param str The <code>String</code> to write chars from 391 * @param offset The position in the <code>String</code> to start 392 * writing chars from 393 * @param count The number of chars to write 394 * 395 * @exception IOException If an error occurs 396 */ 397 public void write (String str, int offset, int count) throws IOException 398 { 399 if(str == null) 400 throw new IOException("String is null."); 401 402 write(str.toCharArray(), offset, count); 403 } 404 405 /** 406 * This method writes a single character to the output stream. 407 * 408 * @param ch The char to write, passed as an int. 409 * 410 * @exception IOException If an error occurs 411 */ 412 public void write (int ch) throws IOException 413 { 414 // No buffering, no encoding ... just pass through 415 if (encoder == null && outputBuffer == null) { 416 out.write(nullConversion((char)ch)); 417 } else { 418 if (outputBuffer != null) { 419 if (outputBuffer.remaining() == 0) { 420 writeConvert(outputBuffer.array(), 0, BUFFER_SIZE); 421 outputBuffer.clear(); 422 } 423 outputBuffer.put((char)ch); 424 } else { 425 writeConvert(new char[]{ (char)ch }, 0, 1); 426 } 427 } 428 } 429} // class OutputStreamWriter