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