GNU Classpath (0.96.1) | |
Frames | No Frames |
1: /* OutputStreamWriter.java -- Writer that converts chars to bytes 2: Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package java.io; 40: 41: import gnu.java.nio.charset.EncodingHelper; 42: 43: import java.nio.ByteBuffer; 44: import java.nio.CharBuffer; 45: import java.nio.charset.CharacterCodingException; 46: import java.nio.charset.Charset; 47: import java.nio.charset.CharsetEncoder; 48: import java.nio.charset.CodingErrorAction; 49: import java.nio.charset.MalformedInputException; 50: 51: /** 52: * This class writes characters to an output stream that is byte oriented 53: * It converts the chars that are written to bytes using an encoding layer, 54: * which is specific to a particular encoding standard. The desired 55: * encoding can either be specified by name, or if no encoding is specified, 56: * the system default encoding will be used. The system default encoding 57: * name is determined from the system property <code>file.encoding</code>. 58: * The only encodings that are guaranteed to be available are "8859_1" 59: * (the Latin-1 character set) and "UTF8". Unfortunately, Java does not 60: * provide a mechanism for listing the encodings that are supported in 61: * a given implementation. 62: * <p> 63: * Here is a list of standard encoding names that may be available: 64: * <p> 65: * <ul> 66: * <li>8859_1 (ISO-8859-1/Latin-1) 67: * <li>8859_2 (ISO-8859-2/Latin-2) 68: * <li>8859_3 (ISO-8859-3/Latin-3) 69: * <li>8859_4 (ISO-8859-4/Latin-4) 70: * <li>8859_5 (ISO-8859-5/Latin-5) 71: * <li>8859_6 (ISO-8859-6/Latin-6) 72: * <li>8859_7 (ISO-8859-7/Latin-7) 73: * <li>8859_8 (ISO-8859-8/Latin-8) 74: * <li>8859_9 (ISO-8859-9/Latin-9) 75: * <li>ASCII (7-bit ASCII) 76: * <li>UTF8 (UCS Transformation Format-8) 77: * <li>More Later 78: * </ul> 79: * 80: * @author Aaron M. Renn (arenn@urbanophile.com) 81: * @author Per Bothner (bothner@cygnus.com) 82: * @date April 17, 1998. 83: */ 84: public class OutputStreamWriter extends Writer 85: { 86: /** 87: * The output stream. 88: */ 89: private OutputStream out; 90: 91: /** 92: * The charset encoder. 93: */ 94: private CharsetEncoder encoder; 95: 96: /** 97: * java.io canonical name of the encoding. 98: */ 99: private String encodingName; 100: 101: /** 102: * Buffer output before character conversion as it has costly overhead. 103: */ 104: private 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: this.out = out; 124: outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 125: 126: try 127: { 128: // Don't use NIO if avoidable 129: if(EncodingHelper.isISOLatin1(encoding_scheme)) 130: { 131: encodingName = "ISO8859_1"; 132: encoder = null; 133: return; 134: } 135: 136: /* 137: * Workaround for encodings with a byte-order-mark. 138: * We only want to write it once per stream. 139: */ 140: try 141: { 142: if(encoding_scheme.equalsIgnoreCase("UnicodeBig") || 143: encoding_scheme.equalsIgnoreCase("UTF-16") || 144: encoding_scheme.equalsIgnoreCase("UTF16")) 145: { 146: encoding_scheme = "UTF-16BE"; 147: out.write((byte)0xFE); 148: out.write((byte)0xFF); 149: } 150: else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle")){ 151: encoding_scheme = "UTF-16LE"; 152: out.write((byte)0xFF); 153: out.write((byte)0xFE); 154: } 155: } 156: catch(IOException ioe) 157: { 158: } 159: 160: Charset cs = EncodingHelper.getCharset(encoding_scheme); 161: if(cs == null) 162: throw new UnsupportedEncodingException("Encoding "+encoding_scheme+ 163: " unknown"); 164: encoder = cs.newEncoder(); 165: encodingName = EncodingHelper.getOldCanonical(cs.name()); 166: 167: encoder.onMalformedInput(CodingErrorAction.REPLACE); 168: encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 169: } 170: catch(RuntimeException e) 171: { 172: // Default to ISO Latin-1, will happen if this is called, for instance, 173: // before the NIO provider is loadable. 174: encoder = null; 175: encodingName = "ISO8859_1"; 176: } 177: } 178: 179: /** 180: * This method initializes a new instance of <code>OutputStreamWriter</code> 181: * to write to the specified stream using the default encoding. 182: * 183: * @param out The <code>OutputStream</code> to write to 184: */ 185: public OutputStreamWriter (OutputStream out) 186: { 187: this.out = out; 188: outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 189: try 190: { 191: String encoding = System.getProperty("file.encoding"); 192: Charset cs = Charset.forName(encoding); 193: encoder = cs.newEncoder(); 194: encodingName = EncodingHelper.getOldCanonical(cs.name()); 195: } 196: catch(RuntimeException e) 197: { 198: encoder = null; 199: encodingName = "ISO8859_1"; 200: } 201: 202: if(encoder != null) 203: { 204: encoder.onMalformedInput(CodingErrorAction.REPLACE); 205: encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 206: } 207: } 208: 209: /** 210: * This method initializes a new instance of <code>OutputStreamWriter</code> 211: * to write to the specified stream using a given <code>Charset</code>. 212: * 213: * @param out The <code>OutputStream</code> to write to 214: * @param cs The <code>Charset</code> of the encoding to use 215: * 216: * @since 1.5 217: */ 218: public OutputStreamWriter(OutputStream out, Charset cs) 219: { 220: this.out = out; 221: encoder = cs.newEncoder(); 222: encoder.onMalformedInput(CodingErrorAction.REPLACE); 223: encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); 224: outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 225: encodingName = EncodingHelper.getOldCanonical(cs.name()); 226: } 227: 228: /** 229: * This method initializes a new instance of <code>OutputStreamWriter</code> 230: * to write to the specified stream using a given 231: * <code>CharsetEncoder</code>. 232: * 233: * @param out The <code>OutputStream</code> to write to 234: * @param enc The <code>CharsetEncoder</code> to encode the output with 235: * 236: * @since 1.5 237: */ 238: public OutputStreamWriter(OutputStream out, CharsetEncoder enc) 239: { 240: this.out = out; 241: encoder = enc; 242: outputBuffer = CharBuffer.allocate(BUFFER_SIZE); 243: Charset cs = enc.charset(); 244: if (cs == null) 245: encodingName = "US-ASCII"; 246: else 247: encodingName = EncodingHelper.getOldCanonical(cs.name()); 248: } 249: 250: /** 251: * This method closes this stream, and the underlying 252: * <code>OutputStream</code> 253: * 254: * @exception IOException If an error occurs 255: */ 256: public void close () throws IOException 257: { 258: if(out == null) 259: return; 260: flush(); 261: out.close (); 262: out = null; 263: } 264: 265: /** 266: * This method returns the name of the character encoding scheme currently 267: * in use by this stream. If the stream has been closed, then this method 268: * may return <code>null</code>. 269: * 270: * @return The encoding scheme name 271: */ 272: public String getEncoding () 273: { 274: return out != null ? encodingName : null; 275: } 276: 277: /** 278: * This method flushes any buffered bytes to the underlying output sink. 279: * 280: * @exception IOException If an error occurs 281: */ 282: public void flush () throws IOException 283: { 284: if(out != null){ 285: if(outputBuffer != null){ 286: char[] buf = new char[outputBuffer.position()]; 287: if(buf.length > 0){ 288: outputBuffer.flip(); 289: outputBuffer.get(buf); 290: writeConvert(buf, 0, buf.length); 291: outputBuffer.clear(); 292: } 293: } 294: out.flush (); 295: } 296: } 297: 298: /** 299: * This method writes <code>count</code> characters from the specified 300: * array to the output stream starting at position <code>offset</code> 301: * into the array. 302: * 303: * @param buf The array of character to write from 304: * @param offset The offset into the array to start writing chars from 305: * @param count The number of chars to write. 306: * 307: * @exception IOException If an error occurs 308: */ 309: public void write (char[] buf, int offset, int count) throws IOException 310: { 311: if(out == null) 312: throw new IOException("Stream is closed."); 313: if(buf == null) 314: throw new IOException("Buffer is null."); 315: 316: if(outputBuffer != null) 317: { 318: if(count >= outputBuffer.remaining()) 319: { 320: int r = outputBuffer.remaining(); 321: outputBuffer.put(buf, offset, r); 322: writeConvert(outputBuffer.array(), 0, BUFFER_SIZE); 323: outputBuffer.clear(); 324: offset += r; 325: count -= r; 326: // if the remaining bytes is larger than the whole buffer, 327: // just don't buffer. 328: if(count >= outputBuffer.remaining()){ 329: writeConvert(buf, offset, count); 330: return; 331: } 332: } 333: outputBuffer.put(buf, offset, count); 334: } else writeConvert(buf, offset, count); 335: } 336: 337: /** 338: * Converts and writes characters. 339: */ 340: private void writeConvert (char[] buf, int offset, int count) 341: throws IOException 342: { 343: if(encoder == null) 344: { 345: byte[] b = new byte[count]; 346: for(int i=0;i<count;i++) 347: b[i] = nullConversion(buf[offset+i]); 348: out.write(b); 349: } else { 350: try { 351: ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count)); 352: encoder.reset(); 353: if(output.hasArray()) 354: out.write(output.array()); 355: else 356: { 357: byte[] outbytes = new byte[output.remaining()]; 358: output.get(outbytes); 359: out.write(outbytes); 360: } 361: } catch(IllegalStateException e) { 362: throw new IOException("Internal error."); 363: } catch(MalformedInputException e) { 364: throw new IOException("Invalid character sequence."); 365: } catch(CharacterCodingException e) { 366: throw new IOException("Unmappable character."); 367: } 368: } 369: } 370: 371: private byte nullConversion(char c) { 372: return (byte)((c <= 0xFF)?c:'?'); 373: } 374: 375: /** 376: * This method writes <code>count</code> bytes from the specified 377: * <code>String</code> starting at position <code>offset</code> into the 378: * <code>String</code>. 379: * 380: * @param str The <code>String</code> to write chars from 381: * @param offset The position in the <code>String</code> to start 382: * writing chars from 383: * @param count The number of chars to write 384: * 385: * @exception IOException If an error occurs 386: */ 387: public void write (String str, int offset, int count) throws IOException 388: { 389: if(str == null) 390: throw new IOException("String is null."); 391: 392: write(str.toCharArray(), offset, count); 393: } 394: 395: /** 396: * This method writes a single character to the output stream. 397: * 398: * @param ch The char to write, passed as an int. 399: * 400: * @exception IOException If an error occurs 401: */ 402: public void write (int ch) throws IOException 403: { 404: // No buffering, no encoding ... just pass through 405: if (encoder == null && outputBuffer == null) { 406: out.write(nullConversion((char)ch)); 407: } else { 408: if (outputBuffer != null) { 409: if (outputBuffer.remaining() == 0) { 410: writeConvert(outputBuffer.array(), 0, BUFFER_SIZE); 411: outputBuffer.clear(); 412: } 413: outputBuffer.put((char)ch); 414: } else { 415: writeConvert(new char[]{ (char)ch }, 0, 1); 416: } 417: } 418: } 419: } // class OutputStreamWriter
GNU Classpath (0.96.1) |