1/*
2 * Copyright (c) 2001, 2011, 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
29package sun.nio.cs;
30
31import java.io.*;
32import java.nio.*;
33import java.nio.channels.*;
34import java.nio.charset.*;
35
36public class StreamDecoder extends Reader
37{
38
39    private static final int MIN_BYTE_BUFFER_SIZE = 32;
40    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
41
42    private volatile boolean closed;
43
44    private void ensureOpen() throws IOException {
45        if (closed)
46            throw new IOException("Stream closed");
47    }
48
49    // In order to handle surrogates properly we must never try to produce
50    // fewer than two characters at a time.  If we're only asked to return one
51    // character then the other is saved here to be returned later.
52    //
53    private boolean haveLeftoverChar = false;
54    private char leftoverChar;
55
56
57    // Factories for java.io.InputStreamReader
58
59    public static StreamDecoder forInputStreamReader(InputStream in,
60                                                     Object lock,
61                                                     String charsetName)
62        throws UnsupportedEncodingException
63    {
64        String csn = charsetName;
65        if (csn == null)
66            csn = Charset.defaultCharset().name();
67        try {
68            if (Charset.isSupported(csn))
69                return new StreamDecoder(in, lock, Charset.forName(csn));
70        } catch (IllegalCharsetNameException x) { }
71        throw new UnsupportedEncodingException (csn);
72    }
73
74    public static StreamDecoder forInputStreamReader(InputStream in,
75                                                     Object lock,
76                                                     Charset cs)
77    {
78        return new StreamDecoder(in, lock, cs);
79    }
80
81    public static StreamDecoder forInputStreamReader(InputStream in,
82                                                     Object lock,
83                                                     CharsetDecoder dec)
84    {
85        return new StreamDecoder(in, lock, dec);
86    }
87
88
89    // Factory for java.nio.channels.Channels.newReader
90
91    public static StreamDecoder forDecoder(ReadableByteChannel ch,
92                                           CharsetDecoder dec,
93                                           int minBufferCap)
94    {
95        return new StreamDecoder(ch, dec, minBufferCap);
96    }
97
98
99    // -- Public methods corresponding to those in InputStreamReader --
100
101    // All synchronization and state/argument checking is done in these public
102    // methods; the concrete stream-decoder subclasses defined below need not
103    // do any such checking.
104
105    public String getEncoding() {
106        if (isOpen())
107            return encodingName();
108        return null;
109    }
110
111    public int read() throws IOException {
112        return read0();
113    }
114
115    @SuppressWarnings("fallthrough")
116    private int read0() throws IOException {
117        synchronized (lock) {
118
119            // Return the leftover char, if there is one
120            if (haveLeftoverChar) {
121                haveLeftoverChar = false;
122                return leftoverChar;
123            }
124
125            // Convert more bytes
126            char cb[] = new char[2];
127            int n = read(cb, 0, 2);
128            switch (n) {
129            case -1:
130                return -1;
131            case 2:
132                leftoverChar = cb[1];
133                haveLeftoverChar = true;
134                // FALL THROUGH
135            case 1:
136                return cb[0];
137            default:
138                assert false : n;
139                return -1;
140            }
141        }
142    }
143
144    public int read(char cbuf[], int offset, int length) throws IOException {
145        int off = offset;
146        int len = length;
147        synchronized (lock) {
148            ensureOpen();
149            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
150                ((off + len) > cbuf.length) || ((off + len) < 0)) {
151                throw new IndexOutOfBoundsException();
152            }
153            if (len == 0)
154                return 0;
155
156            int n = 0;
157
158            if (haveLeftoverChar) {
159                // Copy the leftover char into the buffer
160                cbuf[off] = leftoverChar;
161                off++; len--;
162                haveLeftoverChar = false;
163                n = 1;
164                if ((len == 0) || !implReady())
165                    // Return now if this is all we can produce w/o blocking
166                    return n;
167            }
168
169            if (len == 1) {
170                // Treat single-character array reads just like read()
171                int c = read0();
172                if (c == -1)
173                    return (n == 0) ? -1 : n;
174                cbuf[off] = (char)c;
175                return n + 1;
176            }
177
178            return n + implRead(cbuf, off, off + len);
179        }
180    }
181
182    public boolean ready() throws IOException {
183        synchronized (lock) {
184            ensureOpen();
185            return haveLeftoverChar || implReady();
186        }
187    }
188
189    public void close() throws IOException {
190        synchronized (lock) {
191            if (closed)
192                return;
193            implClose();
194            closed = true;
195        }
196    }
197
198    private boolean isOpen() {
199        return !closed;
200    }
201
202
203    // -- Charset-based stream decoder impl --
204
205    // In the early stages of the build we haven't yet built the NIO native
206    // code, so guard against that by catching the first UnsatisfiedLinkError
207    // and setting this flag so that later attempts fail quickly.
208    //
209    private static volatile boolean channelsAvailable = true;
210
211    private static FileChannel getChannel(FileInputStream in) {
212        if (!channelsAvailable)
213            return null;
214        try {
215            return in.getChannel();
216        } catch (UnsatisfiedLinkError x) {
217            channelsAvailable = false;
218            return null;
219        }
220    }
221
222    private Charset cs;
223    private CharsetDecoder decoder;
224    private ByteBuffer bb;
225
226    // Exactly one of these is non-null
227    private InputStream in;
228    private ReadableByteChannel ch;
229
230    StreamDecoder(InputStream in, Object lock, Charset cs) {
231        this(in, lock,
232         cs.newDecoder()
233         .onMalformedInput(CodingErrorAction.REPLACE)
234         .onUnmappableCharacter(CodingErrorAction.REPLACE));
235    }
236
237    StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
238        super(lock);
239        this.cs = dec.charset();
240        this.decoder = dec;
241
242        // This path disabled until direct buffers are faster
243        if (false && in instanceof FileInputStream) {
244        ch = getChannel((FileInputStream)in);
245        if (ch != null)
246            bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
247        }
248        if (ch == null) {
249        this.in = in;
250        this.ch = null;
251        bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
252        }
253        bb.flip();                      // So that bb is initially empty
254    }
255
256    StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) {
257        this.in = null;
258        this.ch = ch;
259        this.decoder = dec;
260        this.cs = dec.charset();
261        this.bb = ByteBuffer.allocate(mbc < 0
262                                  ? DEFAULT_BYTE_BUFFER_SIZE
263                                  : (mbc < MIN_BYTE_BUFFER_SIZE
264                                     ? MIN_BYTE_BUFFER_SIZE
265                                     : mbc));
266        bb.flip();
267    }
268
269    private int readBytes() throws IOException {
270        bb.compact();
271        try {
272        if (ch != null) {
273            // Read from the channel
274            int n = ch.read(bb);
275            if (n < 0)
276                return n;
277        } else {
278            // Read from the input stream, and then update the buffer
279            int lim = bb.limit();
280            int pos = bb.position();
281            assert (pos <= lim);
282            int rem = (pos <= lim ? lim - pos : 0);
283            assert rem > 0;
284            int n = in.read(bb.array(), bb.arrayOffset() + pos, rem);
285            if (n < 0)
286                return n;
287            if (n == 0)
288                throw new IOException("Underlying input stream returned zero bytes");
289            assert (n <= rem) : "n = " + n + ", rem = " + rem;
290            bb.position(pos + n);
291        }
292        } finally {
293        // Flip even when an IOException is thrown,
294        // otherwise the stream will stutter
295        bb.flip();
296        }
297
298        int rem = bb.remaining();
299            assert (rem != 0) : rem;
300            return rem;
301    }
302
303    int implRead(char[] cbuf, int off, int end) throws IOException {
304
305        // In order to handle surrogate pairs, this method requires that
306        // the invoker attempt to read at least two characters.  Saving the
307        // extra character, if any, at a higher level is easier than trying
308        // to deal with it here.
309        assert (end - off > 1);
310
311        CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off);
312        if (cb.position() != 0)
313        // Ensure that cb[0] == cbuf[off]
314        cb = cb.slice();
315
316        boolean eof = false;
317        for (;;) {
318        CoderResult cr = decoder.decode(bb, cb, eof);
319        if (cr.isUnderflow()) {
320            if (eof)
321                break;
322            if (!cb.hasRemaining())
323                break;
324            if ((cb.position() > 0) && !inReady())
325                break;          // Block at most once
326            int n = readBytes();
327            if (n < 0) {
328                eof = true;
329                if ((cb.position() == 0) && (!bb.hasRemaining()))
330                    break;
331                decoder.reset();
332            }
333            continue;
334        }
335        if (cr.isOverflow()) {
336            assert cb.position() > 0;
337            break;
338        }
339        cr.throwException();
340        }
341
342        if (eof) {
343        // ## Need to flush decoder
344        decoder.reset();
345        }
346
347        if (cb.position() == 0) {
348            if (eof)
349                return -1;
350            assert false;
351        }
352        return cb.position();
353    }
354
355    String encodingName() {
356        return ((cs instanceof HistoricallyNamedCharset)
357            ? ((HistoricallyNamedCharset)cs).historicalName()
358            : cs.name());
359    }
360
361    private boolean inReady() {
362        try {
363        return (((in != null) && (in.available() > 0))
364                || (ch instanceof FileChannel)); // ## RBC.available()?
365        } catch (IOException x) {
366        return false;
367        }
368    }
369
370    boolean implReady() {
371            return bb.hasRemaining() || inReady();
372    }
373
374    void implClose() throws IOException {
375        if (ch != null)
376        ch.close();
377        else
378        in.close();
379    }
380
381}
382