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         NativeConverter.registerConverter(this, converterHandle);
72     }
73 
implReplaceWith(String newReplacement)74     @Override protected void implReplaceWith(String newReplacement) {
75         updateCallback();
76      }
77 
implOnMalformedInput(CodingErrorAction newAction)78     @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
79         updateCallback();
80     }
81 
implOnUnmappableCharacter(CodingErrorAction newAction)82     @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
83         updateCallback();
84     }
85 
updateCallback()86     private void updateCallback() {
87         NativeConverter.setCallbackDecode(converterHandle, this);
88     }
89 
implReset()90     @Override protected void implReset() {
91         NativeConverter.resetByteToChar(converterHandle);
92         data[INPUT_OFFSET] = 0;
93         data[OUTPUT_OFFSET] = 0;
94         data[INVALID_BYTE_COUNT] = 0;
95         output = null;
96         input = null;
97         allocatedInput = null;
98         allocatedOutput = null;
99         inEnd = 0;
100         outEnd = 0;
101     }
102 
implFlush(CharBuffer out)103     @Override protected final CoderResult implFlush(CharBuffer out) {
104         try {
105             // ICU needs to see an empty input.
106             input = EmptyArray.BYTE;
107             inEnd = 0;
108             data[INPUT_OFFSET] = 0;
109 
110             data[OUTPUT_OFFSET] = getArray(out);
111             data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors.
112 
113             int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true);
114             if (ICU.U_FAILURE(error)) {
115                 if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
116                     return CoderResult.OVERFLOW;
117                 } else if (error == ICU.U_TRUNCATED_CHAR_FOUND) {
118                     if (data[INVALID_BYTE_COUNT] > 0) {
119                         return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
120                     }
121                 }
122             }
123             return CoderResult.UNDERFLOW;
124        } finally {
125             setPosition(out);
126             implReset();
127        }
128     }
129 
decodeLoop(ByteBuffer in, CharBuffer out)130     @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
131         if (!in.hasRemaining()) {
132             return CoderResult.UNDERFLOW;
133         }
134 
135         data[INPUT_OFFSET] = getArray(in);
136         data[OUTPUT_OFFSET]= getArray(out);
137 
138         try {
139             int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false);
140             if (ICU.U_FAILURE(error)) {
141                 if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
142                     return CoderResult.OVERFLOW;
143                 } else if (error == ICU.U_INVALID_CHAR_FOUND) {
144                     return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
145                 } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
146                     return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
147                 } else {
148                     throw new AssertionError(error);
149                 }
150             }
151             // Decoding succeeded: give us more data.
152             return CoderResult.UNDERFLOW;
153         } finally {
154             setPosition(in);
155             setPosition(out);
156         }
157     }
158 
159 
getArray(CharBuffer out)160     private int getArray(CharBuffer out) {
161         if (out.hasArray()) {
162             output = out.array();
163             outEnd = out.arrayOffset() + out.limit();
164             return out.arrayOffset() + out.position();
165         } else {
166             outEnd = out.remaining();
167             if (allocatedOutput == null || outEnd > allocatedOutput.length) {
168                 allocatedOutput = new char[outEnd];
169             }
170             // The array's start position is 0.
171             output = allocatedOutput;
172             return 0;
173         }
174     }
175 
getArray(ByteBuffer in)176     private  int getArray(ByteBuffer in) {
177         if (in.hasArray()) {
178             input = in.array();
179             inEnd = in.arrayOffset() + in.limit();
180             return in.arrayOffset() + in.position();
181         } else {
182             inEnd = in.remaining();
183             if (allocatedInput == null || inEnd > allocatedInput.length) {
184                 allocatedInput = new byte[inEnd];
185             }
186             // Copy the input buffer into the allocated array.
187             int pos = in.position();
188             in.get(allocatedInput, 0, inEnd);
189             in.position(pos);
190             // The array's start position is 0.
191             input = allocatedInput;
192             return 0;
193         }
194     }
195 
setPosition(CharBuffer out)196     private void setPosition(CharBuffer out) {
197         if (out.hasArray()) {
198             out.position(out.position() + data[OUTPUT_OFFSET]);
199         } else {
200             out.put(output, 0, data[OUTPUT_OFFSET]);
201         }
202         // release reference to output array, which may not be ours
203         output = null;
204     }
205 
setPosition(ByteBuffer in)206     private void setPosition(ByteBuffer in) {
207         in.position(in.position() + data[INPUT_OFFSET]);
208         // release reference to input array, which may not be ours
209         input = null;
210     }
211 }
212