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