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