]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/translit/src/com/ibm/icu/text/TransliteratorRegistry.java
Added flags.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / translit / src / com / ibm / icu / text / TransliteratorRegistry.java
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
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
140         public boolean hasFallback() {
141             return nextSpec != null;
142         }
143
144         public void reset() {
145             if (spec != top) { // [sic] pointer comparison
146                 spec = top;
147                 isSpecLocale = (res != null);
148                 setupNext();
149             }
150         }
151
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
179         public String next() {
180             spec = nextSpec;
181             isSpecLocale = isNextLocale;
182             setupNext();
183             return spec;
184         }
185
186         public String get() {
187             return spec;
188         }
189
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          */
201         public ResourceBundle getBundle() {
202             if (res != null &&
203                 res.getULocale().toString().equals(spec)) {
204                 return res;
205             }
206             return null;
207         }
208
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;
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;
233         public LocaleEntry(String r, int d) {
234             rule = r;
235             direction = d;
236         }
237     }
238
239     static class AliasEntry {
240         public String alias;
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
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
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
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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
396         public IDEnumeration(Enumeration<CaseInsensitiveString> e) {
397             en = e;
398         }
399
400         public boolean hasMoreElements() {
401             return en != null && en.hasMoreElements();
402         }
403
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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")
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