Home | History | Annotate | Download | only in cs
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 /*
     28  */
     29 
     30 package sun.nio.cs;
     31 
     32 import java.io.*;
     33 import java.nio.*;
     34 import java.nio.channels.*;
     35 import java.nio.charset.*;
     36 
     37 public class StreamDecoder extends Reader
     38 {
     39 
     40     private static final int MIN_BYTE_BUFFER_SIZE = 32;
     41     private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
     42 
     43     private volatile boolean isOpen = true;
     44 
     45     private void ensureOpen() throws IOException {
     46         if (!isOpen)
     47             throw new IOException("Stream closed");
     48     }
     49 
     50     // In order to handle surrogates properly we must never try to produce
     51     // fewer than two characters at a time.  If we're only asked to return one
     52     // character then the other is saved here to be returned later.
     53     //
     54     private boolean haveLeftoverChar = false;
     55     private char leftoverChar;
     56 
     57     private boolean needsFlush = false;
     58 
     59     // Factories for java.io.InputStreamReader
     60 
     61     public static StreamDecoder forInputStreamReader(InputStream in,
     62                                                      Object lock,
     63                                                      String charsetName)
     64         throws UnsupportedEncodingException
     65     {
     66         String csn = charsetName;
     67         if (csn == null)
     68             csn = Charset.defaultCharset().name();
     69         try {
     70             if (Charset.isSupported(csn))
     71                 return new StreamDecoder(in, lock, Charset.forName(csn));
     72         } catch (IllegalCharsetNameException x) { }
     73         throw new UnsupportedEncodingException (csn);
     74     }
     75 
     76     public static StreamDecoder forInputStreamReader(InputStream in,
     77                                                      Object lock,
     78                                                      Charset cs)
     79     {
     80         return new StreamDecoder(in, lock, cs);
     81     }
     82 
     83     public static StreamDecoder forInputStreamReader(InputStream in,
     84                                                      Object lock,
     85                                                      CharsetDecoder dec)
     86     {
     87         return new StreamDecoder(in, lock, dec);
     88     }
     89 
     90 
     91     // Factory for java.nio.channels.Channels.newReader
     92 
     93     public static StreamDecoder forDecoder(ReadableByteChannel ch,
     94                                            CharsetDecoder dec,
     95                                            int minBufferCap)
     96     {
     97         return new StreamDecoder(ch, dec, minBufferCap);
     98     }
     99 
    100 
    101     // -- Public methods corresponding to those in InputStreamReader --
    102 
    103     // All synchronization and state/argument checking is done in these public
    104     // methods; the concrete stream-decoder subclasses defined below need not
    105     // do any such checking.
    106 
    107     public String getEncoding() {
    108         if (isOpen())
    109             return encodingName();
    110         return null;
    111     }
    112 
    113     public int read() throws IOException {
    114         return read0();
    115     }
    116 
    117     @SuppressWarnings("fallthrough")
    118     private int read0() throws IOException {
    119         synchronized (lock) {
    120 
    121             // Return the leftover char, if there is one
    122             if (haveLeftoverChar) {
    123                 haveLeftoverChar = false;
    124                 return leftoverChar;
    125             }
    126 
    127             // Convert more bytes
    128             char cb[] = new char[2];
    129             int n = read(cb, 0, 2);
    130             switch (n) {
    131             case -1:
    132                 return -1;
    133             case 2:
    134                 leftoverChar = cb[1];
    135                 haveLeftoverChar = true;
    136                 // FALL THROUGH
    137             case 1:
    138                 return cb[0];
    139             default:
    140                 assert false : n;
    141                 return -1;
    142             }
    143         }
    144     }
    145 
    146     public int read(char cbuf[], int offset, int length) throws IOException {
    147         int off = offset;
    148         int len = length;
    149         synchronized (lock) {
    150             ensureOpen();
    151             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
    152                 ((off + len) > cbuf.length) || ((off + len) < 0)) {
    153                 throw new IndexOutOfBoundsException();
    154             }
    155             if (len == 0)
    156                 return 0;
    157 
    158             int n = 0;
    159 
    160             if (haveLeftoverChar) {
    161                 // Copy the leftover char into the buffer
    162                 cbuf[off] = leftoverChar;
    163                 off++; len--;
    164                 haveLeftoverChar = false;
    165                 n = 1;
    166                 if ((len == 0) || !implReady())
    167                     // Return now if this is all we can produce w/o blocking
    168                     return n;
    169             }
    170 
    171             if (len == 1) {
    172                 // Treat single-character array reads just like read()
    173                 int c = read0();
    174                 if (c == -1)
    175                     return (n == 0) ? -1 : n;
    176                 cbuf[off] = (char)c;
    177                 return n + 1;
    178             }
    179 
    180             return n + implRead(cbuf, off, off + len);
    181         }
    182     }
    183 
    184     public boolean ready() throws IOException {
    185         synchronized (lock) {
    186             ensureOpen();
    187             return haveLeftoverChar || implReady();
    188         }
    189     }
    190 
    191     public void close() throws IOException {
    192         synchronized (lock) {
    193             if (!isOpen)
    194                 return;
    195             implClose();
    196             isOpen = false;
    197         }
    198     }
    199 
    200     private boolean isOpen() {
    201         return isOpen;
    202     }
    203 
    204 
    205     // -- Charset-based stream decoder impl --
    206 
    207     // In the early stages of the build we haven't yet built the NIO native
    208     // code, so guard against that by catching the first UnsatisfiedLinkError
    209     // and setting this flag so that later attempts fail quickly.
    210     //
    211     private static volatile boolean channelsAvailable = true;
    212 
    213     private static FileChannel getChannel(FileInputStream in) {
    214         if (!channelsAvailable)
    215             return null;
    216         try {
    217             return in.getChannel();
    218         } catch (UnsatisfiedLinkError x) {
    219             channelsAvailable = false;
    220             return null;
    221         }
    222     }
    223 
    224     private Charset cs;
    225     private CharsetDecoder decoder;
    226     private ByteBuffer bb;
    227 
    228     // Exactly one of these is non-null
    229     private InputStream in;
    230     private ReadableByteChannel ch;
    231 
    232     StreamDecoder(InputStream in, Object lock, Charset cs) {
    233         this(in, lock,
    234          cs.newDecoder()
    235          .onMalformedInput(CodingErrorAction.REPLACE)
    236          .onUnmappableCharacter(CodingErrorAction.REPLACE));
    237     }
    238 
    239     StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
    240         super(lock);
    241         this.cs = dec.charset();
    242         this.decoder = dec;
    243 
    244         // This path disabled until direct buffers are faster
    245         if (false && in instanceof FileInputStream) {
    246         ch = getChannel((FileInputStream)in);
    247         if (ch != null)
    248             bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
    249         }
    250         if (ch == null) {
    251         this.in = in;
    252         this.ch = null;
    253         bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
    254         }
    255         bb.flip();                      // So that bb is initially empty
    256     }
    257 
    258     StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) {
    259         this.in = null;
    260         this.ch = ch;
    261         this.decoder = dec;
    262         this.cs = dec.charset();
    263         this.bb = ByteBuffer.allocate(mbc < 0
    264                                   ? DEFAULT_BYTE_BUFFER_SIZE
    265                                   : (mbc < MIN_BYTE_BUFFER_SIZE
    266                                      ? MIN_BYTE_BUFFER_SIZE
    267                                      : mbc));
    268         bb.flip();
    269     }
    270 
    271     private int readBytes() throws IOException {
    272         bb.compact();
    273         try {
    274         if (ch != null) {
    275             // Read from the channel
    276             // Android-changed: Use ChannelInputStream.read to make sure we throw
    277             // the right exception for non-blocking channels.
    278             int n = sun.nio.ch.ChannelInputStream.read(ch, bb);
    279             if (n < 0)
    280                 return n;
    281         } else {
    282             // Read from the input stream, and then update the buffer
    283             int lim = bb.limit();
    284             int pos = bb.position();
    285             assert (pos <= lim);
    286             int rem = (pos <= lim ? lim - pos : 0);
    287             assert rem > 0;
    288             int n = in.read(bb.array(), bb.arrayOffset() + pos, rem);
    289             if (n < 0)
    290                 return n;
    291             if (n == 0)
    292                 throw new IOException("Underlying input stream returned zero bytes");
    293             assert (n <= rem) : "n = " + n + ", rem = " + rem;
    294             bb.position(pos + n);
    295         }
    296         } finally {
    297         // Flip even when an IOException is thrown,
    298         // otherwise the stream will stutter
    299         bb.flip();
    300         }
    301 
    302         int rem = bb.remaining();
    303             assert (rem != 0) : rem;
    304             return rem;
    305     }
    306 
    307     int implRead(char[] cbuf, int off, int end) throws IOException {
    308 
    309         // In order to handle surrogate pairs, this method requires that
    310         // the invoker attempt to read at least two characters.  Saving the
    311         // extra character, if any, at a higher level is easier than trying
    312         // to deal with it here.
    313         assert (end - off > 1);
    314 
    315         CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off);
    316         if (cb.position() != 0)
    317         // Ensure that cb[0] == cbuf[off]
    318         cb = cb.slice();
    319 
    320         // Android-changed: Support flushing the buffer properly.
    321         if (needsFlush) {
    322             CoderResult cr = decoder.flush(cb);
    323             if (cr.isOverflow()) {
    324                 // We've overflowed, we'll have to come back round and ask for more data.
    325                 return cb.position();
    326             }
    327 
    328             // By definition, we're at the end of the stream here.
    329             if (cr.isUnderflow()) {
    330                 if (cb.position() == 0) {
    331                     return -1;
    332                 }
    333 
    334                 return cb.position();
    335             }
    336 
    337             cr.throwException();
    338             // Unreachable.
    339         }
    340 
    341         boolean eof = false;
    342         for (;;) {
    343         CoderResult cr = decoder.decode(bb, cb, eof);
    344         if (cr.isUnderflow()) {
    345             if (eof)
    346                 break;
    347             if (!cb.hasRemaining())
    348                 break;
    349             if ((cb.position() > 0) && !inReady())
    350                 break;          // Block at most once
    351             int n = readBytes();
    352             if (n < 0) {
    353                 eof = true;
    354                 // Android-changed: We want to go 'round the loop one more time
    355                 // with "eof = true". We also don't want to reset the decoder here
    356                 // because we might potentially need to flush it later.
    357                 //
    358                 // if ((cb.position() == 0) && (!bb.hasRemaining()))
    359                 //     break;
    360                 //  decoder.reset();
    361             }
    362             continue;
    363         }
    364         if (cr.isOverflow()) {
    365             assert cb.position() > 0;
    366             break;
    367         }
    368         cr.throwException();
    369         }
    370 
    371         if (eof) {
    372             CoderResult cr = decoder.flush(cb);
    373             if (cr.isOverflow()) {
    374                 needsFlush = true;
    375                 return cb.position();
    376             }
    377 
    378             decoder.reset();
    379             if (!cr.isUnderflow()) {
    380                 cr.throwException();
    381             }
    382         }
    383 
    384         if (cb.position() == 0) {
    385             if (eof)
    386                 return -1;
    387             assert false;
    388         }
    389         return cb.position();
    390     }
    391 
    392     String encodingName() {
    393         return ((cs instanceof HistoricallyNamedCharset)
    394             ? ((HistoricallyNamedCharset)cs).historicalName()
    395             : cs.name());
    396     }
    397 
    398     private boolean inReady() {
    399         try {
    400         return (((in != null) && (in.available() > 0))
    401                 || (ch instanceof FileChannel)); // ## RBC.available()?
    402         } catch (IOException x) {
    403         return false;
    404         }
    405     }
    406 
    407     boolean implReady() {
    408             return bb.hasRemaining() || inReady();
    409     }
    410 
    411     void implClose() throws IOException {
    412         if (ch != null)
    413         ch.close();
    414         else
    415         in.close();
    416     }
    417 
    418 }
    419