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.ICU;
20import libcore.icu.NativeConverter;
21import libcore.util.EmptyArray;
22
23final 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
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
68    private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) {
69        super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE);
70        this.converterHandle = address;
71    }
72
73    @Override protected void implReplaceWith(String newReplacement) {
74        updateCallback();
75     }
76
77    @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
78        updateCallback();
79    }
80
81    @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
82        updateCallback();
83    }
84
85    private void updateCallback() {
86        NativeConverter.setCallbackDecode(converterHandle, this);
87    }
88
89    @Override protected void implReset() {
90        NativeConverter.resetByteToChar(converterHandle);
91        data[INPUT_OFFSET] = 0;
92        data[OUTPUT_OFFSET] = 0;
93        data[INVALID_BYTES] = 0;
94        output = null;
95        input = null;
96        allocatedInput = null;
97        allocatedOutput = null;
98        inEnd = 0;
99        outEnd = 0;
100    }
101
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_BYTES] = 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[INPUT_OFFSET] > 0) {
118                        return CoderResult.malformedForLength(data[INPUT_OFFSET]);
119                    }
120                }
121            }
122            return CoderResult.UNDERFLOW;
123       } finally {
124            setPosition(out);
125            implReset();
126       }
127    }
128
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_BYTES]);
144                } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
145                    return CoderResult.malformedForLength(data[INVALID_BYTES]);
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
158    @Override protected void finalize() throws Throwable {
159        try {
160            NativeConverter.closeConverter(converterHandle);
161            converterHandle = 0;
162        } finally {
163            super.finalize();
164        }
165    }
166
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
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
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
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