/**
* Copyright 2015 Santhosh Kumar Tekuri
*
* The JLibs authors license this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package jlibs.nbp;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
/**
* @author Santhosh Kumar T
*/
public class NBChannel implements ReadableCharChannel{
public static final int DEFAULT_BUFFER_SIZE = 8192;
private ReadableByteChannel channel;
private final ByteBuffer byteBuffer;
public NBChannel(ReadableByteChannel channel, int bufferSize){
byteBuffer = ByteBuffer.allocate(bufferSize);
setChannel(channel);
}
public NBChannel(ReadableByteChannel channel){
this(channel, DEFAULT_BUFFER_SIZE);
}
public ReadableByteChannel getChannel(){
return channel;
}
public void setChannel(ReadableByteChannel channel){
this.channel = channel;
byteBuffer.clear();
eofSeen = decode = false;
fallbackEncoding = Charset.defaultCharset().name();
encoding = null;
decoder = null;
}
public String fallbackEncoding;
public String encoding;
public void setEncoding(String encoding, boolean fallback){
if(fallback)
fallbackEncoding = encoding;
else
this.encoding = encoding;
}
private CharsetDecoder decoder;
public CharsetDecoder decoder(){
return decoder;
}
public void decoder(CharsetDecoder decoder){
this.decoder = decoder;
}
protected CharsetDecoder createDecoder(ByteBuffer byteBuffer, boolean eof){
if(byteBuffer.remaining()>=4 || eof){
byte marker[] = new byte[Math.min(4, byteBuffer.remaining())];
for(int i=marker.length-1; i>=0; i--)
marker[i] = byteBuffer.get(i);
String encoding = this.encoding;
BOM bom = BOM.get(marker, true);
if(bom!=null){
byteBuffer.position(bom.with().length);
if(encoding==null)
encoding = bom.encoding();
}else if(encoding==null){
bom = BOM.get(marker, false);
encoding = bom==null ? fallbackEncoding : bom.encoding();
}
return Charset.forName(encoding).newDecoder();
}else
return null;
}
private boolean eofSeen, decode;
@Override
public int read(CharBuffer charBuffer) throws IOException{
int pos = charBuffer.position();
while(true){
if(!decode){
if(eofSeen)
return -1;
else{
int read=channel.read(byteBuffer);
if(read==0)
break;
else if(read<0)
eofSeen = true;
decode = true;
byteBuffer.flip();
}
}
if(decoder==null){
try{
CharsetDecoder decoder = createDecoder(byteBuffer, eofSeen);
if(decoder!=null)
decoder(decoder);
else {
decode = false;
byteBuffer.position(byteBuffer.limit());
byteBuffer.limit(byteBuffer.capacity());
continue;
}
}catch(Exception ex){
throw new IOException(ex);
}
}
CoderResult cr = decoder.decode(byteBuffer, charBuffer, eofSeen);
if(cr.isOverflow()) // insufficient space in charBuffer
break;
else if(cr.isUnderflow()){ // required more bytes
if(eofSeen){
if(byteBuffer.hasRemaining())
break;
else{
if(charBuffer.position()==pos)
return -1;
decode = false;
break;
}
}else{
decode = false;
byteBuffer.compact();
if(!charBuffer.hasRemaining())
break;
}
}else
cr.throwException();
}
return charBuffer.position()-pos;
}
@Override
public boolean isOpen(){
return channel!=null;
}
@Override
public void close() throws IOException{
channel.close();
channel = null;
}
}