Path: blob/master/src/java.base/share/classes/sun/nio/cs/StreamDecoder.java
41159 views
/*1* Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26*/2728package sun.nio.cs;2930import java.io.IOException;31import java.io.InputStream;32import java.io.UnsupportedEncodingException;33import java.io.Reader;34import java.nio.ByteBuffer;35import java.nio.CharBuffer;36import java.nio.channels.FileChannel;37import java.nio.channels.ReadableByteChannel;38import java.nio.charset.Charset;39import java.nio.charset.CharsetDecoder;40import java.nio.charset.CoderResult;41import java.nio.charset.CodingErrorAction;42import java.nio.charset.IllegalCharsetNameException;43import java.nio.charset.UnsupportedCharsetException;4445public class StreamDecoder extends Reader {4647private static final int MIN_BYTE_BUFFER_SIZE = 32;48private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;4950private volatile boolean closed;5152private void ensureOpen() throws IOException {53if (closed)54throw new IOException("Stream closed");55}5657// In order to handle surrogates properly we must never try to produce58// fewer than two characters at a time. If we're only asked to return one59// character then the other is saved here to be returned later.60//61private boolean haveLeftoverChar = false;62private char leftoverChar;636465// Factories for java.io.InputStreamReader6667public static StreamDecoder forInputStreamReader(InputStream in,68Object lock,69String charsetName)70throws UnsupportedEncodingException71{72String csn = charsetName;73if (csn == null) {74csn = Charset.defaultCharset().name();75}76try {77return new StreamDecoder(in, lock, Charset.forName(csn));78} catch (IllegalCharsetNameException | UnsupportedCharsetException x) {79throw new UnsupportedEncodingException (csn);80}81}8283public static StreamDecoder forInputStreamReader(InputStream in,84Object lock,85Charset cs)86{87return new StreamDecoder(in, lock, cs);88}8990public static StreamDecoder forInputStreamReader(InputStream in,91Object lock,92CharsetDecoder dec)93{94return new StreamDecoder(in, lock, dec);95}969798// Factory for java.nio.channels.Channels.newReader99100public static StreamDecoder forDecoder(ReadableByteChannel ch,101CharsetDecoder dec,102int minBufferCap)103{104return new StreamDecoder(ch, dec, minBufferCap);105}106107108// -- Public methods corresponding to those in InputStreamReader --109110// All synchronization and state/argument checking is done in these public111// methods; the concrete stream-decoder subclasses defined below need not112// do any such checking.113114public String getEncoding() {115if (isOpen())116return encodingName();117return null;118}119120public int read() throws IOException {121return read0();122}123124@SuppressWarnings("fallthrough")125private int read0() throws IOException {126synchronized (lock) {127128// Return the leftover char, if there is one129if (haveLeftoverChar) {130haveLeftoverChar = false;131return leftoverChar;132}133134// Convert more bytes135char[] cb = new char[2];136int n = read(cb, 0, 2);137switch (n) {138case -1:139return -1;140case 2:141leftoverChar = cb[1];142haveLeftoverChar = true;143// FALL THROUGH144case 1:145return cb[0];146default:147assert false : n;148return -1;149}150}151}152153public int read(char[] cbuf, int offset, int length) throws IOException {154int off = offset;155int len = length;156synchronized (lock) {157ensureOpen();158if ((off < 0) || (off > cbuf.length) || (len < 0) ||159((off + len) > cbuf.length) || ((off + len) < 0)) {160throw new IndexOutOfBoundsException();161}162if (len == 0)163return 0;164165int n = 0;166167if (haveLeftoverChar) {168// Copy the leftover char into the buffer169cbuf[off] = leftoverChar;170off++; len--;171haveLeftoverChar = false;172n = 1;173if ((len == 0) || !implReady())174// Return now if this is all we can produce w/o blocking175return n;176}177178if (len == 1) {179// Treat single-character array reads just like read()180int c = read0();181if (c == -1)182return (n == 0) ? -1 : n;183cbuf[off] = (char)c;184return n + 1;185}186187return n + implRead(cbuf, off, off + len);188}189}190191public boolean ready() throws IOException {192synchronized (lock) {193ensureOpen();194return haveLeftoverChar || implReady();195}196}197198public void close() throws IOException {199synchronized (lock) {200if (closed)201return;202try {203implClose();204} finally {205closed = true;206}207}208}209210private boolean isOpen() {211return !closed;212}213214215// -- Charset-based stream decoder impl --216217private final Charset cs;218private final CharsetDecoder decoder;219private final ByteBuffer bb;220221// Exactly one of these is non-null222private final InputStream in;223private final ReadableByteChannel ch;224225StreamDecoder(InputStream in, Object lock, Charset cs) {226this(in, lock,227cs.newDecoder()228.onMalformedInput(CodingErrorAction.REPLACE)229.onUnmappableCharacter(CodingErrorAction.REPLACE));230}231232StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {233super(lock);234this.cs = dec.charset();235this.decoder = dec;236this.in = in;237this.ch = null;238bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);239bb.flip(); // So that bb is initially empty240}241242StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) {243this.in = null;244this.ch = ch;245this.decoder = dec;246this.cs = dec.charset();247this.bb = ByteBuffer.allocate(mbc < 0248? DEFAULT_BYTE_BUFFER_SIZE249: (mbc < MIN_BYTE_BUFFER_SIZE250? MIN_BYTE_BUFFER_SIZE251: mbc));252bb.flip();253}254255private int readBytes() throws IOException {256bb.compact();257try {258if (ch != null) {259// Read from the channel260int n = ch.read(bb);261if (n < 0)262return n;263} else {264// Read from the input stream, and then update the buffer265int lim = bb.limit();266int pos = bb.position();267assert (pos <= lim);268int rem = (pos <= lim ? lim - pos : 0);269int n = in.read(bb.array(), bb.arrayOffset() + pos, rem);270if (n < 0)271return n;272if (n == 0)273throw new IOException("Underlying input stream returned zero bytes");274assert (n <= rem) : "n = " + n + ", rem = " + rem;275bb.position(pos + n);276}277} finally {278// Flip even when an IOException is thrown,279// otherwise the stream will stutter280bb.flip();281}282283int rem = bb.remaining();284assert (rem != 0) : rem;285return rem;286}287288int implRead(char[] cbuf, int off, int end) throws IOException {289290// In order to handle surrogate pairs, this method requires that291// the invoker attempt to read at least two characters. Saving the292// extra character, if any, at a higher level is easier than trying293// to deal with it here.294assert (end - off > 1);295296CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off);297if (cb.position() != 0) {298// Ensure that cb[0] == cbuf[off]299cb = cb.slice();300}301302boolean eof = false;303for (;;) {304CoderResult cr = decoder.decode(bb, cb, eof);305if (cr.isUnderflow()) {306if (eof)307break;308if (!cb.hasRemaining())309break;310if ((cb.position() > 0) && !inReady())311break; // Block at most once312int n = readBytes();313if (n < 0) {314eof = true;315if ((cb.position() == 0) && (!bb.hasRemaining()))316break;317decoder.reset();318}319continue;320}321if (cr.isOverflow()) {322assert cb.position() > 0;323break;324}325cr.throwException();326}327328if (eof) {329// ## Need to flush decoder330decoder.reset();331}332333if (cb.position() == 0) {334if (eof) {335return -1;336}337assert false;338}339return cb.position();340}341342String encodingName() {343return ((cs instanceof HistoricallyNamedCharset)344? ((HistoricallyNamedCharset)cs).historicalName()345: cs.name());346}347348private boolean inReady() {349try {350return (((in != null) && (in.available() > 0))351|| (ch instanceof FileChannel)); // ## RBC.available()?352} catch (IOException x) {353return false;354}355}356357boolean implReady() {358return bb.hasRemaining() || inReady();359}360361void implClose() throws IOException {362if (ch != null) {363ch.close();364} else {365in.close();366}367}368}369370371