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