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     private boolean needsFlush = false;
58 
59     // Factories for java.io.InputStreamReader
60 
forInputStreamReader(InputStream in, Object lock, String charsetName)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 
forInputStreamReader(InputStream in, Object lock, Charset cs)76     public static StreamDecoder forInputStreamReader(InputStream in,
77                                                      Object lock,
78                                                      Charset cs)
79     {
80         return new StreamDecoder(in, lock, cs);
81     }
82 
forInputStreamReader(InputStream in, Object lock, CharsetDecoder dec)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 
forDecoder(ReadableByteChannel ch, CharsetDecoder dec, int minBufferCap)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 
getEncoding()107     public String getEncoding() {
108         if (isOpen())
109             return encodingName();
110         return null;
111     }
112 
read()113     public int read() throws IOException {
114         return read0();
115     }
116 
117     @SuppressWarnings("fallthrough")
read0()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 
read(char cbuf[], int offset, int length)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 
ready()184     public boolean ready() throws IOException {
185         synchronized (lock) {
186             ensureOpen();
187             return haveLeftoverChar || implReady();
188         }
189     }
190 
close()191     public void close() throws IOException {
192         synchronized (lock) {
193             if (!isOpen)
194                 return;
195             implClose();
196             isOpen = false;
197         }
198     }
199 
isOpen()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 
getChannel(FileInputStream in)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 
StreamDecoder(InputStream in, Object lock, Charset cs)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 
StreamDecoder(InputStream in, Object lock, CharsetDecoder dec)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 
StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc)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 
readBytes()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 
implRead(char[] cbuf, int off, int end)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 
encodingName()392     String encodingName() {
393         return ((cs instanceof HistoricallyNamedCharset)
394             ? ((HistoricallyNamedCharset)cs).historicalName()
395             : cs.name());
396     }
397 
inReady()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 
implReady()407     boolean implReady() {
408             return bb.hasRemaining() || inReady();
409     }
410 
implClose()411     void implClose() throws IOException {
412         if (ch != null)
413         ch.close();
414         else
415         in.close();
416     }
417 
418 }
419