/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.awt.font;
import java.io.
IOException;
import java.io.
ObjectOutputStream;
import java.util.
Arrays;
import java.util.
Comparator;
import java.util.
EnumSet;
import java.util.
Set;
/**
* The <code>NumericShaper</code> class is used to convert Latin-1 (European)
* digits to other Unicode decimal digits. Users of this class will
* primarily be people who wish to present data using
* national digit shapes, but find it more convenient to represent the
* data internally using Latin-1 (European) digits. This does not
* interpret the deprecated numeric shape selector character (U+206E).
* <p>
* Instances of <code>NumericShaper</code> are typically applied
* as attributes to text with the
* {@link TextAttribute#NUMERIC_SHAPING NUMERIC_SHAPING} attribute
* of the <code>TextAttribute</code> class.
* For example, this code snippet causes a <code>TextLayout</code> to
* shape European digits to Arabic in an Arabic context:<br>
* <blockquote><pre>
* Map map = new HashMap();
* map.put(TextAttribute.NUMERIC_SHAPING,
* NumericShaper.getContextualShaper(NumericShaper.ARABIC));
* FontRenderContext frc = ...;
* TextLayout layout = new TextLayout(text, map, frc);
* layout.draw(g2d, x, y);
* </pre></blockquote>
* <br>
* It is also possible to perform numeric shaping explicitly using instances
* of <code>NumericShaper</code>, as this code snippet demonstrates:<br>
* <blockquote><pre>
* char[] text = ...;
* // shape all EUROPEAN digits (except zero) to ARABIC digits
* NumericShaper shaper = NumericShaper.getShaper(NumericShaper.ARABIC);
* shaper.shape(text, start, count);
*
* // shape European digits to ARABIC digits if preceding text is Arabic, or
* // shape European digits to TAMIL digits if preceding text is Tamil, or
* // leave European digits alone if there is no preceding text, or
* // preceding text is neither Arabic nor Tamil
* NumericShaper shaper =
* NumericShaper.getContextualShaper(NumericShaper.ARABIC |
* NumericShaper.TAMIL,
* NumericShaper.EUROPEAN);
* shaper.shape(text, start, count);
* </pre></blockquote>
*
* <p><b>Bit mask- and enum-based Unicode ranges</b></p>
*
* <p>This class supports two different programming interfaces to
* represent Unicode ranges for script-specific digits: bit
* mask-based ones, such as {@link #ARABIC NumericShaper.ARABIC}, and
* enum-based ones, such as {@link NumericShaper.Range#ARABIC}.
* Multiple ranges can be specified by ORing bit mask-based constants,
* such as:
* <blockquote><pre>
* NumericShaper.ARABIC | NumericShaper.TAMIL
* </pre></blockquote>
* or creating a {@code Set} with the {@link NumericShaper.Range}
* constants, such as:
* <blockquote><pre>
* EnumSet.of(NumericShaper.Scirpt.ARABIC, NumericShaper.Range.TAMIL)
* </pre></blockquote>
* The enum-based ranges are a super set of the bit mask-based ones.
*
* <p>If the two interfaces are mixed (including serialization),
* Unicode range values are mapped to their counterparts where such
* mapping is possible, such as {@code NumericShaper.Range.ARABIC}
* from/to {@code NumericShaper.ARABIC}. If any unmappable range
* values are specified, such as {@code NumericShaper.Range.BALINESE},
* those ranges are ignored.
*
* <p><b>Decimal Digits Precedence</b></p>
*
* <p>A Unicode range may have more than one set of decimal digits. If
* multiple decimal digits sets are specified for the same Unicode
* range, one of the sets will take precedence as follows.
*
* <table border=1 cellspacing=3 cellpadding=0 summary="NumericShaper constants precedence.">
* <tr>
* <th class="TableHeadingColor">Unicode Range</th>
* <th class="TableHeadingColor"><code>NumericShaper</code> Constants</th>
* <th class="TableHeadingColor">Precedence</th>
* </tr>
* <tr>
* <td rowspan="2">Arabic</td>
* <td>{@link NumericShaper#ARABIC NumericShaper.ARABIC}<br>
* {@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td>
* <td>{@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}</td>
* </tr>
* <tr>
* <td>{@link NumericShaper.Range#ARABIC}<br>
* {@link NumericShaper.Range#EASTERN_ARABIC}</td>
* <td>{@link NumericShaper.Range#EASTERN_ARABIC}</td>
* </tr>
* <tr>
* <td>Tai Tham</td>
* <td>{@link NumericShaper.Range#TAI_THAM_HORA}<br>
* {@link NumericShaper.Range#TAI_THAM_THAM}</td>
* <td>{@link NumericShaper.Range#TAI_THAM_THAM}</td>
* </tr>
* </table>
*
* @since 1.4
*/
public final class
NumericShaper implements java.io.
Serializable {
/**
* A {@code NumericShaper.Range} represents a Unicode range of a
* script having its own decimal digits. For example, the {@link
* NumericShaper.Range#THAI} range has the Thai digits, THAI DIGIT
* ZERO (U+0E50) to THAI DIGIT NINE (U+0E59).
*
* <p>The <code>Range</code> enum replaces the traditional bit
* mask-based values (e.g., {@link NumericShaper#ARABIC}), and
* supports more Unicode ranges than the bit mask-based ones. For
* example, the following code using the bit mask:
* <blockquote><pre>
* NumericShaper.getContextualShaper(NumericShaper.ARABIC |
* NumericShaper.TAMIL,
* NumericShaper.EUROPEAN);
* </pre></blockquote>
* can be written using this enum as:
* <blockquote><pre>
* NumericShaper.getContextualShaper(EnumSet.of(
* NumericShaper.Range.ARABIC,
* NumericShaper.Range.TAMIL),
* NumericShaper.Range.EUROPEAN);
* </pre></blockquote>
*
* @since 1.7
*/
public static enum
Range {
// The order of EUROPEAN to MOGOLIAN must be consistent
// with the bitmask-based constants.
/**
* The Latin (European) range with the Latin (ASCII) digits.
*/
EUROPEAN ('\u0030', '\u0000', '\u0300'),
/**
* The Arabic range with the Arabic-Indic digits.
*/
ARABIC ('\u0660', '\u0600', '\u0780'),
/**
* The Arabic range with the Eastern Arabic-Indic digits.
*/
EASTERN_ARABIC ('\u06f0', '\u0600', '\u0780'),
/**
* The Devanagari range with the Devanagari digits.
*/
DEVANAGARI ('\u0966', '\u0900', '\u0980'),
/**
* The Bengali range with the Bengali digits.
*/
BENGALI ('\u09e6', '\u0980', '\u0a00'),
/**
* The Gurmukhi range with the Gurmukhi digits.
*/
GURMUKHI ('\u0a66', '\u0a00', '\u0a80'),
/**
* The Gujarati range with the Gujarati digits.
*/
GUJARATI ('\u0ae6', '\u0b00', '\u0b80'),
/**
* The Oriya range with the Oriya digits.
*/
ORIYA ('\u0b66', '\u0b00', '\u0b80'),
/**
* The Tamil range with the Tamil digits.
*/
TAMIL ('\u0be6', '\u0b80', '\u0c00'),
/**
* The Telugu range with the Telugu digits.
*/
TELUGU ('\u0c66', '\u0c00', '\u0c80'),
/**
* The Kannada range with the Kannada digits.
*/
KANNADA ('\u0ce6', '\u0c80', '\u0d00'),
/**
* The Malayalam range with the Malayalam digits.
*/
MALAYALAM ('\u0d66', '\u0d00', '\u0d80'),
/**
* The Thai range with the Thai digits.
*/
THAI ('\u0e50', '\u0e00', '\u0e80'),
/**
* The Lao range with the Lao digits.
*/
LAO ('\u0ed0', '\u0e80', '\u0f00'),
/**
* The Tibetan range with the Tibetan digits.
*/
TIBETAN ('\u0f20', '\u0f00', '\u1000'),
/**
* The Myanmar range with the Myanmar digits.
*/
MYANMAR ('\u1040', '\u1000', '\u1080'),
/**
* The Ethiopic range with the Ethiopic digits. Ethiopic
* does not have a decimal digit 0 so Latin (European) 0 is
* used.
*/
ETHIOPIC ('\u1369', '\u1200', '\u1380') {
@
Override
char
getNumericBase() { return 1; }
},
/**
* The Khmer range with the Khmer digits.
*/
KHMER ('\u17e0', '\u1780', '\u1800'),
/**
* The Mongolian range with the Mongolian digits.
*/
MONGOLIAN ('\u1810', '\u1800', '\u1900'),
// The order of EUROPEAN to MOGOLIAN must be consistent
// with the bitmask-based constants.
/**
* The N'Ko range with the N'Ko digits.
*/
NKO ('\u07c0', '\u07c0', '\u0800'),
/**
* The Myanmar range with the Myanmar Shan digits.
*/
MYANMAR_SHAN ('\u1090', '\u1000', '\u10a0'),
/**
* The Limbu range with the Limbu digits.
*/
LIMBU ('\u1946', '\u1900', '\u1950'),
/**
* The New Tai Lue range with the New Tai Lue digits.
*/
NEW_TAI_LUE ('\u19d0', '\u1980', '\u19e0'),
/**
* The Balinese range with the Balinese digits.
*/
BALINESE ('\u1b50', '\u1b00', '\u1b80'),
/**
* The Sundanese range with the Sundanese digits.
*/
SUNDANESE ('\u1bb0', '\u1b80', '\u1bc0'),
/**
* The Lepcha range with the Lepcha digits.
*/
LEPCHA ('\u1c40', '\u1c00', '\u1c50'),
/**
* The Ol Chiki range with the Ol Chiki digits.
*/
OL_CHIKI ('\u1c50', '\u1c50', '\u1c80'),
/**
* The Vai range with the Vai digits.
*/
VAI ('\ua620', '\ua500', '\ua640'),
/**
* The Saurashtra range with the Saurashtra digits.
*/
SAURASHTRA ('\ua8d0', '\ua880', '\ua8e0'),
/**
* The Kayah Li range with the Kayah Li digits.
*/
KAYAH_LI ('\ua900', '\ua900', '\ua930'),
/**
* The Cham range with the Cham digits.
*/
CHAM ('\uaa50', '\uaa00', '\uaa60'),
/**
* The Tai Tham Hora range with the Tai Tham Hora digits.
*/
TAI_THAM_HORA ('\u1a80', '\u1a20', '\u1ab0'),
/**
* The Tai Tham Tham range with the Tai Tham Tham digits.
*/
TAI_THAM_THAM ('\u1a90', '\u1a20', '\u1ab0'),
/**
* The Javanese range with the Javanese digits.
*/
JAVANESE ('\ua9d0', '\ua980', '\ua9e0'),
/**
* The Meetei Mayek range with the Meetei Mayek digits.
*/
MEETEI_MAYEK ('\uabf0', '\uabc0', '\uac00');
private static int
toRangeIndex(
Range script) {
int
index =
script.
ordinal();
return
index <
NUM_KEYS ?
index : -1;
}
private static
Range indexToRange(int
index) {
return
index <
NUM_KEYS ?
Range.
values()[
index] : null;
}
private static int
toRangeMask(
Set<
Range>
ranges) {
int
m = 0;
for (
Range range :
ranges) {
int
index =
range.
ordinal();
if (
index <
NUM_KEYS) {
m |= 1 <<
index;
}
}
return
m;
}
private static
Set<
Range>
maskToRangeSet(int
mask) {
Set<
Range>
set =
EnumSet.
noneOf(
Range.class);
Range[]
a =
Range.
values();
for (int
i = 0;
i <
NUM_KEYS;
i++) {
if ((
mask & (1 <<
i)) != 0) {
set.
add(
a[
i]);
}
}
return
set;
}
// base character of range digits
private final int
base;
// Unicode range
private final int
start, // inclusive
end; // exclusive
private
Range(int
base, int
start, int
end) {
this.
base =
base - ('0' +
getNumericBase());
this.
start =
start;
this.
end =
end;
}
private int
getDigitBase() {
return
base;
}
char
getNumericBase() {
return 0;
}
private boolean
inRange(int
c) {
return
start <=
c &&
c <
end;
}
}
/** index of context for contextual shaping - values range from 0 to 18 */
private int
key;
/** flag indicating whether to shape contextually (high bit) and which
* digit ranges to shape (bits 0-18)
*/
private int
mask;
/**
* The context {@code Range} for contextual shaping or the {@code
* Range} for non-contextual shaping. {@code null} for the bit
* mask-based API.
*
* @since 1.7
*/
private
Range shapingRange;
/**
* {@code Set<Range>} indicating which Unicode ranges to
* shape. {@code null} for the bit mask-based API.
*/
private transient
Set<
Range>
rangeSet;
/**
* rangeSet.toArray() value. Sorted by Range.base when the number
* of elements is greater then BSEARCH_THRESHOLD.
*/
private transient
Range[]
rangeArray;
/**
* If more than BSEARCH_THRESHOLD ranges are specified, binary search is used.
*/
private static final int
BSEARCH_THRESHOLD = 3;
private static final long
serialVersionUID = -8022764705923730308L;
/** Identifies the Latin-1 (European) and extended range, and
* Latin-1 (European) decimal base.
*/
public static final int
EUROPEAN = 1<<0;
/** Identifies the ARABIC range and decimal base. */
public static final int
ARABIC = 1<<1;
/** Identifies the ARABIC range and ARABIC_EXTENDED decimal base. */
public static final int
EASTERN_ARABIC = 1<<2;
/** Identifies the DEVANAGARI range and decimal base. */
public static final int
DEVANAGARI = 1<<3;
/** Identifies the BENGALI range and decimal base. */
public static final int
BENGALI = 1<<4;
/** Identifies the GURMUKHI range and decimal base. */
public static final int
GURMUKHI = 1<<5;
/** Identifies the GUJARATI range and decimal base. */
public static final int
GUJARATI = 1<<6;
/** Identifies the ORIYA range and decimal base. */
public static final int
ORIYA = 1<<7;
/** Identifies the TAMIL range and decimal base. */
// TAMIL DIGIT ZERO was added in Unicode 4.1
public static final int
TAMIL = 1<<8;
/** Identifies the TELUGU range and decimal base. */
public static final int
TELUGU = 1<<9;
/** Identifies the KANNADA range and decimal base. */
public static final int
KANNADA = 1<<10;
/** Identifies the MALAYALAM range and decimal base. */
public static final int
MALAYALAM = 1<<11;
/** Identifies the THAI range and decimal base. */
public static final int
THAI = 1<<12;
/** Identifies the LAO range and decimal base. */
public static final int
LAO = 1<<13;
/** Identifies the TIBETAN range and decimal base. */
public static final int
TIBETAN = 1<<14;
/** Identifies the MYANMAR range and decimal base. */
public static final int
MYANMAR = 1<<15;
/** Identifies the ETHIOPIC range and decimal base. */
public static final int
ETHIOPIC = 1<<16;
/** Identifies the KHMER range and decimal base. */
public static final int
KHMER = 1<<17;
/** Identifies the MONGOLIAN range and decimal base. */
public static final int
MONGOLIAN = 1<<18;
/** Identifies all ranges, for full contextual shaping.
*
* <p>This constant specifies all of the bit mask-based
* ranges. Use {@code EmunSet.allOf(NumericShaper.Range.class)} to
* specify all of the enum-based ranges.
*/
public static final int
ALL_RANGES = 0x0007ffff;
private static final int
EUROPEAN_KEY = 0;
private static final int
ARABIC_KEY = 1;
private static final int
EASTERN_ARABIC_KEY = 2;
private static final int
DEVANAGARI_KEY = 3;
private static final int
BENGALI_KEY = 4;
private static final int
GURMUKHI_KEY = 5;
private static final int
GUJARATI_KEY = 6;
private static final int
ORIYA_KEY = 7;
private static final int
TAMIL_KEY = 8;
private static final int
TELUGU_KEY = 9;
private static final int
KANNADA_KEY = 10;
private static final int
MALAYALAM_KEY = 11;
private static final int
THAI_KEY = 12;
private static final int
LAO_KEY = 13;
private static final int
TIBETAN_KEY = 14;
private static final int
MYANMAR_KEY = 15;
private static final int
ETHIOPIC_KEY = 16;
private static final int
KHMER_KEY = 17;
private static final int
MONGOLIAN_KEY = 18;
private static final int
NUM_KEYS =
MONGOLIAN_KEY + 1; // fixed
private static final int
CONTEXTUAL_MASK = 1<<31;
private static final char[]
bases = {
'\u0030' - '\u0030', // EUROPEAN
'\u0660' - '\u0030', // ARABIC-INDIC
'\u06f0' - '\u0030', // EXTENDED ARABIC-INDIC (EASTERN_ARABIC)
'\u0966' - '\u0030', // DEVANAGARI
'\u09e6' - '\u0030', // BENGALI
'\u0a66' - '\u0030', // GURMUKHI
'\u0ae6' - '\u0030', // GUJARATI
'\u0b66' - '\u0030', // ORIYA
'\u0be6' - '\u0030', // TAMIL - zero was added in Unicode 4.1
'\u0c66' - '\u0030', // TELUGU
'\u0ce6' - '\u0030', // KANNADA
'\u0d66' - '\u0030', // MALAYALAM
'\u0e50' - '\u0030', // THAI
'\u0ed0' - '\u0030', // LAO
'\u0f20' - '\u0030', // TIBETAN
'\u1040' - '\u0030', // MYANMAR
'\u1369' - '\u0031', // ETHIOPIC - no zero
'\u17e0' - '\u0030', // KHMER
'\u1810' - '\u0030', // MONGOLIAN
};
// some ranges adjoin or overlap, rethink if we want to do a binary search on this
private static final char[]
contexts = {
'\u0000', '\u0300', // 'EUROPEAN' (really latin-1 and extended)
'\u0600', '\u0780', // ARABIC
'\u0600', '\u0780', // EASTERN_ARABIC -- note overlap with arabic
'\u0900', '\u0980', // DEVANAGARI
'\u0980', '\u0a00', // BENGALI
'\u0a00', '\u0a80', // GURMUKHI
'\u0a80', '\u0b00', // GUJARATI
'\u0b00', '\u0b80', // ORIYA
'\u0b80', '\u0c00', // TAMIL
'\u0c00', '\u0c80', // TELUGU
'\u0c80', '\u0d00', // KANNADA
'\u0d00', '\u0d80', // MALAYALAM
'\u0e00', '\u0e80', // THAI
'\u0e80', '\u0f00', // LAO
'\u0f00', '\u1000', // TIBETAN
'\u1000', '\u1080', // MYANMAR
'\u1200', '\u1380', // ETHIOPIC - note missing zero
'\u1780', '\u1800', // KHMER
'\u1800', '\u1900', // MONGOLIAN
'\uffff',
};
// assume most characters are near each other so probing the cache is infrequent,
// and a linear probe is ok.
private static int
ctCache = 0;
private static int
ctCacheLimit =
contexts.length - 2;
// warning, synchronize access to this as it modifies state
private static int
getContextKey(char
c) {
if (
c <
contexts[
ctCache]) {
while (
ctCache > 0 &&
c <
contexts[
ctCache]) --
ctCache;
} else if (
c >=
contexts[
ctCache + 1]) {
while (
ctCache <
ctCacheLimit &&
c >=
contexts[
ctCache + 1]) ++
ctCache;
}
// if we're not in a known range, then return EUROPEAN as the range key
return (
ctCache & 0x1) == 0 ? (
ctCache / 2) :
EUROPEAN_KEY;
}
// cache for the NumericShaper.Range version
private transient volatile
Range currentRange =
Range.
EUROPEAN;
private
Range rangeForCodePoint(final int
codepoint) {
if (
currentRange.
inRange(
codepoint)) {
return
currentRange;
}
final
Range[]
ranges =
rangeArray;
if (
ranges.length >
BSEARCH_THRESHOLD) {
int
lo = 0;
int
hi =
ranges.length - 1;
while (
lo <=
hi) {
int
mid = (
lo +
hi) / 2;
Range range =
ranges[
mid];
if (
codepoint <
range.
start) {
hi =
mid - 1;
} else if (
codepoint >=
range.
end) {
lo =
mid + 1;
} else {
currentRange =
range;
return
range;
}
}
} else {
for (int
i = 0;
i <
ranges.length;
i++) {
if (
ranges[
i].
inRange(
codepoint)) {
return
ranges[
i];
}
}
}
return
Range.
EUROPEAN;
}
/*
* A range table of strong directional characters (types L, R, AL).
* Even (left) indexes are starts of ranges of non-strong-directional (or undefined)
* characters, odd (right) indexes are starts of ranges of strong directional
* characters.
*/
private static int[]
strongTable = {
0x0000, 0x0041,
0x005b, 0x0061,
0x007b, 0x00aa,
0x00ab, 0x00b5,
0x00b6, 0x00ba,
0x00bb, 0x00c0,
0x00d7, 0x00d8,
0x00f7, 0x00f8,
0x02b9, 0x02bb,
0x02c2, 0x02d0,
0x02d2, 0x02e0,
0x02e5, 0x02ee,
0x02ef, 0x0370,
0x0374, 0x0376,
0x037e, 0x0386,
0x0387, 0x0388,
0x03f6, 0x03f7,
0x0483, 0x048a,
0x058a, 0x05be,
0x05bf, 0x05c0,
0x05c1, 0x05c3,
0x05c4, 0x05c6,
0x05c7, 0x05d0,
0x0600, 0x0608,
0x0609, 0x060b,
0x060c, 0x060d,
0x060e, 0x061b,
0x064b, 0x066d,
0x0670, 0x0671,
0x06d6, 0x06e5,
0x06e7, 0x06ee,
0x06f0, 0x06fa,
0x0711, 0x0712,
0x0730, 0x074d,
0x07a6, 0x07b1,
0x07eb, 0x07f4,
0x07f6, 0x07fa,
0x0816, 0x081a,
0x081b, 0x0824,
0x0825, 0x0828,
0x0829, 0x0830,
0x0859, 0x085e,
0x08e4, 0x0903,
0x093a, 0x093b,
0x093c, 0x093d,
0x0941, 0x0949,
0x094d, 0x094e,
0x0951, 0x0958,
0x0962, 0x0964,
0x0981, 0x0982,
0x09bc, 0x09bd,
0x09c1, 0x09c7,
0x09cd, 0x09ce,
0x09e2, 0x09e6,
0x09f2, 0x09f4,
0x09fb, 0x0a03,
0x0a3c, 0x0a3e,
0x0a41, 0x0a59,
0x0a70, 0x0a72,
0x0a75, 0x0a83,
0x0abc, 0x0abd,
0x0ac1, 0x0ac9,
0x0acd, 0x0ad0,
0x0ae2, 0x0ae6,
0x0af1, 0x0b02,
0x0b3c, 0x0b3d,
0x0b3f, 0x0b40,
0x0b41, 0x0b47,
0x0b4d, 0x0b57,
0x0b62, 0x0b66,
0x0b82, 0x0b83,
0x0bc0, 0x0bc1,
0x0bcd, 0x0bd0,
0x0bf3, 0x0c01,
0x0c3e, 0x0c41,
0x0c46, 0x0c58,
0x0c62, 0x0c66,
0x0c78, 0x0c7f,
0x0cbc, 0x0cbd,
0x0ccc, 0x0cd5,
0x0ce2, 0x0ce6,
0x0d41, 0x0d46,
0x0d4d, 0x0d4e,
0x0d62, 0x0d66,
0x0dca, 0x0dcf,
0x0dd2, 0x0dd8,
0x0e31, 0x0e32,
0x0e34, 0x0e40,
0x0e47, 0x0e4f,
0x0eb1, 0x0eb2,
0x0eb4, 0x0ebd,
0x0ec8, 0x0ed0,
0x0f18, 0x0f1a,
0x0f35, 0x0f36,
0x0f37, 0x0f38,
0x0f39, 0x0f3e,
0x0f71, 0x0f7f,
0x0f80, 0x0f85,
0x0f86, 0x0f88,
0x0f8d, 0x0fbe,
0x0fc6, 0x0fc7,
0x102d, 0x1031,
0x1032, 0x1038,
0x1039, 0x103b,
0x103d, 0x103f,
0x1058, 0x105a,
0x105e, 0x1061,
0x1071, 0x1075,
0x1082, 0x1083,
0x1085, 0x1087,
0x108d, 0x108e,
0x109d, 0x109e,
0x135d, 0x1360,
0x1390, 0x13a0,
0x1400, 0x1401,
0x1680, 0x1681,
0x169b, 0x16a0,
0x1712, 0x1720,
0x1732, 0x1735,
0x1752, 0x1760,
0x1772, 0x1780,
0x17b4, 0x17b6,
0x17b7, 0x17be,
0x17c6, 0x17c7,
0x17c9, 0x17d4,
0x17db, 0x17dc,
0x17dd, 0x17e0,
0x17f0, 0x1810,
0x18a9, 0x18aa,
0x1920, 0x1923,
0x1927, 0x1929,
0x1932, 0x1933,
0x1939, 0x1946,
0x19de, 0x1a00,
0x1a17, 0x1a19,
0x1a56, 0x1a57,
0x1a58, 0x1a61,
0x1a62, 0x1a63,
0x1a65, 0x1a6d,
0x1a73, 0x1a80,
0x1b00, 0x1b04,
0x1b34, 0x1b35,
0x1b36, 0x1b3b,
0x1b3c, 0x1b3d,
0x1b42, 0x1b43,
0x1b6b, 0x1b74,
0x1b80, 0x1b82,
0x1ba2, 0x1ba6,
0x1ba8, 0x1baa,
0x1bab, 0x1bac,
0x1be6, 0x1be7,
0x1be8, 0x1bea,
0x1bed, 0x1bee,
0x1bef, 0x1bf2,
0x1c2c, 0x1c34,
0x1c36, 0x1c3b,
0x1cd0, 0x1cd3,
0x1cd4, 0x1ce1,
0x1ce2, 0x1ce9,
0x1ced, 0x1cee,
0x1cf4, 0x1cf5,
0x1dc0, 0x1e00,
0x1fbd, 0x1fbe,
0x1fbf, 0x1fc2,
0x1fcd, 0x1fd0,
0x1fdd, 0x1fe0,
0x1fed, 0x1ff2,
0x1ffd, 0x200e,
0x2010, 0x2071,
0x2074, 0x207f,
0x2080, 0x2090,
0x20a0, 0x2102,
0x2103, 0x2107,
0x2108, 0x210a,
0x2114, 0x2115,
0x2116, 0x2119,
0x211e, 0x2124,
0x2125, 0x2126,
0x2127, 0x2128,
0x2129, 0x212a,
0x212e, 0x212f,
0x213a, 0x213c,
0x2140, 0x2145,
0x214a, 0x214e,
0x2150, 0x2160,
0x2189, 0x2336,
0x237b, 0x2395,
0x2396, 0x249c,
0x24ea, 0x26ac,
0x26ad, 0x2800,
0x2900, 0x2c00,
0x2ce5, 0x2ceb,
0x2cef, 0x2cf2,
0x2cf9, 0x2d00,
0x2d7f, 0x2d80,
0x2de0, 0x3005,
0x3008, 0x3021,
0x302a, 0x3031,
0x3036, 0x3038,
0x303d, 0x3041,
0x3099, 0x309d,
0x30a0, 0x30a1,
0x30fb, 0x30fc,
0x31c0, 0x31f0,
0x321d, 0x3220,
0x3250, 0x3260,
0x327c, 0x327f,
0x32b1, 0x32c0,
0x32cc, 0x32d0,
0x3377, 0x337b,
0x33de, 0x33e0,
0x33ff, 0x3400,
0x4dc0, 0x4e00,
0xa490, 0xa4d0,
0xa60d, 0xa610,
0xa66f, 0xa680,
0xa69f, 0xa6a0,
0xa6f0, 0xa6f2,
0xa700, 0xa722,
0xa788, 0xa789,
0xa802, 0xa803,
0xa806, 0xa807,
0xa80b, 0xa80c,
0xa825, 0xa827,
0xa828, 0xa830,
0xa838, 0xa840,
0xa874, 0xa880,
0xa8c4, 0xa8ce,
0xa8e0, 0xa8f2,
0xa926, 0xa92e,
0xa947, 0xa952,
0xa980, 0xa983,
0xa9b3, 0xa9b4,
0xa9b6, 0xa9ba,
0xa9bc, 0xa9bd,
0xaa29, 0xaa2f,
0xaa31, 0xaa33,
0xaa35, 0xaa40,
0xaa43, 0xaa44,
0xaa4c, 0xaa4d,
0xaab0, 0xaab1,
0xaab2, 0xaab5,
0xaab7, 0xaab9,
0xaabe, 0xaac0,
0xaac1, 0xaac2,
0xaaec, 0xaaee,
0xaaf6, 0xab01,
0xabe5, 0xabe6,
0xabe8, 0xabe9,
0xabed, 0xabf0,
0xfb1e, 0xfb1f,
0xfb29, 0xfb2a,
0xfd3e, 0xfd50,
0xfdfd, 0xfe70,
0xfeff, 0xff21,
0xff3b, 0xff41,
0xff5b, 0xff66,
0xffe0, 0x10000,
0x10101, 0x10102,
0x10140, 0x101d0,
0x101fd, 0x10280,
0x1091f, 0x10920,
0x10a01, 0x10a10,
0x10a38, 0x10a40,
0x10b39, 0x10b40,
0x10e60, 0x11000,
0x11001, 0x11002,
0x11038, 0x11047,
0x11052, 0x11066,
0x11080, 0x11082,
0x110b3, 0x110b7,
0x110b9, 0x110bb,
0x11100, 0x11103,
0x11127, 0x1112c,
0x1112d, 0x11136,
0x11180, 0x11182,
0x111b6, 0x111bf,
0x116ab, 0x116ac,
0x116ad, 0x116ae,
0x116b0, 0x116b6,
0x116b7, 0x116c0,
0x16f8f, 0x16f93,
0x1d167, 0x1d16a,
0x1d173, 0x1d183,
0x1d185, 0x1d18c,
0x1d1aa, 0x1d1ae,
0x1d200, 0x1d360,
0x1d6db, 0x1d6dc,
0x1d715, 0x1d716,
0x1d74f, 0x1d750,
0x1d789, 0x1d78a,
0x1d7c3, 0x1d7c4,
0x1d7ce, 0x1ee00,
0x1eef0, 0x1f110,
0x1f16a, 0x1f170,
0x1f300, 0x1f48c,
0x1f48d, 0x1f524,
0x1f525, 0x20000,
0xe0001, 0xf0000,
0x10fffe, 0x10ffff // sentinel
};
// use a binary search with a cache
private transient volatile int
stCache = 0;
private boolean
isStrongDirectional(char
c) {
int
cachedIndex =
stCache;
if (
c <
strongTable[
cachedIndex]) {
cachedIndex =
search(
c,
strongTable, 0,
cachedIndex);
} else if (
c >=
strongTable[
cachedIndex + 1]) {
cachedIndex =
search(
c,
strongTable,
cachedIndex + 1,
strongTable.length -
cachedIndex - 1);
}
boolean
val = (
cachedIndex & 0x1) == 1;
stCache =
cachedIndex;
return
val;
}
private static int
getKeyFromMask(int
mask) {
int
key = 0;
while (
key <
NUM_KEYS && ((
mask & (1<<
key)) == 0)) {
++
key;
}
if (
key ==
NUM_KEYS || ((
mask & ~(1<<
key)) != 0)) {
throw new
IllegalArgumentException("invalid shaper: " +
Integer.
toHexString(
mask));
}
return
key;
}
/**
* Returns a shaper for the provided unicode range. All
* Latin-1 (EUROPEAN) digits are converted
* to the corresponding decimal unicode digits.
* @param singleRange the specified Unicode range
* @return a non-contextual numeric shaper
* @throws IllegalArgumentException if the range is not a single range
*/
public static
NumericShaper getShaper(int
singleRange) {
int
key =
getKeyFromMask(
singleRange);
return new
NumericShaper(
key,
singleRange);
}
/**
* Returns a shaper for the provided Unicode
* range. All Latin-1 (EUROPEAN) digits are converted to the
* corresponding decimal digits of the specified Unicode range.
*
* @param singleRange the Unicode range given by a {@link
* NumericShaper.Range} constant.
* @return a non-contextual {@code NumericShaper}.
* @throws NullPointerException if {@code singleRange} is {@code null}
* @since 1.7
*/
public static
NumericShaper getShaper(
Range singleRange) {
return new
NumericShaper(
singleRange,
EnumSet.
of(
singleRange));
}
/**
* Returns a contextual shaper for the provided unicode range(s).
* Latin-1 (EUROPEAN) digits are converted to the decimal digits
* corresponding to the range of the preceding text, if the
* range is one of the provided ranges. Multiple ranges are
* represented by or-ing the values together, such as,
* <code>NumericShaper.ARABIC | NumericShaper.THAI</code>. The
* shaper assumes EUROPEAN as the starting context, that is, if
* EUROPEAN digits are encountered before any strong directional
* text in the string, the context is presumed to be EUROPEAN, and
* so the digits will not shape.
* @param ranges the specified Unicode ranges
* @return a shaper for the specified ranges
*/
public static
NumericShaper getContextualShaper(int
ranges) {
ranges |=
CONTEXTUAL_MASK;
return new
NumericShaper(
EUROPEAN_KEY,
ranges);
}
/**
* Returns a contextual shaper for the provided Unicode
* range(s). The Latin-1 (EUROPEAN) digits are converted to the
* decimal digits corresponding to the range of the preceding
* text, if the range is one of the provided ranges.
*
* <p>The shaper assumes EUROPEAN as the starting context, that
* is, if EUROPEAN digits are encountered before any strong
* directional text in the string, the context is presumed to be
* EUROPEAN, and so the digits will not shape.
*
* @param ranges the specified Unicode ranges
* @return a contextual shaper for the specified ranges
* @throws NullPointerException if {@code ranges} is {@code null}.
* @since 1.7
*/
public static
NumericShaper getContextualShaper(
Set<
Range>
ranges) {
NumericShaper shaper = new
NumericShaper(
Range.
EUROPEAN,
ranges);
shaper.
mask =
CONTEXTUAL_MASK;
return
shaper;
}
/**
* Returns a contextual shaper for the provided unicode range(s).
* Latin-1 (EUROPEAN) digits will be converted to the decimal digits
* corresponding to the range of the preceding text, if the
* range is one of the provided ranges. Multiple ranges are
* represented by or-ing the values together, for example,
* <code>NumericShaper.ARABIC | NumericShaper.THAI</code>. The
* shaper uses defaultContext as the starting context.
* @param ranges the specified Unicode ranges
* @param defaultContext the starting context, such as
* <code>NumericShaper.EUROPEAN</code>
* @return a shaper for the specified Unicode ranges.
* @throws IllegalArgumentException if the specified
* <code>defaultContext</code> is not a single valid range.
*/
public static
NumericShaper getContextualShaper(int
ranges, int
defaultContext) {
int
key =
getKeyFromMask(
defaultContext);
ranges |=
CONTEXTUAL_MASK;
return new
NumericShaper(
key,
ranges);
}
/**
* Returns a contextual shaper for the provided Unicode range(s).
* The Latin-1 (EUROPEAN) digits will be converted to the decimal
* digits corresponding to the range of the preceding text, if the
* range is one of the provided ranges. The shaper uses {@code
* defaultContext} as the starting context.
*
* @param ranges the specified Unicode ranges
* @param defaultContext the starting context, such as
* {@code NumericShaper.Range.EUROPEAN}
* @return a contextual shaper for the specified Unicode ranges.
* @throws NullPointerException
* if {@code ranges} or {@code defaultContext} is {@code null}
* @since 1.7
*/
public static
NumericShaper getContextualShaper(
Set<
Range>
ranges,
Range defaultContext) {
if (
defaultContext == null) {
throw new
NullPointerException();
}
NumericShaper shaper = new
NumericShaper(
defaultContext,
ranges);
shaper.
mask =
CONTEXTUAL_MASK;
return
shaper;
}
/**
* Private constructor.
*/
private
NumericShaper(int
key, int
mask) {
this.
key =
key;
this.
mask =
mask;
}
private
NumericShaper(
Range defaultContext,
Set<
Range>
ranges) {
shapingRange =
defaultContext;
rangeSet =
EnumSet.
copyOf(
ranges); // throws NPE if ranges is null.
// Give precedance to EASTERN_ARABIC if both ARABIC and
// EASTERN_ARABIC are specified.
if (
rangeSet.
contains(
Range.
EASTERN_ARABIC)
&&
rangeSet.
contains(
Range.
ARABIC)) {
rangeSet.
remove(
Range.
ARABIC);
}
// As well as the above case, give precedance to TAI_THAM_THAM if both
// TAI_THAM_HORA and TAI_THAM_THAM are specified.
if (
rangeSet.
contains(
Range.
TAI_THAM_THAM)
&&
rangeSet.
contains(
Range.
TAI_THAM_HORA)) {
rangeSet.
remove(
Range.
TAI_THAM_HORA);
}
rangeArray =
rangeSet.
toArray(new
Range[
rangeSet.
size()]);
if (
rangeArray.length >
BSEARCH_THRESHOLD) {
// sort rangeArray for binary search
Arrays.
sort(
rangeArray,
new
Comparator<
Range>() {
public int
compare(
Range s1,
Range s2) {
return
s1.
base >
s2.
base ? 1 :
s1.
base ==
s2.
base ? 0 : -1;
}
});
}
}
/**
* Converts the digits in the text that occur between start and
* start + count.
* @param text an array of characters to convert
* @param start the index into <code>text</code> to start
* converting
* @param count the number of characters in <code>text</code>
* to convert
* @throws IndexOutOfBoundsException if start or start + count is
* out of bounds
* @throws NullPointerException if text is null
*/
public void
shape(char[]
text, int
start, int
count) {
checkParams(
text,
start,
count);
if (
isContextual()) {
if (
rangeSet == null) {
shapeContextually(
text,
start,
count,
key);
} else {
shapeContextually(
text,
start,
count,
shapingRange);
}
} else {
shapeNonContextually(
text,
start,
count);
}
}
/**
* Converts the digits in the text that occur between start and
* start + count, using the provided context.
* Context is ignored if the shaper is not a contextual shaper.
* @param text an array of characters
* @param start the index into <code>text</code> to start
* converting
* @param count the number of characters in <code>text</code>
* to convert
* @param context the context to which to convert the
* characters, such as <code>NumericShaper.EUROPEAN</code>
* @throws IndexOutOfBoundsException if start or start + count is
* out of bounds
* @throws NullPointerException if text is null
* @throws IllegalArgumentException if this is a contextual shaper
* and the specified <code>context</code> is not a single valid
* range.
*/
public void
shape(char[]
text, int
start, int
count, int
context) {
checkParams(
text,
start,
count);
if (
isContextual()) {
int
ctxKey =
getKeyFromMask(
context);
if (
rangeSet == null) {
shapeContextually(
text,
start,
count,
ctxKey);
} else {
shapeContextually(
text,
start,
count,
Range.
values()[
ctxKey]);
}
} else {
shapeNonContextually(
text,
start,
count);
}
}
/**
* Converts the digits in the text that occur between {@code
* start} and {@code start + count}, using the provided {@code
* context}. {@code Context} is ignored if the shaper is not a
* contextual shaper.
*
* @param text a {@code char} array
* @param start the index into {@code text} to start converting
* @param count the number of {@code char}s in {@code text}
* to convert
* @param context the context to which to convert the characters,
* such as {@code NumericShaper.Range.EUROPEAN}
* @throws IndexOutOfBoundsException
* if {@code start} or {@code start + count} is out of bounds
* @throws NullPointerException
* if {@code text} or {@code context} is null
* @since 1.7
*/
public void
shape(char[]
text, int
start, int
count,
Range context) {
checkParams(
text,
start,
count);
if (
context == null) {
throw new
NullPointerException("context is null");
}
if (
isContextual()) {
if (
rangeSet != null) {
shapeContextually(
text,
start,
count,
context);
} else {
int
key =
Range.
toRangeIndex(
context);
if (
key >= 0) {
shapeContextually(
text,
start,
count,
key);
} else {
shapeContextually(
text,
start,
count,
shapingRange);
}
}
} else {
shapeNonContextually(
text,
start,
count);
}
}
private void
checkParams(char[]
text, int
start, int
count) {
if (
text == null) {
throw new
NullPointerException("text is null");
}
if ((
start < 0)
|| (
start >
text.length)
|| ((
start +
count) < 0)
|| ((
start +
count) >
text.length)) {
throw new
IndexOutOfBoundsException(
"bad start or count for text of length " +
text.length);
}
}
/**
* Returns a <code>boolean</code> indicating whether or not
* this shaper shapes contextually.
* @return <code>true</code> if this shaper is contextual;
* <code>false</code> otherwise.
*/
public boolean
isContextual() {
return (
mask &
CONTEXTUAL_MASK) != 0;
}
/**
* Returns an <code>int</code> that ORs together the values for
* all the ranges that will be shaped.
* <p>
* For example, to check if a shaper shapes to Arabic, you would use the
* following:
* <blockquote>
* {@code if ((shaper.getRanges() & shaper.ARABIC) != 0) { ... }
* </blockquote>
*
* <p>Note that this method supports only the bit mask-based
* ranges. Call {@link #getRangeSet()} for the enum-based ranges.
*
* @return the values for all the ranges to be shaped.
*/
public int
getRanges() {
return
mask & ~
CONTEXTUAL_MASK;
}
/**
* Returns a {@code Set} representing all the Unicode ranges in
* this {@code NumericShaper} that will be shaped.
*
* @return all the Unicode ranges to be shaped.
* @since 1.7
*/
public
Set<
Range>
getRangeSet() {
if (
rangeSet != null) {
return
EnumSet.
copyOf(
rangeSet);
}
return
Range.
maskToRangeSet(
mask);
}
/**
* Perform non-contextual shaping.
*/
private void
shapeNonContextually(char[]
text, int
start, int
count) {
int
base;
char
minDigit = '0';
if (
shapingRange != null) {
base =
shapingRange.
getDigitBase();
minDigit +=
shapingRange.
getNumericBase();
} else {
base =
bases[
key];
if (
key ==
ETHIOPIC_KEY) {
minDigit++; // Ethiopic doesn't use decimal zero
}
}
for (int
i =
start,
e =
start +
count;
i <
e; ++
i) {
char
c =
text[
i];
if (
c >=
minDigit &&
c <= '\u0039') {
text[
i] = (char)(
c +
base);
}
}
}
/**
* Perform contextual shaping.
* Synchronized to protect caches used in getContextKey.
*/
private synchronized void
shapeContextually(char[]
text, int
start, int
count, int
ctxKey) {
// if we don't support this context, then don't shape
if ((
mask & (1<<
ctxKey)) == 0) {
ctxKey =
EUROPEAN_KEY;
}
int
lastkey =
ctxKey;
int
base =
bases[
ctxKey];
char
minDigit =
ctxKey ==
ETHIOPIC_KEY ? '1' : '0'; // Ethiopic doesn't use decimal zero
synchronized (
NumericShaper.class) {
for (int
i =
start,
e =
start +
count;
i <
e; ++
i) {
char
c =
text[
i];
if (
c >=
minDigit &&
c <= '\u0039') {
text[
i] = (char)(
c +
base);
}
if (
isStrongDirectional(
c)) {
int
newkey =
getContextKey(
c);
if (
newkey !=
lastkey) {
lastkey =
newkey;
ctxKey =
newkey;
if (((
mask &
EASTERN_ARABIC) != 0) &&
(
ctxKey ==
ARABIC_KEY ||
ctxKey ==
EASTERN_ARABIC_KEY)) {
ctxKey =
EASTERN_ARABIC_KEY;
} else if (((
mask &
ARABIC) != 0) &&
(
ctxKey ==
ARABIC_KEY ||
ctxKey ==
EASTERN_ARABIC_KEY)) {
ctxKey =
ARABIC_KEY;
} else if ((
mask & (1<<
ctxKey)) == 0) {
ctxKey =
EUROPEAN_KEY;
}
base =
bases[
ctxKey];
minDigit =
ctxKey ==
ETHIOPIC_KEY ? '1' : '0'; // Ethiopic doesn't use decimal zero
}
}
}
}
}
private void
shapeContextually(char[]
text, int
start, int
count,
Range ctxKey) {
// if we don't support the specified context, then don't shape.
if (
ctxKey == null || !
rangeSet.
contains(
ctxKey)) {
ctxKey =
Range.
EUROPEAN;
}
Range lastKey =
ctxKey;
int
base =
ctxKey.
getDigitBase();
char
minDigit = (char)('0' +
ctxKey.
getNumericBase());
final int
end =
start +
count;
for (int
i =
start;
i <
end; ++
i) {
char
c =
text[
i];
if (
c >=
minDigit &&
c <= '9') {
text[
i] = (char)(
c +
base);
continue;
}
if (
isStrongDirectional(
c)) {
ctxKey =
rangeForCodePoint(
c);
if (
ctxKey !=
lastKey) {
lastKey =
ctxKey;
base =
ctxKey.
getDigitBase();
minDigit = (char)('0' +
ctxKey.
getNumericBase());
}
}
}
}
/**
* Returns a hash code for this shaper.
* @return this shaper's hash code.
* @see java.lang.Object#hashCode
*/
public int
hashCode() {
int
hash =
mask;
if (
rangeSet != null) {
// Use the CONTEXTUAL_MASK bit only for the enum-based
// NumericShaper. A deserialized NumericShaper might have
// bit masks.
hash &=
CONTEXTUAL_MASK;
hash ^=
rangeSet.
hashCode();
}
return
hash;
}
/**
* Returns {@code true} if the specified object is an instance of
* <code>NumericShaper</code> and shapes identically to this one,
* regardless of the range representations, the bit mask or the
* enum. For example, the following code produces {@code "true"}.
* <blockquote><pre>
* NumericShaper ns1 = NumericShaper.getShaper(NumericShaper.ARABIC);
* NumericShaper ns2 = NumericShaper.getShaper(NumericShaper.Range.ARABIC);
* System.out.println(ns1.equals(ns2));
* </pre></blockquote>
*
* @param o the specified object to compare to this
* <code>NumericShaper</code>
* @return <code>true</code> if <code>o</code> is an instance
* of <code>NumericShaper</code> and shapes in the same way;
* <code>false</code> otherwise.
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean
equals(
Object o) {
if (
o != null) {
try {
NumericShaper rhs = (
NumericShaper)
o;
if (
rangeSet != null) {
if (
rhs.
rangeSet != null) {
return
isContextual() ==
rhs.
isContextual()
&&
rangeSet.
equals(
rhs.
rangeSet)
&&
shapingRange ==
rhs.
shapingRange;
}
return
isContextual() ==
rhs.
isContextual()
&&
rangeSet.
equals(
Range.
maskToRangeSet(
rhs.
mask))
&&
shapingRange ==
Range.
indexToRange(
rhs.
key);
} else if (
rhs.
rangeSet != null) {
Set<
Range>
rset =
Range.
maskToRangeSet(
mask);
Range srange =
Range.
indexToRange(
key);
return
isContextual() ==
rhs.
isContextual()
&&
rset.
equals(
rhs.
rangeSet)
&&
srange ==
rhs.
shapingRange;
}
return
rhs.
mask ==
mask &&
rhs.
key ==
key;
}
catch (
ClassCastException e) {
}
}
return false;
}
/**
* Returns a <code>String</code> that describes this shaper. This method
* is used for debugging purposes only.
* @return a <code>String</code> describing this shaper.
*/
public
String toString() {
StringBuilder buf = new
StringBuilder(super.toString());
buf.
append("[contextual:").
append(
isContextual());
String[]
keyNames = null;
if (
isContextual()) {
buf.
append(", context:");
buf.
append(
shapingRange == null ?
Range.
values()[
key] :
shapingRange);
}
if (
rangeSet == null) {
buf.
append(", range(s): ");
boolean
first = true;
for (int
i = 0;
i <
NUM_KEYS; ++
i) {
if ((
mask & (1 <<
i)) != 0) {
if (
first) {
first = false;
} else {
buf.
append(", ");
}
buf.
append(
Range.
values()[
i]);
}
}
} else {
buf.
append(", range set: ").
append(
rangeSet);
}
buf.
append(']');
return
buf.
toString();
}
/**
* Returns the index of the high bit in value (assuming le, actually
* power of 2 >= value). value must be positive.
*/
private static int
getHighBit(int
value) {
if (
value <= 0) {
return -32;
}
int
bit = 0;
if (
value >= 1 << 16) {
value >>= 16;
bit += 16;
}
if (
value >= 1 << 8) {
value >>= 8;
bit += 8;
}
if (
value >= 1 << 4) {
value >>= 4;
bit += 4;
}
if (
value >= 1 << 2) {
value >>= 2;
bit += 2;
}
if (
value >= 1 << 1) {
bit += 1;
}
return
bit;
}
/**
* fast binary search over subrange of array.
*/
private static int
search(int
value, int[]
array, int
start, int
length)
{
int
power = 1 <<
getHighBit(
length);
int
extra =
length -
power;
int
probe =
power;
int
index =
start;
if (
value >=
array[
index +
extra]) {
index +=
extra;
}
while (
probe > 1) {
probe >>= 1;
if (
value >=
array[
index +
probe]) {
index +=
probe;
}
}
return
index;
}
/**
* Converts the {@code NumericShaper.Range} enum-based parameters,
* if any, to the bit mask-based counterparts and writes this
* object to the {@code stream}. Any enum constants that have no
* bit mask-based counterparts are ignored in the conversion.
*
* @param stream the output stream to write to
* @throws IOException if an I/O error occurs while writing to {@code stream}
* @since 1.7
*/
private void
writeObject(
ObjectOutputStream stream) throws
IOException {
if (
shapingRange != null) {
int
index =
Range.
toRangeIndex(
shapingRange);
if (
index >= 0) {
key =
index;
}
}
if (
rangeSet != null) {
mask |=
Range.
toRangeMask(
rangeSet);
}
stream.
defaultWriteObject();
}
}