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