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 
 46 public class StreamDecoder extends Reader {
 47 
 48     private static final int MIN_BYTE_BUFFER_SIZE = 32;
 49     private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
 50 
 51     private volatile boolean closed;
 52 
 53     private void ensureOpen() throws IOException {
 54         if (closed)
 55             throw new IOException("Stream closed");
 56     }
 57 
 58     // In order to handle surrogates properly we must never try to produce
 59     // fewer than two characters at a time.  If we're only asked to return one
 60     // character then the other is saved here to be returned later.
 61     //
 62     private boolean haveLeftoverChar = false;
 63     private char leftoverChar;
 64 
 65 
 66     // Factories for java.io.InputStreamReader
 67 
 68     public static StreamDecoder forInputStreamReader(InputStream in,
 69                                                      Object lock,
 70                                                      String charsetName)
 71         throws UnsupportedEncodingException
 72     {
 73         String csn = charsetName;
 74         if (csn == null) {
 75             csn = Charset.defaultCharset().name();
 76         }
 77         try {
 78             return new StreamDecoder(in, lock, Charset.forName(csn));
 79         } catch (IllegalCharsetNameException | UnsupportedCharsetException x) {
 80             throw new UnsupportedEncodingException (csn);
 81         }
 82     }
 83 
 84     public static StreamDecoder forInputStreamReader(InputStream in,
 85                                                      Object lock,
 86                                                      Charset cs)
 87     {
 88         return new StreamDecoder(in, lock, cs);
 89     }
 90 
 91     public static StreamDecoder forInputStreamReader(InputStream in,
 92                                                      Object lock,
 93                                                      CharsetDecoder dec)
 94     {
 95         return new StreamDecoder(in, lock, dec);
 96     }
 97 
 98 
 99     // Factory for java.nio.channels.Channels.newReader
100 
101     public static StreamDecoder forDecoder(ReadableByteChannel ch,
102                                            CharsetDecoder dec,
103                                            int minBufferCap)
104     {
105         return new StreamDecoder(ch, dec, minBufferCap);
106     }
107 
108 
109     // -- Public methods corresponding to those in InputStreamReader --
110 
111     // All synchronization and state/argument checking is done in these public
112     // methods; the concrete stream-decoder subclasses defined below need not
113     // do any such checking.
114 
115     public String getEncoding() {
116         if (isOpen())
117             return encodingName();
118         return null;
119     }
120 
121     public int read() throws IOException {
122         return read0();
123     }
124 
125     @SuppressWarnings("fallthrough")
126     private int read0() throws IOException {
127         synchronized (lock) {
128 
129             // Return the leftover char, if there is one
130             if (haveLeftoverChar) {
131                 haveLeftoverChar = false;
132                 return leftoverChar;
133             }
134 
135             // Convert more bytes
136             char[] cb = new char[2];
137             int n = read(cb, 0, 2);
138             switch (n) {
139             case -1:
140                 return -1;
141             case 2:
142                 leftoverChar = cb[1];
143                 haveLeftoverChar = true;
144                 // FALL THROUGH
145             case 1:
146                 return cb[0];
147             default:
148                 assert false : n;
149                 return -1;
150             }
151         }
152     }
153 


























154     public int read(char[] cbuf, int offset, int length) throws IOException {
155         int off = offset;
156         int len = length;
157         synchronized (lock) {
158             ensureOpen();
159             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
160                 ((off + len) > cbuf.length) || ((off + len) < 0)) {
161                 throw new IndexOutOfBoundsException();
162             }
163             if (len == 0)
164                 return 0;
165 
166             int n = 0;
167 
168             if (haveLeftoverChar) {
169                 // Copy the leftover char into the buffer
170                 cbuf[off] = leftoverChar;
171                 off++; len--;
172                 haveLeftoverChar = false;
173                 n = 1;
174                 if ((len == 0) || !implReady())
175                     // Return now if this is all we can produce w/o blocking
176                     return n;
177             }


178 
179             if (len == 1) {
180                 // Treat single-character array reads just like read()
181                 int c = read0();
182                 if (c == -1)
183                     return (n == 0) ? -1 : n;
184                 cbuf[off] = (char)c;
185                 return n + 1;
186             }
















187 
188             return n + implRead(cbuf, off, off + len);






189         }


190     }
191 
192     public boolean ready() throws IOException {
193         synchronized (lock) {
194             ensureOpen();
195             return haveLeftoverChar || implReady();








196         }
197     }
198 





