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: import java.nio.ByteBuffer;
  43: import java.nio.CharBuffer;
  44: import java.nio.charset.MalformedInputException;
  45: import java.nio.charset.UnsupportedCharsetException;
  46: import java.nio.charset.CharacterCodingException;
  47: import java.nio.charset.IllegalCharsetNameException;
  48: import java.nio.charset.CoderResult;
  49: import java.nio.charset.CodingErrorAction;
  50: import java.nio.charset.Charset;
  51: import java.nio.charset.CharsetEncoder;
  52: 
  53: /**
  54:  * This class writes characters to an output stream that is byte oriented
  55:  * It converts the chars that are written to bytes using an encoding layer,
  56:  * which is specific to a particular encoding standard.  The desired
  57:  * encoding can either be specified by name, or if no encoding is specified,
  58:  * the system default encoding will be used.  The system default encoding
  59:  * name is determined from the system property <code>file.encoding</code>.
  60:  * The only encodings that are guaranteed to be available are "8859_1"
  61:  * (the Latin-1 character set) and "UTF8".  Unfortunately, Java does not
  62:  * provide a mechanism for listing the encodings that are supported in
  63:  * a given implementation.
  64:  * <p>
  65:  * Here is a list of standard encoding names that may be available:
  66:  * <p>
  67:  * <ul>
  68:  * <li>8859_1 (ISO-8859-1/Latin-1)
  69:  * <li>8859_2 (ISO-8859-2/Latin-2)
  70:  * <li>8859_3 (ISO-8859-3/Latin-3)
  71:  * <li>8859_4 (ISO-8859-4/Latin-4)
  72:  * <li>8859_5 (ISO-8859-5/Latin-5)
  73:  * <li>8859_6 (ISO-8859-6/Latin-6)
  74:  * <li>8859_7 (ISO-8859-7/Latin-7)
  75:  * <li>8859_8 (ISO-8859-8/Latin-8)
  76:  * <li>8859_9 (ISO-8859-9/Latin-9)
  77:  * <li>ASCII (7-bit ASCII)
  78:  * <li>UTF8 (UCS Transformation Format-8)
  79:  * <li>More Later
  80:  * </ul>
  81:  *
  82:  * @author Aaron M. Renn (arenn@urbanophile.com)
  83:  * @author Per Bothner (bothner@cygnus.com)
  84:  * @date April 17, 1998.  
  85:  */
  86: public class OutputStreamWriter extends Writer
  87: {
  88:   /**
  89:    * The output stream.
  90:    */
  91:   private OutputStream out;
  92: 
  93:   /**
  94:    * The charset encoder.
  95:    */
  96:   private CharsetEncoder encoder;
  97: 
  98:   /**
  99:    * java.io canonical name of the encoding.
 100:    */
 101:   private String encodingName;
 102: 
 103:   /**
 104:    * Buffer output before character conversion as it has costly overhead.
 105:    */
 106:   private CharBuffer outputBuffer;
 107:   private final static int BUFFER_SIZE = 1024;
 108: 
 109:   /**
 110:    * This method initializes a new instance of <code>OutputStreamWriter</code>
 111:    * to write to the specified stream using a caller supplied character
 112:    * encoding scheme.  Note that due to a deficiency in the Java language
 113:    * design, there is no way to determine which encodings are supported.
 114:    *
 115:    * @param out The <code>OutputStream</code> to write to
 116:    * @param encoding_scheme The name of the encoding scheme to use for 
 117:    * character to byte translation
 118:    *
 119:    * @exception UnsupportedEncodingException If the named encoding is 
 120:    * not available.
 121:    */
 122:   public OutputStreamWriter (OutputStream out, String encoding_scheme) 
 123:     throws UnsupportedEncodingException
 124:   {
 125:     this.out = out;
 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:        * Workraround for encodings with a byte-order-mark.
 138:        * We only want to write it once per stream.
 139:        */
 140:       try {
 141:     if(encoding_scheme.equalsIgnoreCase("UnicodeBig") || 
 142:        encoding_scheme.equalsIgnoreCase("UTF-16") ||
 143:        encoding_scheme.equalsIgnoreCase("UTF16"))
 144:     {
 145:       encoding_scheme = "UTF-16BE";      
 146:       out.write((byte)0xFE);
 147:       out.write((byte)0xFF);
 148:     } else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle")){
 149:       encoding_scheme = "UTF-16LE";
 150:       out.write((byte)0xFF);
 151:       out.write((byte)0xFE);
 152:     }
 153:       } catch(IOException ioe){
 154:       }
 155: 
 156:       outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
 157: 
 158:       Charset cs = EncodingHelper.getCharset(encoding_scheme);
 159:       if(cs == null)
 160:     throw new UnsupportedEncodingException("Encoding "+encoding_scheme+
 161:                             " unknown");
 162:       encoder = cs.newEncoder();
 163:       encodingName = EncodingHelper.getOldCanonical(cs.name());
 164: 
 165:       encoder.onMalformedInput(CodingErrorAction.REPLACE);
 166:       encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
 167:     } catch(RuntimeException e) {
 168:       // Default to ISO Latin-1, will happen if this is called, for instance,
 169:       //  before the NIO provider is loadable.
 170:       encoder = null; 
 171:       encodingName = "ISO8859_1";
 172:     }
 173:   }
 174: 
 175:   /**
 176:    * This method initializes a new instance of <code>OutputStreamWriter</code>
 177:    * to write to the specified stream using the default encoding.
 178:    *
 179:    * @param out The <code>OutputStream</code> to write to
 180:    */
 181:   public OutputStreamWriter (OutputStream out)
 182:   {
 183:     this.out = out;
 184:     outputBuffer = null;
 185:     try 
 186:     {
 187:       String encoding = System.getProperty("file.encoding");
 188:       Charset cs = Charset.forName(encoding);
 189:       encoder = cs.newEncoder();
 190:       encodingName =  EncodingHelper.getOldCanonical(cs.name());
 191:     } catch(RuntimeException e) {
 192:       encoder = null; 
 193:       encodingName = "ISO8859_1";
 194:     }
 195:     if(encoder != null)
 196:     {
 197:       encoder.onMalformedInput(CodingErrorAction.REPLACE);
 198:       encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
 199:       outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
 200:     }
 201:   }
 202: 
 203:   /**
 204:    * This method closes this stream, and the underlying 
 205:    * <code>OutputStream</code>
 206:    *
 207:    * @exception IOException If an error occurs
 208:    */
 209:   public void close () throws IOException
 210:   {
 211:     if(out == null)
 212:       return;
 213:     flush();
 214:     out.close ();
 215:     out = null;
 216:   }
 217: 
 218:   /**
 219:    * This method returns the name of the character encoding scheme currently
 220:    * in use by this stream.  If the stream has been closed, then this method
 221:    * may return <code>null</code>.
 222:    *
 223:    * @return The encoding scheme name
 224:    */
 225:   public String getEncoding ()
 226:   {
 227:     return out != null ? encodingName : null;
 228:   }
 229: 
 230:   /**
 231:    * This method flushes any buffered bytes to the underlying output sink.
 232:    *
 233:    * @exception IOException If an error occurs
 234:    */
 235:   public void flush () throws IOException
 236:   {
 237:       if(out != null){      
 238:       if(outputBuffer != null){
 239:           char[] buf = new char[outputBuffer.position()];
 240:           if(buf.length > 0){
 241:           outputBuffer.flip();
 242:           outputBuffer.get(buf);
 243:           writeConvert(buf, 0, buf.length);
 244:           outputBuffer.clear();
 245:           }
 246:       }
 247:       out.flush ();
 248:       }
 249:   }
 250: 
 251:   /**
 252:    * This method writes <code>count</code> characters from the specified
 253:    * array to the output stream starting at position <code>offset</code>
 254:    * into the array.
 255:    *
 256:    * @param buf The array of character to write from
 257:    * @param offset The offset into the array to start writing chars from
 258:    * @param count The number of chars to write.
 259:    *
 260:    * @exception IOException If an error occurs
 261:    */
 262:   public void write (char[] buf, int offset, int count) throws IOException
 263:   {
 264:     if(out == null)
 265:       throw new IOException("Stream is closed.");
 266:     if(buf == null)
 267:       throw new IOException("Buffer is null.");
 268: 
 269:     if(outputBuffer != null)
 270:     {
 271:         if(count >= outputBuffer.remaining())
 272:         {
 273:             int r = outputBuffer.remaining();
 274:             outputBuffer.put(buf, offset, r);
 275:             writeConvert(outputBuffer.array(), 0, BUFFER_SIZE);
 276:             outputBuffer.clear();
 277:             offset += r;
 278:             count -= r;
 279:             // if the remaining bytes is larger than the whole buffer, 
 280:             // just don't buffer.
 281:             if(count >= outputBuffer.remaining()){
 282:                       writeConvert(buf, offset, count);
 283:               return;
 284:             }
 285:         }
 286:         outputBuffer.put(buf, offset, count);
 287:     } else writeConvert(buf, offset, count);
 288:   }
 289: 
 290:  /**
 291:   * Converts and writes characters.
 292:   */
 293:   private void writeConvert (char[] buf, int offset, int count) 
 294:       throws IOException
 295:   {
 296:     if(encoder == null)
 297:     {
 298:       byte[] b = new byte[count];
 299:       for(int i=0;i<count;i++)
 300:     b[i] = (byte)((buf[offset+i] <= 0xFF)?buf[offset+i]:'?');
 301:       out.write(b);
 302:     } else {
 303:       try  {
 304:     ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count));
 305:     encoder.reset();
 306:     if(output.hasArray())
 307:       out.write(output.array());
 308:     else
 309:       {
 310:         byte[] outbytes = new byte[output.remaining()];
 311:         output.get(outbytes);
 312:         out.write(outbytes);
 313:       }
 314:       } catch(IllegalStateException e) {
 315:     throw new IOException("Internal error.");
 316:       } catch(MalformedInputException e) {
 317:     throw new IOException("Invalid character sequence.");
 318:       } catch(CharacterCodingException e) {
 319:     throw new IOException("Unmappable character.");
 320:       }
 321:     }
 322:   }
 323: 
 324:   /**
 325:    * This method writes <code>count</code> bytes from the specified 
 326:    * <code>String</code> starting at position <code>offset</code> into the
 327:    * <code>String</code>.
 328:    *
 329:    * @param str The <code>String</code> to write chars from
 330:    * @param offset The position in the <code>String</code> to start 
 331:    * writing chars from
 332:    * @param count The number of chars to write
 333:    *
 334:    * @exception IOException If an error occurs
 335:    */
 336:   public void write (String str, int offset, int count) throws IOException
 337:   {
 338:     if(str == null)
 339:       throw new IOException("String is null.");
 340: 
 341:     write(str.toCharArray(), offset, count);
 342:   }
 343: 
 344:   /**
 345:    * This method writes a single character to the output stream.
 346:    *
 347:    * @param ch The char to write, passed as an int.
 348:    *
 349:    * @exception IOException If an error occurs
 350:    */
 351:   public void write (int ch) throws IOException
 352:   {
 353:     write(new char[]{ (char)ch }, 0, 1);
 354:   }
 355: } // class OutputStreamWriter