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