View Javadoc
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  
29  package sun.nio.cs;
30  
31  import java.io.*;
32  import java.nio.*;
33  import java.nio.channels.*;
34  import java.nio.charset.*;
35  
36  public 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 isOpen = true;
43  
44      private void ensureOpen() throws IOException {
45          if (!isOpen)
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 (!isOpen)
192                 return;
193             implClose();
194             isOpen = false;
195         }
196     }
197 
198     private boolean isOpen() {
199         return isOpen;
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 }