/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * @author Ilya S. Okomin
 * @version $Revision$
 */
package java.awt.font;

import java.io.IOException;
import java.io.Serializable;

/**
 * The Class NumericShaper provides methods to convert latin character codes
 * to unicode character codes.
 * For tables of the character codes used,
 * see <a href="http://www.unicode.org/Public/UNIDATA/">unicode.org</a>.
 */
public final class NumericShaper implements Serializable {

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = -8022764705923730308L;

    /**
     * The Constant EUROPEAN indicates the latin and extended range,
     * and latin decimal base.
     */
    public static final int EUROPEAN = 1;

    /** The Constant ARABIC indicates the ARABIC range and decimal base. */
    public static final int ARABIC = 2;

    /**
     * The Constant EASTERN_ARABIC indicates the ARABIC range and
     * ARABIC_EXTENDED decimal base.
     */
    public static final int EASTERN_ARABIC = 4;

    /**
     * The Constant DEVANAGARI indicates the DEVANAGARI range and
     * decimal base.
     */
    public static final int DEVANAGARI = 8;

    /** The Constant BENGALI indicates the BENGALI range and decimal base. */
    public static final int BENGALI = 16;

    /** The Constant GURMUKHI indicates the GURMUKHI range and decimal base. */
    public static final int GURMUKHI = 32;

    /** The Constant GUJARATI indicates the GUJARATI range and decimal base. */
    public static final int GUJARATI = 64;

    /** The Constant ORIYA indicates the ORIYA range and decimal base. */
    public static final int ORIYA = 128;

    /** The Constant TAMIL indicates the TAMIL range and decimal base. */
    public static final int TAMIL = 256;

    /** The Constant TELUGU indicates the TELUGU range and decimal base. */
    public static final int TELUGU = 512;

    /** The Constant KANNADA indicates the KANNADA range and decimal base. */
    public static final int KANNADA = 1024;

    /**
     * The Constant MALAYALAM indicates the MALAYALAM range and decimal base.
     */
    public static final int MALAYALAM = 2048;

    /** The Constant THAI indicates the THAI range and decimal base. */
    public static final int THAI = 4096;

    /** The Constant LAO indicates the LAO range and decimal base. */
    public static final int LAO = 8192;

    /** The Constant TIBETAN indicates the TIBETAN range and decimal base. */
    public static final int TIBETAN = 16384;

    /** The Constant MYANMAR indicates the MYANMAR range and decimal base. */
    public static final int MYANMAR = 32768;

    /**
     * The Constant ETHIOPIC indicates the ETHIOPIC range and decimal base.
     */
    public static final int ETHIOPIC = 65536;

    /** The Constant KHMER indicates the KHMER range and decimal base. */
    public static final int KHMER = 131072;

    /**
     * The Constant MONGOLIAN indicates the MONGOLIAN range and
     * decimal base.
     */
    public static final int MONGOLIAN = 262144;

    /** The Constant ALL_RANGES indicates all ranges. */
    public static final int ALL_RANGES = 524287;

    /* Further one can find the set of script indices.
     * Index is the power you need the 2 to raise to to get corresponding
     * range constant value. Also script ranges, context names and digits low
     * ranges are indexed with these indices.
     */

    // Index of the EUROPEAN range
    /** The Constant INDEX_EUROPEAN. */
    private static final int INDEX_EUROPEAN = 0;

    // Index of the ARABIC range
    /** The Constant INDEX_ARABIC. */
    private static final int INDEX_ARABIC = 1;

    // Index of the EASTERN_ARABIC range
    /** The Constant INDEX_EASTERN_ARABIC. */
    private static final int INDEX_EASTERN_ARABIC = 2;

    // Index of the DEVANAGARI range
    /** The Constant INDEX_DEVANAGARI. */
    private static final int INDEX_DEVANAGARI = 3;

    // Index of the BENGALI range
    /** The Constant INDEX_BENGALI. */
    private static final int INDEX_BENGALI = 4;

    // Index of the GURMUKHI range
    /** The Constant INDEX_GURMUKHI. */
    private static final int INDEX_GURMUKHI = 5;

