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 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 
     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         NativeConverter.registerConverter(this, converterHandle);
     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         NativeConverter.setCallbackDecode(converterHandle, this);
     88     }
     89 
     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 
    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 
    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 
    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 
    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 
    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 
    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