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 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