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 28 import android.icu.impl.ICUData; 29 import android.icu.impl.ICUResourceBundle; 30 import android.icu.impl.LocaleUtility; 31 import android.icu.impl.Utility; 32 import android.icu.lang.UScript; 33 import android.icu.text.RuleBasedTransliterator.Data; 34 import android.icu.util.CaseInsensitiveString; 35 import android.icu.util.UResourceBundle; 36 37 class TransliteratorRegistry { 38 39 // char constants 40 private static final char LOCALE_SEP = '_'; 41 42 // String constants 43 private static final String NO_VARIANT = ""; // empty string 44 private static final String ANY = "Any"; 45 46 /** 47 * Dynamic registry mapping full IDs to Entry objects. This 48 * contains both public and internal entities. The visibility is 49 * controlled by whether an entry is listed in availableIDs and 50 * specDAG or not. 51 * 52 * Keys are CaseInsensitiveString objects. 53 * Values are objects of class Class (subclass of Transliterator), 54 * RuleBasedTransliterator.Data, Transliterator.Factory, or one 55 * of the entry classes defined here (AliasEntry or ResourceEntry). 56 */ 57 private Map<CaseInsensitiveString, Object[]> registry; 58 59 /** 60 * DAG of visible IDs by spec. Hashtable: source => (Hashtable: 61 * target => (Vector: variant)) The Vector of variants is never 62 * empty. For a source-target with no variant, the special 63 * variant NO_VARIANT (the empty string) is stored in slot zero of 64 * the UVector. 65 * 66 * Keys are CaseInsensitiveString objects. 67 * Values are Hashtable of (CaseInsensitiveString -> Vector of 68 * CaseInsensitiveString) 69 */ 70 private Map<CaseInsensitiveString, Map<CaseInsensitiveString, List<CaseInsensitiveString>>> specDAG; 71 72 /** 73 * Vector of public full IDs (CaseInsensitiveString objects). 74 */ 75 private final Set<CaseInsensitiveString> availableIDs; 76 77 //---------------------------------------------------------------------- 78 // class Spec 79 //---------------------------------------------------------------------- 80 81 /** 82 * A Spec is a string specifying either a source or a target. In more 83 * general terms, it may also specify a variant, but we only use the 84 * Spec class for sources and targets. 85 * 86 * A Spec may be a locale or a script. If it is a locale, it has a 87 * fallback chain that goes xx_YY_ZZZ -> xx_YY -> xx -> ssss, where 88 * ssss is the script mapping of xx_YY_ZZZ. The Spec API methods 89 * hasFallback(), next(), and reset() iterate over this fallback 90 * sequence. 91 * 92 * The Spec class canonicalizes itself, so the locale is put into 93 * canonical form, or the script is transformed from an abbreviation 94 * to a full name. 95 */ 96 static class Spec { 97 98 private String top; // top spec 99 private String spec; // current spec 100 private String nextSpec; // next spec 101 private String scriptName; // script name equivalent of top, if != top 102 private boolean isSpecLocale; // true if spec is a locale 103 private boolean isNextLocale; // true if nextSpec is a locale 104 private ICUResourceBundle res; 105 Spec(String theSpec)106 public Spec(String theSpec) { 107 top = theSpec; 108 spec = null; 109 scriptName = null; 110 try{ 111 // Canonicalize script name. If top is a script name then 112 // script != UScript.INVALID_CODE. 113 int script = UScript.getCodeFromName(top); 114 115 // Canonicalize script name -or- do locale->script mapping 116 int[] s = UScript.getCode(top); 117 if (s != null) { 118 scriptName = UScript.getName(s[0]); 119 // If the script name is the same as top then it's redundant 120 if (scriptName.equalsIgnoreCase(top)) { 121 scriptName = null; 122 } 123 } 124 125 isSpecLocale = false; 126 res = null; 127 // If 'top' is not a script name, try a locale lookup 128 if (script == UScript.INVALID_CODE) { 129 Locale toploc = LocaleUtility.getLocaleFromName(top); 130 res = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_TRANSLIT_BASE_NAME,toploc); 131 // Make sure we got the bundle we wanted; otherwise, don't use it 132 if (res!=null && LocaleUtility.isFallbackOf(res.getULocale().toString(), top)) { 133 isSpecLocale = true; 134 } 135 } 136 }catch(MissingResourceException e){ 137 ///CLOVER:OFF 138 // The constructor is called from multiple private methods 139 // that protects an invalid scriptName 140 scriptName = null; 141 ///CLOVER:ON 142 } 143 // assert(spec != top); 144 reset(); 145 } 146 hasFallback()147 public boolean hasFallback() { 148 return nextSpec != null; 149 } 150 reset()151 public void reset() { 152 if (!Utility.sameObjects(spec, top)) { 153 spec = top; 154 isSpecLocale = (res != null); 155 setupNext(); 156 } 157 } 158 setupNext()159 private void setupNext() { 160 isNextLocale = false; 161 if (isSpecLocale) { 162 nextSpec = spec; 163 int i = nextSpec.lastIndexOf(LOCALE_SEP); 164 // If i == 0 then we have _FOO, so we fall through 165 // to the scriptName. 166 if (i > 0) { 167 nextSpec = spec.substring(0, i); 168 isNextLocale = true; 169 } else { 170 nextSpec = scriptName; // scriptName may be null 171 } 172 } else { 173 // Fallback to the script, which may be null 174 if (!Utility.sameObjects(nextSpec, scriptName)) { 175 nextSpec = scriptName; 176 } else { 177 nextSpec = null; 178 } 179 } 180 } 181 182 // Protocol: 183 // for(String& s(spec.get()); 184 // spec.hasFallback(); s(spec.next())) { ... 185 next()186 public String next() { 187 spec = nextSpec; 188 isSpecLocale = isNextLocale; 189 setupNext(); 190 return spec; 191 } 192 get()193 public String get() { 194 return spec; 195 } 196 isLocale()197 public boolean isLocale() { 198 return isSpecLocale; 199 } 200 201 /** 202 * Return the ResourceBundle for this spec, at the current 203 * level of iteration. The level of iteration goes from 204 * aa_BB_CCC to aa_BB to aa. If the bundle does not 205 * correspond to the current level of iteration, return null. 206 * If isLocale() is false, always return null. 207 */ getBundle()208 public ResourceBundle getBundle() { 209 if (res != null && 210 res.getULocale().toString().equals(spec)) { 211 return res; 212 } 213 return null; 214 } 215 getTop()216 public String getTop() { 217 return top; 218 } 219 } 220 221 //---------------------------------------------------------------------- 222 // Entry classes 223 //---------------------------------------------------------------------- 224 225 static class ResourceEntry { 226 public String resource; 227 public int direction; ResourceEntry(String n, int d)228 public ResourceEntry(String n, int d) { 229 resource = n; 230 direction = d; 231 } 232 } 233 234 // An entry representing a rule in a locale resource bundle 235 static class LocaleEntry { 236 public String rule; 237 public int direction; LocaleEntry(String r, int d)238 public LocaleEntry(String r, int d) { 239 rule = r; 240 direction = d; 241 } 242 } 243 244 static class AliasEntry { 245 public String alias; AliasEntry(String a)246 public AliasEntry(String a) { 247 alias = a; 248 } 249 } 250 251 static class CompoundRBTEntry { 252 private String ID; 253 private List<String> idBlockVector; 254 private List<Data> dataVector; 255 private UnicodeSet compoundFilter; 256 CompoundRBTEntry(String theID, List<String> theIDBlockVector, List<Data> theDataVector, UnicodeSet theCompoundFilter)257 public CompoundRBTEntry(String theID, List<String> theIDBlockVector, 258 List<Data> theDataVector, 259 UnicodeSet theCompoundFilter) { 260 ID = theID; 261 idBlockVector = theIDBlockVector; 262 dataVector = theDataVector; 263 compoundFilter = theCompoundFilter; 264 } 265 getInstance()266 public Transliterator getInstance() { 267 List<Transliterator> transliterators = new ArrayList<Transliterator>(); 268 int passNumber = 1; 269 270 int limit = Math.max(idBlockVector.size(), dataVector.size()); 271 for (int i = 0; i < limit; i++) { 272 if (i < idBlockVector.size()) { 273 String idBlock = idBlockVector.get(i); 274 if (idBlock.length() > 0) 275 transliterators.add(Transliterator.getInstance(idBlock)); 276 } 277 if (i < dataVector.size()) { 278 Data data = dataVector.get(i); 279 transliterators.add(new RuleBasedTransliterator("%Pass" + passNumber++, data, null)); 280 } 281 } 282 283 Transliterator t = new CompoundTransliterator(transliterators, passNumber - 1); 284 t.setID(ID); 285 if (compoundFilter != null) { 286 t.setFilter(compoundFilter); 287 } 288 return t; 289 } 290 } 291 292 //---------------------------------------------------------------------- 293 // class TransliteratorRegistry: Basic public API 294 //---------------------------------------------------------------------- 295 TransliteratorRegistry()296 public TransliteratorRegistry() { 297 registry = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, Object[]>()); 298 specDAG = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, Map<CaseInsensitiveString, List<CaseInsensitiveString>>>()); 299 availableIDs = new LinkedHashSet<>(); 300 } 301 302 /** 303 * Given a simple ID (forward direction, no inline filter, not 304 * compound) attempt to instantiate it from the registry. Return 305 * 0 on failure. 306 * 307 * Return a non-empty aliasReturn value if the ID points to an alias. 308 * We cannot instantiate it ourselves because the alias may contain 309 * filters or compounds, which we do not understand. Caller should 310 * make aliasReturn empty before calling. 311 */ get(String ID, StringBuffer aliasReturn)312 public Transliterator get(String ID, 313 StringBuffer aliasReturn) { 314 Object[] entry = find(ID); 315 return (entry == null) ? null 316 : instantiateEntry(ID, entry, aliasReturn); 317 } 318 319 /** 320 * Register a class. This adds an entry to the 321 * dynamic store, or replaces an existing entry. Any entry in the 322 * underlying static locale resource store is masked. 323 */ put(String ID, Class<? extends Transliterator> transliteratorSubclass, boolean visible)324 public void put(String ID, 325 Class<? extends Transliterator> transliteratorSubclass, 326 boolean visible) { 327 registerEntry(ID, transliteratorSubclass, visible); 328 } 329 330 /** 331 * Register an ID and a factory function pointer. This adds an 332 * entry to the dynamic store, or replaces an existing entry. Any 333 * entry in the underlying static locale resource store is masked. 334 */ put(String ID, Transliterator.Factory factory, boolean visible)335 public void put(String ID, 336 Transliterator.Factory factory, 337 boolean visible) { 338 registerEntry(ID, factory, visible); 339 } 340 341 /** 342 * Register an ID and a resource name. This adds an entry to the 343 * dynamic store, or replaces an existing entry. Any entry in the 344 * underlying static locale resource store is masked. 345 */ put(String ID, String resourceName, int dir, boolean visible)346 public void put(String ID, 347 String resourceName, 348 int dir, 349 boolean visible) { 350 registerEntry(ID, new ResourceEntry(resourceName, dir), visible); 351 } 352 353 /** 354 * Register an ID and an alias ID. This adds an entry to the 355 * dynamic store, or replaces an existing entry. Any entry in the 356 * underlying static locale resource store is masked. 357 */ put(String ID, String alias, boolean visible)358 public void put(String ID, 359 String alias, 360 boolean visible) { 361 registerEntry(ID, new AliasEntry(alias), visible); 362 } 363 364 /** 365 * Register an ID and a Transliterator object. This adds an entry 366 * to the dynamic store, or replaces an existing entry. Any entry 367 * in the underlying static locale resource store is masked. 368 */ put(String ID, Transliterator trans, boolean visible)369 public void put(String ID, 370 Transliterator trans, 371 boolean visible) { 372 registerEntry(ID, trans, visible); 373 } 374 375 /** 376 * Unregister an ID. This removes an entry from the dynamic store 377 * if there is one. The static locale resource store is 378 * unaffected. 379 */ remove(String ID)380 public void remove(String ID) { 381 String[] stv = TransliteratorIDParser.IDtoSTV(ID); 382 // Only need to do this if ID.indexOf('-') < 0 383 String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]); 384 registry.remove(new CaseInsensitiveString(id)); 385 removeSTV(stv[0], stv[1], stv[2]); 386 availableIDs.remove(new CaseInsensitiveString(id)); 387 } 388 389 //---------------------------------------------------------------------- 390 // class TransliteratorRegistry: Public ID and spec management 391 //---------------------------------------------------------------------- 392 393 /** 394 * An internal class that adapts an enumeration over 395 * CaseInsensitiveStrings to an enumeration over Strings. 396 */ 397 private static class IDEnumeration implements Enumeration<String> { 398 Enumeration<CaseInsensitiveString> en; 399 IDEnumeration(Enumeration<CaseInsensitiveString> e)400 public IDEnumeration(Enumeration<CaseInsensitiveString> e) { 401 en = e; 402 } 403 404 @Override hasMoreElements()405 public boolean hasMoreElements() { 406 return en != null && en.hasMoreElements(); 407 } 408 409 @Override nextElement()410 public String nextElement() { 411 return (en.nextElement()).getString(); 412 } 413 } 414 415 /** 416 * Returns an enumeration over the programmatic names of visible 417 * registered transliterators. 418 * 419 * @return An <code>Enumeration</code> over <code>String</code> objects 420 */ getAvailableIDs()421 public Enumeration<String> getAvailableIDs() { 422 // Since the cache contains CaseInsensitiveString objects, but 423 // the caller expects Strings, we have to use an intermediary. 424 return new IDEnumeration(Collections.enumeration(availableIDs)); 425 } 426 427 /** 428 * Returns an enumeration over all visible source names. 429 * 430 * @return An <code>Enumeration</code> over <code>String</code> objects 431 */ getAvailableSources()432 public Enumeration<String> getAvailableSources() { 433 return new IDEnumeration(Collections.enumeration(specDAG.keySet())); 434 } 435 436 /** 437 * Returns an enumeration over visible target names for the given 438 * source. 439 * 440 * @return An <code>Enumeration</code> over <code>String</code> objects 441 */ getAvailableTargets(String source)442 public Enumeration<String> getAvailableTargets(String source) { 443 CaseInsensitiveString cisrc = new CaseInsensitiveString(source); 444 Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc); 445 if (targets == null) { 446 return new IDEnumeration(null); 447 } 448 return new IDEnumeration(Collections.enumeration(targets.keySet())); 449 } 450 451 /** 452 * Returns an enumeration over visible variant names for the given 453 * source and target. 454 * 455 * @return An <code>Enumeration</code> over <code>String</code> objects 456 */ getAvailableVariants(String source, String target)457 public Enumeration<String> getAvailableVariants(String source, String target) { 458 CaseInsensitiveString cisrc = new CaseInsensitiveString(source); 459 CaseInsensitiveString citrg = new CaseInsensitiveString(target); 460 Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc); 461 if (targets == null) { 462 return new IDEnumeration(null); 463 } 464 List<CaseInsensitiveString> variants = targets.get(citrg); 465 if (variants == null) { 466 return new IDEnumeration(null); 467 } 468 return new IDEnumeration(Collections.enumeration(variants)); 469 } 470 471 //---------------------------------------------------------------------- 472 // class TransliteratorRegistry: internal 473 //---------------------------------------------------------------------- 474 475 /** 476 * Convenience method. Calls 6-arg registerEntry(). 477 */ registerEntry(String source, String target, String variant, Object entry, boolean visible)478 private void registerEntry(String source, 479 String target, 480 String variant, 481 Object entry, 482 boolean visible) { 483 String s = source; 484 if (s.length() == 0) { 485 s = ANY; 486 } 487 String ID = TransliteratorIDParser.STVtoID(source, target, variant); 488 registerEntry(ID, s, target, variant, entry, visible); 489 } 490 491 /** 492 * Convenience method. Calls 6-arg registerEntry(). 493 */ registerEntry(String ID, Object entry, boolean visible)494 private void registerEntry(String ID, 495 Object entry, 496 boolean visible) { 497 String[] stv = TransliteratorIDParser.IDtoSTV(ID); 498 // Only need to do this if ID.indexOf('-') < 0 499 String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]); 500 registerEntry(id, stv[0], stv[1], stv[2], entry, visible); 501 } 502 503 /** 504 * Register an entry object (adopted) with the given ID, source, 505 * target, and variant strings. 506 */ registerEntry(String ID, String source, String target, String variant, Object entry, boolean visible)507 private void registerEntry(String ID, 508 String source, 509 String target, 510 String variant, 511 Object entry, 512 boolean visible) { 513 CaseInsensitiveString ciID = new CaseInsensitiveString(ID); 514 Object[] arrayOfObj; 515 516 // Store the entry within an array so it can be modified later 517 if (entry instanceof Object[]) { 518 arrayOfObj = (Object[])entry; 519 } else { 520 arrayOfObj = new Object[] { entry }; 521 } 522 523 registry.put(ciID, arrayOfObj); 524 if (visible) { 525 registerSTV(source, target, variant); 526 availableIDs.add(ciID); 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