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 com.ibm.icu4jni.charset;
16
17import com.ibm.icu4jni.common.ErrorCode;
18// BEGIN android-removed
19// import com.ibm.icu4jni.converters.NativeConverter;
20// END android-removed
21
22
23import java.nio.CharBuffer;
24import java.nio.charset.Charset;
25import java.nio.charset.CharsetDecoder;
26import java.nio.charset.CoderResult;
27import java.nio.charset.CodingErrorAction;
28import java.nio.ByteBuffer;
29
30public final class CharsetDecoderICU extends CharsetDecoder{
31
32
33    private static final int INPUT_OFFSET   = 0,
34                             OUTPUT_OFFSET  = 1,
35                             INVALID_BYTES  = 2,
36                             INPUT_HELD     = 3,
37                             LIMIT          = 4;
38    /* data is 3 element array where
39     * data[INPUT_OFFSET]   = on input contains the start of input and on output the number of input chars consumed
40     * data[OUTPUT_OFFSET]  = on input contains the start of output and on output the number of output bytes written
41     * data[INVALID_CHARS]  = number of invalid chars
42     * data[INPUT_HELD]     = number of input chars held in the converter's state
43     */
44    private int[] data = new int[LIMIT];
45
46    /* handle to the ICU converter that is opened */
47    private long converterHandle=0;
48
49
50    private  byte[] input = null;
51    private  char[] output= null;
52
53    // BEGIN android-added
54    private byte[] allocatedInput = null;
55    private char[] allocatedOutput = null;
56    // END android-added
57
58    // These instance variables are
59    // always assigned in the methods
60    // before being used. This class
61    // inhrently multithread unsafe
62    // so we dont have to worry about
63    // synchronization
64    private int inEnd;
65    private int outEnd;
66    private int ec;
67    private int onUnmappableInput = NativeConverter.STOP_CALLBACK;;
68    private int onMalformedInput = NativeConverter.STOP_CALLBACK;;
69    private int savedInputHeldLen;
70
71    /**
72     * Constructs a new decoder for the given charset
73     * @param cs for which the decoder is created
74     * @param cHandle the address of ICU converter
75     * @exception RuntimeException
76     * @stable ICU 2.4
77     */
78    public CharsetDecoderICU(Charset cs,long cHandle){
79         super(cs,
80               NativeConverter.getAveCharsPerByte(cHandle),
81               NativeConverter.getMaxCharsPerByte(cHandle)
82               );
83
84         char[] sub = replacement().toCharArray();
85         ec = NativeConverter.setCallbackDecode(cHandle,
86                                                onMalformedInput,
87                                                onUnmappableInput,
88                                                sub, sub.length);
89         if(ErrorCode.isFailure(ec)){
90            throw ErrorCode.getException(ec);
91         }
92         // store the converter handle
93         converterHandle=cHandle;
94
95    }
96
97    /**
98     * Sets this decoders replacement string. Substitutes the string in input if an
99     * umappable or illegal sequence is encountered
100     * @param newReplacement to replace the error bytes with
101     * @stable ICU 2.4
102     */
103    protected void implReplaceWith(String newReplacement) {
104        if(converterHandle > 0){
105            if( newReplacement.length() > NativeConverter.getMaxBytesPerChar(converterHandle)) {
106                    throw new IllegalArgumentException();
107            }
108            ec =NativeConverter.setSubstitutionChars(converterHandle,
109                                                    newReplacement.toCharArray(),
110                                                    newReplacement.length()
111                                                    );
112            if(ErrorCode.isFailure(ec)){
113                throw ErrorCode.getException(ec);
114            }
115        }
116     }
117
118    /**
119     * Sets the action to be taken if an illegal sequence is encountered
120     * @param newAction action to be taken
121     * @exception IllegalArgumentException
122     * @stable ICU 2.4
123     */
124    protected final void implOnMalformedInput(CodingErrorAction newAction) {
125        if(newAction.equals(CodingErrorAction.IGNORE)){
126            onMalformedInput = NativeConverter.SKIP_CALLBACK;
127        }else if(newAction.equals(CodingErrorAction.REPLACE)){
128            onMalformedInput = NativeConverter.SUBSTITUTE_CALLBACK;
129        }else if(newAction.equals(CodingErrorAction.REPORT)){
130            onMalformedInput = NativeConverter.STOP_CALLBACK;
131        }
132        char[] sub = replacement().toCharArray();
133        //System.out.println(" setting callbacks mfi " + onMalformedInput +" umi " + onUnmappableInput);
134        ec = NativeConverter.setCallbackDecode(converterHandle, onMalformedInput, onUnmappableInput, sub, sub.length);
135        if(ErrorCode.isFailure(ec)){
136            throw ErrorCode.getException(ec);
137        }
138    }
139
140    /**
141     * Sets the action to be taken if an illegal sequence is encountered
142     * @param newAction action to be taken
143     * @exception IllegalArgumentException
144     * @stable ICU 2.4
145     */
146    protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
147        if(newAction.equals(CodingErrorAction.IGNORE)){
148            onUnmappableInput = NativeConverter.SKIP_CALLBACK;
149        }else if(newAction.equals(CodingErrorAction.REPLACE)){
150            onUnmappableInput = NativeConverter.SUBSTITUTE_CALLBACK;
151        }else if(newAction.equals(CodingErrorAction.REPORT)){
152            onUnmappableInput = NativeConverter.STOP_CALLBACK;
153        }
154        char[] sub = replacement().toCharArray();
155        ec = NativeConverter.setCallbackDecode(converterHandle,onMalformedInput, onUnmappableInput, sub, sub.length);
156        if(ErrorCode.isFailure(ec)){
157            throw ErrorCode.getException(ec);
158        }
159    }
160
161    /**
162     * Flushes any characters saved in the converter's internal buffer and
163     * resets the converter.
164     * @param out action to be taken
165     * @return result of flushing action and completes the decoding all input.
166     *         Returns CoderResult.UNDERFLOW if the action succeeds.
167     * @stable ICU 2.4
168     */
169    protected final CoderResult implFlush(CharBuffer out) {
170       try{
171
172           data[OUTPUT_OFFSET] = getArray(out);
173
174            ec=NativeConverter.flushByteToChar(
175                                            converterHandle,  /* Handle to ICU Converter */
176                                            output,           /* input array of chars */
177                                            outEnd,           /* input index+1 to be written */
178                                            data              /* contains data, inOff,outOff */
179                                            );
180
181
182            /* If we don't have room for the output, throw an exception*/
183            if (ErrorCode.isFailure(ec)) {
184                if (ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR) {
185                    return CoderResult.OVERFLOW;
186                }else if (ec == ErrorCode.U_TRUNCATED_CHAR_FOUND ) {//CSDL: add this truncated character error handling
187                    if(data[INPUT_OFFSET]>0){
188                        return CoderResult.malformedForLength(data[INPUT_OFFSET]);
189                    }
190                }else {
191                    ErrorCode.getException(ec);
192                }
193            }
194            return CoderResult.UNDERFLOW;
195       }finally{
196            /* save the flushed data */
197            setPosition(out);
198            implReset();
199       }
200    }
201
202    /**
203     * Resets the to Unicode mode of converter
204     * @stable ICU 2.4
205     */
206    protected void implReset() {
207        NativeConverter.resetByteToChar(converterHandle);
208        data[INPUT_OFFSET] = 0;
209        data[OUTPUT_OFFSET] = 0;
210        data[INVALID_BYTES] = 0;
211        data[INPUT_HELD] = 0;
212        savedInputHeldLen = 0;
213        output = null;
214        input = null;
215    }
216
217    /**
218     * Decodes one or more bytes. The default behaviour of the converter
219     * is stop and report if an error in input stream is encountered.
220     * To set different behaviour use @see CharsetDecoder.onMalformedInput()
221     * This  method allows a buffer by buffer conversion of a data stream.
222     * The state of the conversion is saved between calls to convert.
223     * Among other things, this means multibyte input sequences can be
224     * split between calls. If a call to convert results in an Error, the
225     * conversion may be continued by calling convert again with suitably
226     * modified parameters.All conversions should be finished with a call to
227     * the flush method.
228     * @param in buffer to decode
229     * @param out buffer to populate with decoded result
230     * @return result of decoding action. Returns CoderResult.UNDERFLOW if the decoding
231     *         action succeeds or more input is needed for completing the decoding action.
232     * @stable ICU 2.4
233     */
234    protected CoderResult decodeLoop(ByteBuffer in,CharBuffer out){
235
236        if(!in.hasRemaining()){
237            return CoderResult.UNDERFLOW;
238        }
239
240        data[INPUT_OFFSET] = getArray(in);
241        data[OUTPUT_OFFSET]= getArray(out);
242        data[INPUT_HELD] = 0;
243
244        try{
245            /* do the conversion */
246            ec=NativeConverter.decode(
247                                converterHandle,  /* Handle to ICU Converter */
248                                input,            /* input array of bytes */
249                                inEnd,            /* last index+1 to be converted */
250                                output,           /* input array of chars */
251                                outEnd,           /* input index+1 to be written */
252                                data,             /* contains data, inOff,outOff */
253                                false             /* donot flush the data */
254                                );
255
256
257            /* return an error*/
258            if(ec == ErrorCode.U_BUFFER_OVERFLOW_ERROR){
259                return CoderResult.OVERFLOW;
260            }else if(ec==ErrorCode.U_INVALID_CHAR_FOUND){
261                return CoderResult.malformedForLength(data[INVALID_BYTES]);
262            }else if(ec==ErrorCode.U_ILLEGAL_CHAR_FOUND){
263                return CoderResult.malformedForLength(data[INVALID_BYTES]);
264            }
265            /* decoding action succeded */
266            return CoderResult.UNDERFLOW;
267        }finally{
268            setPosition(in);
269            setPosition(out);
270        }
271    }
272
273    /**
274     * Releases the system resources by cleanly closing ICU converter opened
275     * @stable ICU 2.4
276     */
277    protected void finalize()throws Throwable{
278        NativeConverter.closeConverter(converterHandle);
279        super.finalize();
280        converterHandle = 0;
281    }
282
283    //------------------------------------------
284    // private utility methods
285    //------------------------------------------
286
287    private final int getArray(CharBuffer out){
288        if(out.hasArray()){
289            // BEGIN android-changed: take arrayOffset into account
290            output = out.array();
291            outEnd = out.arrayOffset() + out.limit();
292            return out.arrayOffset() + out.position();
293            // END android-changed
294        }else{
295            outEnd = out.remaining();
296            // BEGIN android-added
297            if (allocatedOutput == null || (outEnd > allocatedOutput.length)) {
298                allocatedOutput = new char[outEnd];
299            }
300            output = allocatedOutput;
301            // END android-added
302            //since the new
303            // buffer start position
304            // is 0
305            return 0;
306        }
307
308    }
309    private  final int getArray(ByteBuffer in){
310        if(in.hasArray()){
311            // BEGIN android-changed: take arrayOffset into account
312            input = in.array();
313            inEnd = in.arrayOffset() + in.limit();
314            return in.arrayOffset() + in.position() + savedInputHeldLen;/*exclude the number fo bytes held in previous conversion*/
315            // END android-changed
316        }else{
317            inEnd = in.remaining();
318            // BEGIN android-added
319            if (allocatedInput == null || (inEnd > allocatedInput.length)) {
320                allocatedInput = new byte[inEnd];
321            }
322            input = allocatedInput;
323            // END android-added
324            // save the current position
325            int pos = in.position();
326            in.get(input,0,inEnd);
327            // reset the position
328            in.position(pos);
329            // the start position
330            // of the new buffer
331            // is whatever is savedInputLen
332            return savedInputHeldLen;
333        }
334
335    }
336    private final void setPosition(CharBuffer out){
337        if(out.hasArray()){
338            // BEGIN android-changed: take arrayOffset into account
339            out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset());
340            // END android-changed
341        }else{
342            out.put(output,0,data[OUTPUT_OFFSET]);
343        }
344        // BEGIN android-added
345        // release reference to output array, which may not be ours
346        output = null;
347        // END android-added
348    }
349    private final void setPosition(ByteBuffer in){
350
351        // ok was there input held in the previous invocation of decodeLoop
352        // that resulted in output in this invocation?
353        // BEGIN android-changed
354        in.position(in.position() + data[INPUT_OFFSET] + savedInputHeldLen - data[INPUT_HELD]);
355        savedInputHeldLen = data[INPUT_HELD];
356        // release reference to input array, which may not be ours
357        input = null;
358        // END android-changed
359    }
360}
361