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