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