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:   }
 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:   }
 244: 
 245:   /**
 246:    * This method closes this stream, and the underlying 
 247:    * <code>OutputStream</code>
 248:    *
 249:    * @exception IOException If an error occurs
 250:    */
 251:   public void close () throws IOException
 252:   {
 253:     if(out == null)
 254:       return;
 255:     flush();
 256:     out.close ();
 257:     out = null;
 258:   }
 259: 
 260:   /**
 261:    * This method returns the name of the character encoding scheme currently
 262:    * in use by this stream.  If the stream has been closed, then this method
 263:    * may return <code>null</code>.
 264:    *
 265:    * @return The encoding scheme name
 266:    */
 267:   public String getEncoding ()
 268:   {
 269:     return out != null ? encodingName : null;
 270:   }
 271: 
 272:   /**
 273:    * This method flushes any buffered bytes to the underlying output sink.
 274:    *
 275:    * @exception IOException If an error occurs
 276:    */
 277:   public void flush () throws IOException
 278:   {
 279:       if(out != null){      
 280:       if(outputBuffer != null){
 281:           char[] buf = new char[outputBuffer.position()];
 282:           if(buf.length > 0){
 283:           outputBuffer.flip();
 284:           outputBuffer.get(buf);
 285:           writeConvert(buf, 0, buf.length);
 286:           outputBuffer.clear();
 287:           }
 288:       }
 289:       out.flush ();
 290:       }
 291:   }
 292: 
 293:   /**
 294:    * This method writes <code>count</code> characters from the specified
 295:    * array to the output stream starting at position <code>offset</code>
 296:    * into the array.
 297:    *
 298:    * @param buf The array of character to write from
 299:    * @param offset The offset into the array to start writing chars from
 300:    * @param count The number of chars to write.
 301:    *
 302:    * @exception IOException If an error occurs
 303:    */
 304:   public void write (char[] buf, int offset, int count) throws IOException
 305:   {
 306:     if(out == null)
 307:       throw new IOException("Stream is closed.");
 308:     if(buf == null)
 309:       throw new IOException("Buffer is null.");
 310: 
 311:     if(outputBuffer != null)
 312:     {
 313:         if(count >= outputBuffer.remaining())
 314:         {
 315:             int r = outputBuffer.remaining();
 316:             outputBuffer.put(buf, offset, r);
 317:             writeConvert(outputBuffer.array(), 0, BUFFER_SIZE);
 318:             outputBuffer.clear();
 319:             offset += r;
 320:             count -= r;
 321:             // if the remaining bytes is larger than the whole buffer, 
 322:             // just don't buffer.
 323:             if(count >= outputBuffer.remaining()){
 324:                       writeConvert(buf, offset, count);
 325:               return;
 326:             }
 327:         }
 328:         outputBuffer.put(buf, offset, count);
 329:     } else writeConvert(buf, offset, count);
 330:   }
 331: 
 332:  /**
 333:   * Converts and writes characters.
 334:   */
 335:   private void writeConvert (char[] buf, int offset, int count) 
 336:       throws IOException
 337:   {
 338:     if(encoder == null)
 339:     {
 340:       byte[] b = new byte[count];
 341:       for(int i=0;i<count;i++)
 342:     b[i] = (byte)((buf[offset+i] <= 0xFF)?buf[offset+i]:'?');
 343:       out.write(b);
 344:     } else {
 345:       try  {
 346:     ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count));
 347:     encoder.reset();
 348:     if(output.hasArray())
 349:       out.write(output.array());
 350:     else
 351:       {
 352:         byte[] outbytes = new byte[output.remaining()];
 353:         output.get(outbytes);
 354:         out.write(outbytes);
 355:       }
 356:       } catch(IllegalStateException e) {
 357:     throw new IOException("Internal error.");
 358:       } catch(MalformedInputException e) {
 359:     throw new IOException("Invalid character sequence.");
 360:       } catch(CharacterCodingException e) {
 361:     throw new IOException("Unmappable character.");
 362:       }
 363:     }
 364:   }
 365: 
 366:   /**
 367:    * This method writes <code>count</code> bytes from the specified 
 368:    * <code>String</code> starting at position <code>offset</code> into the
 369:    * <code>String</code>.
 370:    *
 371:    * @param str The <code>String</code> to write chars from
 372:    * @param offset The position in the <code>String</code> to start 
 373:    * writing chars from
 374:    * @param count The number of chars to write
 375:    *
 376:    * @exception IOException If an error occurs
 377:    */
 378:   public void write (String str, int offset, int count) throws IOException
 379:   {
 380:     if(str == null)
 381:       throw new IOException("String is null.");
 382: 
 383:     write(str.toCharArray(), offset, count);
 384:   }
 385: 
 386:   /**
 387:    * This method writes a single character to the output stream.
 388:    *
 389:    * @param ch The char to write, passed as an int.
 390:    *
 391:    * @exception IOException If an error occurs
 392:    */
 393:   public void write (int ch) throws IOException
 394:   {
 395:     write(new char[]{ (char)ch }, 0, 1);
 396:   }
 397: } // class OutputStreamWriter