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