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