    // Index of the GUJARTI range
    /** The Constant INDEX_GUJARATI. */
    private static final int INDEX_GUJARATI = 6;

    // Index of the ORIYA range
    /** The Constant INDEX_ORIYA. */
    private static final int INDEX_ORIYA = 7;

    // Index of the TAMIL range
    /** The Constant INDEX_TAMIL. */
    private static final int INDEX_TAMIL = 8;

    // Index of the TELUGU range
    /** The Constant INDEX_TELUGU. */
    private static final int INDEX_TELUGU = 9;

    // Index of the KANNADA range
    /** The Constant INDEX_KANNADA. */
    private static final int INDEX_KANNADA = 10;

    // Index of the MALAYALAM range
    /** The Constant INDEX_MALAYALAM. */
    private static final int INDEX_MALAYALAM = 11;

    // Index of the THAI range
    /** The Constant INDEX_THAI. */
    private static final int INDEX_THAI = 12;

    // Index of the LAO range
    /** The Constant INDEX_LAO. */
    private static final int INDEX_LAO = 13;

    // Index of the TIBETAN range
    /** The Constant INDEX_TIBETAN. */
    private static final int INDEX_TIBETAN = 14;

    // Index of the MYANMAR range
    /** The Constant INDEX_MYANMAR. */
    private static final int INDEX_MYANMAR = 15;

    // Index of the ETHIOPIC range
    /** The Constant INDEX_ETHIOPIC. */
    private static final int INDEX_ETHIOPIC = 16;

    // Index of the KHMER range
    /** The Constant INDEX_KHMER. */
    private static final int INDEX_KHMER = 17;

    // Index of the MONGOLIAN range
    /** The Constant INDEX_MONGOLIAN. */
    private static final int INDEX_MONGOLIAN = 18;

    // Maximum index that range can't exceed
    /** The Constant MAX_INDEX. */
    private static final int MAX_INDEX = 19;

    /*
     * Scripts ranges array. Array represents ranges as pairs of
     * lowest and highest range bounds.
     * Data is taken from the UnicodeData.txt file from
     * http://www.unicode.org/Public/UNIDATA/
     */
    /** The scripts ranges. */
    private final int[] scriptsRanges = {
            0x0000, 0x024F,     // EUROPEAN (basic latin + latin-1 + extended)
            0x0600, 0x06FF,     // ARABIC
            0x0600, 0x06FF,     // EASTERN_ARABIC (XXX: diff with ARABIC ? )
            0x0900, 0x097F,     // DEVANAGARI
            0x0980, 0x09FF,     // BENGALI
            0x0A00, 0x0A7F,     // GURMUKHI
            0x0A80, 0x0AFF,     // GUJARATI
            0x0B00, 0x0B7F,     // ORIYA
            0x0B80, 0x0BFF,     // TAMIL
            0x0C00, 0x0C7F,     // TELUGU
            0x0C80, 0x0CFF,     // KANNADA
            0x0D00, 0x0D7F,     // MALAYALAM
            0x0E00, 0x0E7F,     // THAI
            0x0E80, 0x0EFF,     // LAO
            0x0F00, 0x0FFF,     // TIBETAN
            0x1000, 0x109F,     // MYANMAR
            0x1200, 0x137F,     // ETHIOPIC
            0x1780, 0x17FF,     // KHMER
            0x1800, 0x18AF      // MONGOLIAN
    };

    /*
     * Digit low ranges values decreased by 0x0030. Each low range
     * value decreased by 0x0030 for easy obtaing unicode value of the
     * context dependent digit. European digits starts from 0x0030 hence
     * context dependent unicode digit value equals to
     *      digitsLowRanges[script index] + european digit char unicode value.
     * !! the only exception is ETHIOPIC script where there is no '0' digit
     * Data is taken from the UnicodeData.txt file from
     * http://www.unicode.org/Public/UNIDATA/
     */
    /** The digits low ranges. */
    private final int[] digitsLowRanges = {
            0x0000,             // EUROPEAN
            0x0630,             // ARABIC
            0x0630,             // EASTERN_ARABIC
            0x0936,             // DEVANAGARI
            0x09B6,             // BENGALI
            0x0A36,             // GURMUKHI
            0x0AB6,             // GUJARATI
            0x0B36,             // ORIYA
            0x0BB6,             // TAMIL
            0x0C36,             // TELUGU
            0x0CB6,             // KANNADA
            0x0D36,             // MALAYALAM
            0x0E20,             // THAI
            0x0EA0,             // LAO
            0x0EF0,             // TIBETAN
            0x1010,             // MYANMAR
            0x1338,             // ETHIOPIC - (low range-1) no ETHIOPIC '0' DIGIT!
            0x17B0,             // KHMER
            0x17E0              // MONGOLIAN
    };

