]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/AnyTransliterator.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / AnyTransliterator.java
1 /*\r
2 *****************************************************************\r
3 * Copyright (c) 2002-2009, International Business Machines Corporation\r
4 * and others.  All Rights Reserved.\r
5 *****************************************************************\r
6 * Date        Name        Description\r
7 * 06/06/2002  aliu        Creation.\r
8 *****************************************************************\r
9 */\r
10 package com.ibm.icu.text;\r
11 import com.ibm.icu.lang.UScript;\r
12 import java.lang.Math;\r
13 import java.util.Enumeration;\r
14 import java.util.HashSet;\r
15 import java.util.HashMap;\r
16 import java.util.Map;\r
17 import java.util.MissingResourceException;\r
18 import java.util.Set;\r
19 import java.util.Vector;\r
20 /**\r
21  * A transliterator that translates multiple input scripts to a single\r
22  * output script.  It is named Any-T or Any-T/V, where T is the target\r
23  * and V is the optional variant.  The target T is a script.\r
24  *\r
25  * <p>An AnyTransliterator partitions text into runs of the same\r
26  * script, together with adjacent COMMON or INHERITED characters.\r
27  * After determining the script of each run, it transliterates from\r
28  * that script to the given target/variant.  It does so by\r
29  * instantiating a transliterator from the source script to the\r
30  * target/variant.  If a run consists only of the target script,\r
31  * COMMON, or INHERITED characters, then the run is not changed.\r
32  *\r
33  * <p>At startup, all possible AnyTransliterators are registered with\r
34  * the system, as determined by examining the registered script\r
35  * transliterators.\r
36  *\r
37  * @since ICU 2.2\r
38  * @author Alan Liu\r
39  */\r
40 class AnyTransliterator extends Transliterator {\r
41 \r
42     //------------------------------------------------------------\r
43     // Constants\r
44 \r
45     static final char TARGET_SEP = '-';\r
46     static final char VARIANT_SEP = '/';\r
47     static final String ANY = "Any";\r
48     static final String NULL_ID = "Null";\r
49     static final String LATIN_PIVOT = "-Latin;Latin-";\r
50 \r
51     /**\r
52      * Cache mapping UScriptCode values to Transliterator*.\r
53      */\r
54     private Map cache;\r
55 \r
56     /**\r
57      * The target or target/variant string.\r
58      */\r
59     private String target;\r
60 \r
61     /**\r
62      * The target script code.  Never USCRIPT_INVALID_CODE.\r
63      */\r
64     private int targetScript;\r
65     \r
66     /**\r
67      * Special code for handling width characters\r
68      */\r
69     private Transliterator widthFix = Transliterator.getInstance("[[:dt=Nar:][:dt=Wide:]] nfkd");\r
70 \r
71     /**\r
72      * Implements {@link Transliterator#handleTransliterate}.\r
73      */\r
74     protected void handleTransliterate(Replaceable text,\r
75                                        Position pos, boolean isIncremental) {\r
76         int allStart = pos.start;\r
77         int allLimit = pos.limit;\r
78 \r
79         ScriptRunIterator it =\r
80             new ScriptRunIterator(text, pos.contextStart, pos.contextLimit);\r
81 \r
82         while (it.next()) {\r
83             // Ignore runs in the ante context\r
84             if (it.limit <= allStart) continue;\r
85 \r
86             // Try to instantiate transliterator from it.scriptCode to\r
87             // our target or target/variant\r
88             Transliterator t = getTransliterator(it.scriptCode);\r
89 \r
90             if (t == null) {\r
91                 // We have no transliterator.  Do nothing, but keep\r
92                 // pos.start up to date.\r
93                 pos.start = it.limit;\r
94                 continue;\r
95             }\r
96 \r
97             // If the run end is before the transliteration limit, do\r
98             // a non-incremental transliteration.  Otherwise do an\r
99             // incremental one.\r
100             boolean incremental = isIncremental && (it.limit >= allLimit);\r
101 \r
102             pos.start = Math.max(allStart, it.start);\r
103             pos.limit = Math.min(allLimit, it.limit);\r
104             int limit = pos.limit;\r
105             t.filteredTransliterate(text, pos, incremental);\r
106             int delta = pos.limit - limit;\r
107             allLimit += delta;\r
108             it.adjustLimit(delta);\r
109 \r
110             // We're done if we enter the post context\r
111             if (it.limit >= allLimit) break;\r
112         }\r
113 \r
114         // Restore limit.  pos.start is fine where the last transliterator\r
115         // left it, or at the end of the last run.\r
116         pos.limit = allLimit;\r
117     }\r
118 \r
119     /**\r
120      * Private constructor\r
121      * @param id the ID of the form S-T or S-T/V, where T is theTarget\r
122      * and V is theVariant.  Must not be empty.\r
123      * @param theTarget the target name.  Must not be empty, and must\r
124      * name a script corresponding to theTargetScript.\r
125      * @param theVariant the variant name, or the empty string if\r
126      * there is no variant\r
127      * @param theTargetScript the script code corresponding to\r
128      * theTarget.\r
129      */\r
130     private AnyTransliterator(String id,\r
131                               String theTarget,\r
132                               String theVariant,\r
133                               int theTargetScript) {\r
134         super(id, null);\r
135         targetScript = theTargetScript;\r
136         cache = new HashMap();\r
137 \r
138         target = theTarget;\r
139         if (theVariant.length() > 0) {\r
140             target = theTarget + VARIANT_SEP + theVariant;\r
141         }\r
142     }\r
143 \r
144     /**\r
145      * @param id\r
146      * @param filter\r
147      * @param target2\r
148      * @param targetScript2\r
149      * @param widthFix2\r
150      * @param cache2\r
151      */\r
152     public AnyTransliterator(String id, UnicodeFilter filter, String target2,\r
153             int targetScript2, Transliterator widthFix2, Map cache2) {\r
154         super(id, filter);\r
155         targetScript = targetScript2;\r
156         cache = cache2;\r
157         target = target2;\r
158     }\r
159 \r
160     /**\r
161      * Returns a transliterator from the given source to our target or\r
162      * target/variant.  Returns NULL if the source is the same as our\r
163      * target script, or if the source is USCRIPT_INVALID_CODE.\r
164      * Caches the result and returns the same transliterator the next\r
165      * time.  The caller does NOT own the result and must not delete\r
166      * it.\r
167      */\r
168     private Transliterator getTransliterator(int source) {\r
169         if (source == targetScript || source == UScript.INVALID_CODE) {\r
170             if (isWide(targetScript)) {\r
171                 return null;\r
172             } else {\r
173                 return widthFix;\r
174             }\r
175         }\r
176 \r
177         Integer key = new Integer(source);\r
178         Transliterator t = (Transliterator) cache.get(key);\r
179         if (t == null) {\r
180             String sourceName = UScript.getName(source);\r
181             String id = sourceName + TARGET_SEP + target;\r
182 \r
183             try {\r
184                 t = Transliterator.getInstance(id, FORWARD);\r
185             } catch (RuntimeException e) { }\r
186             if (t == null) {\r
187 \r
188                 // Try to pivot around Latin, our most common script\r
189                 id = sourceName + LATIN_PIVOT + target;\r
190                 try {\r
191                     t = Transliterator.getInstance(id, FORWARD);\r
192                 } catch (RuntimeException e) { }\r
193             }\r
194 \r
195             if (t != null) {\r
196                 if (!isWide(targetScript)) {\r
197                     Vector v = new Vector();\r
198                     v.add(widthFix);\r
199                     v.add(t);\r
200                     t = new CompoundTransliterator(v);\r
201                 }\r
202                 cache.put(key, t);\r
203             } else if (!isWide(targetScript)) {\r
204                 return widthFix;\r
205             }\r
206         }\r
207 \r
208         return t;\r
209     }\r
210 \r
211     /**\r
212      * @param targetScript2\r
213      * @return\r
214      */\r
215     private boolean isWide(int script) {\r
216         return script == UScript.BOPOMOFO || script == UScript.HAN || script == UScript.HANGUL || script == UScript.HIRAGANA || script == UScript.KATAKANA;\r
217     }\r
218 \r
219     /**\r
220      * Registers standard transliterators with the system.  Called by\r
221      * Transliterator during initialization.  Scan all current targets\r
222      * and register those that are scripts T as Any-T/V.\r
223      */\r
224     static void register() {\r
225 \r
226         HashMap seen = new HashMap(); // old code used set, but was dependent on order\r
227 \r
228         for (Enumeration s=Transliterator.getAvailableSources(); s.hasMoreElements(); ) {\r
229             String source = (String) s.nextElement();\r
230 \r
231             // Ignore the "Any" source\r
232             if (source.equalsIgnoreCase(ANY)) continue;\r
233 \r
234             for (Enumeration t=Transliterator.getAvailableTargets(source);\r
235                  t.hasMoreElements(); ) {\r
236                 String target = (String) t.nextElement();\r
237 \r
238                 // Get the script code for the target.  If not a script, ignore.\r
239                 int targetScript = scriptNameToCode(target);\r
240                 if (targetScript == UScript.INVALID_CODE) continue;\r
241                 \r
242                 Set seenVariants = (Set) seen.get(target);\r
243                 if (seenVariants == null) {\r
244                     seen.put(target, seenVariants = new HashSet());\r
245                 }\r
246 \r
247                 for (Enumeration v=Transliterator.getAvailableVariants(source, target);\r
248                      v.hasMoreElements(); ) {\r
249                     String variant = (String) v.nextElement();\r
250                     \r
251                     // Only process each target/variant pair once\r
252                     if (seenVariants.contains(variant)) continue;\r
253                     seenVariants.add(variant);\r
254 \r
255                     String id;\r
256                     id = TransliteratorIDParser.STVtoID(ANY, target, variant);\r
257                     AnyTransliterator trans = new AnyTransliterator(id, target, variant,\r
258                                                                     targetScript);\r
259                     Transliterator.registerInstance(trans);\r
260                     Transliterator.registerSpecialInverse(target, NULL_ID, false);\r
261                 }\r
262             }\r
263         }\r
264     }\r
265 \r
266     /**\r
267      * Return the script code for a given name, or\r
268      * UScript.INVALID_CODE if not found.\r
269      */\r
270     private static int scriptNameToCode(String name) {\r
271         try{\r
272             int[] codes = UScript.getCode(name);\r
273             return codes != null ? codes[0] : UScript.INVALID_CODE;\r
274         }catch( MissingResourceException e){\r
275             return UScript.INVALID_CODE;\r
276         }\r
277     }\r
278 \r
279     //------------------------------------------------------------\r
280     // ScriptRunIterator\r
281 \r
282     /**\r
283      * Returns a series of ranges corresponding to scripts. They will be\r
284      * of the form:\r
285      *\r
286      * ccccSScSSccccTTcTcccc   - c = common, S = first script, T = second\r
287      * |            |          - first run (start, limit)\r
288      *          |           |  - second run (start, limit)\r
289      *\r
290      * That is, the runs will overlap. The reason for this is so that a\r
291      * transliterator can consider common characters both before and after\r
292      * the scripts.\r
293      */\r
294     private static class ScriptRunIterator {\r
295 \r
296         private Replaceable text;\r
297         private int textStart;\r
298         private int textLimit;\r
299 \r
300         /**\r
301          * The code of the current run, valid after next() returns.  May\r
302          * be UScript.INVALID_CODE if and only if the entire text is\r
303          * COMMON/INHERITED.\r
304          */\r
305         public int scriptCode;\r
306 \r
307         /**\r
308          * The start of the run, inclusive, valid after next() returns.\r
309          */\r
310         public int start;\r
311 \r
312         /**\r
313          * The end of the run, exclusive, valid after next() returns.\r
314          */\r
315         public int limit;\r
316 \r
317         /**\r
318          * Constructs a run iterator over the given text from start\r
319          * (inclusive) to limit (exclusive).\r
320          */\r
321         public ScriptRunIterator(Replaceable text, int start, int limit) {\r
322             this.text = text;\r
323             this.textStart = start;\r
324             this.textLimit = limit;\r
325             this.limit = start;\r
326         }\r
327 \r
328 \r
329         /**\r
330          * Returns TRUE if there are any more runs.  TRUE is always\r
331          * returned at least once.  Upon return, the caller should\r
332          * examine scriptCode, start, and limit.\r
333          */\r
334         public boolean next() {\r
335             int ch;\r
336             int s;\r
337 \r
338             scriptCode = UScript.INVALID_CODE; // don't know script yet\r
339             start = limit;\r
340 \r
341             // Are we done?\r
342             if (start == textLimit) {\r
343                 return false;\r
344             }\r
345 \r
346             // Move start back to include adjacent COMMON or INHERITED\r
347             // characters\r
348             while (start > textStart) {\r
349                 ch = text.char32At(start - 1); // look back\r
350                 s = UScript.getScript(ch);\r
351                 if (s == UScript.COMMON || s == UScript.INHERITED) {\r
352                     --start;\r
353                 } else {\r
354                     break;\r
355                 }\r
356             }\r
357 \r
358             // Move limit ahead to include COMMON, INHERITED, and characters\r
359             // of the current script.\r
360             while (limit < textLimit) {\r
361                 ch = text.char32At(limit); // look ahead\r
362                 s = UScript.getScript(ch);\r
363                 if (s != UScript.COMMON && s != UScript.INHERITED) {\r
364                     if (scriptCode == UScript.INVALID_CODE) {\r
365                         scriptCode = s;\r
366                     } else if (s != scriptCode) {\r
367                         break;\r
368                     }\r
369                 }\r
370                 ++limit;\r
371             }\r
372 \r
373             // Return TRUE even if the entire text is COMMON / INHERITED, in\r
374             // which case scriptCode will be UScript.INVALID_CODE.\r
375             return true;\r
376         }\r
377 \r
378         /**\r
379          * Adjusts internal indices for a change in the limit index of the\r
380          * given delta.  A positive delta means the limit has increased.\r
381          */\r
382         public void adjustLimit(int delta) {\r
383             limit += delta;\r
384             textLimit += delta;\r
385         }\r
386     }\r
387 \r
388     /**\r
389      * Temporary hack for registry problem. Needs to be replaced by better architecture.\r
390      * @internal\r
391      * @deprecated\r
392      */\r
393     public Transliterator safeClone() {\r
394         UnicodeFilter filter = getFilter();\r
395         if (filter != null && filter instanceof UnicodeSet) {\r
396             filter = new UnicodeSet((UnicodeSet)filter);\r
397         }\r
398         return new AnyTransliterator(getID(), filter, target, targetScript, widthFix, cache);\r
399     }\r
400 }\r
401 \r
402 //eof\r