Home | History | Annotate | Download | only in charset
      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 dalvik.annotation.optimization.ReachabilitySensitive;
     18 import java.nio.ByteBuffer;
     19 import java.nio.CharBuffer;
     20 import libcore.icu.ICU;
     21 import libcore.icu.NativeConverter;
     22 import libcore.util.EmptyArray;
     23 
     24 final class CharsetDecoderICU extends CharsetDecoder {
     25     private static final int MAX_CHARS_PER_BYTE = 2;
     26 
     27     private static final int INPUT_OFFSET = 0;
     28     private static final int OUTPUT_OFFSET = 1;
     29     private static final int INVALID_BYTE_COUNT = 2;
     30     /*
     31      * data[INPUT_OFFSET]   = on input contains the start of input and on output the number of input bytes consumed
     32      * data[OUTPUT_OFFSET]  = on input contains the start of output and on output the number of output chars written
     33      * data[INVALID_BYTE_COUNT]  = number of invalid bytes
     34      */
     35     private final int[] data = new int[3];
     36 
     37     /* Handle to the ICU converter that is opened, cleaned up via NativeAllocationRegistry. */
     38     @ReachabilitySensitive
     39     private long converterHandle = 0;
     40 
     41     private byte[] input = null;
     42     private char[] output= null;
     43 
     44     private byte[] allocatedInput = null;
     45     private char[] allocatedOutput = null;
     46 
     47     // These instance variables are always assigned in the methods before being used. This class
     48     // is inherently thread-unsafe so we don't have to worry about synchronization.
     49     private int inEnd;
     50     private int outEnd;
     51 
     52     public static CharsetDecoderICU newInstance(Charset cs, String icuCanonicalName) {
     53         // This complexity is necessary to ensure that even if the constructor, superclass
     54         // constructor, or call to updateCallback throw, we still free the native peer.
     55         long address = 0;
     56         CharsetDecoderICU result;
     57         try {
     58             address = NativeConverter.openConverter(icuCanonicalName);
     59             float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address);
     60             result = new CharsetDecoderICU(cs, averageCharsPerByte, address);
     61         } catch (Throwable t) {
     62             if (address != 0) {
     63                 NativeConverter.closeConverter(address);
     64             }
     65             throw t;
     66         }
     67         // An exception in registerConverter() will deallocate address:
     68         NativeConverter.registerConverter(result, address);
     69         result.updateCallback();
     70         return result;
     71     }
     72 
     73     private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) {
     74         super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE);
     75         this.converterHandle = address;
     76     }
     77 
     78     @Override protected void implReplaceWith(String newReplacement) {
     79         updateCallback();
     80     }
     81 
     82     @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
     83         updateCallback();
     84     }
     85 
     86     @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
     87         updateCallback();
     88     }
     89 
     90     private void updateCallback() {
     91         NativeConverter.setCallbackDecode(converterHandle, this);
     92     }
     93 
     94     @Override protected void implReset() {
     95         NativeConverter.resetByteToChar(converterHandle);
     96         data[INPUT_OFFSET] = 0;
     97         data[OUTPUT_OFFSET] = 0;
     98         data[INVALID_BYTE_COUNT] = 0;
     99         output = null;
    100         input = null;
    101         allocatedInput = null;
    102         allocatedOutput = null;
    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_BYTE_COUNT] = 0; // Make sure we don't see earlier errors.
    116 
    117             int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true);
    118             if (ICU.U_FAILURE(error)) {
    119                 if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
    120                     return CoderResult.OVERFLOW;
    121                 } else if (error == ICU.U_TRUNCATED_CHAR_FOUND) {
    122                     if (data[INVALID_BYTE_COUNT] > 0) {
    123                         return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
    124                     }
    125                 }
    126             }
    127             return CoderResult.UNDERFLOW;
    128        } finally {
    129             setPosition(out);
    130             implReset();
    131        }
    132     }
    133 
    134     @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
    135         if (!in.hasRemaining()) {
    136             return CoderResult.UNDERFLOW;
    137         }
    138 
    139         data[INPUT_OFFSET] = getArray(in);
    140         data[OUTPUT_OFFSET]= getArray(out);
    141 
    142         try {
    143             int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false);
    144             if (ICU.U_FAILURE(error)) {
    145                 if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
    146                     return CoderResult.OVERFLOW;
    147                 } else if (error == ICU.U_INVALID_CHAR_FOUND) {
    148                     return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
    149                 } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
    150                     return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
    151                 } else {
    152                     throw new AssertionError(error);
    153                 }
    154             }
    155             // Decoding succeeded: give us more data.
    156             return CoderResult.UNDERFLOW;
    157         } finally {
    158             setPosition(in);
    159             setPosition(out);
    160         }
    161     }
    162 
    163 
    164     private int getArray(CharBuffer out) {
    165         if (out.hasArray()) {
    166             output = out.array();
    167             outEnd = out.arrayOffset() + out.limit();
    168             return out.arrayOffset() + out.position();
    169         } else {
    170             outEnd = out.remaining();
    171             if (allocatedOutput == null || outEnd > allocatedOutput.length) {
    172                 allocatedOutput = new char[outEnd];
    173             }
    174             // The array's start position is 0.
    175             output = allocatedOutput;
    176             return 0;
    177         }
    178     }
    179 
    180     private  int getArray(ByteBuffer in) {
    181         if (in.hasArray()) {
    182             input = in.array();
    183             inEnd = in.arrayOffset() + in.limit();
    184             return in.arrayOffset() + in.position();
    185         } else {
    186             inEnd = in.remaining();
    187             if (allocatedInput == null || inEnd > allocatedInput.length) {
    188                 allocatedInput = new byte[inEnd];
    189             }
    190             // Copy the input buffer into the allocated array.
    191             int pos = in.position();
    192             in.get(allocatedInput, 0, inEnd);
    193             in.position(pos);
    194             // The array's start position is 0.
    195             input = allocatedInput;
    196             return 0;
    197         }
    198     }
    199 
    200     private void setPosition(CharBuffer out) {
    201         if (out.hasArray()) {
    202             out.position(out.position() + data[OUTPUT_OFFSET]);
    203         } else {
    204             out.put(output, 0, data[OUTPUT_OFFSET]);
    205         }
    206         // release reference to output array, which may not be ours
    207         output = null;
    208     }
    209 
    210     private void setPosition(ByteBuffer in) {
    211         in.position(in.position() + data[INPUT_OFFSET]);
    212         // release reference to input array, which may not be ours
    213         input = null;
    214     }
    215 }
    216