Home | History | Annotate | Download | only in cs
      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     void implWrite(char cbuf[], int off, int len)
    263         throws IOException
    264     {
    265         CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
    266 
    267         if (haveLeftoverChar)
    268         flushLeftoverChar(cb, false);
    269 
    270         while (cb.hasRemaining()) {
    271         CoderResult cr = encoder.encode(cb, bb, false);
    272         if (cr.isUnderflow()) {
    273            assert (cb.remaining() <= 1) : cb.remaining();
    274            if (cb.remaining() == 1) {
    275                 haveLeftoverChar = true;
    276                 leftoverChar = cb.get();
    277             }
    278             break;
    279         }
    280         if (cr.isOverflow()) {
    281             assert bb.position() > 0;
    282             writeBytes();
    283             continue;
    284         }
    285         cr.throwException();
    286         }
    287     }
    288 
    289     void implFlushBuffer() throws IOException {
    290         if (bb.position() > 0)
    291         writeBytes();
    292     }
    293 
    294     void implFlush() throws IOException {
    295         implFlushBuffer();
    296         if (out != null)
    297         out.flush();
    298     }
    299 
    300     void implClose() throws IOException {
    301         flushLeftoverChar(null, true);
    302         try {
    303             for (;;) {
    304                 CoderResult cr = encoder.flush(bb);
    305                 if (cr.isUnderflow())
    306                     break;
    307                 if (cr.isOverflow()) {
    308                     assert bb.position() > 0;
    309                     writeBytes();
    310                     continue;
    311                 }
    312                 cr.throwException();
    313             }
    314 
    315             if (bb.position() > 0)
    316                 writeBytes();
    317             if (ch != null)
    318                 ch.close();
    319             else
    320                 out.close();
    321         } catch (IOException x) {
    322             encoder.reset();
    323             throw x;
    324         }
    325     }
    326 
    327     String encodingName() {
    328         return ((cs instanceof HistoricallyNamedCharset)
    329             ? ((HistoricallyNamedCharset)cs).historicalName()
    330             : cs.name());
    331     }
    332 }
    333