/**
*******************************************************************************
* Copyright (C) 1996-2006, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
*******************************************************************************
*/
/**
* A JNI interface for ICU converters.
*
*
* @author Ram Viswanadha, IBM
*/
package java.nio.charset;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import libcore.icu.ICU;
import libcore.icu.NativeConverter;
import libcore.util.EmptyArray;
final class CharsetDecoderICU extends CharsetDecoder {
private static final int MAX_CHARS_PER_BYTE = 2;
private static final int INPUT_OFFSET = 0;
private static final int OUTPUT_OFFSET = 1;
private static final int INVALID_BYTE_COUNT = 2;
/*
* data[INPUT_OFFSET] = on input contains the start of input and on output the number of input bytes consumed
* data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output chars written
* data[INVALID_BYTE_COUNT] = number of invalid bytes
*/
private final int[] data = new int[3];
/* handle to the ICU converter that is opened */
private long converterHandle = 0;
private byte[] input = null;
private char[] output= null;
private byte[] allocatedInput = null;
private char[] allocatedOutput = null;
// These instance variables are always assigned in the methods before being used. This class
// is inherently thread-unsafe so we don't have to worry about synchronization.
private int inEnd;
private int outEnd;
public static CharsetDecoderICU newInstance(Charset cs, String icuCanonicalName) {
// This complexity is necessary to ensure that even if the constructor, superclass
// constructor, or call to updateCallback throw, we still free the native peer.
long address = 0;
try {
address = NativeConverter.openConverter(icuCanonicalName);
float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address);
CharsetDecoderICU result = new CharsetDecoderICU(cs, averageCharsPerByte, address);
address = 0; // CharsetDecoderICU has taken ownership; its finalizer will do the free.
result.updateCallback();
return result;
} finally {
if (address != 0) {
NativeConverter.closeConverter(address);
}
}
}
private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) {
super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE);
this.converterHandle = address;
}
@Override protected void implReplaceWith(String newReplacement) {
updateCallback();
}
@Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
updateCallback();
}
@Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
updateCallback();
}
private void updateCallback() {
NativeConverter.setCallbackDecode(converterHandle, this);
}
@Override protected void implReset() {
NativeConverter.resetByteToChar(converterHandle);
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = 0;
data[INVALID_BYTE_COUNT] = 0;
output = null;
input = null;
allocatedInput = null;
allocatedOutput = null;
inEnd = 0;
outEnd = 0;
}
@Override protected final CoderResult implFlush(CharBuffer out) {
try {
// ICU needs to see an empty input.
input = EmptyArray.BYTE;
inEnd = 0;
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = getArray(out);
data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors.
int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true);
if (ICU.U_FAILURE(error)) {
if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
} else if (error == ICU.U_TRUNCATED_CHAR_FOUND) {
if (data[INVALID_BYTE_COUNT] > 0) {
return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
}
}
}
return CoderResult.UNDERFLOW;
} finally {
setPosition(out);
implReset();
}
}
@Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
if (!in.hasRemaining()) {
return CoderResult.UNDERFLOW;
}
data[INPUT_OFFSET] = getArray(in);
data[OUTPUT_OFFSET]= getArray(out);
try {
int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false);
if (ICU.U_FAILURE(error)) {
if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
} else if (error == ICU.U_INVALID_CHAR_FOUND) {
return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
} else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
} else {
throw new AssertionError(error);
}
}
// Decoding succeeded: give us more data.
return CoderResult.UNDERFLOW;
} finally {
setPosition(in);
setPosition(out);
}
}
@Override protected void finalize() throws Throwable {
try {
NativeConverter.closeConverter(converterHandle);
converterHandle = 0;
} finally {
super.finalize();
}
}
private int getArray(CharBuffer out) {
if (out.hasArray()) {
output = out.array();
outEnd = out.arrayOffset() + out.limit();
return out.arrayOffset() + out.position();
} else {
outEnd = out.remaining();
if (allocatedOutput == null || outEnd > allocatedOutput.length) {
allocatedOutput = new char[outEnd];
}
// The array's start position is 0.
output = allocatedOutput;
return 0;
}
}
private int getArray(ByteBuffer in) {
if (in.hasArray()) {
input = in.array();
inEnd = in.arrayOffset() + in.limit();
return in.arrayOffset() + in.position();
} else {
inEnd = in.remaining();
if (allocatedInput == null || inEnd > allocatedInput.length) {
allocatedInput = new byte[inEnd];
}
// Copy the input buffer into the allocated array.
int pos = in.position();
in.get(allocatedInput, 0, inEnd);
in.position(pos);
// The array's start position is 0.
input = allocatedInput;
return 0;
}
}
private void setPosition(CharBuffer out) {
if (out.hasArray()) {
out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset());
} else {
out.put(output, 0, data[OUTPUT_OFFSET]);
}
// release reference to output array, which may not be ours
output = null;
}
private void setPosition(ByteBuffer in) {
in.position(in.position() + data[INPUT_OFFSET]);
// release reference to input array, which may not be ours
input = null;
}
}