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