219 lines
7.8 KiB
Java
219 lines
7.8 KiB
Java
/**
|
|
*******************************************************************************
|
|
* 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.android.icu.charset;
|
|
|
|
import dalvik.annotation.optimization.ReachabilitySensitive;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.CharBuffer;
|
|
import java.nio.charset.Charset;
|
|
import java.nio.charset.CharsetDecoder;
|
|
import java.nio.charset.CoderResult;
|
|
import java.nio.charset.CodingErrorAction;
|
|
|
|
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;
|
|
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
|
|
|
/*
|
|
* 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, cleaned up via NativeAllocationRegistry. */
|
|
@ReachabilitySensitive
|
|
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;
|
|
CharsetDecoderICU result;
|
|
try {
|
|
address = NativeConverter.openConverter(icuCanonicalName);
|
|
float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address);
|
|
result = new CharsetDecoderICU(cs, averageCharsPerByte, address);
|
|
} catch (Throwable t) {
|
|
if (address != 0) {
|
|
NativeConverter.closeConverter(address);
|
|
}
|
|
throw t;
|
|
}
|
|
// An exception in registerConverter() will deallocate address:
|
|
NativeConverter.registerConverter(result, address);
|
|
result.updateCallback();
|
|
return result;
|
|
}
|
|
|
|
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 = EMPTY_BYTE_ARRAY;
|
|
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 (NativeConverter.U_FAILURE(error)) {
|
|
if (error == NativeConverter.U_BUFFER_OVERFLOW_ERROR) {
|
|
return CoderResult.OVERFLOW;
|
|
} else if (error == NativeConverter.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 (NativeConverter.U_FAILURE(error)) {
|
|
if (error == NativeConverter.U_BUFFER_OVERFLOW_ERROR) {
|
|
return CoderResult.OVERFLOW;
|
|
} else if (error == NativeConverter.U_INVALID_CHAR_FOUND) {
|
|
return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
|
|
} else if (error == NativeConverter.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);
|
|
}
|
|
}
|
|
|
|
|
|
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]);
|
|
} 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;
|
|
}
|
|
}
|