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