CharsetDecoderICU.java revision 3664d8839f0ba794f428119ee7f7304a66861da5
1/** 2******************************************************************************* 3* Copyright (C) 1996-2006, International Business Machines Corporation and * 4* others. All Rights Reserved. * 5******************************************************************************* 6* 7******************************************************************************* 8*/ 9 /** 10 * A JNI interface for ICU converters. 11 * 12 * 13 * @author Ram Viswanadha, IBM 14 */ 15package java.nio.charset; 16 17import java.nio.ByteBuffer; 18import java.nio.CharBuffer; 19import libcore.icu.ErrorCode; 20import libcore.icu.NativeConverter; 21import libcore.util.EmptyArray; 22 23public final class CharsetDecoderICU extends CharsetDecoder { 24 private static final int MAX_CHARS_PER_BYTE = 2; 25 26 private static final int INPUT_OFFSET = 0; 27 private static final int OUTPUT_OFFSET = 1; 28 private static final int INVALID_BYTES = 2; 29 /* 30 * data[INPUT_OFFSET] = on input contains the start of input and on output the number of input bytes consumed 31 * data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output chars written 32 * data[INVALID_BYTES] = number of invalid bytes 33 */ 34 private int[] data = new int[3]; 35 36 /* handle to the ICU converter that is opened */ 37 private long converterHandle = 0; 38 39 private byte[] input = null; 40 private char[] output= null; 41 42 private byte[] allocatedInput = null; 43 private char[] allocatedOutput = null; 44 45 // These instance variables are always assigned in the methods before being used. This class 46 // is inherently thread-unsafe so we don't have to worry about synchronization. 47 private int inEnd; 48 private int outEnd; 49 private int ec; 50 51 public static CharsetDecoderICU newInstance(Charset cs, String icuCanonicalName) { 52 // This complexity is necessary to ensure that even if the constructor, superclass 53 // constructor, or call to updateCallback throw, we still free the native peer. 54 long address = 0; 55 try { 56 address = NativeConverter.openConverter(icuCanonicalName); 57 float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address); 58 CharsetDecoderICU result = new CharsetDecoderICU(cs, averageCharsPerByte, address); 59 address = 0; // CharsetDecoderICU has taken ownership; its finalizer will do the free. 60 result.updateCallback(); 61 return result; 62 } finally { 63 if (address != 0) { 64 NativeConverter.closeConverter(address); 65 } 66 } 67 } 68 69 private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) { 70 super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE); 71 this.converterHandle = address; 72 } 73 74 @Override protected void implReplaceWith(String newReplacement) { 75 updateCallback(); 76 } 77 78 @Override protected final void implOnMalformedInput(CodingErrorAction newAction) { 79 updateCallback(); 80 } 81 82 @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) { 83 updateCallback(); 84 } 85 86 private void updateCallback() { 87 ec = NativeConverter.setCallbackDecode(converterHandle, this); 88 if (ErrorCode.isFailure(ec)) { 89 throw ErrorCode.throwException(ec); 90 } 91 } 92 93 @Override protected void implReset() { 94 NativeConverter.resetByteToChar(converterHandle); 95 data[INPUT_OFFSET] = 0; 96 data[OUTPUT_OFFSET] = 0; 97 data[INVALID_BYTES] = 0; 98 output = null; 99 input = null; 100 allocatedInput = null; 101 allocatedOutput = null; 102 ec = 0; 103 inEnd = 0; 104 outEnd = 0; 105 } 106 107 @Override protected final CoderResult implFlush(CharBuffer out) { 108 try { 109 // ICU needs to see an empty input. 110 input = EmptyArray.BYTE; 111 inEnd = 0; 112 data[INPUT_OFFSET] = 0; 113 114 data[OUTPUT_OFFSET] = getArray(out); 115 data[INVALID_BYTES] = 0; // Make sure we don't see earlier errors. 116 117 ec = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true); 118 if (ErrorCode.isFailure(ec)) { 119 if (ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR) { 120 return CoderResult.OVERFLOW; 121 } else if (ec == ErrorCode.U_TRUNCATED_CHAR_FOUND) { 122 if (data[INPUT_OFFSET] > 0) { 123 return CoderResult.malformedForLength(data[INPUT_OFFSET]); 124 } 125 } else { 126 throw ErrorCode.throwException(ec); 127 } 128 } 129 return CoderResult.UNDERFLOW; 130 } finally { 131 setPosition(out); 132 implReset(); 133 } 134 } 135 136 @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { 137 if (!in.hasRemaining()) { 138 return CoderResult.UNDERFLOW; 139 } 140 141 data[INPUT_OFFSET] = getArray(in); 142 data[OUTPUT_OFFSET]= getArray(out); 143 144 try { 145 ec = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false); 146 if (ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR) { 147 return CoderResult.OVERFLOW; 148 } else if (ec == ErrorCode.U_INVALID_CHAR_FOUND) { 149 return CoderResult.unmappableForLength(data[INVALID_BYTES]); 150 } else if (ec == ErrorCode.U_ILLEGAL_CHAR_FOUND) { 151 return CoderResult.malformedForLength(data[INVALID_BYTES]); 152 } 153 // Decoding succeeded: give us more data. 154 return CoderResult.UNDERFLOW; 155 } finally { 156 setPosition(in); 157 setPosition(out); 158 } 159 } 160 161 @Override protected void finalize() throws Throwable { 162 try { 163 NativeConverter.closeConverter(converterHandle); 164 converterHandle = 0; 165 } finally { 166 super.finalize(); 167 } 168 } 169 170 private int getArray(CharBuffer out) { 171 if (out.hasArray()) { 172 output = out.array(); 173 outEnd = out.arrayOffset() + out.limit(); 174 return out.arrayOffset() + out.position(); 175 } else { 176 outEnd = out.remaining(); 177 if (allocatedOutput == null || outEnd > allocatedOutput.length) { 178 allocatedOutput = new char[outEnd]; 179 } 180 // The array's start position is 0. 181 output = allocatedOutput; 182 return 0; 183 } 184 } 185 186 private int getArray(ByteBuffer in) { 187 if (in.hasArray()) { 188 input = in.array(); 189 inEnd = in.arrayOffset() + in.limit(); 190 return in.arrayOffset() + in.position(); 191 } else { 192 inEnd = in.remaining(); 193 if (allocatedInput == null || inEnd > allocatedInput.length) { 194 allocatedInput = new byte[inEnd]; 195 } 196 // Copy the input buffer into the allocated array. 197 int pos = in.position(); 198 in.get(allocatedInput, 0, inEnd); 199 in.position(pos); 200 // The array's start position is 0. 201 input = allocatedInput; 202 return 0; 203 } 204 } 205 206 private void setPosition(CharBuffer out) { 207 if (out.hasArray()) { 208 out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset()); 209 } else { 210 out.put(output, 0, data[OUTPUT_OFFSET]); 211 } 212 // release reference to output array, which may not be ours 213 output = null; 214 } 215 216 private void setPosition(ByteBuffer in) { 217 in.position(in.position() + data[INPUT_OFFSET]); 218 // release reference to input array, which may not be ours 219 input = null; 220 } 221} 222