Home | History | Annotate | Download | only in text
      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