1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html 4 /* 5 ********************************************************************** 6 * Copyright (c) 2001-2016, International Business Machines 7 * Corporation and others. All Rights Reserved. 8 ********************************************************************** 9 * Date Name Description 10 * 08/19/2001 aliu Creation. 11 ********************************************************************** 12 */ 13 14 package android.icu.text; 15 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.Enumeration; 19 import java.util.HashMap; 20 import java.util.LinkedHashSet; 21 import java.util.List; 22 import java.util.Locale; 23 import java.util.Map; 24 import java.util.MissingResourceException; 25 import java.util.ResourceBundle; 26 import java.util.Set; 27 import java.util.function.Supplier; 28 29 import android.icu.impl.ICUData; 30 import android.icu.impl.ICUResourceBundle; 31 import android.icu.impl.LocaleUtility; 32 import android.icu.impl.Utility; 33 import android.icu.lang.UScript; 34 import android.icu.text.RuleBasedTransliterator.Data; 35 import android.icu.util.CaseInsensitiveString; 36 import android.icu.util.UResourceBundle; 37 38 class TransliteratorRegistry { 39 40 // char constants 41 private static final char LOCALE_SEP = '_'; 42 43 // String constants 44 private static final String NO_VARIANT = ""; // empty string 45 private static final String ANY = "Any"; 46 47 /** 48 * Dynamic registry mapping full IDs to Entry objects. This 49 * contains both public and internal entities. The visibility is 50 * controlled by whether an entry is listed in availableIDs and 51 * specDAG or not. 52 * 53 * Keys are CaseInsensitiveString objects. 54 * Values are objects of class Class (subclass of Transliterator), 55 * RuleBasedTransliterator.Data, Transliterator.Factory, or one 56 * of the entry classes defined here (AliasEntry or ResourceEntry). 57 */ 58 private Map<CaseInsensitiveString, Object[]> registry; 59 60 /** 61 * DAG of visible IDs by spec. Hashtable: source => (Hashtable: 62 * target => (Vector: variant)) The Vector of variants is never 63 * empty. For a source-target with no variant, the special 64 * variant NO_VARIANT (the empty string) is stored in slot zero of 65 * the UVector. 66 * 67 * Keys are CaseInsensitiveString objects. 68 * Values are Hashtable of (CaseInsensitiveString -> Vector of 69 * CaseInsensitiveString) 70 */ 71 private Map<CaseInsensitiveString, Map<CaseInsensitiveString, List<CaseInsensitiveString>>> specDAG; 72 73 /** 74 * Vector of public full IDs (CaseInsensitiveString objects). 75 */ 76 private final Set<CaseInsensitiveString> availableIDs; 77 78 //---------------------------------------------------------------------- 79 // class Spec 80 //---------------------------------------------------------------------- 81 82 /** 83 * A Spec is a string specifying either a source or a target. In more 84 * general terms, it may also specify a variant, but we only use the 85 * Spec class for sources and targets. 86 * 87 * A Spec may be a locale or a script. If it is a locale, it has a 88 * fallback chain that goes xx_YY_ZZZ -> xx_YY -> xx -> ssss, where 89 * ssss is the script mapping of xx_YY_ZZZ. The Spec API methods 90 * hasFallback(), next(), and reset() iterate over this fallback 91 * sequence. 92 * 93 * The Spec class canonicalizes itself, so the locale is put into 94 * canonical form, or the script is transformed from an abbreviation 95 * to a full name. 96 */ 97 static class Spec { 98 99 private String top; // top spec 100 private String spec; // current spec 101 private String nextSpec; // next spec 102 private String scriptName; // script name equivalent of top, if != top 103 private boolean isSpecLocale; // true if spec is a locale 104 private boolean isNextLocale; // true if nextSpec is a locale 105 private ICUResourceBundle res; 106 Spec(String theSpec)107 public Spec(String theSpec) { 108 top = theSpec; 109 spec = null; 110 scriptName = null; 111 try{ 112 // Canonicalize script name. If top is a script name then 113 // script != UScript.INVALID_CODE. 114 int script = UScript.getCodeFromName(top); 115 116 // Canonicalize script name -or- do locale->script mapping 117 int[] s = UScript.getCode(top); 118 if (s != null) { 119 scriptName = UScript.getName(s[0]); 120 // If the script name is the same as top then it's redundant 121 if (scriptName.equalsIgnoreCase(top)) { 122 scriptName = null; 123 } 124 } 125 126 isSpecLocale = false; 127 res = null; 128 // If 'top' is not a script name, try a locale lookup 129 if (script == UScript.INVALID_CODE) { 130 Locale toploc = LocaleUtility.getLocaleFromName(top); 131 res = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_TRANSLIT_BASE_NAME,toploc); 132 // Make sure we got the bundle we wanted; otherwise, don't use it 133 if (res!=null && LocaleUtility.isFallbackOf(res.getULocale().toString(), top)) { 134 isSpecLocale = true; 135 } 136 } 137 }catch(MissingResourceException e){ 138 ///CLOVER:OFF 139 // The constructor is called from multiple private methods 140 // that protects an invalid scriptName 141 scriptName = null; 142 ///CLOVER:ON 143 } 144 // assert(spec != top); 145 reset(); 146 } 147 hasFallback()148 public boolean hasFallback() { 149 return nextSpec != null; 150 } 151 reset()152 public void reset() { 153 if (!Utility.sameObjects(spec, top)) { 154 spec = top; 155 isSpecLocale = (res != null); 156 setupNext(); 157 } 158 } 159 setupNext()160 private void setupNext() { 161 isNextLocale = false; 162 if (isSpecLocale) { 163 nextSpec = spec; 164 int i = nextSpec.lastIndexOf(LOCALE_SEP); 165 // If i == 0 then we have _FOO, so we fall through 166 // to the scriptName. 167 if (i > 0) { 168 nextSpec = spec.substring(0, i); 169 isNextLocale = true; 170 } else { 171 nextSpec = scriptName; // scriptName may be null 172 } 173 } else { 174 // Fallback to the script, which may be null 175 if (!Utility.sameObjects(nextSpec, scriptName)) { 176 nextSpec = scriptName; 177 } else { 178 nextSpec = null; 179 } 180 } 181 } 182 183 // Protocol: 184 // for(String& s(spec.get()); 185 // spec.hasFallback(); s(spec.next())) { ... 186 next()187 public String next() { 188 spec = nextSpec; 189 isSpecLocale = isNextLocale; 190 setupNext(); 191 return spec; 192 } 193 get()194 public String get() { 195 return spec; 196 } 197 isLocale()198 public boolean isLocale() { 199 return isSpecLocale; 200 } 201 202 /** 203 * Return the ResourceBundle for this spec, at the current 204 * level of iteration. The level of iteration goes from 205 * aa_BB_CCC to aa_BB to aa. If the bundle does not 206 * correspond to the current level of iteration, return null. 207 * If isLocale() is false, always return null. 208 */ getBundle()209 public ResourceBundle getBundle() { 210 if (res != null && 211 res.getULocale().toString().equals(spec)) { 212 return res; 213 } 214 return null; 215 } 216 getTop()217 public String getTop() { 218 return top; 219 } 220 } 221 222 //---------------------------------------------------------------------- 223 // Entry classes 224 //---------------------------------------------------------------------- 225 226 // BEGIN Android patch: Lazily load transliterator rules. 227 static class ResourceEntry { 228 private final Supplier<String> resourceSupplier; 229 public final int direction; 230 private String resource; ResourceEntry(String n, int d)231 public ResourceEntry(String n, int d) { 232 resource = n; 233 direction = d; 234 resourceSupplier = null; 235 } 236 ResourceEntry(Supplier<String> resourceSupplier, int dir)237 public ResourceEntry(Supplier<String> resourceSupplier, int dir) { 238 this.resourceSupplier = resourceSupplier; 239 direction = dir; 240 } 241 getResource()242 public String getResource() { 243 if (resourceSupplier == null) { 244 return resource; 245 } 246 247 synchronized (this) { 248 if (resource != null) { 249 return resource; 250 } 251 252 String str = resourceSupplier.get(); 253 resource = str; 254 return str; 255 } 256 } 257 } 258 // END Android patch: Lazily load transliterator rules. 259 260 // An entry representing a rule in a locale resource bundle 261 static class LocaleEntry { 262 public String rule; 263 public int direction; LocaleEntry(String r, int d)264 public LocaleEntry(String r, int d) { 265 rule = r; 266 direction = d; 267 } 268 } 269 270 static class AliasEntry { 271 public String alias; AliasEntry(String a)272 public AliasEntry(String a) { 273 alias = a; 274 } 275 } 276 277 static class CompoundRBTEntry { 278 private String ID; 279 private List<String> idBlockVector; 280 private List<Data> dataVector; 281 private UnicodeSet compoundFilter; 282 CompoundRBTEntry(String theID, List<String> theIDBlockVector, List<Data> theDataVector, UnicodeSet theCompoundFilter)283 public CompoundRBTEntry(String theID, List<String> theIDBlockVector, 284 List<Data> theDataVector, 285 UnicodeSet theCompoundFilter) { 286 ID = theID; 287 idBlockVector = theIDBlockVector; 288 dataVector = theDataVector; 289 compoundFilter = theCompoundFilter; 290 } 291 getInstance()292 public Transliterator getInstance() { 293 List<Transliterator> transliterators = new ArrayList<Transliterator>(); 294 int passNumber = 1; 295 296 int limit = Math.max(idBlockVector.size(), dataVector.size()); 297 for (int i = 0; i < limit; i++) { 298 if (i < idBlockVector.size()) { 299 String idBlock = idBlockVector.get(i); 300 if (idBlock.length() > 0) 301 transliterators.add(Transliterator.getInstance(idBlock)); 302 } 303 if (i < dataVector.size()) { 304 Data data = dataVector.get(i); 305 transliterators.add(new RuleBasedTransliterator("%Pass" + passNumber++, data, null)); 306 } 307 } 308 309 Transliterator t = new CompoundTransliterator(transliterators, passNumber - 1); 310 t.setID(ID); 311 if (compoundFilter != null) { 312 t.setFilter(compoundFilter); 313 } 314 return t; 315 } 316 } 317 318 //---------------------------------------------------------------------- 319 // class TransliteratorRegistry: Basic public API 320 //---------------------------------------------------------------------- 321 TransliteratorRegistry()322 public TransliteratorRegistry() { 323 registry = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, Object[]>()); 324 specDAG = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, Map<CaseInsensitiveString, List<CaseInsensitiveString>>>()); 325 availableIDs = new LinkedHashSet<>(); 326 } 327 328 /** 329 * Given a simple ID (forward direction, no inline filter, not 330 * compound) attempt to instantiate it from the registry. Return 331 * 0 on failure. 332 * 333 * Return a non-empty aliasReturn value if the ID points to an alias. 334 * We cannot instantiate it ourselves because the alias may contain 335 * filters or compounds, which we do not understand. Caller should 336 * make aliasReturn empty before calling. 337 */ get(String ID, StringBuffer aliasReturn)338 public Transliterator get(String ID, 339 StringBuffer aliasReturn) { 340 Object[] entry = find(ID); 341 return (entry == null) ? null 342 : instantiateEntry(ID, entry, aliasReturn); 343 } 344 345 /** 346 * Register a class. This adds an entry to the 347 * dynamic store, or replaces an existing entry. Any entry in the 348 * underlying static locale resource store is masked. 349 */ put(String ID, Class<? extends Transliterator> transliteratorSubclass, boolean visible)350 public void put(String ID, 351 Class<? extends Transliterator> transliteratorSubclass, 352 boolean visible) { 353 registerEntry(ID, transliteratorSubclass, visible); 354 } 355 356 /** 357 * Register an ID and a factory function pointer. This adds an 358 * entry to the dynamic store, or replaces an existing entry. Any 359 * entry in the underlying static locale resource store is masked. 360 */ put(String ID, Transliterator.Factory factory, boolean visible)361 public void put(String ID, 362 Transliterator.Factory factory, 363 boolean visible) { 364 registerEntry(ID, factory, visible); 365 } 366 367 /** 368 * Register an ID and a resource name. This adds an entry to the 369 * dynamic store, or replaces an existing entry. Any entry in the 370 * underlying static locale resource store is masked. 371 */ put(String ID, String resourceName, int dir, boolean visible)372 public void put(String ID, 373 String resourceName, 374 int dir, 375 boolean visible) { 376 registerEntry(ID, new ResourceEntry(resourceName, dir), visible); 377 } 378 379 // BEGIN Android patch: Lazily load transliterator rules. put(String ID, Supplier<String> resourceSupplier, int dir, boolean visible)380 void put(String ID, 381 Supplier<String> resourceSupplier, 382 int dir, 383 boolean visible) { 384 registerEntry(ID, new ResourceEntry(resourceSupplier, dir), visible); 385 } 386 // END Android patch: Lazily load transliterator rules. 387 388 /** 389 * Register an ID and an alias ID. This adds an entry to the 390 * dynamic store, or replaces an existing entry. Any entry in the 391 * underlying static locale resource store is masked. 392 */ put(String ID, String alias, boolean visible)393 public void put(String ID, 394 String alias, 395 boolean visible) { 396 registerEntry(ID, new AliasEntry(alias), visible); 397 } 398 399 /** 400 * Register an ID and a Transliterator object. This adds an entry 401 * to the dynamic store, or replaces an existing entry. Any entry 402 * in the underlying static locale resource store is masked. 403 */ put(String ID, Transliterator trans, boolean visible)404 public void put(String ID, 405 Transliterator trans, 406 boolean visible) { 407 registerEntry(ID, trans, visible); 408 } 409 410 /** 411 * Unregister an ID. This removes an entry from the dynamic store 412 * if there is one. The static locale resource store is 413 * unaffected. 414 */ remove(String ID)415 public void remove(String ID) { 416 String[] stv = TransliteratorIDParser.IDtoSTV(ID); 417 // Only need to do this if ID.indexOf('-') < 0 418 String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]); 419 registry.remove(new CaseInsensitiveString(id)); 420 removeSTV(stv[0], stv[1], stv[2]); 421 availableIDs.remove(new CaseInsensitiveString(id)); 422 } 423 424 //---------------------------------------------------------------------- 425 // class TransliteratorRegistry: Public ID and spec management 426 //---------------------------------------------------------------------- 427 428 /** 429 * An internal class that adapts an enumeration over 430 * CaseInsensitiveStrings to an enumeration over Strings. 431 */ 432 private static class IDEnumeration implements Enumeration<String> { 433 Enumeration<CaseInsensitiveString> en; 434 IDEnumeration(Enumeration<CaseInsensitiveString> e)435 public IDEnumeration(Enumeration<CaseInsensitiveString> e) { 436 en = e; 437 } 438 439 @Override hasMoreElements()440 public boolean hasMoreElements() { 441 return en != null && en.hasMoreElements(); 442 } 443 444 @Override nextElement()445 public String nextElement() { 446 return (en.nextElement()).getString(); 447 } 448 } 449 450 /** 451 * Returns an enumeration over the programmatic names of visible 452 * registered transliterators. 453 * 454 * @return An <code>Enumeration</code> over <code>String</code> objects 455 */ getAvailableIDs()456 public Enumeration<String> getAvailableIDs() { 457 // Since the cache contains CaseInsensitiveString objects, but 458 // the caller expects Strings, we have to use an intermediary. 459 return new IDEnumeration(Collections.enumeration(availableIDs)); 460 } 461 462 /** 463 * Returns an enumeration over all visible source names. 464 * 465 * @return An <code>Enumeration</code> over <code>String</code> objects 466 */ getAvailableSources()467 public Enumeration<String> getAvailableSources() { 468 return new IDEnumeration(Collections.enumeration(specDAG.keySet())); 469 } 470 471 /** 472 * Returns an enumeration over visible target names for the given 473 * source. 474 * 475 * @return An <code>Enumeration</code> over <code>String</code> objects 476 */ getAvailableTargets(String source)477 public Enumeration<String> getAvailableTargets(String source) { 478 CaseInsensitiveString cisrc = new CaseInsensitiveString(source); 479 Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc); 480 if (targets == null) { 481 return new IDEnumeration(null); 482 } 483 return new IDEnumeration(Collections.enumeration(targets.keySet())); 484 } 485 486 /** 487 * Returns an enumeration over visible variant names for the given 488 * source and target. 489 * 490 * @return An <code>Enumeration</code> over <code>String</code> objects 491 */ getAvailableVariants(String source, String target)492 public Enumeration<String> getAvailableVariants(String source, String target) { 493 CaseInsensitiveString cisrc = new CaseInsensitiveString(source); 494 CaseInsensitiveString citrg = new CaseInsensitiveString(target); 495 Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc); 496 if (targets == null) { 497 return new IDEnumeration(null); 498 } 499 List<CaseInsensitiveString> variants = targets.get(citrg); 500 if (variants == null) { 501 return new IDEnumeration(null); 502 } 503 return new IDEnumeration(Collections.enumeration(variants)); 504 } 505 506 //---------------------------------------------------------------------- 507 // class TransliteratorRegistry: internal 508 //---------------------------------------------------------------------- 509 510 /** 511 * Convenience method. Calls 6-arg registerEntry(). 512 */ registerEntry(String source, String target, String variant, Object entry, boolean visible)513 private void registerEntry(String source, 514 String target, 515 String variant, 516 Object entry, 517 boolean visible) { 518 String s = source; 519 if (s.length() == 0) { 520 s = ANY; 521 } 522 String ID = TransliteratorIDParser.STVtoID(source, target, variant); 523 registerEntry(ID, s, target, variant, entry, visible); 524 } 525 526 /** 527 * Convenience method. Calls 6-arg registerEntry(). 528 */ registerEntry(String ID, Object entry, boolean visible)529 private void registerEntry(String ID, 530 Object entry, 531 boolean visible) { 532 String[] stv = TransliteratorIDParser.IDtoSTV(ID); 533 // Only need to do this if ID.indexOf('-') < 0 534 String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]); 535 registerEntry(id, stv[0], stv[1], stv[2], entry, visible); 536 } 537 538 /** 539 * Register an entry object (adopted) with the given ID, source, 540 * target, and variant strings. 541 */ registerEntry(String ID, String source, String target, String variant, Object entry, boolean visible)542 private void registerEntry(String ID, 543 String source, 544 String target, 545 String variant, 546 Object entry, 547 boolean visible) { 548 CaseInsensitiveString ciID = new CaseInsensitiveString(ID); 549 Object[] arrayOfObj; 550 551 // Store the entry within an array so it can be modified later 552 if (entry instanceof Object[]) { 553 arrayOfObj = (Object[])entry; 554 } else { 555 arrayOfObj = new Object[] { entry }; 556 } 557 558 registry.put(ciID, arrayOfObj); 559 if (visible) { 560 registerSTV(source, target, variant); 561 availableIDs.add(ciID); 562 } else { 563 removeSTV(source, target, variant); 564 availableIDs.remove(ciID); 565 } 566 } 567 568 /** 569 * Register a source-target/variant in the specDAG. Variant may be 570 * empty, but source and target must not be. If variant is empty then 571 * the special variant NO_VARIANT is stored in slot zero of the 572 * UVector of variants. 573 */ registerSTV(String source, String target, String variant)574 private void registerSTV(String source, 575 String target, 576 String variant) { 577 // assert(source.length() > 0); 578 // assert(target.length() > 0); 579 CaseInsensitiveString cisrc = new CaseInsensitiveString(source); 580 CaseInsensitiveString citrg = new CaseInsensitiveString(target); 581 CaseInsensitiveString civar = new CaseInsensitiveString(variant); 582 Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc); 583 if (targets == null) { 584 targets = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, List<CaseInsensitiveString>>()); 585 specDAG.put(cisrc, targets); 586 } 587 List<CaseInsensitiveString> variants = targets.get(citrg); 588 if (variants == null) { 589 variants = new ArrayList<CaseInsensitiveString>(); 590 targets.put(citrg, variants); 591 } 592 // assert(NO_VARIANT == ""); 593 // We add the variant string. If it is the special "no variant" 594 // string, that is, the empty string, we add it at position zero. 595 if (!variants.contains(civar)) { 596 if (variant.length() > 0) { 597 variants.add(civar); 598 } else { 599 variants.add(0, civar); 600 } 601 } 602 } 603 604 /** 605 * Remove a source-target/variant from the specDAG. 606 */ removeSTV(String source, String target, String variant)607 private void removeSTV(String source, 608 String target, 609 String variant) { 610 // assert(source.length() > 0); 611 // assert(target.length() > 0); 612 CaseInsensitiveString cisrc = new CaseInsensitiveString(source); 613 CaseInsensitiveString citrg = new CaseInsensitiveString(target); 614 CaseInsensitiveString civar = new CaseInsensitiveString(variant); 615 Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc); 616 if (targets == null) { 617 return; // should never happen for valid s-t/v 618 } 619 List<CaseInsensitiveString> variants = targets.get(citrg); 620 if (variants == null) { 621 return; // should never happen for valid s-t/v 622 } 623 variants.remove(civar); 624 if (variants.size() == 0) { 625 targets.remove(citrg); // should delete variants 626 if (targets.size() == 0) { 627 specDAG.remove(cisrc); // should delete targets 628 } 629 } 630 } 631 632 private static final boolean DEBUG = false; 633 634 /** 635 * Attempt to find a source-target/variant in the dynamic registry 636 * store. Return 0 on failure. 637 */ findInDynamicStore(Spec src, Spec trg, String variant)638 private Object[] findInDynamicStore(Spec src, 639 Spec trg, 640 String variant) { 641 String ID = TransliteratorIDParser.STVtoID(src.get(), trg.get(), variant); 642 ///CLOVER:OFF 643 if (DEBUG) { 644 System.out.println("TransliteratorRegistry.findInDynamicStore:" + 645 ID); 646 } 647 ///CLOVER:ON 648 return registry.get(new CaseInsensitiveString(ID)); 649 } 650 651 /** 652 * Attempt to find a source-target/variant in the static locale 653 * resource store. Do not perform fallback. Return 0 on failure. 654 * 655 * On success, create a new entry object, register it in the dynamic 656 * store, and return a pointer to it, but do not make it public -- 657 * just because someone requested something, we do not expand the 658 * available ID list (or spec DAG). 659 */ findInStaticStore(Spec src, Spec trg, String variant)660 private Object[] findInStaticStore(Spec src, 661 Spec trg, 662 String variant) { 663 ///CLOVER:OFF 664 if (DEBUG) { 665 String ID = TransliteratorIDParser.STVtoID(src.get(), trg.get(), variant); 666 System.out.println("TransliteratorRegistry.findInStaticStore:" + 667 ID); 668 } 669 ///CLOVER:ON 670 Object[] entry = null; 671 if (src.isLocale()) { 672 entry = findInBundle(src, trg, variant, Transliterator.FORWARD); 673 } else if (trg.isLocale()) { 674 entry = findInBundle(trg, src, variant, Transliterator.REVERSE); 675 } 676 677 // If we found an entry, store it in the Hashtable for next 678 // time. 679 if (entry != null) { 680 registerEntry(src.getTop(), trg.getTop(), variant, entry, false); 681 } 682 683 return entry; 684 } 685 686 /** 687 * Attempt to find an entry in a single resource bundle. This is 688 * a one-sided lookup. findInStaticStore() performs up to two such 689 * lookups, one for the source, and one for the target. 690 * 691 * Do not perform fallback. Return 0 on failure. 692 * 693 * On success, create a new Entry object, populate it, and return it. 694 * The caller owns the returned object. 695 */ findInBundle(Spec specToOpen, Spec specToFind, String variant, int direction)696 private Object[] findInBundle(Spec specToOpen, 697 Spec specToFind, 698 String variant, 699 int direction) { 700 // assert(specToOpen.isLocale()); 701 ResourceBundle res = specToOpen.getBundle(); 702 703 if (res == null) { 704 // This means that the bundle's locale does not match 705 // the current level of iteration for the spec. 706 return null; 707 } 708 709 for (int pass=0; pass<2; ++pass) { 710 StringBuilder tag = new StringBuilder(); 711 // First try either TransliteratorTo_xxx or 712 // TransliterateFrom_xxx, then try the bidirectional 713 // Transliterate_xxx. This precedence order is arbitrary 714 // but must be consistent and documented. 715 if (pass == 0) { 716 tag.append(direction == Transliterator.FORWARD ? 717 "TransliterateTo" : "TransliterateFrom"); 718 } else { 719 tag.append("Transliterate"); 720 } 721 tag.append(specToFind.get().toUpperCase(Locale.ENGLISH)); 722 723 try { 724 // The Transliterate*_xxx resource is an array of 725 // strings of the format { <v0>, <r0>, ... }. Each 726 // <vi> is a variant name, and each <ri> is a rule. 727 String[] subres = res.getStringArray(tag.toString()); 728 729 // assert(subres != null); 730 // assert(subres.length % 2 == 0); 731 int i = 0; 732 if (variant.length() != 0) { 733 for (i=0; i<subres.length; i+= 2) { 734 if (subres[i].equalsIgnoreCase(variant)) { 735 break; 736 } 737 } 738 } 739 740 if (i < subres.length) { 741 // We have a match, or there is no variant and i == 0. 742 // We have succeeded in loading a string from the 743 // locale resources. Return the rule string which 744 // will itself become the registry entry. 745 746 // The direction is always forward for the 747 // TransliterateTo_xxx and TransliterateFrom_xxx 748 // items; those are unidirectional forward rules. 749 // For the bidirectional Transliterate_xxx items, 750 // the direction is the value passed in to this 751 // function. 752 int dir = (pass == 0) ? Transliterator.FORWARD : direction; 753 return new Object[] { new LocaleEntry(subres[i+1], dir) }; 754 } 755 756 } catch (MissingResourceException e) { 757 ///CLOVER:OFF 758 if (DEBUG) System.out.println("missing resource: " + e); 759 ///CLOVER:ON 760 } 761 } 762 763 // If we get here we had a missing resource exception or we 764 // failed to find a desired variant. 765 return null; 766 } 767 768 /** 769 * Convenience method. Calls 3-arg find(). 770 */ find(String ID)771 private Object[] find(String ID) { 772 String[] stv = TransliteratorIDParser.IDtoSTV(ID); 773 return find(stv[0], stv[1], stv[2]); 774 } 775 776 /** 777 * Top-level find method. Attempt to find a source-target/variant in 778 * either the dynamic or the static (locale resource) store. Perform 779 * fallback. 780 * 781 * Lookup sequence for ss_SS_SSS-tt_TT_TTT/v: 782 * 783 * ss_SS_SSS-tt_TT_TTT/v -- in hashtable 784 * ss_SS_SSS-tt_TT_TTT/v -- in ss_SS_SSS (no fallback) 785 * 786 * repeat with t = tt_TT_TTT, tt_TT, tt, and tscript 787 * 788 * ss_SS_SSS-t/* 789 * ss_SS-t/* 790 * ss-t/* 791 * sscript-t/* 792 * 793 * Here * matches the first variant listed. 794 * 795 * Caller does NOT own returned object. Return 0 on failure. 796 */ find(String source, String target, String variant)797 private Object[] find(String source, 798 String target, 799 String variant) { 800 801 Spec src = new Spec(source); 802 Spec trg = new Spec(target); 803 Object[] entry = null; 804 805 if (variant.length() != 0) { 806 807 // Seek exact match in hashtable 808 entry = findInDynamicStore(src, trg, variant); 809 if (entry != null) { 810 return entry; 811 } 812 813 // Seek exact match in locale resources 814 entry = findInStaticStore(src, trg, variant); 815 if (entry != null) { 816 return entry; 817 } 818 } 819 820 for (;;) { 821 src.reset(); 822 for (;;) { 823 // Seek match in hashtable 824 entry = findInDynamicStore(src, trg, NO_VARIANT); 825 if (entry != null) { 826 return entry; 827 } 828 829 // Seek match in locale resources 830 entry = findInStaticStore(src, trg, NO_VARIANT); 831 if (entry != null) { 832 return entry; 833 } 834 if (!src.hasFallback()) { 835 break; 836 } 837 src.next(); 838 } 839 if (!trg.hasFallback()) { 840 break; 841 } 842 trg.next(); 843 } 844 845 return null; 846 } 847 848 /** 849 * Given an Entry object, instantiate it. Caller owns result. Return 850 * 0 on failure. 851 * 852 * Return a non-empty aliasReturn value if the ID points to an alias. 853 * We cannot instantiate it ourselves because the alias may contain 854 * filters or compounds, which we do not understand. Caller should 855 * make aliasReturn empty before calling. 856 * 857 * The entry object is assumed to reside in the dynamic store. It may be 858 * modified. 859 */ 860 @SuppressWarnings("rawtypes") instantiateEntry(String ID, Object[] entryWrapper, StringBuffer aliasReturn)861 private Transliterator instantiateEntry(String ID, 862 Object[] entryWrapper, 863 StringBuffer aliasReturn) { 864 // We actually modify the entry object in some cases. If it 865 // is a string, we may partially parse it and turn it into a 866 // more processed precursor. This makes the next 867 // instantiation faster and allows sharing of immutable 868 // components like the RuleBasedTransliterator.Data objects. 869 // For this reason, the entry object is an Object[] of length 870 // 1. 871 872 for (;;) { 873 Object entry = entryWrapper[0]; 874 875 if (entry instanceof RuleBasedTransliterator.Data) { 876 RuleBasedTransliterator.Data data = (RuleBasedTransliterator.Data) entry; 877 return new RuleBasedTransliterator(ID, data, null); 878 } else if (entry instanceof Class) { 879 try { 880 return (Transliterator) ((Class) entry).newInstance(); 881 } catch (InstantiationException e) { 882 } catch (IllegalAccessException e2) {} 883 return null; 884 } else if (entry instanceof AliasEntry) { 885 aliasReturn.append(((AliasEntry) entry).alias); 886 return null; 887 } else if (entry instanceof Transliterator.Factory) { 888 return ((Transliterator.Factory) entry).getInstance(ID); 889 } else if (entry instanceof CompoundRBTEntry) { 890 return ((CompoundRBTEntry) entry).getInstance(); 891 } else if (entry instanceof AnyTransliterator) { 892 AnyTransliterator temp = (AnyTransliterator) entry; 893 return temp.safeClone(); 894 } else if (entry instanceof RuleBasedTransliterator) { 895 RuleBasedTransliterator temp = (RuleBasedTransliterator) entry; 896 return temp.safeClone(); 897 } else if (entry instanceof CompoundTransliterator) { 898 CompoundTransliterator temp = (CompoundTransliterator) entry; 899 return temp.safeClone(); 900 } else if (entry instanceof Transliterator) { 901 return (Transliterator) entry; 902 } 903 904 // At this point entry type must be either RULES_FORWARD or 905 // RULES_REVERSE. We process the rule data into a 906 // TransliteratorRuleData object, and possibly also into an 907 // .id header and/or footer. Then we modify the registry with 908 // the parsed data and retry. 909 910 TransliteratorParser parser = new TransliteratorParser(); 911 912 try { 913 914 ResourceEntry re = (ResourceEntry) entry; 915 // Android patch: Lazily load transliterator rules. 916 // parser.parse(re.resource, re.direction); 917 parser.parse(re.getResource(), re.direction); 918 919 } catch (ClassCastException e) { 920 // If we pull a rule from a locale resource bundle it will 921 // be a LocaleEntry. 922 LocaleEntry le = (LocaleEntry) entry; 923 parser.parse(le.rule, le.direction); 924 } 925 926 // Reset entry to something that we process at the 927 // top of the loop, then loop back to the top. As long as we 928 // do this, we only loop through twice at most. 929 // NOTE: The logic here matches that in 930 // Transliterator.createFromRules(). 931 if (parser.idBlockVector.size() == 0 && parser.dataVector.size() == 0) { 932 // No idBlock, no data -- this is just an 933 // alias for Null 934 entryWrapper[0] = new AliasEntry(NullTransliterator._ID); 935 } 936 else if (parser.idBlockVector.size() == 0 && parser.dataVector.size() == 1) { 937 // No idBlock, data != 0 -- this is an 938 // ordinary RBT_DATA 939 entryWrapper[0] = parser.dataVector.get(0); 940 } 941 else if (parser.idBlockVector.size() == 1 && parser.dataVector.size() == 0) { 942 // idBlock, no data -- this is an alias. The ID has 943 // been munged from reverse into forward mode, if 944 // necessary, so instantiate the ID in the forward 945 // direction. 946 if (parser.compoundFilter != null) { 947 entryWrapper[0] = new AliasEntry(parser.compoundFilter.toPattern(false) + ";" 948 + parser.idBlockVector.get(0)); 949 } else { 950 entryWrapper[0] = new AliasEntry(parser.idBlockVector.get(0)); 951 } 952 } 953 else { 954 entryWrapper[0] = new CompoundRBTEntry(ID, parser.idBlockVector, parser.dataVector, 955 parser.compoundFilter); 956 } 957 } 958 } 959 } 960 961 //eof 962