    // Set of context names used in toString method
    /** The contexts. */
    private final String[] contexts = {
            "EUROPEAN",
            "ARABIC",
            "EASTERN_ARABIC",
            "DEVANAGARI",
            "BENGALI",
            "GURMUKHI",
            "GUJARATI",
            "ORIYA",
            "TAMIL",
            "TELUGU",
            "KANNADA",
            "MALAYALAM",
            "THAI",
            "LAO",
            "TIBETAN",
            "MYANMAR",
            "ETHIOPIC",
            "KHMER",
            "MONGOLIAN"
    };

    /*
     * Strong characters flags array is to determine if the
     * unicode bidirectional category of the character is strong,
     * according to Unicode specification. If the bit with index equals to
     * character's unicode value is 1 - the character is strong.
     * This array was generated using UnicodeData.txt file from
     * http://www.unicode.org/Public/UNIDATA/
     */

    /** The Constant STRONG_TEXT_FLAGS. */
    private static final int[] STRONG_TEXT_FLAGS = { 0, 0, 134217726, 134217726,
            0, 69207040, -8388609, -8388609, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -65533, -1, -1, -100663297, 196611, 16415, 0, 0, 0,
            67108864, -10432, -5, -32769, -4194305, -1, -1, -1, -1, -1017, -1,
            -32769, 67108863, 65535, -131072, -25165825, -2, 767, 1073741824,
            -65463, 2033663, -939513841, 134217726, 2047, -73728, -1, -1,
            541065215, -67059616, -180225, 65535, -8192, 16383, -1, 131135, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, -8, -469762049, -16703999, 537001971,
            -417812, -473563649, -1333765759, 133431235, -423960, -1016201729,
            1577058305, 1900480, -278552, -470942209, 72193, 65475, -417812,
            1676541439, -1333782143, 262083, -700594200, -1006647528, 8396230,
            524224, -139282, 66059775, 30, 65475, -139284, -470811137,
            1080036831, 65475, -139284, -1006633473, 8396225, 65475, -58720276,
            805044223, -16547713, 1835008, -2, 917503, 268402815, 0, -17816170,
            537783470, 872349791, 0, -50331649, -1050673153, -257, -2147481601,
            3872, -1073741824, 237503, 0, -1, 16914171, 16777215, 0, 0, -1,
            -65473, 536870911, -1, -1, -2080374785, -1, -1, -249, -1, 67108863,
            -1, -1, 1031749119, -1, -49665, 2134769663, -8388803, -1,
            -12713985, -1, 134217727, 536870911, 65535, -1, -1, 2097151, -2,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, 8388607, 134217726, -1, -1, 131071, 253951, 6553599, 262143,
            122879, -1, -1065353217, 401605055, 1023, 67043328, -1, -1,
            16777215, -1, 511, 0, 0, 536870911, 33226872, -64, 2047999, -1,
            -64513, 67044351, 0, -830472193, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, 0, 0,
            -1, -1, -1, -1, 268435455, -1, -1, 67108863, 1061158911, -1,
            -1426112705, 1073741823, -1, 1608515583, 265232348, 534519807,
            49152, 27648, 0, -2147352576, 2031616, 0, 0, 0, 1043332228,
            -201605808, 992, -1, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            -4194304, -1, 134217727, 2097152, 0, 0, 0, 0, 0, 0, 0, -268435456,
            -1, -1, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4096, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1,
            -32769, 2147483647, 0, -1, -1, -1, 31, -1, -65473, -1, 32831,
            8388607, 2139062143, 2139062143, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 224, 524157950, -2, -1, -528482305, -2, -1,
            -134217729, -32, -122881, -1, -1, -32769, 16777215, 0, -65536,
            536870911, -1, 15, -1879048193, -1, 131071, -61441, 2147483647, -1,
            -1, -1, -125829121, -1, -1, 1073741823, 2147483647, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 2097152, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            134217728, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, 8191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2117, 159, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2147483648, 1, 0, 0, -2147483648,
            1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, -2147483648, 1, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2147483648, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -49153, -1, -63489, -1, -1, 67108863, 0,
            -1594359681, 1602223615, -37, -1, -1, 262143, -524288, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, 1073741823, -65536, -1, -196609, -1,
            255, 536805376, 0, 0, 0, -2162688, -1, -1, -1, 536870911, 0,
            134217726, 134217726, -64, -1, 2147483647, 486341884, 0

    };

