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