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