199     public void close() throws IOException {
200         synchronized (lock) {
201             if (closed)
202                 return;
203             try {
204                 implClose();
205             } finally {
206                 closed = true;
207             }














208         }
209     }
210 
211     private boolean isOpen() {
212         return !closed;
213     }
214 
215 
216     // -- Charset-based stream decoder impl --
217 
218     private final Charset cs;
219     private final CharsetDecoder decoder;
220     private final ByteBuffer bb;
221 
222     // Exactly one of these is non-null
223     private final InputStream in;
224     private final ReadableByteChannel ch;
225 
226     StreamDecoder(InputStream in, Object lock, Charset cs) {
227         this(in, lock,
228             cs.newDecoder()
229                 .onMalformedInput(CodingErrorAction.REPLACE)
230                 .onUnmappableCharacter(CodingErrorAction.REPLACE));
231     }
232 
233     StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
234         super(lock);
235         this.cs = dec.charset();
236         this.decoder = dec;
237         this.in = in;
238         this.ch = null;
239         bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
240         bb.flip();                      // So that bb is initially empty
241     }
242 
243     StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) {
244         this.in = null;
245         this.ch = ch;
246         this.decoder = dec;
247         this.cs = dec.charset();
248         this.bb = ByteBuffer.allocate(mbc < 0
249                                   ? DEFAULT_BYTE_BUFFER_SIZE
250                                   : (mbc < MIN_BYTE_BUFFER_SIZE
251                                      ? MIN_BYTE_BUFFER_SIZE
252                                      : mbc));
253         bb.flip();
254     }
255 
256     private int readBytes() throws IOException {
257         bb.compact();
258         try {
259             if (ch != null) {
260                 // Read from the channel
261                 int n = ch.read(bb);
262                 if (n < 0)
263                     return n;
264             } else {
265                 // Read from the input stream, and then update the buffer
266                 int lim = bb.limit();
267                 int pos = bb.position();
268                 assert (pos <= lim);
269                 int rem = (pos <= lim ? lim - pos : 0);
270                 int n = in.read(bb.array(), bb.arrayOffset() + pos, rem);
271                 if (n < 0)
272                     return n;
273                 if (n == 0)
274                     throw new IOException("Underlying input stream returned zero bytes");
275                 assert (n <= rem) : "n = " + n + ", rem = " + rem;
276                 bb.position(pos + n);
277             }
278         } finally {
279             // Flip even when an IOException is thrown,
280             // otherwise the stream will stutter
281             bb.flip();
282         }
283 
284         int rem = bb.remaining();
285         assert (rem != 0) : rem;
286         return rem;
287     }
288 
289     int implRead(char[] cbuf, int off, int end) throws IOException {
290 
291         // In order to handle surrogate pairs, this method requires that
292         // the invoker attempt to read at least two characters.  Saving the
293         // extra character, if any, at a higher level is easier than trying
294         // to deal with it here.
295         assert (end - off > 1);
296 
297         CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off);
298         if (cb.position() != 0) {
299             // Ensure that cb[0] == cbuf[off]
300             cb = cb.slice();
301         }
302 
303         boolean eof = false;
304         for (;;) {
305             CoderResult cr = decoder.decode(bb, cb, eof);
306             if (cr.isUnderflow()) {
307                 if (eof)
308                     break;
309                 if (!cb.hasRemaining())
310                     break;
311                 if ((cb.position() > 0) && !inReady())
312                     break;          // Block at most once
313                 int n = readBytes();
314                 if (n < 0) {
315                     eof = true;
316                     if ((cb.position() == 0) && (!bb.hasRemaining()))
317                         break;
318                     decoder.reset();
319                 }
320                 continue;
321             }
322             if (cr.isOverflow()) {
323                 assert cb.position() > 0;
324                 break;
325             }
326             cr.throwException();
327         }
328 
329         if (eof) {
330             // ## Need to flush decoder
331             decoder.reset();
332         }
333 
334         if (cb.position() == 0) {
335             if (eof) {
336                 return -1;
337             }
338             assert false;
339         }
340         return cb.position();
341     }
342 
343     String encodingName() {
344         return ((cs instanceof HistoricallyNamedCharset)
345             ? ((HistoricallyNamedCharset)cs).historicalName()
346             : cs.name());
347     }
348 
349     private boolean inReady() {
350         try {
351             return (((in != null) && (in.available() > 0))
352                     || (ch instanceof FileChannel)); // ## RBC.available()?
353         } catch (IOException x) {
354             return false;
355         }
356     }
357 
358     boolean implReady() {
359         return bb.hasRemaining() || inReady();
360     }
361 
362     void implClose() throws IOException {
363         if (ch != null) {
364             ch.close();
365         } else {
366             in.close();
367         }
368     }
369 }
--- EOF ---