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