1 /*
   2  * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  */
  28 
  29 package sun.nio.cs;
  30 
  31 import java.io.*;
  32 import java.nio.*;
  33 import java.nio.channels.*;
  34 import java.nio.charset.*;
  35 
  36 public class StreamEncoder extends Writer
  37 {
  38 
  39     private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
  40 
  41     private volatile boolean closed;
  42 
  43     private void ensureOpen() throws IOException {
  44         if (closed)
  45             throw new IOException("Stream closed");
  46     }
  47 
  48     // Factories for java.io.OutputStreamWriter
  49     public static StreamEncoder forOutputStreamWriter(OutputStream out,
  50                                                       Object lock,
  51                                                       String charsetName)
  52         throws UnsupportedEncodingException
  53     {
  54         String csn = charsetName;
  55         if (csn == null)
  56             csn = Charset.defaultCharset().name();
  57         try {
  58             if (Charset.isSupported(csn))
  59                 return new StreamEncoder(out, lock, Charset.forName(csn));
  60         } catch (IllegalCharsetNameException x) { }
  61         throw new UnsupportedEncodingException (csn);
  62     }
  63 
  64     public static StreamEncoder forOutputStreamWriter(OutputStream out,
  65                                                       Object lock,
  66                                                       Charset cs)
  67     {
  68         return new StreamEncoder(out, lock, cs);
  69     }
  70 
  71     public static StreamEncoder forOutputStreamWriter(OutputStream out,
  72                                                       Object lock,
  73                                                       CharsetEncoder enc)
  74     {
  75         return new StreamEncoder(out, lock, enc);
  76     }
  77 
  78 
  79     // Factory for java.nio.channels.Channels.newWriter
  80 
  81     public static StreamEncoder forEncoder(WritableByteChannel ch,
  82                                            CharsetEncoder enc,
  83                                            int minBufferCap)
  84     {
  85         return new StreamEncoder(ch, enc, minBufferCap);
  86     }
  87 
  88 
  89     // -- Public methods corresponding to those in OutputStreamWriter --
  90 
  91     // All synchronization and state/argument checking is done in these public
  92     // methods; the concrete stream-encoder subclasses defined below need not
  93     // do any such checking.
  94 
  95     public String getEncoding() {
  96         if (isOpen())
  97             return encodingName();
  98         return null;
  99     }
 100 
 101     public void flushBuffer() throws IOException {
 102         synchronized (lock) {
 103             if (isOpen())
 104                 implFlushBuffer();
 105             else
 106                 throw new IOException("Stream closed");
 107         }
 108     }
 109 
 110     public void write(int c) throws IOException {
 111         char cbuf[] = new char[1];
 112         cbuf[0] = (char) c;
 113         write(cbuf, 0, 1);
 114     }
 115 
 116     public void write(char cbuf[], int off, int len) throws IOException {
 117         synchronized (lock) {
 118             ensureOpen();
 119             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
 120                 ((off + len) > cbuf.length) || ((off + len) < 0)) {
 121                 throw new IndexOutOfBoundsException();
 122             } else if (len == 0) {
 123                 return;
 124             }
 125             implWrite(cbuf, off, len);
 126         }
 127     }
 128 
 129     public void write(String str, int off, int len) throws IOException {
 130         /* Check the len before creating a char buffer */
 131         if (len < 0)
 132             throw new IndexOutOfBoundsException();
 133         char cbuf[] = new char[len];
 134         str.getChars(off, off + len, cbuf, 0);
 135         write(cbuf, 0, len);
 136     }
 137 
 138     public void write(CharBuffer cb) throws IOException {
 139         int position = cb.position();
 140         try {
 141             synchronized (lock) {
 142                 ensureOpen();
 143                 implWrite(cb);
 144             }
 145         } finally {
 146             cb.position(position);
 147         }
 148     }
 149 
 150     public void flush() throws IOException {
 151         synchronized (lock) {
 152             ensureOpen();
 153             implFlush();
 154         }
 155     }
 156 
 157     public void close() throws IOException {
 158         synchronized (lock) {
 159             if (closed)
 160                 return;
 161             implClose();
 162             closed = true;
 163         }
 164     }
 165 
 166     private boolean isOpen() {
 167         return !closed;
 168     }
 169 
 170 
 171     // -- Charset-based stream encoder impl --
 172 
 173     private Charset cs;
 174     private CharsetEncoder encoder;
 175     private ByteBuffer bb;
 176 
 177     // Exactly one of these is non-null
 178     private final OutputStream out;
 179     private WritableByteChannel ch;
 180 
 181     // Leftover first char in a surrogate pair
 182     private boolean haveLeftoverChar = false;
 183     private char leftoverChar;
 184     private CharBuffer lcb = null;
 185 
 186     private StreamEncoder(OutputStream out, Object lock, Charset cs) {
 187         this(out, lock,
 188          cs.newEncoder()
 189          .onMalformedInput(CodingErrorAction.REPLACE)
 190          .onUnmappableCharacter(CodingErrorAction.REPLACE));
 191     }
 192 
 193     private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
 194         super(lock);
 195         this.out = out;
 196         this.ch = null;
 197         this.cs = enc.charset();
 198         this.encoder = enc;
 199 
 200         // This path disabled until direct buffers are faster
 201         if (false && out instanceof FileOutputStream) {
 202                 ch = ((FileOutputStream)out).getChannel();
 203         if (ch != null)
 204                     bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
 205         }
 206             if (ch == null) {
 207         bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
 208         }
 209     }
 210 
 211     private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
 212         this.out = null;
 213         this.ch = ch;
 214         this.cs = enc.charset();
 215         this.encoder = enc;
 216         this.bb = ByteBuffer.allocate(mbc < 0
 217                                   ? DEFAULT_BYTE_BUFFER_SIZE
 218                                   : mbc);
 219     }
 220 
 221     private void writeBytes() throws IOException {
 222         bb.flip();
 223         int lim = bb.limit();
 224         int pos = bb.position();
 225         assert (pos <= lim);
 226         int rem = (pos <= lim ? lim - pos : 0);
 227 
 228             if (rem > 0) {
 229         if (ch != null) {
 230             if (ch.write(bb) != rem)
 231                 assert false : rem;
 232         } else {
 233             out.write(bb.array(), bb.arrayOffset() + pos, rem);
 234         }
 235         }
 236         bb.clear();
 237         }
 238 
 239     private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
 240         throws IOException
 241     {
 242         if (!haveLeftoverChar && !endOfInput)
 243             return;
 244         if (lcb == null)
 245             lcb = CharBuffer.allocate(2);
 246         else
 247             lcb.clear();
 248         if (haveLeftoverChar)
 249             lcb.put(leftoverChar);
 250         if ((cb != null) && cb.hasRemaining())
 251             lcb.put(cb.get());
 252         lcb.flip();
 253         while (lcb.hasRemaining() || endOfInput) {
 254             CoderResult cr = encoder.encode(lcb, bb, endOfInput);
 255             if (cr.isUnderflow()) {
 256                 if (lcb.hasRemaining()) {
 257                     leftoverChar = lcb.get();
 258                     if (cb != null && cb.hasRemaining()) {
 259                         lcb.clear();
 260                         lcb.put(leftoverChar).put(cb.get()).flip();
 261                         continue;
 262                     }
 263                     return;
 264                 }
 265                 break;
 266             }
 267             if (cr.isOverflow()) {
 268                 assert bb.position() > 0;
 269                 writeBytes();
 270                 continue;
 271             }
 272             cr.throwException();
 273         }
 274         haveLeftoverChar = false;
 275     }
 276 
 277     void implWrite(char cbuf[], int off, int len)
 278         throws IOException
 279     {
 280         CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
 281         implWrite(cb);
 282     }
 283 
 284     void implWrite(CharBuffer cb)
 285         throws IOException
 286     {
 287         if (haveLeftoverChar) {
 288             flushLeftoverChar(cb, false);
 289         }
 290 
 291         while (cb.hasRemaining()) {
 292             CoderResult cr = encoder.encode(cb, bb, false);
 293             if (cr.isUnderflow()) {
 294                 assert (cb.remaining() <= 1) : cb.remaining();
 295                 if (cb.remaining() == 1) {
 296                     haveLeftoverChar = true;
 297                     leftoverChar = cb.get();
 298                 }
 299                 break;
 300             }
 301             if (cr.isOverflow()) {
 302                 assert bb.position() > 0;
 303                 writeBytes();
 304                 continue;
 305             }
 306             cr.throwException();
 307         }
 308     }
 309 
 310     void implFlushBuffer() throws IOException {
 311         if (bb.position() > 0)
 312         writeBytes();
 313     }
 314 
 315     void implFlush() throws IOException {
 316         implFlushBuffer();
 317         if (out != null)
 318         out.flush();
 319     }
 320 
 321     void implClose() throws IOException {
 322         flushLeftoverChar(null, true);
 323         try {
 324             for (;;) {
 325                 CoderResult cr = encoder.flush(bb);
 326                 if (cr.isUnderflow())
 327                     break;
 328                 if (cr.isOverflow()) {
 329                     assert bb.position() > 0;
 330                     writeBytes();
 331                     continue;
 332                 }
 333                 cr.throwException();
 334             }
 335 
 336             if (bb.position() > 0)
 337                 writeBytes();
 338             if (ch != null)
 339                 ch.close();
 340             else
 341                 out.close();
 342         } catch (IOException x) {
 343             encoder.reset();
 344             throw x;
 345         }
 346     }
 347 
 348     String encodingName() {
 349         return ((cs instanceof HistoricallyNamedCharset)
 350             ? ((HistoricallyNamedCharset)cs).historicalName()
 351             : cs.name());
 352     }
 353 }