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 java.nio.ByteBuffer;
18 import java.nio.CharBuffer;
19 import libcore.icu.ICU;
20 import libcore.icu.NativeConverter;
21 import libcore.util.EmptyArray;
22 
23 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_BYTE_COUNT = 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_BYTE_COUNT]  = number of invalid bytes
33      */
34     private final 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 
newInstance(Charset cs, String icuCanonicalName)50     public static CharsetDecoderICU newInstance(Charset cs, String icuCanonicalName) {
51         // This complexity is necessary to ensure that even if the constructor, superclass
52         // constructor, or call to updateCallback throw, we still free the native peer.
53         long address = 0;
54         try {
55             address = NativeConverter.openConverter(icuCanonicalName);
56             float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address);
57             CharsetDecoderICU result = new CharsetDecoderICU(cs, averageCharsPerByte, address);
58             address = 0; // CharsetDecoderICU has taken ownership; its finalizer will do the free.
59             result.updateCallback();
60             return result;
61         } finally {
62             if (address != 0) {
63                 NativeConverter.closeConverter(address);
64             }
65         }
66     }
67 
CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address)68     private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) {
69         super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE);
70         this.converterHandle = address;
71     }
72 
implReplaceWith(String newReplacement)73     @Override protected void implReplaceWith(String newReplacement) {
74         updateCallback();
75      }
76 
implOnMalformedInput(CodingErrorAction newAction)77     @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
78         updateCallback();
79     }
80 
implOnUnmappableCharacter(CodingErrorAction newAction)81     @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
82         updateCallback();
83     }
84 
updateCallback()85     private void updateCallback() {
86         NativeConverter.setCallbackDecode(converterHandle, this);
87     }
88 
implReset()89     @Override protected void implReset() {
90         NativeConverter.resetByteToChar(converterHandle);
91         data[INPUT_OFFSET] = 0;
92         data[OUTPUT_OFFSET] = 0;
93         data[INVALID_BYTE_COUNT] = 0;
94         output = null;
95         input = null;
96         allocatedInput = null;
97         allocatedOutput = null;
98         inEnd = 0;
99         outEnd = 0;
100     }
101 
implFlush(CharBuffer out)102     @Override protected final CoderResult implFlush(CharBuffer out) {
103         try {
104             // ICU needs to see an empty input.
105             input = EmptyArray.BYTE;
106             inEnd = 0;
107             data[INPUT_OFFSET] = 0;
108 
109             data[OUTPUT_OFFSET] = getArray(out);
110             data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors.
111 
112             int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true);
113             if (ICU.U_FAILURE(error)) {
114                 if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
115                     return CoderResult.OVERFLOW;
116                 } else if (error == ICU.U_TRUNCATED_CHAR_FOUND) {
117                     if (data[INVALID_BYTE_COUNT] > 0) {
118                         return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
119                     }
120                 }
121             }
122             return CoderResult.UNDERFLOW;
123        } finally {
124             setPosition(out);
125             implReset();
126        }
127     }
128 
decodeLoop(ByteBuffer in, CharBuffer out)129     @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
130         if (!in.hasRemaining()) {
131             return CoderResult.UNDERFLOW;
132         }
133 
134         data[INPUT_OFFSET] = getArray(in);
135         data[OUTPUT_OFFSET]= getArray(out);
136 
137         try {
138             int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false);
139             if (ICU.U_FAILURE(error)) {
140                 if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
141                     return CoderResult.OVERFLOW;
142                 } else if (error == ICU.U_INVALID_CHAR_FOUND) {
143                     return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
144                 } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
145                     return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
146                 } else {
147                     throw new AssertionError(error);
148                 }
149             }
150             // Decoding succeeded: give us more data.
151             return CoderResult.UNDERFLOW;
152         } finally {
153             setPosition(in);
154             setPosition(out);
155         }
156     }
157 
finalize()158     @Override protected void finalize() throws Throwable {
159         try {
160             NativeConverter.closeConverter(converterHandle);
161             converterHandle = 0;
162         } finally {
163             super.finalize();
164         }
165     }
166 
getArray(CharBuffer out)167     private int getArray(CharBuffer out) {
168         if (out.hasArray()) {
169             output = out.array();
170             outEnd = out.arrayOffset() + out.limit();
171             return out.arrayOffset() + out.position();
172         } else {
173             outEnd = out.remaining();
174             if (allocatedOutput == null || outEnd > allocatedOutput.length) {
175                 allocatedOutput = new char[outEnd];
176             }
177             // The array's start position is 0.
178             output = allocatedOutput;
179             return 0;
180         }
181     }
182 
getArray(ByteBuffer in)183     private  int getArray(ByteBuffer in) {
184         if (in.hasArray()) {
185             input = in.array();
186             inEnd = in.arrayOffset() + in.limit();
187             return in.arrayOffset() + in.position();
188         } else {
189             inEnd = in.remaining();
190             if (allocatedInput == null || inEnd > allocatedInput.length) {
191                 allocatedInput = new byte[inEnd];
192             }
193             // Copy the input buffer into the allocated array.
194             int pos = in.position();
195             in.get(allocatedInput, 0, inEnd);
196             in.position(pos);
197             // The array's start position is 0.
198             input = allocatedInput;
199             return 0;
200         }
201     }
202 
setPosition(CharBuffer out)203     private void setPosition(CharBuffer out) {
204         if (out.hasArray()) {
205             out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset());
206         } else {
207             out.put(output, 0, data[OUTPUT_OFFSET]);
208         }
209         // release reference to output array, which may not be ours
210         output = null;
211     }
212 
setPosition(ByteBuffer in)213     private void setPosition(ByteBuffer in) {
214         in.position(in.position() + data[INPUT_OFFSET]);
215         // release reference to input array, which may not be ours
216         input = null;
217     }
218 }
219