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 closed; 43 44 private void ensureOpen() throws IOException { 45 if (closed) 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 (closed) 192 return; 193 implClose(); 194 closed = true; 195 } 196 } 197 198 private boolean isOpen() { 199 return !closed; 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 }