427 lines
13 KiB
Java
427 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
* 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 isOpen = true;
|
|
|
|
private void ensureOpen() throws IOException {
|
|
if (!isOpen)
|
|
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;
|
|
|
|
// Android-added: Flush the CharsetDecoder correctly.
|
|
private boolean needsFlush = false;
|
|
|
|
// 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 (!isOpen)
|
|
return;
|
|
implClose();
|
|
isOpen = false;
|
|
}
|
|
}
|
|
|
|
private boolean isOpen() {
|
|
return isOpen;
|
|
}
|
|
|
|
|
|
// -- 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
|
|
// Android-changed: Use ChannelInputStream.read which throws on non-blocking channels.
|
|
// Other implementations of ReadableByteChannel.read do not, and Channels.newReader
|
|
// is documented to throw on non-blocking.
|
|
// int n = ch.read(bb);
|
|
int n = sun.nio.ch.ChannelInputStream.read(ch, bb, true);
|
|
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();
|
|
|
|
// BEGIN Android-added: Flush the CharsetDecoder properly.
|
|
if (needsFlush) {
|
|
CoderResult cr = decoder.flush(cb);
|
|
if (cr.isOverflow()) {
|
|
// We've overflowed, we'll have to come back round and ask for more data.
|
|
return cb.position();
|
|
}
|
|
|
|
// By definition, we're at the end of the stream here.
|
|
if (cr.isUnderflow()) {
|
|
if (cb.position() == 0) {
|
|
return -1;
|
|
}
|
|
|
|
return cb.position();
|
|
}
|
|
|
|
cr.throwException();
|
|
// Unreachable.
|
|
}
|
|
// END Android-added: Flush the CharsetDecoder properly.
|
|
|
|
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;
|
|
// Android-removed: Flush the CharsetDecoder correctly.
|
|
// We want to go 'round the loop one more time with "eof = true".
|
|
// We also don't want to reset the decoder here because we might potentially need
|
|
// to flush it later.
|
|
// if ((cb.position() == 0) && (!bb.hasRemaining()))
|
|
// break;
|
|
// decoder.reset();
|
|
}
|
|
continue;
|
|
}
|
|
if (cr.isOverflow()) {
|
|
assert cb.position() > 0;
|
|
break;
|
|
}
|
|
cr.throwException();
|
|
}
|
|
|
|
if (eof) {
|
|
// BEGIN Android-changed: Flush the CharsetDecoder correctly.
|
|
// // ## Need to flush decoder
|
|
// decoder.reset();
|
|
CoderResult cr = decoder.flush(cb);
|
|
if (cr.isOverflow()) {
|
|
needsFlush = true;
|
|
return cb.position();
|
|
}
|
|
|
|
decoder.reset();
|
|
if (!cr.isUnderflow()) {
|
|
cr.throwException();
|
|
}
|
|
// END Android-changed: Flush the CharsetDecoder correctly.
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
}
|