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