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