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