blob: 206f0c8f74b377a8fa84367002cd2be1a9d2f3a2 [file] [log] [blame]
/**
*******************************************************************************
* Copyright (C) 1996-2006, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
*******************************************************************************
*/
/**
* A JNI interface for ICU converters.
*
*
* @author Ram Viswanadha, IBM
*/
package com.ibm.icu4jni.charset;
import com.ibm.icu4jni.common.ErrorCode;
// BEGIN android-removed
// import com.ibm.icu4jni.converters.NativeConverter;
// END android-removed
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.ByteBuffer;
public final class CharsetDecoderICU extends CharsetDecoder{
private static final int INPUT_OFFSET = 0,
OUTPUT_OFFSET = 1,
INVALID_BYTES = 2,
INPUT_HELD = 3,
LIMIT = 4;
/* data is 3 element array where
* data[INPUT_OFFSET] = on input contains the start of input and on output the number of input chars consumed
* data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output bytes written
* data[INVALID_CHARS] = number of invalid chars
* data[INPUT_HELD] = number of input chars held in the converter's state
*/
private int[] data = new int[LIMIT];
/* handle to the ICU converter that is opened */
private long converterHandle=0;
private byte[] input = null;
private char[] output= null;
// BEGIN android-added
private byte[] allocatedInput = null;
private char[] allocatedOutput = null;
// END android-added
// These instance variables are
// always assigned in the methods
// before being used. This class
// inhrently multithread unsafe
// so we dont have to worry about
// synchronization
private int inEnd;
private int outEnd;
private int ec;
private int onUnmappableInput = NativeConverter.STOP_CALLBACK;;
private int onMalformedInput = NativeConverter.STOP_CALLBACK;;
private int savedInputHeldLen;
/**
* Constructs a new decoder for the given charset
* @param cs for which the decoder is created
* @param cHandle the address of ICU converter
* @exception RuntimeException
* @stable ICU 2.4
*/
public CharsetDecoderICU(Charset cs,long cHandle){
super(cs,
NativeConverter.getAveCharsPerByte(cHandle),
NativeConverter.getMaxCharsPerByte(cHandle)
);
char[] sub = replacement().toCharArray();
ec = NativeConverter.setCallbackDecode(cHandle,
onMalformedInput,
onUnmappableInput,
sub, sub.length);
if(ErrorCode.isFailure(ec)){
throw ErrorCode.getException(ec);
}
// store the converter handle
converterHandle=cHandle;
}
/**
* Sets this decoders replacement string. Substitutes the string in input if an
* umappable or illegal sequence is encountered
* @param newReplacement to replace the error bytes with
* @stable ICU 2.4
*/
protected void implReplaceWith(String newReplacement) {
if(converterHandle > 0){
if( newReplacement.length() > NativeConverter.getMaxBytesPerChar(converterHandle)) {
throw new IllegalArgumentException();
}
ec =NativeConverter.setSubstitutionChars(converterHandle,
newReplacement.toCharArray(),
newReplacement.length()
);
if(ErrorCode.isFailure(ec)){
throw ErrorCode.getException(ec);
}
}
}
/**
* Sets the action to be taken if an illegal sequence is encountered
* @param newAction action to be taken
* @exception IllegalArgumentException
* @stable ICU 2.4
*/
protected final void implOnMalformedInput(CodingErrorAction newAction) {
if(newAction.equals(CodingErrorAction.IGNORE)){
onMalformedInput = NativeConverter.SKIP_CALLBACK;
}else if(newAction.equals(CodingErrorAction.REPLACE)){
onMalformedInput = NativeConverter.SUBSTITUTE_CALLBACK;
}else if(newAction.equals(CodingErrorAction.REPORT)){
onMalformedInput = NativeConverter.STOP_CALLBACK;
}
char[] sub = replacement().toCharArray();
//System.out.println(" setting callbacks mfi " + onMalformedInput +" umi " + onUnmappableInput);
ec = NativeConverter.setCallbackDecode(converterHandle, onMalformedInput, onUnmappableInput, sub, sub.length);
if(ErrorCode.isFailure(ec)){
throw ErrorCode.getException(ec);
}
}
/**
* Sets the action to be taken if an illegal sequence is encountered
* @param newAction action to be taken
* @exception IllegalArgumentException
* @stable ICU 2.4
*/
protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
if(newAction.equals(CodingErrorAction.IGNORE)){
onUnmappableInput = NativeConverter.SKIP_CALLBACK;
}else if(newAction.equals(CodingErrorAction.REPLACE)){
onUnmappableInput = NativeConverter.SUBSTITUTE_CALLBACK;
}else if(newAction.equals(CodingErrorAction.REPORT)){
onUnmappableInput = NativeConverter.STOP_CALLBACK;
}
char[] sub = replacement().toCharArray();
ec = NativeConverter.setCallbackDecode(converterHandle,onMalformedInput, onUnmappableInput, sub, sub.length);
if(ErrorCode.isFailure(ec)){
throw ErrorCode.getException(ec);
}
}
/**
* Flushes any characters saved in the converter's internal buffer and
* resets the converter.
* @param out action to be taken
* @return result of flushing action and completes the decoding all input.
* Returns CoderResult.UNDERFLOW if the action succeeds.
* @stable ICU 2.4
*/
protected final CoderResult implFlush(CharBuffer out) {
try{
data[OUTPUT_OFFSET] = getArray(out);
ec=NativeConverter.flushByteToChar(
converterHandle, /* Handle to ICU Converter */
output, /* input array of chars */
outEnd, /* input index+1 to be written */
data /* contains data, inOff,outOff */
);
/* If we don't have room for the output, throw an exception*/
if (ErrorCode.isFailure(ec)) {
if (ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
}else if (ec == ErrorCode.U_TRUNCATED_CHAR_FOUND ) {//CSDL: add this truncated character error handling
if(data[INPUT_OFFSET]>0){
return CoderResult.malformedForLength(data[INPUT_OFFSET]);
}
}else {
ErrorCode.getException(ec);
}
}
return CoderResult.UNDERFLOW;
}finally{
/* save the flushed data */
setPosition(out);
implReset();
}
}
/**
* Resets the to Unicode mode of converter
* @stable ICU 2.4
*/
protected void implReset() {
NativeConverter.resetByteToChar(converterHandle);
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = 0;
data[INVALID_BYTES] = 0;
data[INPUT_HELD] = 0;
savedInputHeldLen = 0;
output = null;
input = null;
}
/**
* Decodes one or more bytes. The default behaviour of the converter
* is stop and report if an error in input stream is encountered.
* To set different behaviour use @see CharsetDecoder.onMalformedInput()
* This method allows a buffer by buffer conversion of a data stream.
* The state of the conversion is saved between calls to convert.
* Among other things, this means multibyte input sequences can be
* split between calls. If a call to convert results in an Error, the
* conversion may be continued by calling convert again with suitably
* modified parameters.All conversions should be finished with a call to
* the flush method.
* @param in buffer to decode
* @param out buffer to populate with decoded result
* @return result of decoding action. Returns CoderResult.UNDERFLOW if the decoding
* action succeeds or more input is needed for completing the decoding action.
* @stable ICU 2.4
*/
protected CoderResult decodeLoop(ByteBuffer in,CharBuffer out){
if(!in.hasRemaining()){
return CoderResult.UNDERFLOW;
}
data[INPUT_OFFSET] = getArray(in);
data[OUTPUT_OFFSET]= getArray(out);
data[INPUT_HELD] = 0;
try{
/* do the conversion */
ec=NativeConverter.decode(
converterHandle, /* Handle to ICU Converter */
input, /* input array of bytes */
inEnd, /* last index+1 to be converted */
output, /* input array of chars */
outEnd, /* input index+1 to be written */
data, /* contains data, inOff,outOff */
false /* donot flush the data */
);
/* return an error*/
if(ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR){
return CoderResult.OVERFLOW;
}else if(ec==ErrorCode.U_INVALID_CHAR_FOUND){
return CoderResult.malformedForLength(data[INVALID_BYTES]);
}else if(ec==ErrorCode.U_ILLEGAL_CHAR_FOUND){
return CoderResult.malformedForLength(data[INVALID_BYTES]);
}
/* decoding action succeded */
return CoderResult.UNDERFLOW;
}finally{
setPosition(in);
setPosition(out);
}
}
/**
* Releases the system resources by cleanly closing ICU converter opened
* @stable ICU 2.4
*/
protected void finalize()throws Throwable{
NativeConverter.closeConverter(converterHandle);
super.finalize();
converterHandle = 0;
}
//------------------------------------------
// private utility methods
//------------------------------------------
private final int getArray(CharBuffer out){
if(out.hasArray()){
output = out.array();
outEnd = out.limit();
return out.position();
}else{
outEnd = out.remaining();
// BEGIN android-added
if (allocatedOutput == null || (outEnd > allocatedOutput.length)) {
allocatedOutput = new char[outEnd];
}
output = allocatedOutput;
// END android-added
//since the new
// buffer start position
// is 0
return 0;
}
}
private final int getArray(ByteBuffer in){
if(in.hasArray()){
input = in.array();
inEnd = in.limit();
return in.position()+savedInputHeldLen;/*exclude the number fo bytes held in previous conversion*/
}else{
inEnd = in.remaining();
// BEGIN android-added
if (allocatedInput == null || (inEnd > allocatedInput.length)) {
allocatedInput = new byte[inEnd];
}
input = allocatedInput;
// END android-added
// save the current position
int pos = in.position();
in.get(input,0,inEnd);
// reset the position
in.position(pos);
// the start position
// of the new buffer
// is whatever is savedInputLen
return savedInputHeldLen;
}
}
private final void setPosition(CharBuffer out){
if(out.hasArray()){
out.position(out.position() + data[OUTPUT_OFFSET]);
}else{
out.put(output,0,data[OUTPUT_OFFSET]);
}
// BEGIN android-added
// release reference to output array, which may not be ours
output = null;
// END android-added
}
private final void setPosition(ByteBuffer in){
// ok was there input held in the previous invocation of decodeLoop
// that resulted in output in this invocation?
if(data[OUTPUT_OFFSET]>0 && savedInputHeldLen >0){
int len = in.position() + data[INPUT_OFFSET] + savedInputHeldLen;
in.position(len);
savedInputHeldLen = data[INPUT_HELD];
}else{
in.position(in.position() + data[INPUT_OFFSET] + savedInputHeldLen);
savedInputHeldLen = data[INPUT_HELD];
in.position(in.position() - savedInputHeldLen);
}
// BEGIN android-added
// release reference to input array, which may not be ours
input = null;
// END android-added
}
}