1 /*
2  * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package java.awt.font;
27 
28 import java.io.IOException;
29 import java.io.ObjectOutputStream;
30 import java.util.Arrays;
31 import java.util.Comparator;
32 import java.util.EnumSet;
33 import java.util.Set;
34 import jdk.internal.access.SharedSecrets;
35 
36 /**
37  * The {@code NumericShaper} class is used to convert Latin-1 (European)
38  * digits to other Unicode decimal digits.  Users of this class will
39  * primarily be people who wish to present data using
40  * national digit shapes, but find it more convenient to represent the
41  * data internally using Latin-1 (European) digits.  This does not
42  * interpret the deprecated numeric shape selector character (U+206E).
43  * <p>
44  * Instances of {@code NumericShaper} are typically applied
45  * as attributes to text with the
46  * {@link TextAttribute#NUMERIC_SHAPING NUMERIC_SHAPING} attribute
47  * of the {@code TextAttribute} class.
48  * For example, this code snippet causes a {@code TextLayout} to
49  * shape European digits to Arabic in an Arabic context:<br>
50  * <blockquote><pre>
51  * Map map = new HashMap();
52  * map.put(TextAttribute.NUMERIC_SHAPING,
53  *     NumericShaper.getContextualShaper(NumericShaper.ARABIC));
54  * FontRenderContext frc = ...;
55  * TextLayout layout = new TextLayout(text, map, frc);
56  * layout.draw(g2d, x, y);
57  * </pre></blockquote>
58  * <br>
59  * It is also possible to perform numeric shaping explicitly using instances
60  * of {@code NumericShaper}, as this code snippet demonstrates:<br>
61  * <blockquote><pre>
62  * char[] text = ...;
63  * // shape all EUROPEAN digits (except zero) to ARABIC digits
64  * NumericShaper shaper = NumericShaper.getShaper(NumericShaper.ARABIC);
65  * shaper.shape(text, start, count);
66  *
67  * // shape European digits to ARABIC digits if preceding text is Arabic, or
68  * // shape European digits to TAMIL digits if preceding text is Tamil, or
69  * // leave European digits alone if there is no preceding text, or
70  * // preceding text is neither Arabic nor Tamil
71  * NumericShaper shaper =
72  *     NumericShaper.getContextualShaper(NumericShaper.ARABIC |
73  *                                         NumericShaper.TAMIL,
74  *                                       NumericShaper.EUROPEAN);
75  * shaper.shape(text, start, count);
76  * </pre></blockquote>
77  *
78  * <p><b>Bit mask- and enum-based Unicode ranges</b></p>
79  *
80  * <p>This class supports two different programming interfaces to
81  * represent Unicode ranges for script-specific digits: bit
82  * mask-based ones, such as {@link #ARABIC NumericShaper.ARABIC}, and
83  * enum-based ones, such as {@link NumericShaper.Range#ARABIC}.
84  * Multiple ranges can be specified by ORing bit mask-based constants,
85  * such as:
86  * <blockquote><pre>
87  * NumericShaper.ARABIC | NumericShaper.TAMIL
88  * </pre></blockquote>
89  * or creating a {@code Set} with the {@link NumericShaper.Range}
90  * constants, such as:
91  * <blockquote><pre>
92  * EnumSet.of(NumericShaper.Range.ARABIC, NumericShaper.Range.TAMIL)
93  * </pre></blockquote>
94  * The enum-based ranges are a super set of the bit mask-based ones.
95  *
96  * <p>If the two interfaces are mixed (including serialization),
97  * Unicode range values are mapped to their counterparts where such
98  * mapping is possible, such as {@code NumericShaper.Range.ARABIC}
99  * from/to {@code NumericShaper.ARABIC}.  If any unmappable range
100  * values are specified, such as {@code NumericShaper.Range.BALINESE},
101  * those ranges are ignored.
102  *
103  * <p><b>Decimal Digits Precedence</b></p>
104  *
105  * <p>A Unicode range may have more than one set of decimal digits. If
106  * multiple decimal digits sets are specified for the same Unicode
107  * range, one of the sets will take precedence as follows.
108  *
109  * <table class="plain">
110  * <caption>NumericShaper constants precedence</caption>
111  * <thead>
112  *   <tr>
113  *     <th scope="col">Unicode Range
114  *     <th scope="col">{@code NumericShaper} Constants
115  *     <th scope="col">Precedence
116  * </thead>
117  * <tbody>
118  *   <tr>
119  *     <th scope="rowgroup" rowspan="2">Arabic
120  *     <td>{@link NumericShaper#ARABIC NumericShaper.ARABIC}
121  *     <br>
122  *     {@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}
123  *     <td>{@link NumericShaper#EASTERN_ARABIC NumericShaper.EASTERN_ARABIC}
124  *   </tr>
125  *   <tr>
126  *     <td>{@link NumericShaper.Range#ARABIC}
127  *     <br>
128  *     {@link NumericShaper.Range#EASTERN_ARABIC}
129  *     <td>{@link NumericShaper.Range#EASTERN_ARABIC}
130  * </tbody>
131  * <tbody>
132  *   <tr>
133  *     <th scope="row">Tai Tham
134  *     <td>{@link NumericShaper.Range#TAI_THAM_HORA}
135  *     <br>
136  *     {@link NumericShaper.Range#TAI_THAM_THAM}
137  *     <td>{@link NumericShaper.Range#TAI_THAM_THAM}
138  * </tbody>
139  * </table>
140  *
141  * @since 1.4
142  */
143 public final class NumericShaper implements java.io.Serializable {
144 
145     // For access from java.text.Bidi
146     static {
147         if (SharedSecrets.getJavaAWTFontAccess() == null) {
SharedSecrets.setJavaAWTFontAccess(new JavaAWTFontAccessImpl())148             SharedSecrets.setJavaAWTFontAccess(new JavaAWTFontAccessImpl());
149         }
150     }
151 
152     /**
153      * A {@code NumericShaper.Range} represents a Unicode range of a
154      * script having its own decimal digits. For example, the {@link
155      * NumericShaper.Range#THAI} range has the Thai digits, THAI DIGIT
156      * ZERO (U+0E50) to THAI DIGIT NINE (U+0E59).
157      *
158      * <p>The {@code Range} enum replaces the traditional bit
159      * mask-based values (e.g., {@link NumericShaper#ARABIC}), and
160      * supports more Unicode ranges than the bit mask-based ones. For
161      * example, the following code using the bit mask:
162      * <blockquote><pre>
163      * NumericShaper.getContextualShaper(NumericShaper.ARABIC |
164      *                                     NumericShaper.TAMIL,
165      *                                   NumericShaper.EUROPEAN);
166      * </pre></blockquote>
167      * can be written using this enum as:
168      * <blockquote><pre>
169      * NumericShaper.getContextualShaper(EnumSet.of(
170      *                                     NumericShaper.Range.ARABIC,
171      *                                     NumericShaper.Range.TAMIL),
172      *                                   NumericShaper.Range.EUROPEAN);
173      * </pre></blockquote>
174      *
175      * @since 1.7
176      */
177     public static enum Range {
178         // The order of EUROPEAN to MOGOLIAN must be consistent
179         // with the bitmask-based constants.
180         /**
181          * The Latin (European) range with the Latin (ASCII) digits.
182          */
183         EUROPEAN        ('\u0030', '\u0000', '\u0300'),
184         /**
185          * The Arabic range with the Arabic-Indic digits.
186          */
187         ARABIC          ('\u0660', '\u0600', '\u0780'),
188         /**
189          * The Arabic range with the Eastern Arabic-Indic digits.
190          */
191         EASTERN_ARABIC  ('\u06f0', '\u0600', '\u0780'),
192         /**
193          * The Devanagari range with the Devanagari digits.
194          */
195         DEVANAGARI      ('\u0966', '\u0900', '\u0980'),
196         /**
197          * The Bengali range with the Bengali digits.
198          */
199         BENGALI         ('\u09e6', '\u0980', '\u0a00'),
200         /**
201          * The Gurmukhi range with the Gurmukhi digits.
202          */
203         GURMUKHI        ('\u0a66', '\u0a00', '\u0a80'),
204         /**
205          * The Gujarati range with the Gujarati digits.
206          */
207         GUJARATI        ('\u0ae6', '\u0b00', '\u0b80'),
208         /**
209          * The Oriya range with the Oriya digits.
210          */
211         ORIYA           ('\u0b66', '\u0b00', '\u0b80'),
212         /**
213          * The Tamil range with the Tamil digits.
214          */
215         TAMIL           ('\u0be6', '\u0b80', '\u0c00'),
216         /**
217          * The Telugu range with the Telugu digits.
218          */
219         TELUGU          ('\u0c66', '\u0c00', '\u0c80'),
220         /**
221          * The Kannada range with the Kannada digits.
222          */
223         KANNADA         ('\u0ce6', '\u0c80', '\u0d00'),
224         /**
225          * The Malayalam range with the Malayalam digits.
226          */
227         MALAYALAM       ('\u0d66', '\u0d00', '\u0d80'),
228         /**
229          * The Thai range with the Thai digits.
230          */
231         THAI            ('\u0e50', '\u0e00', '\u0e80'),
232         /**
233          * The Lao range with the Lao digits.
234          */
235         LAO             ('\u0ed0', '\u0e80', '\u0f00'),
236         /**
237          * The Tibetan range with the Tibetan digits.
238          */
239         TIBETAN         ('\u0f20', '\u0f00', '\u1000'),
240         /**
241          * The Myanmar range with the Myanmar digits.
242          */
243         MYANMAR         ('\u1040', '\u1000', '\u1080'),
244         /**
245          * The Ethiopic range with the Ethiopic digits. Ethiopic
246          * does not have a decimal digit 0 so Latin (European) 0 is
247          * used.
248          */
249         ETHIOPIC        ('\u1369', '\u1200', '\u1380') {
250             @Override
getNumericBase()251             char getNumericBase() { return 1; }
252         },
253         /**
254          * The Khmer range with the Khmer digits.
255          */
256         KHMER           ('\u17e0', '\u1780', '\u1800'),
257         /**
258          * The Mongolian range with the Mongolian digits.
259          */
260         MONGOLIAN       ('\u1810', '\u1800', '\u1900'),
261         // The order of EUROPEAN to MOGOLIAN must be consistent
262         // with the bitmask-based constants.
263 
264         /**
265          * The N'Ko range with the N'Ko digits.
266          */
267         NKO             ('\u07c0', '\u07c0', '\u0800'),
268         /**
269          * The Myanmar range with the Myanmar Shan digits.
270          */
271         MYANMAR_SHAN    ('\u1090', '\u1000', '\u10a0'),
272         /**
273          * The Limbu range with the Limbu digits.
274          */
275         LIMBU           ('\u1946', '\u1900', '\u1950'),
276         /**
277          * The New Tai Lue range with the New Tai Lue digits.
278          */
279         NEW_TAI_LUE     ('\u19d0', '\u1980', '\u19e0'),
280         /**
281          * The Balinese range with the Balinese digits.
282          */
283         BALINESE        ('\u1b50', '\u1b00', '\u1b80'),
284         /**
285          * The Sundanese range with the Sundanese digits.
286          */
287         SUNDANESE       ('\u1bb0', '\u1b80', '\u1bc0'),
288         /**
289          * The Lepcha range with the Lepcha digits.
290          */
291         LEPCHA          ('\u1c40', '\u1c00', '\u1c50'),
292         /**
293          * The Ol Chiki range with the Ol Chiki digits.
294          */
295         OL_CHIKI        ('\u1c50', '\u1c50', '\u1c80'),
296         /**
297          * The Vai range with the Vai digits.
298          */
299         VAI             ('\ua620', '\ua500', '\ua640'),
300         /**
301          * The Saurashtra range with the Saurashtra digits.
302          */
303         SAURASHTRA      ('\ua8d0', '\ua880', '\ua8e0'),
304         /**
305          * The Kayah Li range with the Kayah Li digits.
306          */
307         KAYAH_LI        ('\ua900', '\ua900', '\ua930'),
308         /**
309          * The Cham range with the Cham digits.
310          */
311         CHAM            ('\uaa50', '\uaa00', '\uaa60'),
312         /**
313          * The Tai Tham Hora range with the Tai Tham Hora digits.
314          */
315         TAI_THAM_HORA   ('\u1a80', '\u1a20', '\u1ab0'),
316         /**
317          * The Tai Tham Tham range with the Tai Tham Tham digits.
318          */
319         TAI_THAM_THAM   ('\u1a90', '\u1a20', '\u1ab0'),
320         /**
321          * The Javanese range with the Javanese digits.
322          */
323         JAVANESE        ('\ua9d0', '\ua980', '\ua9e0'),
324         /**
325          * The Meetei Mayek range with the Meetei Mayek digits.
326          */
327         MEETEI_MAYEK    ('\uabf0', '\uabc0', '\uac00'),
328         /**
329          * The Sinhala range with the Sinhala digits.
330          * @since 9
331          */
332         SINHALA         ('\u0de6', '\u0d80', '\u0e00'),
333         /**
334          * The Myanmar Extended-B range with the Myanmar Tai Laing digits.
335          * @since 9
336          */
337         MYANMAR_TAI_LAING ('\ua9f0', '\ua9e0', '\uaa00');
338 
toRangeIndex(Range script)339         private static int toRangeIndex(Range script) {
340             int index = script.ordinal();
341             return index < NUM_KEYS ? index : -1;
342         }
343 
indexToRange(int index)344         private static Range indexToRange(int index) {
345             return index < NUM_KEYS ? Range.values()[index] : null;
346         }
347 
toRangeMask(Set<Range> ranges)348         private static int toRangeMask(Set<Range> ranges) {
349             int m = 0;
350             for (Range range : ranges) {
351                 int index = range.ordinal();
352                 if (index < NUM_KEYS) {
353                     m |= 1 << index;
354                 }
355             }
356             return m;
357         }
358 
maskToRangeSet(int mask)359         private static Set<Range> maskToRangeSet(int mask) {
360             Set<Range> set = EnumSet.noneOf(Range.class);
361             Range[] a = Range.values();
362             for (int i = 0; i < NUM_KEYS; i++) {
363                 if ((mask & (1 << i)) != 0) {
364                     set.add(a[i]);
365                 }
366             }
367             return set;
368         }
369 
370         // base character of range digits
371         private final int base;
372         // Unicode range
373         private final int start, // inclusive
374                           end;   // exclusive
375 
Range(int base, int start, int end)376         private Range(int base, int start, int end) {
377             this.base = base - ('0' + getNumericBase());
378             this.start = start;
379             this.end = end;
380         }
381 
getDigitBase()382         private int getDigitBase() {
383             return base;
384         }
385 
getNumericBase()386         char getNumericBase() {
387             return 0;
388         }
389 
inRange(int c)390         private boolean inRange(int c) {
391             return start <= c && c < end;
392         }
393     }
394 
395     /** index of context for contextual shaping - values range from 0 to 18 */
396     private int key;
397 
398     /** flag indicating whether to shape contextually (high bit) and which
399      *  digit ranges to shape (bits 0-18)
400      */
401     private int mask;
402 
403     /**
404      * The context {@code Range} for contextual shaping or the {@code
405      * Range} for non-contextual shaping. {@code null} for the bit
406      * mask-based API.
407      *
408      * @since 1.7
409      */
410     private Range shapingRange;
411 
412     /**
413      * {@code Set<Range>} indicating which Unicode ranges to
414      * shape. {@code null} for the bit mask-based API.
415      */
416     private transient Set<Range> rangeSet;
417 
418     /**
419      * rangeSet.toArray() value. Sorted by Range.base when the number
420      * of elements is greater than BSEARCH_THRESHOLD.
421      */
422     private transient Range[] rangeArray;
423 
424     /**
425      * If more than BSEARCH_THRESHOLD ranges are specified, binary search is used.
426      */
427     private static final int BSEARCH_THRESHOLD = 3;
428 
429     private static final long serialVersionUID = -8022764705923730308L;
430 
431     /** Identifies the Latin-1 (European) and extended range, and
432      *  Latin-1 (European) decimal base.
433      */
434     public static final int EUROPEAN = 1<<0;
435 
436     /** Identifies the ARABIC range and decimal base. */
437     public static final int ARABIC = 1<<1;
438 
439     /** Identifies the ARABIC range and ARABIC_EXTENDED decimal base. */
440     public static final int EASTERN_ARABIC = 1<<2;
441 
442     /** Identifies the DEVANAGARI range and decimal base. */
443     public static final int DEVANAGARI = 1<<3;
444 
445     /** Identifies the BENGALI range and decimal base. */
446     public static final int BENGALI = 1<<4;
447 
448     /** Identifies the GURMUKHI range and decimal base. */
449     public static final int GURMUKHI = 1<<5;
450 
451     /** Identifies the GUJARATI range and decimal base. */
452     public static final int GUJARATI = 1<<6;
453 
454     /** Identifies the ORIYA range and decimal base. */
455     public static final int ORIYA = 1<<7;
456 
457     /** Identifies the TAMIL range and decimal base. */
458     // TAMIL DIGIT ZERO was added in Unicode 4.1
459     public static final int TAMIL = 1<<8;
460 
461     /** Identifies the TELUGU range and decimal base. */
462     public static final int TELUGU = 1<<9;
463 
464     /** Identifies the KANNADA range and decimal base. */
465     public static final int KANNADA = 1<<10;
466 
467     /** Identifies the MALAYALAM range and decimal base. */
468     public static final int MALAYALAM = 1<<11;
469 
470     /** Identifies the THAI range and decimal base. */
471     public static final int THAI = 1<<12;
472 
473     /** Identifies the LAO range and decimal base. */
474     public static final int LAO = 1<<13;
475 
476     /** Identifies the TIBETAN range and decimal base. */
477     public static final int TIBETAN = 1<<14;
478 
479     /** Identifies the MYANMAR range and decimal base. */
480     public static final int MYANMAR = 1<<15;
481 
482     /** Identifies the ETHIOPIC range and decimal base. */
483     public static final int ETHIOPIC = 1<<16;
484 
485     /** Identifies the KHMER range and decimal base. */
486     public static final int KHMER = 1<<17;
487 
488     /** Identifies the MONGOLIAN range and decimal base. */
489     public static final int MONGOLIAN = 1<<18;
490 
491     /** Identifies all ranges, for full contextual shaping.
492      *
493      * <p>This constant specifies all of the bit mask-based
494      * ranges. Use {@code EnumSet.allOf(NumericShaper.Range.class)} to
495      * specify all of the enum-based ranges.
496      */
497     public static final int ALL_RANGES = 0x0007ffff;
498 
499     private static final int EUROPEAN_KEY = 0;
500     private static final int ARABIC_KEY = 1;
501     private static final int EASTERN_ARABIC_KEY = 2;
502     private static final int DEVANAGARI_KEY = 3;
503     private static final int BENGALI_KEY = 4;
504     private static final int GURMUKHI_KEY = 5;
505     private static final int GUJARATI_KEY = 6;
506     private static final int ORIYA_KEY = 7;
507     private static final int TAMIL_KEY = 8;
508     private static final int TELUGU_KEY = 9;
509     private static final int KANNADA_KEY = 10;
510     private static final int MALAYALAM_KEY = 11;
511     private static final int THAI_KEY = 12;
512     private static final int LAO_KEY = 13;
513     private static final int TIBETAN_KEY = 14;
514     private static final int MYANMAR_KEY = 15;
515     private static final int ETHIOPIC_KEY = 16;
516     private static final int KHMER_KEY = 17;
517     private static final int MONGOLIAN_KEY = 18;
518 
519     private static final int NUM_KEYS = MONGOLIAN_KEY + 1; // fixed
520 
521     private static final int CONTEXTUAL_MASK = 1<<31;
522 
523     private static final char[] bases = {
524         '\u0030' - '\u0030', // EUROPEAN
525         '\u0660' - '\u0030', // ARABIC-INDIC
526         '\u06f0' - '\u0030', // EXTENDED ARABIC-INDIC (EASTERN_ARABIC)
527         '\u0966' - '\u0030', // DEVANAGARI
528         '\u09e6' - '\u0030', // BENGALI
529         '\u0a66' - '\u0030', // GURMUKHI
530         '\u0ae6' - '\u0030', // GUJARATI
531         '\u0b66' - '\u0030', // ORIYA
532         '\u0be6' - '\u0030', // TAMIL - zero was added in Unicode 4.1
533         '\u0c66' - '\u0030', // TELUGU
534         '\u0ce6' - '\u0030', // KANNADA
535         '\u0d66' - '\u0030', // MALAYALAM
536         '\u0e50' - '\u0030', // THAI
537         '\u0ed0' - '\u0030', // LAO
538         '\u0f20' - '\u0030', // TIBETAN
539         '\u1040' - '\u0030', // MYANMAR
540         '\u1369' - '\u0031', // ETHIOPIC - no zero
541         '\u17e0' - '\u0030', // KHMER
542         '\u1810' - '\u0030', // MONGOLIAN
543     };
544 
545     // some ranges adjoin or overlap, rethink if we want to do a binary search on this
546 
547     private static final char[] contexts = {
548         '\u0000', '\u0300', // 'EUROPEAN' (really latin-1 and extended)
549         '\u0600', '\u0780', // ARABIC
550         '\u0600', '\u0780', // EASTERN_ARABIC -- note overlap with arabic
551         '\u0900', '\u0980', // DEVANAGARI
552         '\u0980', '\u0a00', // BENGALI
553         '\u0a00', '\u0a80', // GURMUKHI
554         '\u0a80', '\u0b00', // GUJARATI
555         '\u0b00', '\u0b80', // ORIYA
556         '\u0b80', '\u0c00', // TAMIL
557         '\u0c00', '\u0c80', // TELUGU
558         '\u0c80', '\u0d00', // KANNADA
559         '\u0d00', '\u0d80', // MALAYALAM
560         '\u0e00', '\u0e80', // THAI
561         '\u0e80', '\u0f00', // LAO
562         '\u0f00', '\u1000', // TIBETAN
563         '\u1000', '\u1080', // MYANMAR
564         '\u1200', '\u1380', // ETHIOPIC - note missing zero
565         '\u1780', '\u1800', // KHMER
566         '\u1800', '\u1900', // MONGOLIAN
567         '\uffff',
568     };
569 
570     // assume most characters are near each other so probing the cache is infrequent,
571     // and a linear probe is ok.
572 
573     private static int ctCache = 0;
574     private static int ctCacheLimit = contexts.length - 2;
575 
576     // warning, synchronize access to this as it modifies state
getContextKey(char c)577     private static int getContextKey(char c) {
578         if (c < contexts[ctCache]) {
579             while (ctCache > 0 && c < contexts[ctCache]) --ctCache;
580         } else if (c >= contexts[ctCache + 1]) {
581             while (ctCache < ctCacheLimit && c >= contexts[ctCache + 1]) ++ctCache;
582         }
583 
584         // if we're not in a known range, then return EUROPEAN as the range key
585         return (ctCache & 0x1) == 0 ? (ctCache / 2) : EUROPEAN_KEY;
586     }
587 
588     // cache for the NumericShaper.Range version
589     private transient volatile Range currentRange = Range.EUROPEAN;
590 
rangeForCodePoint(final int codepoint)591     private Range rangeForCodePoint(final int codepoint) {
592         if (currentRange.inRange(codepoint)) {
593             return currentRange;
594         }
595 
596         final Range[] ranges = rangeArray;
597         if (ranges.length > BSEARCH_THRESHOLD) {
598             int lo = 0;
599             int hi = ranges.length - 1;
600             while (lo <= hi) {
601                 int mid = (lo + hi) / 2;
602                 Range range = ranges[mid];
603                 if (codepoint < range.start) {
604                     hi = mid - 1;
605                 } else if (codepoint >= range.end) {
606                     lo = mid + 1;
607                 } else {
608                     currentRange = range;
609                     return range;
610                 }
611             }
612         } else {
613             for (int i = 0; i < ranges.length; i++) {
614                 if (ranges[i].inRange(codepoint)) {
615                     return ranges[i];
616                 }
617             }
618         }
619         return Range.EUROPEAN;
620     }
621 
622     /*
623      * A range table of strong directional characters (types L, R, AL).
624      * Even (left) indexes are starts of ranges of non-strong-directional (or undefined)
625      * characters, odd (right) indexes are starts of ranges of strong directional
626      * characters.
627      */
628     private static int[] strongTable = {
629         0x0000, 0x0041,
630         0x005b, 0x0061,
631         0x007b, 0x00aa,
632         0x00ab, 0x00b5,
633         0x00b6, 0x00ba,
634         0x00bb, 0x00c0,
635         0x00d7, 0x00d8,
636         0x00f7, 0x00f8,
637         0x02b9, 0x02bb,
638         0x02c2, 0x02d0,
639         0x02d2, 0x02e0,
640         0x02e5, 0x02ee,
641         0x02ef, 0x0370,
642         0x0374, 0x0376,
643         0x0378, 0x037a,
644         0x037e, 0x037f,
645         0x0380, 0x0386,
646         0x0387, 0x0388,
647         0x038b, 0x038c,
648         0x038d, 0x038e,
649         0x03a2, 0x03a3,
650         0x03f6, 0x03f7,
651         0x0483, 0x048a,
652         0x0530, 0x0531,
653         0x0557, 0x0559,
654         0x058a, 0x0590,
655         0x0591, 0x05be,
656         0x05bf, 0x05c0,
657         0x05c1, 0x05c3,
658         0x05c4, 0x05c6,
659         0x05c7, 0x05c8,
660         0x0600, 0x0608,
661         0x0609, 0x060b,
662         0x060c, 0x060d,
663         0x060e, 0x061b,
664         0x064b, 0x066d,
665         0x0670, 0x0671,
666         0x06d6, 0x06e5,
667         0x06e7, 0x06ee,
668         0x06f0, 0x06fa,
669         0x0711, 0x0712,
670         0x0730, 0x074b,
671         0x07a6, 0x07b1,
672         0x07eb, 0x07f4,
673         0x07f6, 0x07fa,
674         0x07fd, 0x07fe,
675         0x0816, 0x081a,
676         0x081b, 0x0824,
677         0x0825, 0x0828,
678         0x0829, 0x082e,
679         0x0859, 0x085c,
680         0x08e3, 0x0903,
681         0x093a, 0x093b,
682         0x093c, 0x093d,
683         0x0941, 0x0949,
684         0x094d, 0x094e,
685         0x0951, 0x0958,
686         0x0962, 0x0964,
687         0x0981, 0x0982,
688         0x0984, 0x0985,
689         0x098d, 0x098f,
690         0x0991, 0x0993,
691         0x09a9, 0x09aa,
692         0x09b1, 0x09b2,
693         0x09b3, 0x09b6,
694         0x09ba, 0x09bd,
695         0x09c1, 0x09c7,
696         0x09c9, 0x09cb,
697         0x09cd, 0x09ce,
698         0x09cf, 0x09d7,
699         0x09d8, 0x09dc,
700         0x09de, 0x09df,
701         0x09e2, 0x09e6,
702         0x09f2, 0x09f4,
703         0x09fb, 0x09fc,
704         0x09fe, 0x0a03,
705         0x0a04, 0x0a05,
706         0x0a0b, 0x0a0f,
707         0x0a11, 0x0a13,
708         0x0a29, 0x0a2a,
709         0x0a31, 0x0a32,
710         0x0a34, 0x0a35,
711         0x0a37, 0x0a38,
712         0x0a3a, 0x0a3e,
713         0x0a41, 0x0a59,
714         0x0a5d, 0x0a5e,
715         0x0a5f, 0x0a66,
716         0x0a70, 0x0a72,
717         0x0a75, 0x0a76,
718         0x0a73, 0x0a83,
719         0x0a84, 0x0a85,
720         0x0a8e, 0x0a8f,
721         0x0a92, 0x0a93,
722         0x0aa9, 0x0aaa,
723         0x0ab1, 0x0ab2,
724         0x0ab4, 0x0ab5,
725         0x0aba, 0x0abd,
726         0x0ac1, 0x0ac9,
727         0x0aca, 0x0acb,
728         0x0acd, 0x0ad0,
729         0x0ad1, 0x0ae0,
730         0x0ae2, 0x0ae6,
731         0x0af1, 0x0af9,
732         0x0afa, 0x0b02,
733         0x0b04, 0x0b05,
734         0x0b0d, 0x0b0f,
735         0x0b11, 0x0b13,
736         0x0b29, 0x0b2a,
737         0x0b31, 0x0b32,
738         0x0b34, 0x0b35,
739         0x0b3a, 0x0b3d,
740         0x0b3f, 0x0b40,
741         0x0b41, 0x0b47,
742         0x0b49, 0x0b4b,
743         0x0b4d, 0x0b57,
744         0x0b58, 0x0b5c,
745         0x0b5e, 0x0b5f,
746         0x0b62, 0x0b66,
747         0x0b78, 0x0b83,
748         0x0b84, 0x0b85,
749         0x0b8b, 0x0b8e,
750         0x0b91, 0x0b92,
751         0x0b96, 0x0b99,
752         0x0b9b, 0x0b9c,
753         0x0b9d, 0x0b9e,
754         0x0ba0, 0x0ba3,
755         0x0ba5, 0x0ba8,
756         0x0bab, 0x0bae,
757         0x0bba, 0x0bbe,
758         0x0bc0, 0x0bc1,
759         0x0bc3, 0x0bc6,
760         0x0bc9, 0x0bca,
761         0x0bcd, 0x0bd0,
762         0x0bd1, 0x0bd7,
763         0x0bd8, 0x0be6,
764         0x0bf3, 0x0c01,
765         0x0c04, 0x0c05,
766         0x0c0d, 0x0c0e,
767         0x0c11, 0x0c12,
768         0x0c29, 0x0c2a,
769         0x0c3a, 0x0c3d,
770         0x0c3e, 0x0c41,
771         0x0c45, 0x0c58,
772         0x0c5b, 0x0c60,
773         0x0c62, 0x0c66,
774         0x0c70, 0x0c7f,
775         0x0c81, 0x0c82,
776         0x0c8d, 0x0c8e,
777         0x0c91, 0x0c92,
778         0x0ca9, 0x0caa,
779         0x0cb4, 0x0cb5,
780         0x0cba, 0x0cbd,
781         0x0cc5, 0x0cc6,
782         0x0cc9, 0x0cca,
783         0x0ccc, 0x0cd5,
784         0x0cd7, 0x0cde,
785         0x0cdf, 0x0ce0,
786         0x0ce2, 0x0ce6,
787         0x0cf0, 0x0cf1,
788         0x0cf3, 0x0d02,
789         0x0d04, 0x0d05,
790         0x0d0d, 0x0d0e,
791         0x0d11, 0x0d12,
792         0x0d3b, 0x0d3d,
793         0x0d41, 0x0d46,
794         0x0d49, 0x0d4a,
795         0x0d4d, 0x0d4e,
796         0x0d62, 0x0d66,
797         0x0d80, 0x0d82,
798         0x0d84, 0x0d85,
799         0x0d97, 0x0d9a,
800         0x0db2, 0x0db3,
801         0x0dbc, 0x0dbd,
802         0x0dbe, 0x0dc0,
803         0x0dc7, 0x0dcf,
804         0x0dd2, 0x0dd8,
805         0x0de0, 0x0de6,
806         0x0df0, 0x0df2,
807         0x0df5, 0x0e01,
808         0x0e31, 0x0e32,
809         0x0e34, 0x0e40,
810         0x0e47, 0x0e4f,
811         0x0e5c, 0x0e81,
812         0x0e83, 0x0e84,
813         0x0e85, 0x0e87,
814         0x0e89, 0x0e8a,
815         0x0e8b, 0x0e8d,
816         0x0e8e, 0x0e94,
817         0x0e98, 0x0e99,
818         0x0ea0, 0x0ea1,
819         0x0ea4, 0x0ea5,
820         0x0ea6, 0x0ea7,
821         0x0ea8, 0x0eaa,
822         0x0eac, 0x0ead,
823         0x0eb1, 0x0eb2,
824         0x0eb4, 0x0ebd,
825         0x0ebe, 0x0ec0,
826         0x0ec5, 0x0ec6,
827         0x0ec7, 0x0ed0,
828         0x0eda, 0x0edc,
829         0x0ee0, 0x0f00,
830         0x0f18, 0x0f1a,
831         0x0f35, 0x0f36,
832         0x0f37, 0x0f38,
833         0x0f39, 0x0f3e,
834         0x0f48, 0x0f49,
835         0x0f6d, 0x0f7f,
836         0x0f80, 0x0f85,
837         0x0f86, 0x0f88,
838         0x0f8d, 0x0fbe,
839         0x0fc6, 0x0fc7,
840         0x0fcd, 0x0fce,
841         0x0fdb, 0x1000,
842         0x102d, 0x1031,
843         0x1032, 0x1038,
844         0x1039, 0x103b,
845         0x103d, 0x103f,
846         0x1058, 0x105a,
847         0x105e, 0x1061,
848         0x1071, 0x1075,
849         0x1082, 0x1083,
850         0x1085, 0x1087,
851         0x108d, 0x108e,
852         0x109d, 0x109e,
853         0x10c6, 0x10c7,
854         0x10c8, 0x10cd,
855         0x10ce, 0x10d0,
856         0x1249, 0x124a,
857         0x124e, 0x1250,
858         0x1257, 0x1258,
859         0x1259, 0x125a,
860         0x125e, 0x1260,
861         0x1289, 0x128a,
862         0x128e, 0x1290,
863         0x12b1, 0x12b2,
864         0x12b6, 0x12b8,
865         0x12bf, 0x12c0,
866         0x12c1, 0x12c2,
867         0x12c6, 0x12c8,
868         0x12d7, 0x12d8,
869         0x1311, 0x1312,
870         0x1316, 0x1318,
871         0x135b, 0x1360,
872         0x137d, 0x1380,
873         0x1390, 0x13a0,
874         0x13f6, 0x13f8,
875         0x13fe, 0x1401,
876         0x1680, 0x1681,
877         0x169b, 0x16a0,
878         0x16f9, 0x1700,
879         0x170d, 0x170e,
880         0x1712, 0x1720,
881         0x1732, 0x1735,
882         0x1737, 0x1740,
883         0x1752, 0x1760,
884         0x176d, 0x176e,
885         0x1771, 0x1780,
886         0x17b4, 0x17b6,
887         0x17b7, 0x17be,
888         0x17c6, 0x17c7,
889         0x17c9, 0x17d4,
890         0x17db, 0x17dc,
891         0x17dd, 0x17e0,
892         0x17ea, 0x1810,
893         0x181a, 0x1820,
894         0x1879, 0x1884,
895         0x1885, 0x1887,
896         0x18a9, 0x18aa,
897         0x18ab, 0x18b0,
898         0x18f6, 0x1900,
899         0x191f, 0x1923,
900         0x1927, 0x1929,
901         0x192c, 0x1930,
902         0x1932, 0x1933,
903         0x1939, 0x1946,
904         0x196e, 0x1970,
905         0x1975, 0x1980,
906         0x19ac, 0x19b0,
907         0x19ca, 0x19d0,
908         0x19db, 0x1a00,
909         0x1a17, 0x1a19,
910         0x1a1b, 0x1a1e,
911         0x1a56, 0x1a57,
912         0x1a58, 0x1a61,
913         0x1a62, 0x1a63,
914         0x1a65, 0x1a6d,
915         0x1a73, 0x1a80,
916         0x1a8a, 0x1a90,
917         0x1a9a, 0x1aa0,
918         0x1aae, 0x1b04,
919         0x1b34, 0x1b35,
920         0x1b36, 0x1b3b,
921         0x1b3c, 0x1b3d,
922         0x1b42, 0x1b43,
923         0x1b4c, 0x1b50,
924         0x1b6b, 0x1b74,
925         0x1b7d, 0x1b82,
926         0x1ba2, 0x1ba6,
927         0x1ba8, 0x1baa,
928         0x1bab, 0x1bae,
929         0x1be6, 0x1be7,
930         0x1be8, 0x1bea,
931         0x1bed, 0x1bee,
932         0x1bef, 0x1bf2,
933         0x1bf4, 0x1bfc,
934         0x1c2c, 0x1c34,
935         0x1c36, 0x1c3b,
936         0x1c4a, 0x1c4d,
937         0x1c89, 0x1c90,
938         0x1cbb, 0x1cbd,
939         0x1cc8, 0x1cd3,
940         0x1cd4, 0x1ce1,
941         0x1ce2, 0x1ce9,
942         0x1ced, 0x1cee,
943         0x1cf4, 0x1cf5,
944         0x1cf8, 0x1d00,
945         0x1dc0, 0x1e00,
946         0x1f16, 0x1f18,
947         0x1f1e, 0x1f20,
948         0x1f46, 0x1f48,
949         0x1f4e, 0x1f50,
950         0x1f58, 0x1f59,
951         0x1f5a, 0x1f5b,
952         0x1f5c, 0x1f5d,
953         0x1f5e, 0x1f5f,
954         0x1f7e, 0x1f80,
955         0x1fb5, 0x1fb6,
956         0x1fbd, 0x1fbe,
957         0x1fbf, 0x1fc2,
958         0x1fc5, 0x1fc6,
959         0x1fcd, 0x1fd0,
960         0x1fd4, 0x1fd6,
961         0x1fdc, 0x1fe0,
962         0x1fed, 0x1ff2,
963         0x1ff5, 0x1ff6,
964         0x1ffd, 0x200e,
965         0x2010, 0x2071,
966         0x2072, 0x207f,
967         0x2080, 0x2090,
968         0x209d, 0x2102,
969         0x2103, 0x2107,
970         0x2108, 0x210a,
971         0x2114, 0x2115,
972         0x2116, 0x2119,
973         0x211e, 0x2124,
974         0x2125, 0x2126,
975         0x2127, 0x2128,
976         0x2129, 0x212a,
977         0x212e, 0x212f,
978         0x213a, 0x213c,
979         0x2140, 0x2145,
980         0x214a, 0x214e,
981         0x2150, 0x2160,
982         0x2189, 0x2336,
983         0x237b, 0x2395,
984         0x2396, 0x249c,
985         0x24ea, 0x26ac,
986         0x26ad, 0x2800,
987         0x2900, 0x2c00,
988         0x2c2f, 0x2c30,
989         0x2c5f, 0x2c60,
990         0x2ce5, 0x2ceb,
991         0x2cef, 0x2cf2,
992         0x2cf4, 0x2d00,
993         0x2d26, 0x2d27,
994         0x2d28, 0x2d2d,
995         0x2d2e, 0x2d30,
996         0x2d68, 0x2d6f,
997         0x2d71, 0x2d80,
998         0x2d97, 0x2da0,
999         0x2da7, 0x2da8,
1000         0x2daf, 0x2db0,
1001         0x2db7, 0x2db8,
1002         0x2dbf, 0x2dc0,
1003         0x2dc7, 0x2dc8,
1004         0x2dcf, 0x2dd0,
1005         0x2dd7, 0x2dd8,
1006         0x2ddf, 0x3005,
1007         0x3008, 0x3021,
1008         0x302a, 0x302e,
1009         0x3030, 0x3031,
1010         0x3036, 0x3038,
1011         0x303d, 0x3041,
1012         0x3097, 0x309d,
1013         0x30a0, 0x30a1,
1014         0x30fb, 0x30fc,
1015         0x3100, 0x3105,
1016         0x3130, 0x3131,
1017         0x318f, 0x3190,
1018         0x31bb, 0x31f0,
1019         0x321d, 0x3220,
1020         0x3250, 0x3260,
1021         0x327c, 0x327f,
1022         0x32b1, 0x32c0,
1023         0x32cc, 0x32d0,
1024         0x32ff, 0x3300,
1025         0x3377, 0x337b,
1026         0x33de, 0x33e0,
1027         0x33ff, 0x3400,
1028         0x4db6, 0x4e00,
1029         0x9ff0, 0xa000,
1030         0xa48d, 0xa4d0,
1031         0xa60d, 0xa610,
1032         0xa62c, 0xa640,
1033         0xa66f, 0xa680,
1034         0xa69e, 0xa6a0,
1035         0xa6f0, 0xa6f2,
1036         0xa6f8, 0xa722,
1037         0xa788, 0xa789,
1038         0xa7ba, 0xa7f7,
1039         0xa802, 0xa803,
1040         0xa806, 0xa807,
1041         0xa80b, 0xa80c,
1042         0xa825, 0xa827,
1043         0xa828, 0xa830,
1044         0xa838, 0xa840,
1045         0xa874, 0xa880,
1046         0xa8c4, 0xa8ce,
1047         0xa8da, 0xa8f2,
1048         0xa8ff, 0xa900,
1049         0xa926, 0xa92e,
1050         0xa947, 0xa952,
1051         0xa954, 0xa95f,
1052         0xa97d, 0xa983,
1053         0xa9b3, 0xa9b4,
1054         0xa9b6, 0xa9ba,
1055         0xa9bc, 0xa9bd,
1056         0xa9ce, 0xa9cf,
1057         0xa9da, 0xa9de,
1058         0xa9e5, 0xa9e6,
1059         0xa9ff, 0xaa00,
1060         0xaa29, 0xaa2f,
1061         0xaa31, 0xaa33,
1062         0xaa35, 0xaa40,
1063         0xaa43, 0xaa44,
1064         0xaa4c, 0xaa4d,
1065         0xaa4e, 0xaa50,
1066         0xaa5a, 0xaa5c,
1067         0xaa7c, 0xaa7d,
1068         0xaab0, 0xaab1,
1069         0xaab2, 0xaab5,
1070         0xaab7, 0xaab9,
1071         0xaabe, 0xaac0,
1072         0xaac1, 0xaac2,
1073         0xaac3, 0xaadb,
1074         0xaaec, 0xaaee,
1075         0xaaf6, 0xab01,
1076         0xab07, 0xab09,
1077         0xab0f, 0xab11,
1078         0xab17, 0xab20,
1079         0xab27, 0xab28,
1080         0xab2f, 0xab30,
1081         0xab66, 0xab70,
1082         0xabe5, 0xabe6,
1083         0xabe8, 0xabe9,
1084         0xabed, 0xabf0,
1085         0xabfa, 0xac00,
1086         0xd7a4, 0xd7b0,
1087         0xd7c7, 0xd7cb,
1088         0xd7fc, 0xe000,
1089         0xfa6e, 0xfa70,
1090         0xfada, 0xfb00,
1091         0xfb07, 0xfb13,
1092         0xfb18, 0xfb1d,
1093         0xfb1e, 0xfb1f,
1094         0xfb29, 0xfb2a,
1095         0xfd3e, 0xfd40,
1096         0xfdd0, 0xfdf0,
1097         0xfdfd, 0xfdfe,
1098         0xfe00, 0xfe70,
1099         0xfeff, 0xff21,
1100         0xff3b, 0xff41,
1101         0xff5b, 0xff66,
1102         0xffbf, 0xffc2,
1103         0xffc8, 0xffca,
1104         0xffd0, 0xffd2,
1105         0xffd8, 0xffda,
1106         0xffdd, 0x10000,
1107         0x1000c, 0x1000d,
1108         0x10027, 0x10028,
1109         0x1003b, 0x1003c,
1110         0x1003e, 0x1003f,
1111         0x1004e, 0x10050,
1112         0x1005e, 0x10080,
1113         0x100fb, 0x10100,
1114         0x10101, 0x10102,
1115         0x10103, 0x10107,
1116         0x10134, 0x10137,
1117         0x10140, 0x1018d,
1118         0x1018f, 0x101d0,
1119         0x101fd, 0x10280,
1120         0x1029d, 0x102a0,
1121         0x102d1, 0x10300,
1122         0x10324, 0x1032d,
1123         0x1034b, 0x10350,
1124         0x10376, 0x10380,
1125         0x1039e, 0x1039f,
1126         0x103c4, 0x103c8,
1127         0x103d6, 0x10400,
1128         0x1049e, 0x104a0,
1129         0x104aa, 0x104d3,
1130         0x104d4, 0x104d8,
1131         0x104fc, 0x10500,
1132         0x10528, 0x10530,
1133         0x10564, 0x1056f,
1134         0x10570, 0x10600,
1135         0x10737, 0x10740,
1136         0x10756, 0x10760,
1137         0x10768, 0x10800,
1138         0x1091f, 0x10920,
1139         0x10a01, 0x10a04,
1140         0x10a05, 0x10a07,
1141         0x10a0c, 0x10a10,
1142         0x10a38, 0x10a3b,
1143         0x10a3f, 0x10a40,
1144         0x10ae5, 0x10ae7,
1145         0x10b39, 0x10b40,
1146         0x10d00, 0x10d40,
1147         0x10e60, 0x10e7f,
1148         0x10f30, 0x10f70,
1149         0x11001, 0x11002,
1150         0x11038, 0x11047,
1151         0x1104e, 0x11066,
1152         0x11070, 0x11082,
1153         0x110b3, 0x110b7,
1154         0x110b9, 0x110bb,
1155         0x110c2, 0x110cd,
1156         0x110ce, 0x110d0,
1157         0x110e9, 0x110f0,
1158         0x110fa, 0x11103,
1159         0x11127, 0x1112c,
1160         0x1112d, 0x11136,
1161         0x11147, 0x11150,
1162         0x11173, 0x11174,
1163         0x11177, 0x11182,
1164         0x111b6, 0x111bf,
1165         0x111c9, 0x111cd,
1166         0x111ce, 0x111d0,
1167         0x111e0, 0x111e1,
1168         0x111f5, 0x11200,
1169         0x11212, 0x11213,
1170         0x1122f, 0x11232,
1171         0x11234, 0x11235,
1172         0x11236, 0x11238,
1173         0x1123e, 0x11280,
1174         0x11287, 0x11288,
1175         0x11289, 0x1128a,
1176         0x1128e, 0x1128f,
1177         0x1129e, 0x1129f,
1178         0x112aa, 0x112b0,
1179         0x112df, 0x112e0,
1180         0x112e3, 0x112f0,
1181         0x112fa, 0x11302,
1182         0x11304, 0x11305,
1183         0x1130d, 0x1130f,
1184         0x11311, 0x11313,
1185         0x11329, 0x1132a,
1186         0x11331, 0x11332,
1187         0x11334, 0x11335,
1188         0x1133a, 0x1133d,
1189         0x11340, 0x11341,
1190         0x11345, 0x11347,
1191         0x11349, 0x1134b,
1192         0x1134e, 0x11350,
1193         0x11351, 0x11357,
1194         0x11358, 0x1135d,
1195         0x11364, 0x11400,
1196         0x11438, 0x11440,
1197         0x11442, 0x11445,
1198         0x11446, 0x11447,
1199         0x1145a, 0x1145b,
1200         0x1145c, 0x1145d,
1201         0x1145e, 0x11480,
1202         0x114b3, 0x114b9,
1203         0x114ba, 0x114bb,
1204         0x114bf, 0x114c1,
1205         0x114c2, 0x114c4,
1206         0x114c8, 0x114d0,
1207         0x114da, 0x11580,
1208         0x115b2, 0x115b8,
1209         0x115bc, 0x115be,
1210         0x115bf, 0x115c1,
1211         0x115dc, 0x11600,
1212         0x11633, 0x1163b,
1213         0x1163d, 0x1163e,
1214         0x1163f, 0x11641,
1215         0x11645, 0x11650,
1216         0x1165a, 0x11680,
1217         0x116ab, 0x116ac,
1218         0x116ad, 0x116ae,
1219         0x116b0, 0x116b6,
1220         0x116b7, 0x116c0,
1221         0x116ca, 0x11700,
1222         0x1171b, 0x11720,
1223         0x11722, 0x11726,
1224         0x11727, 0x11730,
1225         0x1182f, 0x11838,
1226         0x11839, 0x1183b,
1227         0x1183c, 0x118a0,
1228         0x118f3, 0x118ff,
1229         0x11900, 0x11a00,
1230         0x11a01, 0x11a07,
1231         0x11a09, 0x11a0b,
1232         0x11a33, 0x11a3a,
1233         0x11a3b, 0x11a3f,
1234         0x11a47, 0x11a50,
1235         0x11a51, 0x11a57,
1236         0x11a59, 0x11a5c,
1237         0x11a84, 0x11a86,
1238         0x11a8a, 0x11a97,
1239         0x11a98, 0x11a9a,
1240         0x11aa3, 0x11ac0,
1241         0x11af9, 0x11c00,
1242         0x11c09, 0x11c0a,
1243         0x11c30, 0x11c3e,
1244         0x11c46, 0x11c50,
1245         0x11c6d, 0x11c70,
1246         0x11c90, 0x11ca9,
1247         0x11caa, 0x11cb1,
1248         0x11cb2, 0x11cb4,
1249         0x11cb5, 0x11d00,
1250         0x11d07, 0x11d08,
1251         0x11d0a, 0x11d0b,
1252         0x11d31, 0x11d46,
1253         0x11d47, 0x11d50,
1254         0x11d5a, 0x11d60,
1255         0x11d66, 0x11d67,
1256         0x11d69, 0x11d6a,
1257         0x11d8f, 0x11d93,
1258         0x11d95, 0x11d96,
1259         0x11d97, 0x11d98,
1260         0x11d99, 0x11da0,
1261         0x11daa, 0x11ee0,
1262         0x11ef3, 0x11ef5,
1263         0x11ef9, 0x12000,
1264         0x1239a, 0x12400,
1265         0x1246f, 0x12470,
1266         0x12475, 0x12480,
1267         0x12544, 0x13000,
1268         0x1342f, 0x14400,
1269         0x14647, 0x16800,
1270         0x16a39, 0x16a40,
1271         0x16a5f, 0x16a60,
1272         0x16a6a, 0x16a6e,
1273         0x16a70, 0x16ad0,
1274         0x16aee, 0x16af5,
1275         0x16af6, 0x16b00,
1276         0x16b30, 0x16b37,
1277         0x16b46, 0x16b50,
1278         0x16b5a, 0x16b5b,
1279         0x16b62, 0x16b63,
1280         0x16b78, 0x16b7d,
1281         0x16b90, 0x16e40,
1282         0x16e9b, 0x16f00,
1283         0x16f45, 0x16f50,
1284         0x16f7f, 0x16f93,
1285         0x16fa0, 0x16fe0,
1286         0x16fe2, 0x17000,
1287         0x187f2, 0x18800,
1288         0x18af3, 0x1b000,
1289         0x1b11f, 0x1b170,
1290         0x1b2fc, 0x1bc00,
1291         0x1bc6b, 0x1bc70,
1292         0x1bc7d, 0x1bc80,
1293         0x1bc89, 0x1bc90,
1294         0x1bc9a, 0x1bc9c,
1295         0x1bc9d, 0x1bc9f,
1296         0x1bca0, 0x1d000,
1297         0x1d0f6, 0x1d100,
1298         0x1d127, 0x1d129,
1299         0x1d167, 0x1d16a,
1300         0x1d173, 0x1d183,
1301         0x1d185, 0x1d18c,
1302         0x1d1aa, 0x1d1ae,
1303         0x1d1e9, 0x1d2e0,
1304         0x1d2f4, 0x1d360,
1305         0x1d379, 0x1d400,
1306         0x1d455, 0x1d456,
1307         0x1d49d, 0x1d49e,
1308         0x1d4a0, 0x1d4a2,
1309         0x1d4a3, 0x1d4a5,
1310         0x1d4a7, 0x1d4a9,
1311         0x1d4ad, 0x1d4ae,
1312         0x1d4ba, 0x1d4bb,
1313         0x1d4bc, 0x1d4bd,
1314         0x1d4c4, 0x1d4c5,
1315         0x1d506, 0x1d507,
1316         0x1d50b, 0x1d50d,
1317         0x1d515, 0x1d516,
1318         0x1d51d, 0x1d51e,
1319         0x1d53a, 0x1d53b,
1320         0x1d53f, 0x1d540,
1321         0x1d545, 0x1d546,
1322         0x1d547, 0x1d54a,
1323         0x1d551, 0x1d552,
1324         0x1d6a6, 0x1d6a8,
1325         0x1d6db, 0x1d6dc,
1326         0x1d715, 0x1d716,
1327         0x1d74f, 0x1d750,
1328         0x1d789, 0x1d78a,
1329         0x1d7c3, 0x1d7c4,
1330         0x1d7cc, 0x1d800,
1331         0x1da00, 0x1da37,
1332         0x1da3b, 0x1da6d,
1333         0x1da75, 0x1da76,
1334         0x1da84, 0x1da85,
1335         0x1da8c, 0x1e800,
1336         0x1e8d0, 0x1e8d7,
1337         0x1e944, 0x1e94b,
1338         0x1ec70, 0x1ecc0,
1339         0x1ee00, 0x1ef00,
1340         0x1f000, 0x1f110,
1341         0x1f12f, 0x1f130,
1342         0x1f16a, 0x1f170,
1343         0x1f1ad, 0x1f1e6,
1344         0x1f203, 0x1f210,
1345         0x1f23c, 0x1f240,
1346         0x1f249, 0x1f250,
1347         0x1f252, 0x20000,
1348         0x2a6d7, 0x2a700,
1349         0x2b735, 0x2b740,
1350         0x2b81e, 0x2b820,
1351         0x2cea2, 0x2ceb0,
1352         0x2ebe1, 0x2f800,
1353         0x2fa1e, 0xf0000,
1354         0xffffe, 0x100000,
1355         0x10fffe, 0x10ffff // sentinel
1356     };
1357 
1358 
1359     // use a binary search with a cache
1360 
1361     private transient volatile int stCache = 0;
1362 
isStrongDirectional(char c)1363     private boolean isStrongDirectional(char c) {
1364         int cachedIndex = stCache;
1365         if (c < strongTable[cachedIndex]) {
1366             cachedIndex = search(c, strongTable, 0, cachedIndex);
1367         } else if (c >= strongTable[cachedIndex + 1]) {
1368             cachedIndex = search(c, strongTable, cachedIndex + 1,
1369                                  strongTable.length - cachedIndex - 1);
1370         }
1371         boolean val = (cachedIndex & 0x1) == 1;
1372         stCache = cachedIndex;
1373         return val;
1374     }
1375 
getKeyFromMask(int mask)1376     private static int getKeyFromMask(int mask) {
1377         int key = 0;
1378         while (key < NUM_KEYS && ((mask & (1<<key)) == 0)) {
1379             ++key;
1380         }
1381         if (key == NUM_KEYS || ((mask & ~(1<<key)) != 0)) {
1382             throw new IllegalArgumentException("invalid shaper: " + Integer.toHexString(mask));
1383         }
1384         return key;
1385     }
1386 
1387     /**
1388      * Returns a shaper for the provided unicode range.  All
1389      * Latin-1 (EUROPEAN) digits are converted
1390      * to the corresponding decimal unicode digits.
1391      * @param singleRange the specified Unicode range
1392      * @return a non-contextual numeric shaper
1393      * @throws IllegalArgumentException if the range is not a single range
1394      */
getShaper(int singleRange)1395     public static NumericShaper getShaper(int singleRange) {
1396         int key = getKeyFromMask(singleRange);
1397         return new NumericShaper(key, singleRange);
1398     }
1399 
1400     /**
1401      * Returns a shaper for the provided Unicode
1402      * range. All Latin-1 (EUROPEAN) digits are converted to the
1403      * corresponding decimal digits of the specified Unicode range.
1404      *
1405      * @param singleRange the Unicode range given by a {@link
1406      *                    NumericShaper.Range} constant.
1407      * @return a non-contextual {@code NumericShaper}.
1408      * @throws NullPointerException if {@code singleRange} is {@code null}
1409      * @since 1.7
1410      */
getShaper(Range singleRange)1411     public static NumericShaper getShaper(Range singleRange) {
1412         return new NumericShaper(singleRange, EnumSet.of(singleRange));
1413     }
1414 
1415     /**
1416      * Returns a contextual shaper for the provided unicode range(s).
1417      * Latin-1 (EUROPEAN) digits are converted to the decimal digits
1418      * corresponding to the range of the preceding text, if the
1419      * range is one of the provided ranges.  Multiple ranges are
1420      * represented by or-ing the values together, such as,
1421      * {@code NumericShaper.ARABIC | NumericShaper.THAI}.  The
1422      * shaper assumes EUROPEAN as the starting context, that is, if
1423      * EUROPEAN digits are encountered before any strong directional
1424      * text in the string, the context is presumed to be EUROPEAN, and
1425      * so the digits will not shape.
1426      * @param ranges the specified Unicode ranges
1427      * @return a shaper for the specified ranges
1428      */
getContextualShaper(int ranges)1429     public static NumericShaper getContextualShaper(int ranges) {
1430         ranges |= CONTEXTUAL_MASK;
1431         return new NumericShaper(EUROPEAN_KEY, ranges);
1432     }
1433 
1434     /**
1435      * Returns a contextual shaper for the provided Unicode
1436      * range(s). The Latin-1 (EUROPEAN) digits are converted to the
1437      * decimal digits corresponding to the range of the preceding
1438      * text, if the range is one of the provided ranges.
1439      *
1440      * <p>The shaper assumes EUROPEAN as the starting context, that
1441      * is, if EUROPEAN digits are encountered before any strong
1442      * directional text in the string, the context is presumed to be
1443      * EUROPEAN, and so the digits will not shape.
1444      *
1445      * @param ranges the specified Unicode ranges
1446      * @return a contextual shaper for the specified ranges
1447      * @throws NullPointerException if {@code ranges} is {@code null}.
1448      * @since 1.7
1449      */
getContextualShaper(Set<Range> ranges)1450     public static NumericShaper getContextualShaper(Set<Range> ranges) {
1451         NumericShaper shaper = new NumericShaper(Range.EUROPEAN, ranges);
1452         shaper.mask = CONTEXTUAL_MASK;
1453         return shaper;
1454     }
1455 
1456     /**
1457      * Returns a contextual shaper for the provided unicode range(s).
1458      * Latin-1 (EUROPEAN) digits will be converted to the decimal digits
1459      * corresponding to the range of the preceding text, if the
1460      * range is one of the provided ranges.  Multiple ranges are
1461      * represented by or-ing the values together, for example,
1462      * {@code NumericShaper.ARABIC | NumericShaper.THAI}.  The
1463      * shaper uses defaultContext as the starting context.
1464      * @param ranges the specified Unicode ranges
1465      * @param defaultContext the starting context, such as
1466      * {@code NumericShaper.EUROPEAN}
1467      * @return a shaper for the specified Unicode ranges.
1468      * @throws IllegalArgumentException if the specified
1469      * {@code defaultContext} is not a single valid range.
1470      */
getContextualShaper(int ranges, int defaultContext)1471     public static NumericShaper getContextualShaper(int ranges, int defaultContext) {
1472         int key = getKeyFromMask(defaultContext);
1473         ranges |= CONTEXTUAL_MASK;
1474         return new NumericShaper(key, ranges);
1475     }
1476 
1477     /**
1478      * Returns a contextual shaper for the provided Unicode range(s).
1479      * The Latin-1 (EUROPEAN) digits will be converted to the decimal
1480      * digits corresponding to the range of the preceding text, if the
1481      * range is one of the provided ranges. The shaper uses {@code
1482      * defaultContext} as the starting context.
1483      *
1484      * @param ranges the specified Unicode ranges
1485      * @param defaultContext the starting context, such as
1486      *                       {@code NumericShaper.Range.EUROPEAN}
1487      * @return a contextual shaper for the specified Unicode ranges.
1488      * @throws NullPointerException
1489      *         if {@code ranges} or {@code defaultContext} is {@code null}
1490      * @since 1.7
1491      */
getContextualShaper(Set<Range> ranges, Range defaultContext)1492     public static NumericShaper getContextualShaper(Set<Range> ranges,
1493                                                     Range defaultContext) {
1494         if (defaultContext == null) {
1495             throw new NullPointerException();
1496         }
1497         NumericShaper shaper = new NumericShaper(defaultContext, ranges);
1498         shaper.mask = CONTEXTUAL_MASK;
1499         return shaper;
1500     }
1501 
1502     /**
1503      * Private constructor.
1504      */
NumericShaper(int key, int mask)1505     private NumericShaper(int key, int mask) {
1506         this.key = key;
1507         this.mask = mask;
1508     }
1509 
NumericShaper(Range defaultContext, Set<Range> ranges)1510     private NumericShaper(Range defaultContext, Set<Range> ranges) {
1511         shapingRange = defaultContext;
1512         rangeSet = EnumSet.copyOf(ranges); // throws NPE if ranges is null.
1513 
1514         // Give precedence to EASTERN_ARABIC if both ARABIC and
1515         // EASTERN_ARABIC are specified.
1516         if (rangeSet.contains(Range.EASTERN_ARABIC)
1517             && rangeSet.contains(Range.ARABIC)) {
1518             rangeSet.remove(Range.ARABIC);
1519         }
1520 
1521         // As well as the above case, give precedence to TAI_THAM_THAM if both
1522         // TAI_THAM_HORA and TAI_THAM_THAM are specified.
1523         if (rangeSet.contains(Range.TAI_THAM_THAM)
1524             && rangeSet.contains(Range.TAI_THAM_HORA)) {
1525             rangeSet.remove(Range.TAI_THAM_HORA);
1526         }
1527 
1528         rangeArray = rangeSet.toArray(new Range[rangeSet.size()]);
1529         if (rangeArray.length > BSEARCH_THRESHOLD) {
1530             // sort rangeArray for binary search
1531             Arrays.sort(rangeArray,
1532                         new Comparator<Range>() {
1533                             public int compare(Range s1, Range s2) {
1534                                 return s1.base > s2.base ? 1 : s1.base == s2.base ? 0 : -1;
1535                             }
1536                         });
1537         }
1538     }
1539 
1540     /**
1541      * Converts the digits in the text that occur between start and
1542      * start + count.
1543      * @param text an array of characters to convert
1544      * @param start the index into {@code text} to start
1545      *        converting
1546      * @param count the number of characters in {@code text}
1547      *        to convert
1548      * @throws IndexOutOfBoundsException if start or start + count is
1549      *        out of bounds
1550      * @throws NullPointerException if text is null
1551      */
shape(char[] text, int start, int count)1552     public void shape(char[] text, int start, int count) {
1553         checkParams(text, start, count);
1554         if (isContextual()) {
1555             if (rangeSet == null) {
1556                 shapeContextually(text, start, count, key);
1557             } else {
1558                 shapeContextually(text, start, count, shapingRange);
1559             }
1560         } else {
1561             shapeNonContextually(text, start, count);
1562         }
1563     }
1564 
1565     /**
1566      * Converts the digits in the text that occur between start and
1567      * start + count, using the provided context.
1568      * Context is ignored if the shaper is not a contextual shaper.
1569      * @param text an array of characters
1570      * @param start the index into {@code text} to start
1571      *        converting
1572      * @param count the number of characters in {@code text}
1573      *        to convert
1574      * @param context the context to which to convert the
1575      *        characters, such as {@code NumericShaper.EUROPEAN}
1576      * @throws IndexOutOfBoundsException if start or start + count is
1577      *        out of bounds
1578      * @throws NullPointerException if text is null
1579      * @throws IllegalArgumentException if this is a contextual shaper
1580      * and the specified {@code context} is not a single valid
1581      * range.
1582      */
shape(char[] text, int start, int count, int context)1583     public void shape(char[] text, int start, int count, int context) {
1584         checkParams(text, start, count);
1585         if (isContextual()) {
1586             int ctxKey = getKeyFromMask(context);
1587             if (rangeSet == null) {
1588                 shapeContextually(text, start, count, ctxKey);
1589             } else {
1590                 shapeContextually(text, start, count, Range.values()[ctxKey]);
1591             }
1592         } else {
1593             shapeNonContextually(text, start, count);
1594         }
1595     }
1596 
1597     /**
1598      * Converts the digits in the text that occur between {@code
1599      * start} and {@code start + count}, using the provided {@code
1600      * context}. {@code Context} is ignored if the shaper is not a
1601      * contextual shaper.
1602      *
1603      * @param text  a {@code char} array
1604      * @param start the index into {@code text} to start converting
1605      * @param count the number of {@code char}s in {@code text}
1606      *              to convert
1607      * @param context the context to which to convert the characters,
1608      *                such as {@code NumericShaper.Range.EUROPEAN}
1609      * @throws IndexOutOfBoundsException
1610      *         if {@code start} or {@code start + count} is out of bounds
1611      * @throws NullPointerException
1612      *         if {@code text} or {@code context} is null
1613      * @since 1.7
1614      */
shape(char[] text, int start, int count, Range context)1615     public void shape(char[] text, int start, int count, Range context) {
1616         checkParams(text, start, count);
1617         if (context == null) {
1618             throw new NullPointerException("context is null");
1619         }
1620 
1621         if (isContextual()) {
1622             if (rangeSet != null) {
1623                 shapeContextually(text, start, count, context);
1624             } else {
1625                 int key = Range.toRangeIndex(context);
1626                 if (key >= 0) {
1627                     shapeContextually(text, start, count, key);
1628                 } else {
1629                     shapeContextually(text, start, count, shapingRange);
1630                 }
1631             }
1632         } else {
1633             shapeNonContextually(text, start, count);
1634         }
1635     }
1636 
checkParams(char[] text, int start, int count)1637     private void checkParams(char[] text, int start, int count) {
1638         if (text == null) {
1639             throw new NullPointerException("text is null");
1640         }
1641         if ((start < 0)
1642             || (start > text.length)
1643             || ((start + count) < 0)
1644             || ((start + count) > text.length)) {
1645             throw new IndexOutOfBoundsException(
1646                 "bad start or count for text of length " + text.length);
1647         }
1648     }
1649 
1650     /**
1651      * Returns a {@code boolean} indicating whether or not
1652      * this shaper shapes contextually.
1653      * @return {@code true} if this shaper is contextual;
1654      *         {@code false} otherwise.
1655      */
isContextual()1656     public boolean isContextual() {
1657         return (mask & CONTEXTUAL_MASK) != 0;
1658     }
1659 
1660     /**
1661      * Returns an {@code int} that ORs together the values for
1662      * all the ranges that will be shaped.
1663      * <p>
1664      * For example, to check if a shaper shapes to Arabic, you would use the
1665      * following:
1666      * <blockquote>
1667      *   {@code if ((shaper.getRanges() & shaper.ARABIC) != 0) &#123; ... }
1668      * </blockquote>
1669      *
1670      * <p>Note that this method supports only the bit mask-based
1671      * ranges. Call {@link #getRangeSet()} for the enum-based ranges.
1672      *
1673      * @return the values for all the ranges to be shaped.
1674      */
getRanges()1675     public int getRanges() {
1676         return mask & ~CONTEXTUAL_MASK;
1677     }
1678 
1679     /**
1680      * Returns a {@code Set} representing all the Unicode ranges in
1681      * this {@code NumericShaper} that will be shaped.
1682      *
1683      * @return all the Unicode ranges to be shaped.
1684      * @since 1.7
1685      */
getRangeSet()1686     public Set<Range> getRangeSet() {
1687         if (rangeSet != null) {
1688             return EnumSet.copyOf(rangeSet);
1689         }
1690         return Range.maskToRangeSet(mask);
1691     }
1692 
1693     /**
1694      * Perform non-contextual shaping.
1695      */
shapeNonContextually(char[] text, int start, int count)1696     private void shapeNonContextually(char[] text, int start, int count) {
1697         int base;
1698         char minDigit = '0';
1699         if (shapingRange != null) {
1700             base = shapingRange.getDigitBase();
1701             minDigit += shapingRange.getNumericBase();
1702         } else {
1703             base = bases[key];
1704             if (key == ETHIOPIC_KEY) {
1705                 minDigit++; // Ethiopic doesn't use decimal zero
1706             }
1707         }
1708         for (int i = start, e = start + count; i < e; ++i) {
1709             char c = text[i];
1710             if (c >= minDigit && c <= '\u0039') {
1711                 text[i] = (char)(c + base);
1712             }
1713         }
1714     }
1715 
1716     /**
1717      * Perform contextual shaping.
1718      * Synchronized to protect caches used in getContextKey.
1719      */
shapeContextually(char[] text, int start, int count, int ctxKey)1720     private synchronized void shapeContextually(char[] text, int start, int count, int ctxKey) {
1721 
1722         // if we don't support this context, then don't shape
1723         if ((mask & (1<<ctxKey)) == 0) {
1724             ctxKey = EUROPEAN_KEY;
1725         }
1726         int lastkey = ctxKey;
1727 
1728         int base = bases[ctxKey];
1729         char minDigit = ctxKey == ETHIOPIC_KEY ? '1' : '0'; // Ethiopic doesn't use decimal zero
1730 
1731         synchronized (NumericShaper.class) {
1732             for (int i = start, e = start + count; i < e; ++i) {
1733                 char c = text[i];
1734                 if (c >= minDigit && c <= '\u0039') {
1735                     text[i] = (char)(c + base);
1736                 }
1737 
1738                 if (isStrongDirectional(c)) {
1739                     int newkey = getContextKey(c);
1740                     if (newkey != lastkey) {
1741                         lastkey = newkey;
1742 
1743                         ctxKey = newkey;
1744                         if (((mask & EASTERN_ARABIC) != 0) &&
1745                              (ctxKey == ARABIC_KEY ||
1746                               ctxKey == EASTERN_ARABIC_KEY)) {
1747                             ctxKey = EASTERN_ARABIC_KEY;
1748                         } else if (((mask & ARABIC) != 0) &&
1749                              (ctxKey == ARABIC_KEY ||
1750                               ctxKey == EASTERN_ARABIC_KEY)) {
1751                             ctxKey = ARABIC_KEY;
1752                         } else if ((mask & (1<<ctxKey)) == 0) {
1753                             ctxKey = EUROPEAN_KEY;
1754                         }
1755 
1756                         base = bases[ctxKey];
1757 
1758                         minDigit = ctxKey == ETHIOPIC_KEY ? '1' : '0'; // Ethiopic doesn't use decimal zero
1759                     }
1760                 }
1761             }
1762         }
1763     }
1764 
shapeContextually(char[] text, int start, int count, Range ctxKey)1765     private void shapeContextually(char[] text, int start, int count, Range ctxKey) {
1766         // if we don't support the specified context, then don't shape.
1767         if (ctxKey == null || !rangeSet.contains(ctxKey)) {
1768             ctxKey = Range.EUROPEAN;
1769         }
1770 
1771         Range lastKey = ctxKey;
1772         int base = ctxKey.getDigitBase();
1773         char minDigit = (char)('0' + ctxKey.getNumericBase());
1774         final int end = start + count;
1775         for (int i = start; i < end; ++i) {
1776             char c = text[i];
1777             if (c >= minDigit && c <= '9') {
1778                 text[i] = (char)(c + base);
1779                 continue;
1780             }
1781             if (isStrongDirectional(c)) {
1782                 ctxKey = rangeForCodePoint(c);
1783                 if (ctxKey != lastKey) {
1784                     lastKey = ctxKey;
1785                     base = ctxKey.getDigitBase();
1786                     minDigit = (char)('0' + ctxKey.getNumericBase());
1787                 }
1788             }
1789         }
1790     }
1791 
1792     /**
1793      * Returns a hash code for this shaper.
1794      * @return this shaper's hash code.
1795      * @see java.lang.Object#hashCode
1796      */
hashCode()1797     public int hashCode() {
1798         int hash = mask;
1799         if (rangeSet != null) {
1800             // Use the CONTEXTUAL_MASK bit only for the enum-based
1801             // NumericShaper. A deserialized NumericShaper might have
1802             // bit masks.
1803             hash &= CONTEXTUAL_MASK;
1804             hash ^= rangeSet.hashCode();
1805         }
1806         return hash;
1807     }
1808 
1809     /**
1810      * Returns {@code true} if the specified object is an instance of
1811      * {@code NumericShaper} and shapes identically to this one,
1812      * regardless of the range representations, the bit mask or the
1813      * enum. For example, the following code produces {@code "true"}.
1814      * <blockquote><pre>
1815      * NumericShaper ns1 = NumericShaper.getShaper(NumericShaper.ARABIC);
1816      * NumericShaper ns2 = NumericShaper.getShaper(NumericShaper.Range.ARABIC);
1817      * System.out.println(ns1.equals(ns2));
1818      * </pre></blockquote>
1819      *
1820      * @param o the specified object to compare to this
1821      *          {@code NumericShaper}
1822      * @return {@code true} if {@code o} is an instance
1823      *         of {@code NumericShaper} and shapes in the same way;
1824      *         {@code false} otherwise.
1825      * @see java.lang.Object#equals(java.lang.Object)
1826      */
equals(Object o)1827     public boolean equals(Object o) {
1828         if (o != null) {
1829             try {
1830                 NumericShaper rhs = (NumericShaper)o;
1831                 if (rangeSet != null) {
1832                     if (rhs.rangeSet != null) {
1833                         return isContextual() == rhs.isContextual()
1834                             && rangeSet.equals(rhs.rangeSet)
1835                             && shapingRange == rhs.shapingRange;
1836                     }
1837                     return isContextual() == rhs.isContextual()
1838                         && rangeSet.equals(Range.maskToRangeSet(rhs.mask))
1839                         && shapingRange == Range.indexToRange(rhs.key);
1840                 } else if (rhs.rangeSet != null) {
1841                     Set<Range> rset = Range.maskToRangeSet(mask);
1842                     Range srange = Range.indexToRange(key);
1843                     return isContextual() == rhs.isContextual()
1844                         && rset.equals(rhs.rangeSet)
1845                         && srange == rhs.shapingRange;
1846                 }
1847                 return rhs.mask == mask && rhs.key == key;
1848             }
1849             catch (ClassCastException e) {
1850             }
1851         }
1852         return false;
1853     }
1854 
1855     /**
1856      * Returns a {@code String} that describes this shaper. This method
1857      * is used for debugging purposes only.
1858      * @return a {@code String} describing this shaper.
1859      */
toString()1860     public String toString() {
1861         StringBuilder buf = new StringBuilder(super.toString());
1862 
1863         buf.append("[contextual:").append(isContextual());
1864 
1865         String[] keyNames = null;
1866         if (isContextual()) {
1867             buf.append(", context:");
1868             buf.append(shapingRange == null ? Range.values()[key] : shapingRange);
1869         }
1870 
1871         if (rangeSet == null) {
1872             buf.append(", range(s): ");
1873             boolean first = true;
1874             for (int i = 0; i < NUM_KEYS; ++i) {
1875                 if ((mask & (1 << i)) != 0) {
1876                     if (first) {
1877                         first = false;
1878                     } else {
1879                         buf.append(", ");
1880                     }
1881                     buf.append(Range.values()[i]);
1882                 }
1883             }
1884         } else {
1885             buf.append(", range set: ").append(rangeSet);
1886         }
1887         buf.append(']');
1888 
1889         return buf.toString();
1890     }
1891 
1892     /**
1893      * Returns the index of the high bit in value (assuming le, actually
1894      * power of 2 >= value). value must be positive.
1895      */
getHighBit(int value)1896     private static int getHighBit(int value) {
1897         if (value <= 0) {
1898             return -32;
1899         }
1900 
1901         int bit = 0;
1902 
1903         if (value >= 1 << 16) {
1904             value >>= 16;
1905             bit += 16;
1906         }
1907 
1908         if (value >= 1 << 8) {
1909             value >>= 8;
1910             bit += 8;
1911         }
1912 
1913         if (value >= 1 << 4) {
1914             value >>= 4;
1915             bit += 4;
1916         }
1917 
1918         if (value >= 1 << 2) {
1919             value >>= 2;
1920             bit += 2;
1921         }
1922 
1923         if (value >= 1 << 1) {
1924             bit += 1;
1925         }
1926 
1927         return bit;
1928     }
1929 
1930     /**
1931      * fast binary search over subrange of array.
1932      */
search(int value, int[] array, int start, int length)1933     private static int search(int value, int[] array, int start, int length)
1934     {
1935         int power = 1 << getHighBit(length);
1936         int extra = length - power;
1937         int probe = power;
1938         int index = start;
1939 
1940         if (value >= array[index + extra]) {
1941             index += extra;
1942         }
1943 
1944         while (probe > 1) {
1945             probe >>= 1;
1946 
1947             if (value >= array[index + probe]) {
1948                 index += probe;
1949             }
1950         }
1951 
1952         return index;
1953     }
1954 
1955     /**
1956      * Converts the {@code NumericShaper.Range} enum-based parameters,
1957      * if any, to the bit mask-based counterparts and writes this
1958      * object to the {@code stream}. Any enum constants that have no
1959      * bit mask-based counterparts are ignored in the conversion.
1960      *
1961      * @param stream the output stream to write to
1962      * @throws IOException if an I/O error occurs while writing to {@code stream}
1963      * @since 1.7
1964      */
writeObject(ObjectOutputStream stream)1965     private void writeObject(ObjectOutputStream stream) throws IOException {
1966         if (shapingRange != null) {
1967             int index = Range.toRangeIndex(shapingRange);
1968             if (index >= 0) {
1969                 key = index;
1970             }
1971         }
1972         if (rangeSet != null) {
1973             mask |= Range.toRangeMask(rangeSet);
1974         }
1975         stream.defaultWriteObject();
1976     }
1977 }
1978