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:     try 
 125:       {
 126:     // Don't use NIO if avoidable
 127:     if(EncodingHelper.isISOLatin1(encoding_scheme))
 128:       {
 129:         encodingName = "ISO8859_1";
 130:         encoder = null;
 131:         return;
 132:       }
 133: 
 134:     /*
 135:      * Workraround for encodings with a byte-order-mark.
 136:      * We only want to write it once per stream.
 137:      */
 138:     try 
 139:       {
 140:         if(encoding_scheme.equalsIgnoreCase("UnicodeBig") || 
 141:            encoding_scheme.equalsIgnoreCase("UTF-16") ||
 142:            encoding_scheme.equalsIgnoreCase("UTF16"))
 143:           {
 144:         encoding_scheme = "UTF-16BE";      
 145:         out.write((byte)0xFE);
 146:         out.write((byte)0xFF);
 147:           } 
 148:         else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle")){
 149:           encoding_scheme = "UTF-16LE";
 150:           out.write((byte)0xFF);
 151:           out.write((byte)0xFE);
 152:         }
 153:       }
 154:     catch(IOException ioe)
 155:       {
 156:       }
 157:       
 158:     outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
 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 = null;
 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:     outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
 207:       }
 208:   }
 209: 
 210:   /**
 211:    * This method initializes a new instance of <code>OutputStreamWriter</code>
 212:    * to write to the specified stream using a given <code>Charset</code>.
 213:    *
 214:    * @param out The <code>OutputStream</code> to write to
 215:    * @param cs The <code>Charset</code> of the encoding to use
 216:    * 
 217:    * @since 1.5
 218:    */
 219:   public OutputStreamWriter(OutputStream out, Charset cs)
 220:   {
 221:     this.out = out;
 222:     encoder = cs.newEncoder();
 223:     encoder.onMalformedInput(CodingErrorAction.REPLACE);
 224:     encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
 225:     outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
 226:     encodingName = EncodingHelper.getOldCanonical(cs.name());
 227:   }
 228:   
 229:   /**
 230:    * This method initializes a new instance of <code>OutputStreamWriter</code>
 231:    * to write to the specified stream using a given
 232:    * <code>CharsetEncoder</code>.
 233:    *
 234:    * @param out The <code>OutputStream</code> to write to
 235:    * @param enc The <code>CharsetEncoder</code> to encode the output with
 236:    * 
 237:    * @since 1.5
 238:    */
 239:   public OutputStreamWriter(OutputStream out, CharsetEncoder enc)
 240:   {
 241:     this.out = out;
 242:     encoder = enc;
 243:     outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
 244:     Charset cs = enc.charset();
 245:     if (cs == null)
 246:       encodingName = "US-ASCII";
 247:     else
 248:       encodingName = EncodingHelper.getOldCanonical(cs.name());
 249:   }
 250: 
 251:   /**
 252:    * This method closes this stream, and the underlying 
 253:    * <code>OutputStream</code>
 254:    *
 255:    * @exception IOException If an error occurs
 256:    */
 257:   public void close () throws IOException
 258:   {
 259:     if(out == null)
 260:       return;
 261:     flush();
 262:     out.close ();
 263:     out = null;
 264:   }
 265: 
 266:   /**
 267:    * This method returns the name of the character encoding scheme currently
 268:    * in use by this stream.  If the stream has been closed, then this method
 269:    * may return <code>null</code>.
 270:    *
 271:    * @return The encoding scheme name
 272:    */
 273:   public String getEncoding ()
 274:   {
 275:     return out != null ? encodingName : null;
 276:   }
 277: 
 278:   /**
 279:    * This method flushes any buffered bytes to the underlying output sink.
 280:    *
 281:    * @exception IOException If an error occurs
 282:    */
 283:   public void flush () throws IOException
 284:   {
 285:       if(out != null){      
 286:       if(outputBuffer != null){
 287:           char[] buf = new char[outputBuffer.position()];
 288:           if(buf.length > 0){
 289:           outputBuffer.flip();
 290:           outputBuffer.get(buf);
 291:           writeConvert(buf, 0, buf.length);
 292:           outputBuffer.clear();
 293:           }
 294:       }
 295:       out.flush ();
 296:       }
 297:   }
 298: 
 299:   /**
 300:    * This method writes <code>count</code> characters from the specified
 301:    * array to the output stream starting at position <code>offset</code>
 302:    * into the array.
 303:    *
 304:    * @param buf The array of character to write from
 305:    * @param offset The offset into the array to start writing chars from
 306:    * @param count The number of chars to write.
 307:    *
 308:    * @exception IOException If an error occurs
 309:    */
 310:   public void write (char[] buf, int offset, int count) throws IOException
 311:   {
 312:     if(out == null)
 313:       throw new IOException("Stream is closed.");
 314:     if(buf == null)
 315:       throw new IOException("Buffer is null.");
 316: 
 317:     if(outputBuffer != null)
 318:     {
 319:         if(count >= outputBuffer.remaining())
 320:         {
 321:             int r = outputBuffer.remaining();
 322:             outputBuffer.put(buf, offset, r);
 323:             writeConvert(outputBuffer.array(), 0, BUFFER_SIZE);
 324:             outputBuffer.clear();
 325:             offset += r;
 326:             count -= r;
 327:             // if the remaining bytes is larger than the whole buffer, 
 328:             // just don't buffer.
 329:             if(count >= outputBuffer.remaining()){
 330:                       writeConvert(buf, offset, count);
 331:               return;
 332:             }
 333:         }
 334:         outputBuffer.put(buf, offset, count);
 335:     } else writeConvert(buf, offset, count);
 336:   }
 337: 
 338:  /**
 339:   * Converts and writes characters.
 340:   */
 341:   private void writeConvert (char[] buf, int offset, int count) 
 342:       throws IOException
 343:   {
 344:     if(encoder == null)
 345:     {
 346:       byte[] b = new byte[count];
 347:       for(int i=0;i<count;i++)
 348:     b[i] = (byte)((buf[offset+i] <= 0xFF)?buf[offset+i]:'?');
 349:       out.write(b);
 350:     } else {
 351:       try  {
 352:     ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count));
 353:     encoder.reset();
 354:     if(output.hasArray())
 355:       out.write(output.array());
 356:     else
 357:       {
 358:         byte[] outbytes = new byte[output.remaining()];
 359:         output.get(outbytes);
 360:         out.write(outbytes);
 361:       }
 362:       } catch(IllegalStateException e) {
 363:     throw new IOException("Internal error.");
 364:       } catch(MalformedInputException e) {
 365:     throw new IOException("Invalid character sequence.");
 366:       } catch(CharacterCodingException e) {
 367:     throw new IOException("Unmappable character.");
 368:       }
 369:     }
 370:   }
 371: 
 372:   /**
 373:    * This method writes <code>count</code> bytes from the specified 
 374:    * <code>String</code> starting at position <code>offset</code> into the
 375:    * <code>String</code>.
 376:    *
 377:    * @param str The <code>String</code> to write chars from
 378:    * @param offset The position in the <code>String</code> to start 
 379:    * writing chars from
 380:    * @param count The number of chars to write
 381:    *
 382:    * @exception IOException If an error occurs
 383:    */
 384:   public void write (String str, int offset, int count) throws IOException
 385:   {
 386:     if(str == null)
 387:       throw new IOException("String is null.");
 388: 
 389:     write(str.toCharArray(), offset, count);
 390:   }
 391: 
 392:   /**
 393:    * This method writes a single character to the output stream.
 394:    *
 395:    * @param ch The char to write, passed as an int.
 396:    *
 397:    * @exception IOException If an error occurs
 398:    */
 399:   public void write (int ch) throws IOException
 400:   {
 401:     write(new char[]{ (char)ch }, 0, 1);
 402:   }
 403: } // class OutputStreamWriter