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