GNU Classpath (0.98) | |
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 final CharsetEncoder encoder; 95: 96: /** 97: * java.io canonical name of the encoding. 98: */ 99: 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
GNU Classpath (0.98) |