package org.commoncrawl.io;
/*
* 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/*
* @(#)NIOStreamDecoder.java 1.24 07/05/05
*/
/**
* MODIFIED TO UTILIZE NIOBuffer more efficiently
*
* @author rana
*
*/
/*Derrived FROM Decoder in Java Source */
import java.io.IOException;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
public class NIOStreamDecoder extends Reader {
private volatile boolean isOpen = true;
// 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;
private Charset cs;
// -- 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.
private CharsetDecoder decoder;
private ByteBuffer bb;
// Exactly one of these is non-null
private NIOBufferList ch;
NIOStreamDecoder(NIOBufferList ch, CharsetDecoder dec) throws IOException {
this.ch = ch;
this.decoder = dec;
this.cs = dec.charset();
this.bb = ch.read();
}
@Override
public void close() throws IOException {
synchronized (lock) {
if (!isOpen)
return;
implClose();
isOpen = false;
}
}
String encodingName() {
return (cs.name());
}
private void ensureOpen() throws IOException {
if (!isOpen)
throw new IOException("Stream closed");
}
// -- Charset-based stream decoder impl --
public String getEncoding() {
if (isOpen())
return encodingName();
return null;
}
void implClose() throws IOException {
if (bb != null)
bb = null;
}
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 == null || !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();
}
boolean implReady() {
return bb.hasRemaining() || inReady();
}
private boolean inReady() {
return ((ch != null) && (ch.isDataAvailable()));
}
private boolean isOpen() {
return isOpen;
}
@Override
public int read() throws IOException {
return read0();
}
@Override
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);
}
}
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;
}
}
}
private int readBytes() throws IOException {
try {
// put back existing buffer if there is still usable data in it ...
if (bb.remaining() != 0)
ch.putBack(bb);
bb = null;
// now read from the buffer (with a min size of 8)...
bb = ch.read(8);
} catch (IOException e) {
bb = null;
}
if (bb != null)
return bb.remaining();
else
return -1;
}
@Override
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen();
return haveLeftoverChar || implReady();
}
}
}