1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 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 Spec(String theSpec)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 hasFallback()144 public boolean hasFallback() { 145 return nextSpec != null; 146 } 147 reset()148 public void reset() { 149 if (!Utility.sameObjects(spec, top)) { 150 spec = top; 151 isSpecLocale = (res != null); 152 setupNext(); 153 } 154 } 155 setupNext()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 next()183 public String next() { 184 spec = nextSpec; 185 isSpecLocale = isNextLocale; 186 setupNext(); 187 return spec; 188 } 189 get()190 public String get() { 191 return spec; 192 } 193 isLocale()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 */ getBundle()205 public ResourceBundle getBundle() { 206 if (res != null && 207 res.getULocale().toString().equals(spec)) { 208 return res; 209 } 210 return null; 211 } 212 getTop()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; ResourceEntry(String n, int d)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; LocaleEntry(String r, int d)235 public LocaleEntry(String r, int d) { 236 rule = r; 237 direction = d; 238 } 239 } 240 241 static class AliasEntry { 242 public String alias; AliasEntry(String a)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 CompoundRBTEntry(String theID, List<String> theIDBlockVector, List<Data> theDataVector, UnicodeSet theCompoundFilter)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 getInstance()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 TransliteratorRegistry()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 */ get(String ID, StringBuffer aliasReturn)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 */ put(String ID, Class<? extends Transliterator> transliteratorSubclass, boolean visible)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 */ put(String ID, Transliterator.Factory factory, boolean visible)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 */ put(String ID, String resourceName, int dir, boolean visible)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 */ put(String ID, String alias, boolean visible)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 */ put(String ID, Transliterator trans, boolean visible)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 */ remove(String ID)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 IDEnumeration(Enumeration<CaseInsensitiveString> e)397 public IDEnumeration(Enumeration<CaseInsensitiveString> e) { 398 en = e; 399 } 400 401 @Override hasMoreElements()402 public boolean hasMoreElements() { 403 return en != null && en.hasMoreElements(); 404 } 405 406 @Override nextElement()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 */ getAvailableIDs()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 */ getAvailableSources()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 */ getAvailableTargets(String source)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 */ getAvailableVariants(String source, String target)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 */ registerEntry(String source, String target, String variant, Object entry, boolean visible)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 */ registerEntry(String ID, Object entry, boolean visible)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 */ registerEntry(String ID, String source, String target, String variant, Object entry, boolean visible)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 */ registerSTV(String source, String target, String variant)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 */ removeSTV(String source, String target, String variant)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 */ findInDynamicStore(Spec src, Spec trg, String variant)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 */ findInStaticStore(Spec src, Spec trg, String variant)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 */ findInBundle(Spec specToOpen, Spec specToFind, String variant, int direction)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 */ find(String ID)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 */ find(String source, String target, String variant)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") instantiateEntry(String ID, Object[] entryWrapper, StringBuffer aliasReturn)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