    // index of context range (Serialization support)
    /** The key. */
    private int key;

    // flag, true if shaping contextual (Serialization support)
    /** The mask. */
    private int mask;

    // ranges to be shaped
    /** The ranges. */
    private int fRanges;

    // index of the default context
    /** The default context index. */
    private int fDefaultContextIndex;

    // flag if NumericShaper shapes contextually
    /** The contextual. */
    private boolean fContextual;

    // uses for non-context dependent case only
    /** The single range index. */
    private int fSingleRangeIndex;

    /**
     * Creates NumericShaper with specified parameters.
     *
     * @param ranges specified ranges to be shaped
     * @param defaultContext default context range
     * @param isContextual specifies if the instance is contextual
     */
    private NumericShaper(int ranges, int defaultContext, boolean isContextual){
        this.fRanges = ranges;
        this.fDefaultContextIndex = getIndexFromRange(defaultContext);
        this.fContextual = isContextual;

        if (!fContextual){
            fSingleRangeIndex = getIndexFromRange(ranges);
        }
    }

    /**
     * Returns script index of the specified context range.
     *
     * @param range specified range
     *
     * @return one of the script indices according to the specified range.
     */
    private int getIndexFromRange(int range){
        if (range == 0) {
            throw rangeException(range);
        }

        int index = 0;
        while (index < MAX_INDEX){
            if (range == (1 << index)){
                return index;
            }
            index++;
        }

        throw rangeException(range);
    }

    /**
     * Returns range corresponding to the specified script index.
     *
     * @param index specified script index
     *
     * @return one of the range constants according to the specified script index.
     */
    private int getRangeFromIndex(int index){
        if (index < 0 || index >= MAX_INDEX){
            throw rangeException(index);
        }

        return 1 << index;
    }

    private static IllegalArgumentException rangeException(int value) {
        throw new IllegalArgumentException("Illegal range argument value: " + value);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + fRanges;
        result = 31 * result + fDefaultContextIndex;
        result = 31 * result + (fContextual ? 1 : 0);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (obj == this) {
            return true;
        }

        try {
            NumericShaper ns = (NumericShaper)obj;
            return (fRanges == ns.fRanges &&
                    fDefaultContextIndex == ns.fDefaultContextIndex &&
                    fContextual == ns.fContextual);
        } catch (ClassCastException e){
        }

        return false;
    }

    @Override
    public String toString() {
        /* !! There is no description in the documentation what this method must
         * return. Thus format of toString method is based on 1.5 release
         * behavior and can be obtained using next test sample:
         *
         * // Simple shapers toString format
         * System.out.println(NumericShaper.getShaper(NumericShaper.EASTERN_ARABIC));
         *
         * // Context shapers with default context toString format
         * System.out.println(NumericShaper.getContextualShaper(
         *      NumericShaper.ARABIC | NumericShaper.TAMIL));
         *
         * // Context shapers with context
         * System.out.println(NumericShaper.getContextualShaper(
         *      NumericShaper.ARABIC | NumericShaper.TAMIL,
         *      NumericShaper.EASTERN_ARABIC));
         */
        StringBuilder sb = new StringBuilder(super.toString());

        sb.append("[contextual:");
        sb.append(fContextual);

        if (fContextual){
            sb.append(", context:");
            sb.append(contexts[fDefaultContextIndex]);
        }

        sb.append(", range(s): ");
        if (fContextual) {
            int index = 0;
            boolean isFirst = true;
            while (index < MAX_INDEX){
                if ((fRanges & (1 << index)) != 0){
                    if (isFirst){
                        isFirst = false;
                    } else {
                        sb.append(", ");
                    }
                    sb.append(contexts[index]);
                }
                index++;
            }
        } else {
            sb.append(contexts[fSingleRangeIndex]);
        }
        sb.append("]");

        return sb.toString();
    }

