• 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.List;
21 import java.util.Locale;
22 import java.util.Map;
23 import java.util.MissingResourceException;
24 import java.util.ResourceBundle;
25 
26 import android.icu.impl.ICUData;
27 import android.icu.impl.ICUResourceBundle;
28 import android.icu.impl.LocaleUtility;
29 import android.icu.impl.Utility;
30 import android.icu.lang.UScript;
31 import android.icu.text.RuleBasedTransliterator.Data;
32 import android.icu.util.CaseInsensitiveString;
33 import android.icu.util.UResourceBundle;
34 
35 class TransliteratorRegistry {
36 
37     // char constants
38     private static final char LOCALE_SEP  = '_';
39 
40     // String constants
41     private static final String NO_VARIANT = ""; // empty string
42     private static final String ANY = "Any";
43 
44     /**
45      * Dynamic registry mapping full IDs to Entry objects.  This
46      * contains both public and internal entities.  The visibility is
47      * controlled by whether an entry is listed in availableIDs and
48      * specDAG or not.
49      *
50      * Keys are CaseInsensitiveString objects.
51      * Values are objects of class Class (subclass of Transliterator),
52      * RuleBasedTransliterator.Data, Transliterator.Factory, or one
53      * of the entry classes defined here (AliasEntry or ResourceEntry).
54      */
55     private Map<CaseInsensitiveString, Object[]> registry;
56 
57     /**
58      * DAG of visible IDs by spec.  Hashtable: source => (Hashtable:
59      * target => (Vector: variant)) The Vector of variants is never
60      * empty.  For a source-target with no variant, the special
61      * variant NO_VARIANT (the empty string) is stored in slot zero of
62      * the UVector.
63      *
64      * Keys are CaseInsensitiveString objects.
65      * Values are Hashtable of (CaseInsensitiveString -> Vector of
66      * CaseInsensitiveString)
67      */
68     private Map<CaseInsensitiveString, Map<CaseInsensitiveString, List<CaseInsensitiveString>>> specDAG;
69 
70     /**
71      * Vector of public full IDs (CaseInsensitiveString objects).
72      */
73     private List<CaseInsensitiveString> availableIDs;
74 
75     //----------------------------------------------------------------------
76     // class Spec
77     //----------------------------------------------------------------------
78 
79     /**
80      * A Spec is a string specifying either a source or a target.  In more
81      * general terms, it may also specify a variant, but we only use the
82      * Spec class for sources and targets.
83      *
84      * A Spec may be a locale or a script.  If it is a locale, it has a
85      * fallback chain that goes xx_YY_ZZZ -> xx_YY -> xx -> ssss, where
86      * ssss is the script mapping of xx_YY_ZZZ.  The Spec API methods
87      * hasFallback(), next(), and reset() iterate over this fallback
88      * sequence.
89      *
90      * The Spec class canonicalizes itself, so the locale is put into
91      * canonical form, or the script is transformed from an abbreviation
92      * to a full name.
93      */
94     static class Spec {
95 
96         private String top;        // top spec
97         private String spec;       // current spec
98         private String nextSpec;   // next spec
99         private String scriptName; // script name equivalent of top, if != top
100         private boolean isSpecLocale; // TRUE if spec is a locale
101         private boolean isNextLocale; // TRUE if nextSpec is a locale
102         private ICUResourceBundle res;
103 
Spec(String theSpec)104         public Spec(String theSpec) {
105             top = theSpec;
106             spec = null;
107             scriptName = null;
108             try{
109                 // Canonicalize script name.  If top is a script name then
110                 // script != UScript.INVALID_CODE.
111                 int script = UScript.getCodeFromName(top);
112 
113                 // Canonicalize script name -or- do locale->script mapping
114                 int[] s = UScript.getCode(top);
115                 if (s != null) {
116                     scriptName = UScript.getName(s[0]);
117                     // If the script name is the same as top then it's redundant
118                     if (scriptName.equalsIgnoreCase(top)) {
119                         scriptName = null;
120                     }
121                 }
122 
123                 isSpecLocale = false;
124                 res = null;
125                 // If 'top' is not a script name, try a locale lookup
126                 if (script == UScript.INVALID_CODE) {
127                     Locale toploc = LocaleUtility.getLocaleFromName(top);
128                     res  = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_TRANSLIT_BASE_NAME,toploc);
129                     // Make sure we got the bundle we wanted; otherwise, don't use it
130                     if (res!=null && LocaleUtility.isFallbackOf(res.getULocale().toString(), top)) {
131                         isSpecLocale = true;
132                     }
133                 }
134             }catch(MissingResourceException e){
135                 ///CLOVER:OFF
136                 // The constructor is called from multiple private methods
137                 //  that protects an invalid scriptName
138                 scriptName = null;
139                 ///CLOVER:ON
140             }
141             // assert(spec != top);
142             reset();
143         }
144 
hasFallback()145         public boolean hasFallback() {
146             return nextSpec != null;
147         }
148 
reset()149         public void reset() {
150             if (!Utility.sameObjects(spec, top)) {
151                 spec = top;
152                 isSpecLocale = (res != null);
153                 setupNext();
154             }
155         }
156 
setupNext()157         private void setupNext() {
158             isNextLocale = false;
159             if (isSpecLocale) {
160                 nextSpec = spec;
161                 int i = nextSpec.lastIndexOf(LOCALE_SEP);
162                 // If i == 0 then we have _FOO, so we fall through
163                 // to the scriptName.
164                 if (i > 0) {
165                     nextSpec = spec.substring(0, i);
166                     isNextLocale = true;
167                 } else {
168                     nextSpec = scriptName; // scriptName may be null
169                 }
170             } else {
171                 // Fallback to the script, which may be null
172                 if (!Utility.sameObjects(nextSpec, scriptName)) {
173                     nextSpec = scriptName;
174                 } else {
175                     nextSpec = null;
176                 }
177             }
178         }
179 
180         // Protocol:
181         // for(String& s(spec.get());
182         //     spec.hasFallback(); s(spec.next())) { ...
183 
next()184         public String next() {
185             spec = nextSpec;
186             isSpecLocale = isNextLocale;
187             setupNext();
188             return spec;
189         }
190 
get()191         public String get() {
192             return spec;
193         }
194 
isLocale()195         public boolean isLocale() {
196             return isSpecLocale;
197         }
198 
199         /**
200          * Return the ResourceBundle for this spec, at the current
201          * level of iteration.  The level of iteration goes from
202          * aa_BB_CCC to aa_BB to aa.  If the bundle does not
203          * correspond to the current level of iteration, return null.
204          * If isLocale() is false, always return null.
205          */
getBundle()206         public ResourceBundle getBundle() {
207             if (res != null &&
208                 res.getULocale().toString().equals(spec)) {
209                 return res;
210             }
211             return null;
212         }
213 
getTop()214         public String getTop() {
215             return top;
216         }
217     }
218 
219     //----------------------------------------------------------------------
220     // Entry classes
221     //----------------------------------------------------------------------
222 
223     static class ResourceEntry {
224         public String resource;
225         public int direction;
ResourceEntry(String n, int d)226         public ResourceEntry(String n, int d) {
227             resource = n;
228             direction = d;
229         }
230     }
231 
232     // An entry representing a rule in a locale resource bundle
233     static class LocaleEntry {
234         public String rule;
235         public int direction;
LocaleEntry(String r, int d)236         public LocaleEntry(String r, int d) {
237             rule = r;
238             direction = d;
239         }
240     }
241 
242     static class AliasEntry {
243         public String alias;
AliasEntry(String a)244         public AliasEntry(String a) {
245             alias = a;
246         }
247     }
248 
249     static class CompoundRBTEntry {
250         private String ID;
251         private List<String> idBlockVector;
252         private List<Data> dataVector;
253         private UnicodeSet compoundFilter;
254 
CompoundRBTEntry(String theID, List<String> theIDBlockVector, List<Data> theDataVector, UnicodeSet theCompoundFilter)255         public CompoundRBTEntry(String theID, List<String> theIDBlockVector,
256                                 List<Data> theDataVector,
257                                 UnicodeSet theCompoundFilter) {
258             ID = theID;
259             idBlockVector = theIDBlockVector;
260             dataVector = theDataVector;
261             compoundFilter = theCompoundFilter;
262         }
263 
getInstance()264         public Transliterator getInstance() {
265             List<Transliterator> transliterators = new ArrayList<Transliterator>();
266             int passNumber = 1;
267 
268             int limit = Math.max(idBlockVector.size(), dataVector.size());
269             for (int i = 0; i < limit; i++) {
270                 if (i < idBlockVector.size()) {
271                     String idBlock = idBlockVector.get(i);
272                     if (idBlock.length() > 0)
273                         transliterators.add(Transliterator.getInstance(idBlock));
274                 }
275                 if (i < dataVector.size()) {
276                     Data data = dataVector.get(i);
277                     transliterators.add(new RuleBasedTransliterator("%Pass" + passNumber++, data, null));
278                 }
279             }
280 
281             Transliterator t = new CompoundTransliterator(transliterators, passNumber - 1);
282             t.setID(ID);
283             if (compoundFilter != null) {
284                 t.setFilter(compoundFilter);
285             }
286             return t;
287         }
288     }
289 
290     //----------------------------------------------------------------------
291     // class TransliteratorRegistry: Basic public API
292     //----------------------------------------------------------------------
293 
TransliteratorRegistry()294     public TransliteratorRegistry() {
295         registry = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, Object[]>());
296         specDAG = Collections.synchronizedMap(new HashMap<CaseInsensitiveString, Map<CaseInsensitiveString, List<CaseInsensitiveString>>>());
297         availableIDs = new ArrayList<CaseInsensitiveString>();
298     }
299 
300     /**
301      * Given a simple ID (forward direction, no inline filter, not
302      * compound) attempt to instantiate it from the registry.  Return
303      * 0 on failure.
304      *
305      * Return a non-empty aliasReturn value if the ID points to an alias.
306      * We cannot instantiate it ourselves because the alias may contain
307      * filters or compounds, which we do not understand.  Caller should
308      * make aliasReturn empty before calling.
309      */
get(String ID, StringBuffer aliasReturn)310     public Transliterator get(String ID,
311                               StringBuffer aliasReturn) {
312         Object[] entry = find(ID);
313         return (entry == null) ? null
314             : instantiateEntry(ID, entry, aliasReturn);
315     }
316 
317     /**
318      * Register a class.  This adds an entry to the
319      * dynamic store, or replaces an existing entry.  Any entry in the
320      * underlying static locale resource store is masked.
321      */
put(String ID, Class<? extends Transliterator> transliteratorSubclass, boolean visible)322     public void put(String ID,
323                     Class<? extends Transliterator> transliteratorSubclass,
324                     boolean visible) {
325         registerEntry(ID, transliteratorSubclass, visible);
326     }
327 
328     /**
329      * Register an ID and a factory function pointer.  This adds an
330      * entry to the dynamic store, or replaces an existing entry.  Any
331      * entry in the underlying static locale resource store is masked.
332      */
put(String ID, Transliterator.Factory factory, boolean visible)333     public void put(String ID,
334                     Transliterator.Factory factory,
335                     boolean visible) {
336         registerEntry(ID, factory, visible);
337     }
338 
339     /**
340      * Register an ID and a resource name.  This adds an entry to the
341      * dynamic store, or replaces an existing entry.  Any entry in the
342      * underlying static locale resource store is masked.
343      */
put(String ID, String resourceName, int dir, boolean visible)344     public void put(String ID,
345                     String resourceName,
346                     int dir,
347                     boolean visible) {
348         registerEntry(ID, new ResourceEntry(resourceName, dir), visible);
349     }
350 
351     /**
352      * Register an ID and an alias ID.  This adds an entry to the
353      * dynamic store, or replaces an existing entry.  Any entry in the
354      * underlying static locale resource store is masked.
355      */
put(String ID, String alias, boolean visible)356     public void put(String ID,
357                     String alias,
358                     boolean visible) {
359         registerEntry(ID, new AliasEntry(alias), visible);
360     }
361 
362     /**
363      * Register an ID and a Transliterator object.  This adds an entry
364      * to the dynamic store, or replaces an existing entry.  Any entry
365      * in the underlying static locale resource store is masked.
366      */
put(String ID, Transliterator trans, boolean visible)367     public void put(String ID,
368                     Transliterator trans,
369                     boolean visible) {
370         registerEntry(ID, trans, visible);
371     }
372 
373     /**
374      * Unregister an ID.  This removes an entry from the dynamic store
375      * if there is one.  The static locale resource store is
376      * unaffected.
377      */
remove(String ID)378     public void remove(String ID) {
379         String[] stv = TransliteratorIDParser.IDtoSTV(ID);
380         // Only need to do this if ID.indexOf('-') < 0
381         String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]);
382         registry.remove(new CaseInsensitiveString(id));
383         removeSTV(stv[0], stv[1], stv[2]);
384         availableIDs.remove(new CaseInsensitiveString(id));
385     }
386 
387     //----------------------------------------------------------------------
388     // class TransliteratorRegistry: Public ID and spec management
389     //----------------------------------------------------------------------
390 
391     /**
392      * An internal class that adapts an enumeration over
393      * CaseInsensitiveStrings to an enumeration over Strings.
394      */
395     private static class IDEnumeration implements Enumeration<String> {
396         Enumeration<CaseInsensitiveString> en;
397 
IDEnumeration(Enumeration<CaseInsensitiveString> e)398         public IDEnumeration(Enumeration<CaseInsensitiveString> e) {
399             en = e;
400         }
401 
402         @Override
hasMoreElements()403         public boolean hasMoreElements() {
404             return en != null && en.hasMoreElements();
405         }
406 
407         @Override
nextElement()408         public String nextElement() {
409             return (en.nextElement()).getString();
410         }
411     }
412 
413     /**
414      * Returns an enumeration over the programmatic names of visible
415      * registered transliterators.
416      *
417      * @return An <code>Enumeration</code> over <code>String</code> objects
418      */
getAvailableIDs()419     public Enumeration<String> getAvailableIDs() {
420         // Since the cache contains CaseInsensitiveString objects, but
421         // the caller expects Strings, we have to use an intermediary.
422         return new IDEnumeration(Collections.enumeration(availableIDs));
423     }
424 
425     /**
426      * Returns an enumeration over all visible source names.
427      *
428      * @return An <code>Enumeration</code> over <code>String</code> objects
429      */
getAvailableSources()430     public Enumeration<String> getAvailableSources() {
431         return new IDEnumeration(Collections.enumeration(specDAG.keySet()));
432     }
433 
434     /**
435      * Returns an enumeration over visible target names for the given
436      * source.
437      *
438      * @return An <code>Enumeration</code> over <code>String</code> objects
439      */
getAvailableTargets(String source)440     public Enumeration<String> getAvailableTargets(String source) {
441         CaseInsensitiveString cisrc = new CaseInsensitiveString(source);
442         Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc);
443         if (targets == null) {
444             return new IDEnumeration(null);
445         }
446         return new IDEnumeration(Collections.enumeration(targets.keySet()));
447     }
448 
449     /**
450      * Returns an enumeration over visible variant names for the given
451      * source and target.
452      *
453      * @return An <code>Enumeration</code> over <code>String</code> objects
454      */
getAvailableVariants(String source, String target)455     public Enumeration<String> getAvailableVariants(String source, String target) {
456         CaseInsensitiveString cisrc = new CaseInsensitiveString(source);
457         CaseInsensitiveString citrg = new CaseInsensitiveString(target);
458         Map<CaseInsensitiveString, List<CaseInsensitiveString>> targets = specDAG.get(cisrc);
459         if (targets == null) {
460             return new IDEnumeration(null);
461         }
462         List<CaseInsensitiveString> variants = targets.get(citrg);
463         if (variants == null) {
464             return new IDEnumeration(null);
465         }
466         return new IDEnumeration(Collections.enumeration(variants));
467     }
468 
469     //----------------------------------------------------------------------
470     // class TransliteratorRegistry: internal
471     //----------------------------------------------------------------------
472 
473     /**
474      * Convenience method.  Calls 6-arg registerEntry().
475      */
registerEntry(String source, String target, String variant, Object entry, boolean visible)476     private void registerEntry(String source,
477                                String target,
478                                String variant,
479                                Object entry,
480                                boolean visible) {
481         String s = source;
482         if (s.length() == 0) {
483             s = ANY;
484         }
485         String ID = TransliteratorIDParser.STVtoID(source, target, variant);
486         registerEntry(ID, s, target, variant, entry, visible);
487     }
488 
489     /**
490      * Convenience method.  Calls 6-arg registerEntry().
491      */
registerEntry(String ID, Object entry, boolean visible)492     private void registerEntry(String ID,
493                                Object entry,
494                                boolean visible) {
495         String[] stv = TransliteratorIDParser.IDtoSTV(ID);
496         // Only need to do this if ID.indexOf('-') < 0
497         String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]);
498         registerEntry(id, stv[0], stv[1], stv[2], entry, visible);
499     }
500 
501     /**
502      * Register an entry object (adopted) with the given ID, source,
503      * target, and variant strings.
504      */
registerEntry(String ID, String source, String target, String variant, Object entry, boolean visible)505     private void registerEntry(String ID,
506                                String source,
507                                String target,
508                                String variant,
509                                Object entry,
510                                boolean visible) {
511         CaseInsensitiveString ciID = new CaseInsensitiveString(ID);
512         Object[] arrayOfObj;
513 
514         // Store the entry within an array so it can be modified later
515         if (entry instanceof Object[]) {
516             arrayOfObj = (Object[])entry;
517         } else {
518             arrayOfObj = new Object[] { entry };
519         }
520 
521         registry.put(ciID, arrayOfObj);
522         if (visible) {
523             registerSTV(source, target, variant);
524             if (!availableIDs.contains(ciID)) {
525                 availableIDs.add(ciID);
526             }
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