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
30package sun.nio.cs;
31
32import java.io.*;
33import java.nio.*;
34import java.nio.channels.*;
35import java.nio.charset.*;
36
37public 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