| /* |
| * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| */ |
| |
| package sun.nio.cs; |
| |
| import java.io.*; |
| import java.nio.*; |
| import java.nio.channels.*; |
| import java.nio.charset.*; |
| |
| public class StreamDecoder extends Reader |
| { |
| |
| private static final int MIN_BYTE_BUFFER_SIZE = 32; |
| private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192; |
| |
| private volatile boolean closed; |
| |
| private void ensureOpen() throws IOException { |
| if (closed) |
| throw new IOException("Stream closed"); |
| } |
| |
| // In order to handle surrogates properly we must never try to produce |
| // fewer than two characters at a time. If we're only asked to return one |
| // character then the other is saved here to be returned later. |
| // |
| private boolean haveLeftoverChar = false; |
| private char leftoverChar; |
| |
| |
| // Factories for java.io.InputStreamReader |
| |
| public static StreamDecoder forInputStreamReader(InputStream in, |
| Object lock, |
| String charsetName) |
| throws UnsupportedEncodingException |
| { |
| String csn = charsetName; |
| if (csn == null) |
| csn = Charset.defaultCharset().name(); |
| try { |
| if (Charset.isSupported(csn)) |
| return new StreamDecoder(in, lock, Charset.forName(csn)); |
| } catch (IllegalCharsetNameException x) { } |
| throw new UnsupportedEncodingException (csn); |
| } |
| |
| public static StreamDecoder forInputStreamReader(InputStream in, |
| Object lock, |
| Charset cs) |
| { |
| return new StreamDecoder(in, lock, cs); |
| } |
| |
| public static StreamDecoder forInputStreamReader(InputStream in, |
| Object lock, |
| CharsetDecoder dec) |
| { |
| return new StreamDecoder(in, lock, dec); |
| } |
| |
| |
| // Factory for java.nio.channels.Channels.newReader |
| |
| public static StreamDecoder forDecoder(ReadableByteChannel ch, |
| CharsetDecoder dec, |
| int minBufferCap) |
| { |
| return new StreamDecoder(ch, dec, minBufferCap); |
| } |
| |
| |
| // -- Public methods corresponding to those in InputStreamReader -- |
| |
| // All synchronization and state/argument checking is done in these public |
| // methods; the concrete stream-decoder subclasses defined below need not |
| // do any such checking. |
| |
| public String getEncoding() { |
| if (isOpen()) |
| return encodingName(); |
| return null; |
| } |
| |
| public int read() throws IOException { |
| return read0(); |
| } |
| |
| @SuppressWarnings("fallthrough") |
| private int read0() throws IOException { |
| synchronized (lock) { |
| |
| // Return the leftover char, if there is one |
| if (haveLeftoverChar) { |
| haveLeftoverChar = false; |
| return leftoverChar; |
| } |
| |
| // Convert more bytes |
| char cb[] = new char[2]; |
| int n = read(cb, 0, 2); |
| switch (n) { |
| case -1: |
| return -1; |
| case 2: |
| leftoverChar = cb[1]; |
| haveLeftoverChar = true; |
| // FALL THROUGH |
| case 1: |
| return cb[0]; |
| default: |
| assert false : n; |
| return -1; |
| } |
| } |
| } |
| |
| public int read(char cbuf[], int offset, int length) throws IOException { |
| int off = offset; |
| int len = length; |
| synchronized (lock) { |
| ensureOpen(); |
| if ((off < 0) || (off > cbuf.length) || (len < 0) || |
| ((off + len) > cbuf.length) || ((off + len) < 0)) { |
| throw new IndexOutOfBoundsException(); |
| } |
| if (len == 0) |
| return 0; |
| |
| int n = 0; |
| |
| if (haveLeftoverChar) { |
| // Copy the leftover char into the buffer |
| cbuf[off] = leftoverChar; |
| off++; len--; |
| haveLeftoverChar = false; |
| n = 1; |
| if ((len == 0) || !implReady()) |
| // Return now if this is all we can produce w/o blocking |
| return n; |
| } |
| |
| if (len == 1) { |
| // Treat single-character array reads just like read() |
| int c = read0(); |
| if (c == -1) |
| return (n == 0) ? -1 : n; |
| cbuf[off] = (char)c; |
| return n + 1; |
| } |
| |
| return n + implRead(cbuf, off, off + len); |
| } |
| } |
| |
| public boolean ready() throws IOException { |
| synchronized (lock) { |
| ensureOpen(); |
| return haveLeftoverChar || implReady(); |
| } |
| } |
| |
| public void close() throws IOException { |
| synchronized (lock) { |
| if (closed) |
| return; |
| implClose(); |
| closed = true; |
| } |
| } |
| |
| private boolean isOpen() { |
| return !closed; |
| } |
| |
| |
| // -- Charset-based stream decoder impl -- |
| |
| // In the early stages of the build we haven't yet built the NIO native |
| // code, so guard against that by catching the first UnsatisfiedLinkError |
| // and setting this flag so that later attempts fail quickly. |
| // |
| private static volatile boolean channelsAvailable = true; |
| |
| private static FileChannel getChannel(FileInputStream in) { |
| if (!channelsAvailable) |
| return null; |
| try { |
| return in.getChannel(); |
| } catch (UnsatisfiedLinkError x) { |
| channelsAvailable = false; |
| return null; |
| } |
| } |
| |
| private Charset cs; |
| private CharsetDecoder decoder; |
| private ByteBuffer bb; |
| |
| // Exactly one of these is non-null |
| private InputStream in; |
| private ReadableByteChannel ch; |
| |
| StreamDecoder(InputStream in, Object lock, Charset cs) { |
| this(in, lock, |
| cs.newDecoder() |
| .onMalformedInput(CodingErrorAction.REPLACE) |
| .onUnmappableCharacter(CodingErrorAction.REPLACE)); |
| } |
| |
| StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) { |
| super(lock); |
| this.cs = dec.charset(); |
| this.decoder = dec; |
| |
| // This path disabled until direct buffers are faster |
| if (false && in instanceof FileInputStream) { |
| ch = getChannel((FileInputStream)in); |
| if (ch != null) |
| bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE); |
| } |
| if (ch == null) { |
| this.in = in; |
| this.ch = null; |
| bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE); |
| } |
| bb.flip(); // So that bb is initially empty |
| } |
| |
| StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) { |
| this.in = null; |
| this.ch = ch; |
| this.decoder = dec; |
| this.cs = dec.charset(); |
| this.bb = ByteBuffer.allocate(mbc < 0 |
| ? DEFAULT_BYTE_BUFFER_SIZE |
| : (mbc < MIN_BYTE_BUFFER_SIZE |
| ? MIN_BYTE_BUFFER_SIZE |
| : mbc)); |
| bb.flip(); |
| } |
| |
| private int readBytes() throws IOException { |
| bb.compact(); |
| try { |
| if (ch != null) { |
| // Read from the channel |
| int n = ch.read(bb); |
| if (n < 0) |
| return n; |
| } else { |
| // Read from the input stream, and then update the buffer |
| int lim = bb.limit(); |
| int pos = bb.position(); |
| assert (pos <= lim); |
| int rem = (pos <= lim ? lim - pos : 0); |
| assert rem > 0; |
| int n = in.read(bb.array(), bb.arrayOffset() + pos, rem); |
| if (n < 0) |
| return n; |
| if (n == 0) |
| throw new IOException("Underlying input stream returned zero bytes"); |
| assert (n <= rem) : "n = " + n + ", rem = " + rem; |
| bb.position(pos + n); |
| } |
| } finally { |
| // Flip even when an IOException is thrown, |
| // otherwise the stream will stutter |
| bb.flip(); |
| } |
| |
| int rem = bb.remaining(); |
| assert (rem != 0) : rem; |
| return rem; |
| } |
| |
| int implRead(char[] cbuf, int off, int end) throws IOException { |
| |
| // In order to handle surrogate pairs, this method requires that |
| // the invoker attempt to read at least two characters. Saving the |
| // extra character, if any, at a higher level is easier than trying |
| // to deal with it here. |
| assert (end - off > 1); |
| |
| CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off); |
| if (cb.position() != 0) |
| // Ensure that cb[0] == cbuf[off] |
| cb = cb.slice(); |
| |
| boolean eof = false; |
| for (;;) { |
| CoderResult cr = decoder.decode(bb, cb, eof); |
| if (cr.isUnderflow()) { |
| if (eof) |
| break; |
| if (!cb.hasRemaining()) |
| break; |
| if ((cb.position() > 0) && !inReady()) |
| break; // Block at most once |
| int n = readBytes(); |
| if (n < 0) { |
| eof = true; |
| if ((cb.position() == 0) && (!bb.hasRemaining())) |
| break; |
| decoder.reset(); |
| } |
| continue; |
| } |
| if (cr.isOverflow()) { |
| assert cb.position() > 0; |
| break; |
| } |
| cr.throwException(); |
| } |
| |
| if (eof) { |
| // ## Need to flush decoder |
| decoder.reset(); |
| } |
| |
| if (cb.position() == 0) { |
| if (eof) |
| return -1; |
| assert false; |
| } |
| return cb.position(); |
| } |
| |
| String encodingName() { |
| return ((cs instanceof HistoricallyNamedCharset) |
| ? ((HistoricallyNamedCharset)cs).historicalName() |
| : cs.name()); |
| } |
| |
| private boolean inReady() { |
| try { |
| return (((in != null) && (in.available() > 0)) |
| || (ch instanceof FileChannel)); // ## RBC.available()? |
| } catch (IOException x) { |
| return false; |
| } |
| } |
| |
| boolean implReady() { |
| return bb.hasRemaining() || inReady(); |
| } |
| |
| void implClose() throws IOException { |
| if (ch != null) |
| ch.close(); |
| else |
| in.close(); |
| } |
| |
| } |