Source for java.io.OutputStreamWriter

   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