    /**
     * Gets the NumericShaper for the specified unicode ranges
     * and default unicode range. The defaultContext parameter
     * is used as the starting context (which indicates the
     * language/script being used). The OR logical operation
     * should be used for multiple ranges:
     * NumericShaper.DEVANAGARI | NumericShaper.BENGALI.
     * The NumericShaper returned by this method is contextual
     * in that it supports multiple character ranges, depending
     * on the context.
     *
     * @param ranges the unicode ranges.
     * @param defaultContext the default, starting context.
     *
     * @return the NumericShaper for the specified ranges.
     */
    public static NumericShaper getContextualShaper(int ranges,
            int defaultContext) {
        ranges &= ALL_RANGES;
        defaultContext &= ALL_RANGES;
        return new NumericShaper(ranges, defaultContext, true);
    }

    /**
     * Gets the NumericShaper for the specified unicode ranges.
     * The OR logical operation should be used for multiple ranges:
     * NumericShaper.DEVANAGARI | NumericShaper.BENGALI.
     * The NumericShaper returned by this method is contextual
     * in that it supports multiple character ranges, depending
     * on the context.
     *
     * @param ranges the unicode ranges.
     *
     * @return the NumericShaper for the specified ranges.
     */
    public static NumericShaper getContextualShaper(int ranges) {
        ranges &= ALL_RANGES;
        return new NumericShaper(ranges, EUROPEAN, true);
    }

    /**
     * Gets the masks for all of the ranges supported by this NumericShaper,
     * packed into an int value using the logical OR logical operation
     * for multiple ranges:
     * NumericShaper.DEVANAGARI | NumericShaper.BENGALI.
     *
     * @return all ranges of this NumericShaper.
     */
    public int getRanges() {
        return fRanges;
    }

    /**
     * Gets a NumericShaper for the specified unicode range.
     * The NumericShaper supports only a single range and
     * hence is not contextual.
     *
     * @param singleRange the specified unicode single range.
     *
     * @return the NumericShaper for the specified unicode range.
     */
    public static NumericShaper getShaper(int singleRange) {
        singleRange &= ALL_RANGES;
        return new NumericShaper(singleRange, EUROPEAN, false);
    }

    /**
     * Checks if this NumericShaper is contextual (supporting
     * multiple script ranges) or not.
     *
     * @return true, if this NumericShaper is contextual, false otherwise.
     */
    public boolean isContextual() {
        return fContextual;
    }

    /**
     * Transforms the encoding of the text, starting from the character
     * at index start and transforming count characters,
     * using the specified context.
     *
     * @param text the text to be shaped.
     * @param start the start offset of the text.
     * @param count the number of characters to be shaped.
     * @param context the context to be used for shaping.
     */
    public void shape(char[] text, int start, int count, int context) {
        if (isContextual()){
            contextualShape(text, start, count, getIndexFromRange(context));
        } else {
            nonContextualShape(text, start, count);
        }
    }

    /**
     * Transforms the encoding of the text, starting from the character
     * at index start and transforming count characters.
     *
     * @param text the text to be shaped.
     * @param start the start offset of the text.
     * @param count the number of characters to be shaped.
     */
    public void shape(char[] text, int start, int count) {
        if (isContextual()){
            contextualShape(text, start, count, fDefaultContextIndex);
        } else {
            nonContextualShape(text, start, count);
        }
    }

