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 com.ibm.icu4jni.charset; 16 17import com.ibm.icu4jni.common.ErrorCode; 18// BEGIN android-removed 19// import com.ibm.icu4jni.converters.NativeConverter; 20// END android-removed 21 22 23import java.nio.CharBuffer; 24import java.nio.charset.Charset; 25import java.nio.charset.CharsetDecoder; 26import java.nio.charset.CoderResult; 27import java.nio.charset.CodingErrorAction; 28import java.nio.ByteBuffer; 29 30public final class CharsetDecoderICU extends CharsetDecoder{ 31 32 33 private static final int INPUT_OFFSET = 0, 34 OUTPUT_OFFSET = 1, 35 INVALID_BYTES = 2, 36 INPUT_HELD = 3, 37 LIMIT = 4; 38 /* data is 3 element array where 39 * data[INPUT_OFFSET] = on input contains the start of input and on output the number of input chars consumed 40 * data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output bytes written 41 * data[INVALID_CHARS] = number of invalid chars 42 * data[INPUT_HELD] = number of input chars held in the converter's state 43 */ 44 private int[] data = new int[LIMIT]; 45 46 /* handle to the ICU converter that is opened */ 47 private long converterHandle=0; 48 49 50 private byte[] input = null; 51 private char[] output= null; 52 53 // BEGIN android-added 54 private byte[] allocatedInput = null; 55 private char[] allocatedOutput = null; 56 // END android-added 57 58 // These instance variables are 59 // always assigned in the methods 60 // before being used. This class 61 // inhrently multithread unsafe 62 // so we dont have to worry about 63 // synchronization 64 private int inEnd; 65 private int outEnd; 66 private int ec; 67 private int onUnmappableInput = NativeConverter.STOP_CALLBACK;; 68 private int onMalformedInput = NativeConverter.STOP_CALLBACK;; 69 private int savedInputHeldLen; 70 71 /** 72 * Constructs a new decoder for the given charset 73 * @param cs for which the decoder is created 74 * @param cHandle the address of ICU converter 75 * @exception RuntimeException 76 * @stable ICU 2.4 77 */ 78 public CharsetDecoderICU(Charset cs,long cHandle){ 79 super(cs, 80 NativeConverter.getAveCharsPerByte(cHandle), 81 NativeConverter.getMaxCharsPerByte(cHandle) 82 ); 83 84 char[] sub = replacement().toCharArray(); 85 ec = NativeConverter.setCallbackDecode(cHandle, 86 onMalformedInput, 87 onUnmappableInput, 88 sub, sub.length); 89 if(ErrorCode.isFailure(ec)){ 90 throw ErrorCode.getException(ec); 91 } 92 // store the converter handle 93 converterHandle=cHandle; 94 95 } 96 97 /** 98 * Sets this decoders replacement string. Substitutes the string in input if an 99 * umappable or illegal sequence is encountered 100 * @param newReplacement to replace the error bytes with 101 * @stable ICU 2.4 102 */ 103 protected void implReplaceWith(String newReplacement) { 104 if(converterHandle > 0){ 105 if( newReplacement.length() > NativeConverter.getMaxBytesPerChar(converterHandle)) { 106 throw new IllegalArgumentException(); 107 } 108 ec =NativeConverter.setSubstitutionChars(converterHandle, 109 newReplacement.toCharArray(), 110 newReplacement.length() 111 ); 112 if(ErrorCode.isFailure(ec)){ 113 throw ErrorCode.getException(ec); 114 } 115 } 116 } 117 118 /** 119 * Sets the action to be taken if an illegal sequence is encountered 120 * @param newAction action to be taken 121 * @exception IllegalArgumentException 122 * @stable ICU 2.4 123 */ 124 protected final void implOnMalformedInput(CodingErrorAction newAction) { 125 if(newAction.equals(CodingErrorAction.IGNORE)){ 126 onMalformedInput = NativeConverter.SKIP_CALLBACK; 127 }else if(newAction.equals(CodingErrorAction.REPLACE)){ 128 onMalformedInput = NativeConverter.SUBSTITUTE_CALLBACK; 129 }else if(newAction.equals(CodingErrorAction.REPORT)){ 130 onMalformedInput = NativeConverter.STOP_CALLBACK; 131 } 132 char[] sub = replacement().toCharArray(); 133 //System.out.println(" setting callbacks mfi " + onMalformedInput +" umi " + onUnmappableInput); 134 ec = NativeConverter.setCallbackDecode(converterHandle, onMalformedInput, onUnmappableInput, sub, sub.length); 135 if(ErrorCode.isFailure(ec)){ 136 throw ErrorCode.getException(ec); 137 } 138 } 139 140 /** 141 * Sets the action to be taken if an illegal sequence is encountered 142 * @param newAction action to be taken 143 * @exception IllegalArgumentException 144 * @stable ICU 2.4 145 */ 146 protected final void implOnUnmappableCharacter(CodingErrorAction newAction) { 147 if(newAction.equals(CodingErrorAction.IGNORE)){ 148 onUnmappableInput = NativeConverter.SKIP_CALLBACK; 149 }else if(newAction.equals(CodingErrorAction.REPLACE)){ 150 onUnmappableInput = NativeConverter.SUBSTITUTE_CALLBACK; 151 }else if(newAction.equals(CodingErrorAction.REPORT)){ 152 onUnmappableInput = NativeConverter.STOP_CALLBACK; 153 } 154 char[] sub = replacement().toCharArray(); 155 ec = NativeConverter.setCallbackDecode(converterHandle,onMalformedInput, onUnmappableInput, sub, sub.length); 156 if(ErrorCode.isFailure(ec)){ 157 throw ErrorCode.getException(ec); 158 } 159 } 160 161 /** 162 * Flushes any characters saved in the converter's internal buffer and 163 * resets the converter. 164 * @param out action to be taken 165 * @return result of flushing action and completes the decoding all input. 166 * Returns CoderResult.UNDERFLOW if the action succeeds. 167 * @stable ICU 2.4 168 */ 169 protected final CoderResult implFlush(CharBuffer out) { 170 try{ 171 172 data[OUTPUT_OFFSET] = getArray(out); 173 174 ec=NativeConverter.flushByteToChar( 175 converterHandle, /* Handle to ICU Converter */ 176 output, /* input array of chars */ 177 outEnd, /* input index+1 to be written */ 178 data /* contains data, inOff,outOff */ 179 ); 180 181 182 /* If we don't have room for the output, throw an exception*/ 183 if (ErrorCode.isFailure(ec)) { 184 if (ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR) { 185 return CoderResult.OVERFLOW; 186 }else if (ec == ErrorCode.U_TRUNCATED_CHAR_FOUND ) {//CSDL: add this truncated character error handling 187 if(data[INPUT_OFFSET]>0){ 188 return CoderResult.malformedForLength(data[INPUT_OFFSET]); 189 } 190 }else { 191 ErrorCode.getException(ec); 192 } 193 } 194 return CoderResult.UNDERFLOW; 195 }finally{ 196 /* save the flushed data */ 197 setPosition(out); 198 implReset(); 199 } 200 } 201 202 /** 203 * Resets the to Unicode mode of converter 204 * @stable ICU 2.4 205 */ 206 protected void implReset() { 207 NativeConverter.resetByteToChar(converterHandle); 208 data[INPUT_OFFSET] = 0; 209 data[OUTPUT_OFFSET] = 0; 210 data[INVALID_BYTES] = 0; 211 data[INPUT_HELD] = 0; 212 savedInputHeldLen = 0; 213 output = null; 214 input = null; 215 } 216 217 /** 218 * Decodes one or more bytes. The default behaviour of the converter 219 * is stop and report if an error in input stream is encountered. 220 * To set different behaviour use @see CharsetDecoder.onMalformedInput() 221 * This method allows a buffer by buffer conversion of a data stream. 222 * The state of the conversion is saved between calls to convert. 223 * Among other things, this means multibyte input sequences can be 224 * split between calls. If a call to convert results in an Error, the 225 * conversion may be continued by calling convert again with suitably 226 * modified parameters.All conversions should be finished with a call to 227 * the flush method. 228 * @param in buffer to decode 229 * @param out buffer to populate with decoded result 230 * @return result of decoding action. Returns CoderResult.UNDERFLOW if the decoding 231 * action succeeds or more input is needed for completing the decoding action. 232 * @stable ICU 2.4 233 */ 234 protected CoderResult decodeLoop(ByteBuffer in,CharBuffer out){ 235 236 if(!in.hasRemaining()){ 237 return CoderResult.UNDERFLOW; 238 } 239 240 data[INPUT_OFFSET] = getArray(in); 241 data[OUTPUT_OFFSET]= getArray(out); 242 data[INPUT_HELD] = 0; 243 244 try{ 245 /* do the conversion */ 246 ec=NativeConverter.decode( 247 converterHandle, /* Handle to ICU Converter */ 248 input, /* input array of bytes */ 249 inEnd, /* last index+1 to be converted */ 250 output, /* input array of chars */ 251 outEnd, /* input index+1 to be written */ 252 data, /* contains data, inOff,outOff */ 253 false /* donot flush the data */ 254 ); 255 256 257 /* return an error*/ 258 if(ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR){ 259 return CoderResult.OVERFLOW; 260 }else if(ec==ErrorCode.U_INVALID_CHAR_FOUND){ 261 return CoderResult.malformedForLength(data[INVALID_BYTES]); 262 }else if(ec==ErrorCode.U_ILLEGAL_CHAR_FOUND){ 263 return CoderResult.malformedForLength(data[INVALID_BYTES]); 264 } 265 /* decoding action succeded */ 266 return CoderResult.UNDERFLOW; 267 }finally{ 268 setPosition(in); 269 setPosition(out); 270 } 271 } 272 273 /** 274 * Releases the system resources by cleanly closing ICU converter opened 275 * @stable ICU 2.4 276 */ 277 protected void finalize()throws Throwable{ 278 NativeConverter.closeConverter(converterHandle); 279 super.finalize(); 280 converterHandle = 0; 281 } 282 283 //------------------------------------------ 284 // private utility methods 285 //------------------------------------------ 286 287 private final int getArray(CharBuffer out){ 288 if(out.hasArray()){ 289 // BEGIN android-changed: take arrayOffset into account 290 output = out.array(); 291 outEnd = out.arrayOffset() + out.limit(); 292 return out.arrayOffset() + out.position(); 293 // END android-changed 294 }else{ 295 outEnd = out.remaining(); 296 // BEGIN android-added 297 if (allocatedOutput == null || (outEnd > allocatedOutput.length)) { 298 allocatedOutput = new char[outEnd]; 299 } 300 output = allocatedOutput; 301 // END android-added 302 //since the new 303 // buffer start position 304 // is 0 305 return 0; 306 } 307 308 } 309 private final int getArray(ByteBuffer in){ 310 if(in.hasArray()){ 311 // BEGIN android-changed: take arrayOffset into account 312 input = in.array(); 313 inEnd = in.arrayOffset() + in.limit(); 314 return in.arrayOffset() + in.position() + savedInputHeldLen;/*exclude the number fo bytes held in previous conversion*/ 315 // END android-changed 316 }else{ 317 inEnd = in.remaining(); 318 // BEGIN android-added 319 if (allocatedInput == null || (inEnd > allocatedInput.length)) { 320 allocatedInput = new byte[inEnd]; 321 } 322 input = allocatedInput; 323 // END android-added 324 // save the current position 325 int pos = in.position(); 326 in.get(input,0,inEnd); 327 // reset the position 328 in.position(pos); 329 // the start position 330 // of the new buffer 331 // is whatever is savedInputLen 332 return savedInputHeldLen; 333 } 334 335 } 336 private final void setPosition(CharBuffer out){ 337 if(out.hasArray()){ 338 // BEGIN android-changed: take arrayOffset into account 339 out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset()); 340 // END android-changed 341 }else{ 342 out.put(output,0,data[OUTPUT_OFFSET]); 343 } 344 // BEGIN android-added 345 // release reference to output array, which may not be ours 346 output = null; 347 // END android-added 348 } 349 private final void setPosition(ByteBuffer in){ 350 351 // ok was there input held in the previous invocation of decodeLoop 352 // that resulted in output in this invocation? 353 // BEGIN android-changed 354 in.position(in.position() + data[INPUT_OFFSET] + savedInputHeldLen - data[INPUT_HELD]); 355 savedInputHeldLen = data[INPUT_HELD]; 356 // release reference to input array, which may not be ours 357 input = null; 358 // END android-changed 359 } 360} 361