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 isOpen = true;
  42 
  43     private void ensureOpen() throws IOException {
  44         if (!isOpen)
  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 flush() throws IOException {
 139         synchronized (lock) {
 140             ensureOpen();
 141             implFlush();
 142         }
 143     }
 144 
 145     public void close() throws IOException {
 146         synchronized (lock) {
 147             if (!isOpen)
 148                 return;
 149             implClose();
 150             isOpen = false;
 151         }
 152     }
 153 
 154     private boolean isOpen() {
 155         return isOpen;
 156     }
 157 
 158 
 159     // -- Charset-based stream encoder impl --
 160 
 161     private Charset cs;
 162     private CharsetEncoder encoder;
 163     private ByteBuffer bb;
 164 
 165     // Exactly one of these is non-null
 166     private final OutputStream out;
 167     private WritableByteChannel ch;
 168 
 169     // Leftover first char in a surrogate pair
 170     private boolean haveLeftoverChar = false;
 171     private char leftoverChar;
 172     private CharBuffer lcb = null;
 173 
 174     private StreamEncoder(OutputStream out, Object lock, Charset cs) {
 175         this(out, lock,
 176          cs.newEncoder()
 177          .onMalformedInput(CodingErrorAction.REPLACE)
 178          .onUnmappableCharacter(CodingErrorAction.REPLACE));
 179     }
 180 
 181     private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
 182         super(lock);
 183         this.out = out;
 184         this.ch = null;
 185         this.cs = enc.charset();
 186         this.encoder = enc;
 187 
 188         // This path disabled until direct buffers are faster
 189         if (false && out instanceof FileOutputStream) {
 190                 ch = ((FileOutputStream)out).getChannel();
 191         if (ch != null)
 192                     bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
 193         }
 194             if (ch == null) {
 195         bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
 196         }
 197     }
 198 
 199     private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
 200         this.out = null;
 201         this.ch = ch;
 202         this.cs = enc.charset();
 203         this.encoder = enc;
 204         this.bb = ByteBuffer.allocate(mbc < 0
 205                                   ? DEFAULT_BYTE_BUFFER_SIZE
 206                                   : mbc);
 207     }
 208 
 209     private void writeBytes() throws IOException {
 210         bb.flip();
 211         int lim = bb.limit();
 212         int pos = bb.position();
 213         assert (pos <= lim);
 214         int rem = (pos <= lim ? lim - pos : 0);
 215 
 216         if (rem > 0) {
 217             if (ch != null) {
 218                 if (ch.write(bb) != rem)
 219                     assert false : rem;
 220             } else {
 221                 out.write(bb.array(), bb.arrayOffset() + pos, rem);
 222             }
 223         }
 224         bb.clear();
 225     }
 226 
 227     private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
 228         throws IOException
 229     {
 230         if (!haveLeftoverChar && !endOfInput)
 231             return;
 232         if (lcb == null)
 233             lcb = CharBuffer.allocate(2);
 234         else
 235             lcb.clear();
 236         if (haveLeftoverChar)
 237             lcb.put(leftoverChar);
 238         if ((cb != null) && cb.hasRemaining())
 239             lcb.put(cb.get());
 240         lcb.flip();
 241         while (lcb.hasRemaining() || endOfInput) {
 242             CoderResult cr = encoder.encode(lcb, bb, endOfInput);
 243             if (cr.isUnderflow()) {
 244                 if (lcb.hasRemaining()) {
 245                     leftoverChar = lcb.get();
 246                     if (cb != null && cb.hasRemaining())
 247                         flushLeftoverChar(cb, endOfInput);
 248                     return;
 249                 }
 250                 break;
 251             }
 252             if (cr.isOverflow()) {
 253                 assert bb.position() > 0;
 254                 writeBytes();
 255                 continue;
 256             }
 257             cr.throwException();
 258         }
 259         haveLeftoverChar = false;
 260     }
 261 
 262     private void flushEncoder() throws IOException {
 263         for (;;) {
 264             CoderResult cr = encoder.flush(bb);
 265             if (cr.isUnderflow())
 266                 break;
 267             if (cr.isOverflow()) {
 268                 assert bb.position() > 0;
 269                 writeBytes();
 270                 continue;
 271             }
 272             cr.throwException();
 273         }
 274     }
 275 
 276     void implWrite(char cbuf[], int off, int len)
 277         throws IOException
 278     {
 279         CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
 280 
 281         if (haveLeftoverChar)
 282             flushLeftoverChar(cb, false);
 283 
 284         while (cb.hasRemaining()) {
 285             CoderResult cr = encoder.encode(cb, bb, false);
 286             if (cr.isUnderflow()) {
 287                 assert (cb.remaining() <= 1) : cb.remaining();
 288                 if (cb.remaining() == 1) {
 289                     haveLeftoverChar = true;
 290                     leftoverChar = cb.get();
 291                 }
 292                 break;
 293             }
 294             if (cr.isOverflow()) {
 295                 assert bb.position() > 0;
 296                 writeBytes();
 297                 continue;
 298             }
 299             cr.throwException();
 300         }
 301     }
 302 
 303     void implFlushBuffer() throws IOException {
 304         try {
 305             // flush underlying encoder
 306             flushLeftoverChar(null, true);
 307             flushEncoder();
 308             encoder.reset();
 309         } catch (IOException x) {
 310             encoder.reset();
 311             throw x;
 312         }
 313         if (bb.position() > 0)
 314             writeBytes();
 315     }
 316 
 317     void implFlush() throws IOException {
 318         implFlushBuffer();
 319         if (out != null)
 320             out.flush();
 321     }
 322 
 323     void implClose() throws IOException {
 324         flushLeftoverChar(null, true);
 325         try {
 326             flushEncoder();
 327             if (bb.position() > 0)
 328                 writeBytes();
 329             if (ch != null)
 330                 ch.close();
 331             else
 332                 out.close();
 333         } catch (IOException x) {
 334             encoder.reset();
 335             throw x;
 336         }
 337     }
 338 
 339     String encodingName() {
 340         return ((cs instanceof HistoricallyNamedCharset)
 341             ? ((HistoricallyNamedCharset)cs).historicalName()
 342             : cs.name());
 343     }
 344 }