    /**
     * Converts count of digits of the given array of characters from the start
     * index using specified context. This method is applied for the contextual
     * shaping, if the shaper instance is not contextual use nonContextualShape
     * method.
     *
     * @param text an array of chars
     * @param start index of the first character to convert
     * @param count a number of characters to convert
     * @param contextIndex index of the script index to use in shaper
     */
    private void contextualShape(char[] text, int start, int count,
            int contextIndex){
        char maxDigit = (char)0x0039;
        char minDigit = (char)0x0030;

        int currIndex;
        if (((1 << contextIndex) & fRanges) == 0 ){
            currIndex = INDEX_EUROPEAN;
        } else {
            currIndex = contextIndex;
        }

        for (int ind = start; ind < start + count; ind++){
            if (minDigit <= text[ind] && text[ind] <= maxDigit){
                if (currIndex != INDEX_ETHIOPIC || text[ind] != '0'){
                    text[ind] = (char)(digitsLowRanges[currIndex] + text[ind]);
                }
            } else {
                if(isCharStrong(text[ind])){
                    int index = getCharIndex(text[ind]);
                    if (currIndex != index){
                        if (((1 << index) & fRanges) != 0){
                            currIndex = index;
                        } else {
                            currIndex = INDEX_EUROPEAN;
                        }
                    }
                }
            }
        }

    }

    /**
     * Converts count of digits of the given array of characters from the start
     * index. Method is applied for non-contextual shaper.
     *
     * @param text an array of chars
     * @param start index of the first character to convert
     * @param count a number of characters to convert
     */
    private void nonContextualShape(char[] text, int start, int count){
        char maxDigit = (char)0x0039;
        char minDigit = (char)((fRanges == ETHIOPIC) ? 0x0031 : 0x0030);
        for (int ind = start; ind < start + count; ind++){
            if (minDigit <= text[ind] && text[ind] <= maxDigit){
                    text[ind] = (char)(digitsLowRanges[fSingleRangeIndex] + text[ind]);
            }
        }

    }

    /**
     * Returns the index of the script of the specified char.
     *
     * @param ch specified unicode character
     *
     * @return script index corresponding to the given char
     */
    private int getCharIndex(char ch){
        int index = INDEX_EUROPEAN;
        for (int i=0; i < MAX_INDEX; i++){
            int j = i * 2;
            if (scriptsRanges[j] <= ch && ch <= scriptsRanges[j+1]){
                return i;
            }
        }

        return index;
    }

    /**
     * Returns true if the bidirectional category of the character
     * is strong.
     *
     * @param chr the chr
     *
     * @return true, if the character is strong, false otherwise
     */
    private boolean isCharStrong(int chr) {
        return (STRONG_TEXT_FLAGS[chr >> 5] & (1 << (chr % 32))) != 0;
    }

    /**
     * Updates all private serialized fields for object to be correctly serialized
     * according to the serialized form of this class mentioned in the
     * documentation.
     */
     private void updateRangesFields(){
        fRanges = (mask & ~(1 << 31));
        fContextual = ((mask &(1 << 31)) != 0);
        if (fContextual){
            fRanges = (mask & ~(1 << 31));
            fDefaultContextIndex = key;
        } else {
            fRanges = mask;
            fSingleRangeIndex = key;
        }
    }

    /**
     * Updates private fields for object after deserialization
     * according to the serialized form of this class mentioned in the
     * documentation.
     */
    private void updateKeyMaskFields(){
        mask = fRanges;
        if (fContextual){
            mask |= (1 << 31);
            key = fDefaultContextIndex;
        } else{
            key = fSingleRangeIndex;
        }
    }

    /**
     * Write object.
     *
     * @param out the out
     *
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void writeObject(java.io.ObjectOutputStream out)
                                throws IOException{
        updateKeyMaskFields();
        out.defaultWriteObject();
    }

    /**
     * Read object.
     *
     * @param in the in
     *
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws ClassNotFoundException the class not found exception
     */
    private void readObject(java.io.ObjectInputStream in)
                                throws IOException, ClassNotFoundException{
        in.defaultReadObject();
        updateRangesFields();
    }

}
