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_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