]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/CompoundTransliterator.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / CompoundTransliterator.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 1996-2009, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 package com.ibm.icu.text;\r
8 import com.ibm.icu.impl.Utility;\r
9 import com.ibm.icu.impl.UtilityExtensions;\r
10 import java.util.Vector;\r
11 \r
12 /**\r
13  * A transliterator that is composed of two or more other\r
14  * transliterator objects linked together.  For example, if one\r
15  * transliterator transliterates from script A to script B, and\r
16  * another transliterates from script B to script C, the two may be\r
17  * combined to form a new transliterator from A to C.\r
18  *\r
19  * <p>Composed transliterators may not behave as expected.  For\r
20  * example, inverses may not combine to form the identity\r
21  * transliterator.  See the class documentation for {@link\r
22  * Transliterator} for details.\r
23  *\r
24  * <p>Copyright &copy; IBM Corporation 1999.  All rights reserved.\r
25  *\r
26  * @author Alan Liu\r
27  * @internal\r
28  */\r
29 class CompoundTransliterator extends Transliterator {\r
30 \r
31     private Transliterator[] trans;\r
32 \r
33     private int numAnonymousRBTs = 0;\r
34 \r
35     /**\r
36      * Constructs a new compound transliterator given an array of\r
37      * transliterators.  The array of transliterators may be of any\r
38      * length, including zero or one, however, useful compound\r
39      * transliterators have at least two components.\r
40      * @param transliterators array of <code>Transliterator</code>\r
41      * objects\r
42      * @param filter the filter.  Any character for which\r
43      * <tt>filter.contains()</tt> returns <tt>false</tt> will not be\r
44      * altered by this transliterator.  If <tt>filter</tt> is\r
45      * <tt>null</tt> then no filtering is applied.\r
46      * @internal\r
47      */\r
48     /*public CompoundTransliterator(Transliterator[] transliterators,\r
49                                   UnicodeFilter filter) {\r
50         super(joinIDs(transliterators), filter);\r
51         trans = new Transliterator[transliterators.length];\r
52         System.arraycopy(transliterators, 0, trans, 0, trans.length);\r
53         computeMaximumContextLength();\r
54     }*/\r
55 \r
56     /**\r
57      * Constructs a new compound transliterator given an array of\r
58      * transliterators.  The array of transliterators may be of any\r
59      * length, including zero or one, however, useful compound\r
60      * transliterators have at least two components.\r
61      * @param transliterators array of <code>Transliterator</code>\r
62      * objects\r
63      * @internal\r
64      */\r
65     /*public CompoundTransliterator(Transliterator[] transliterators) {\r
66         this(transliterators, null);\r
67     }*/\r
68 \r
69     /**\r
70      * Constructs a new compound transliterator.\r
71      * @param ID compound ID\r
72      * @param direction either Transliterator.FORWARD or Transliterator.REVERSE\r
73      * @param filter a global filter for this compound transliterator\r
74      * or null\r
75      * @internal\r
76      */\r
77     /*public CompoundTransliterator(String ID, int direction,\r
78                                   UnicodeFilter filter) {\r
79         super(ID, filter);\r
80         init(ID, direction, true);\r
81     }*/\r
82 \r
83     /**\r
84      * Constructs a new compound transliterator with no filter.\r
85      * @param ID compound ID\r
86      * @param direction either Transliterator.FORWARD or Transliterator.REVERSE\r
87      * @internal\r
88      */\r
89     /*public CompoundTransliterator(String ID, int direction) {\r
90         this(ID, direction, null);\r
91     }*/\r
92 \r
93     /**\r
94      * Constructs a new forward compound transliterator with no filter.\r
95      * @param ID compound ID\r
96      * @internal\r
97      */\r
98     /*public CompoundTransliterator(String ID) {\r
99         this(ID, FORWARD, null);\r
100     }*/\r
101 \r
102     /**\r
103      * Package private constructor for Transliterator from a vector of\r
104      * transliterators.  The caller is responsible for fixing up the\r
105      * ID.\r
106      */\r
107     CompoundTransliterator(Vector list) {\r
108         this(list, 0);\r
109     }\r
110 \r
111     CompoundTransliterator(Vector list, int numAnonymousRBTs) {\r
112         super("", null);\r
113         trans = null;\r
114         init(list, FORWARD, false);\r
115         this.numAnonymousRBTs = numAnonymousRBTs;\r
116         // assume caller will fixup ID\r
117     }\r
118 \r
119     /**\r
120      * Internal method for safeClone...\r
121      * @param id\r
122      * @param filter2 \r
123      * @param trans2\r
124      * @param numAnonymousRBTs2\r
125      */\r
126     CompoundTransliterator(String id, UnicodeFilter filter2, Transliterator[] trans2, int numAnonymousRBTs2) {\r
127         super(id, filter2);\r
128         trans = trans2;\r
129         numAnonymousRBTs = numAnonymousRBTs2;\r
130     }\r
131     \r
132     /**\r
133      * Finish constructing a transliterator: only to be called by\r
134      * constructors.  Before calling init(), set trans and filter to NULL.\r
135      * @param id the id containing ';'-separated entries\r
136      * @param direction either FORWARD or REVERSE\r
137      * @param idSplitPoint the index into id at which the\r
138      * splitTrans should be inserted, if there is one, or\r
139      * -1 if there is none.\r
140      * @param splitTrans a transliterator to be inserted\r
141      * before the entry at offset idSplitPoint in the id string.  May be\r
142      * NULL to insert no entry.\r
143      * @param fixReverseID if TRUE, then reconstruct the ID of reverse\r
144      * entries by calling getID() of component entries.  Some constructors\r
145      * do not require this because they apply a facade ID anyway.\r
146      */\r
147     /*private void init(String id,\r
148                       int direction,\r
149                       boolean fixReverseID) {\r
150         // assert(trans == 0);\r
151 \r
152         Vector list = new Vector();\r
153         UnicodeSet[] compoundFilter = new UnicodeSet[1];\r
154         StringBuffer regenID = new StringBuffer();\r
155         if (!TransliteratorIDParser.parseCompoundID(id, direction,\r
156                  regenID, list, compoundFilter)) {\r
157             throw new IllegalArgumentException("Invalid ID " + id);\r
158         }\r
159 \r
160         TransliteratorIDParser.instantiateList(list);\r
161 \r
162         init(list, direction, fixReverseID);\r
163 \r
164         if (compoundFilter[0] != null) {\r
165             setFilter(compoundFilter[0]);\r
166         }\r
167     }*/\r
168 \r
169 \r
170     /**\r
171      * Finish constructing a transliterator: only to be called by\r
172      * constructors.  Before calling init(), set trans and filter to NULL.\r
173      * @param list a vector of transliterator objects to be adopted.  It\r
174      * should NOT be empty.  The list should be in declared order.  That\r
175      * is, it should be in the FORWARD order; if direction is REVERSE then\r
176      * the list order will be reversed.\r
177      * @param direction either FORWARD or REVERSE\r
178      * @param fixReverseID if TRUE, then reconstruct the ID of reverse\r
179      * entries by calling getID() of component entries.  Some constructors\r
180      * do not require this because they apply a facade ID anyway.\r
181      */\r
182     private void init(Vector list,\r
183                       int direction,\r
184                       boolean fixReverseID) {\r
185         // assert(trans == 0);\r
186 \r
187         // Allocate array\r
188         int count = list.size();\r
189         trans = new Transliterator[count];\r
190 \r
191         // Move the transliterators from the vector into an array.\r
192         // Reverse the order if necessary.\r
193         int i;\r
194         for (i=0; i<count; ++i) {\r
195             int j = (direction == FORWARD) ? i : count - 1 - i;\r
196             trans[i] = (Transliterator) list.elementAt(j);\r
197         }\r
198 \r
199         // If the direction is UTRANS_REVERSE then we may need to fix the\r
200         // ID.\r
201         if (direction == REVERSE && fixReverseID) {\r
202             StringBuffer newID = new StringBuffer();\r
203             for (i=0; i<count; ++i) {\r
204                 if (i > 0) {\r
205                     newID.append(ID_DELIM);\r
206                 }\r
207                 newID.append(trans[i].getID());\r
208             }\r
209             setID(newID.toString());\r
210         }\r
211 \r
212         computeMaximumContextLength();\r
213     }\r
214 \r
215     /**\r
216      * Return the IDs of the given list of transliterators, concatenated\r
217      * with ';' delimiting them.  Equivalent to the perlish expression\r
218      * join(';', map($_.getID(), transliterators).\r
219      */\r
220     /*private static String joinIDs(Transliterator[] transliterators) {\r
221         StringBuffer id = new StringBuffer();\r
222         for (int i=0; i<transliterators.length; ++i) {\r
223             if (i > 0) {\r
224                 id.append(';');\r
225             }\r
226             id.append(transliterators[i].getID());\r
227         }\r
228         return id.toString();\r
229     }*/\r
230 \r
231     /**\r
232      * Returns the number of transliterators in this chain.\r
233      * @return number of transliterators in this chain.\r
234      * @internal\r
235      */\r
236     public int getCount() {\r
237         return trans.length;\r
238     }\r
239 \r
240     /**\r
241      * Returns the transliterator at the given index in this chain.\r
242      * @param index index into chain, from 0 to <code>getCount() - 1</code>\r
243      * @return transliterator at the given index\r
244      * @internal\r
245      */\r
246     public Transliterator getTransliterator(int index) {\r
247         return trans[index];\r
248     }\r
249 \r
250     /**\r
251      * Append c to buf, unless buf is empty or buf already ends in c.\r
252      */\r
253     private static void _smartAppend(StringBuffer buf, char c) {\r
254         if (buf.length() != 0 &&\r
255             buf.charAt(buf.length() - 1) != c) {\r
256             buf.append(c);\r
257         }\r
258     }\r
259 \r
260     /**\r
261      * Override Transliterator:\r
262      * Create a rule string that can be passed to createFromRules()\r
263      * to recreate this transliterator.\r
264      * @param escapeUnprintable if TRUE then convert unprintable\r
265      * character to their hex escape representations, \\uxxxx or\r
266      * \\Uxxxxxxxx.  Unprintable characters are those other than\r
267      * U+000A, U+0020..U+007E.\r
268      * @return the rule string\r
269      * @internal\r
270      */\r
271     public String toRules(boolean escapeUnprintable) {\r
272         // We do NOT call toRules() on our component transliterators, in\r
273         // general.  If we have several rule-based transliterators, this\r
274         // yields a concatenation of the rules -- not what we want.  We do\r
275         // handle compound RBT transliterators specially -- those for which\r
276         // compoundRBTIndex >= 0.  For the transliterator at compoundRBTIndex,\r
277         // we do call toRules() recursively.\r
278         StringBuffer rulesSource = new StringBuffer();\r
279         if (numAnonymousRBTs >= 1 && getFilter() != null) {\r
280             // If we are a compound RBT and if we have a global\r
281             // filter, then emit it at the top.\r
282             rulesSource.append("::").append(getFilter().toPattern(escapeUnprintable)).append(ID_DELIM);\r
283         }\r
284         for (int i=0; i<trans.length; ++i) {\r
285             String rule;\r
286 \r
287             // Anonymous RuleBasedTransliterators (inline rules and\r
288             // ::BEGIN/::END blocks) are given IDs that begin with\r
289             // "%Pass": use toRules() to write all the rules to the output\r
290             // (and insert "::Null;" if we have two in a row)\r
291             if (trans[i].getID().startsWith("%Pass")) {\r
292                 rule = trans[i].toRules(escapeUnprintable);\r
293                 if (numAnonymousRBTs > 1 && i > 0 && trans[i - 1].getID().startsWith("%Pass"))\r
294                     rule = "::Null;" + rule;\r
295 \r
296             // we also use toRules() on CompoundTransliterators (which we\r
297             // check for by looking for a semicolon in the ID)-- this gets\r
298             // the list of their child transliterators output in the right\r
299             // format\r
300             } else if (trans[i].getID().indexOf(';') >= 0) {\r
301                 rule = trans[i].toRules(escapeUnprintable);\r
302 \r
303             // for everything else, use baseToRules()\r
304             } else {\r
305                 rule = trans[i].baseToRules(escapeUnprintable);\r
306             }\r
307             _smartAppend(rulesSource, '\n');\r
308             rulesSource.append(rule);\r
309             _smartAppend(rulesSource, ID_DELIM);\r
310         }\r
311         return rulesSource.toString();\r
312     }\r
313 \r
314     /**\r
315      * Return the set of all characters that may be modified by this\r
316      * Transliterator, ignoring the effect of our filter.\r
317      * @internal\r
318      */\r
319     protected UnicodeSet handleGetSourceSet() {\r
320         UnicodeSet set = new UnicodeSet();\r
321         for (int i=0; i<trans.length; ++i) {\r
322             set.addAll(trans[i].getSourceSet());\r
323             // Take the example of Hiragana-Latin.  This is really\r
324             // Hiragana-Katakana; Katakana-Latin.  The source set of\r
325             // these two is roughly [:Hiragana:] and [:Katakana:].\r
326             // But the source set for the entire transliterator is\r
327             // actually [:Hiragana:] ONLY -- that is, the first\r
328             // non-empty source set.\r
329 \r
330             // This is a heuristic, and not 100% reliable.\r
331             if (!set.isEmpty()) {\r
332                 break;\r
333             }\r
334         }\r
335         return set;\r
336     }\r
337 \r
338     /**\r
339      * Returns the set of all characters that may be generated as\r
340      * replacement text by this transliterator.\r
341      * @internal\r
342      */\r
343     public UnicodeSet getTargetSet() {\r
344         UnicodeSet set = new UnicodeSet();\r
345         for (int i=0; i<trans.length; ++i) {\r
346             // This is a heuristic, and not 100% reliable.\r
347             set.addAll(trans[i].getTargetSet());\r
348         }\r
349         return set;\r
350     }\r
351 \r
352     /**\r
353      * Implements {@link Transliterator#handleTransliterate}.\r
354      * @internal\r
355      */\r
356     protected void handleTransliterate(Replaceable text,\r
357                                        Position index, boolean incremental) {\r
358         /* Call each transliterator with the same start value and\r
359          * initial cursor index, but with the limit index as modified\r
360          * by preceding transliterators.  The cursor index must be\r
361          * reset for each transliterator to give each a chance to\r
362          * transliterate the text.  The initial cursor index is known\r
363          * to still point to the same place after each transliterator\r
364          * is called because each transliterator will not change the\r
365          * text between start and the initial value of cursor.\r
366          *\r
367          * IMPORTANT: After the first transliterator, each subsequent\r
368          * transliterator only gets to transliterate text committed by\r
369          * preceding transliterators; that is, the cursor (output\r
370          * value) of transliterator i becomes the limit (input value)\r
371          * of transliterator i+1.  Finally, the overall limit is fixed\r
372          * up before we return.\r
373          *\r
374          * Assumptions we make here:\r
375          * (1) contextStart <= start <= limit <= contextLimit <= text.length()\r
376          * (2) start <= start' <= limit'  ;cursor doesn't move back\r
377          * (3) start <= limit'            ;text before cursor unchanged\r
378          * - start' is the value of start after calling handleKT\r
379          * - limit' is the value of limit after calling handleKT\r
380          */\r
381 \r
382         /**\r
383          * Example: 3 transliterators.  This example illustrates the\r
384          * mechanics we need to implement.  C, S, and L are the contextStart,\r
385          * start, and limit.  gl is the globalLimit.  contextLimit is\r
386          * equal to limit throughout.\r
387          *\r
388          * 1. h-u, changes hex to Unicode\r
389          *\r
390          *    4  7  a  d  0      4  7  a\r
391          *    abc/u0061/u    =>  abca/u\r
392          *    C  S       L       C   S L   gl=f->a\r
393          *\r
394          * 2. upup, changes "x" to "XX"\r
395          *\r
396          *    4  7  a       4  7  a\r
397          *    abca/u    =>  abcAA/u\r
398          *    C  SL         C    S\r
399          *                       L    gl=a->b\r
400          * 3. u-h, changes Unicode to hex\r
401          *\r
402          *    4  7  a        4  7  a  d  0  3\r
403          *    abcAA/u    =>  abc/u0041/u0041/u\r
404          *    C  S L         C              S\r
405          *                                  L   gl=b->15\r
406          * 4. return\r
407          *\r
408          *    4  7  a  d  0  3\r
409          *    abc/u0041/u0041/u\r
410          *    C S L\r
411          */\r
412 \r
413         if (trans.length < 1) {\r
414             index.start = index.limit;\r
415             return; // Short circuit for empty compound transliterators\r
416         }\r
417 \r
418         // compoundLimit is the limit value for the entire compound\r
419         // operation.  We overwrite index.limit with the previous\r
420         // index.start.  After each transliteration, we update\r
421         // compoundLimit for insertions or deletions that have happened.\r
422         int compoundLimit = index.limit;\r
423 \r
424         // compoundStart is the start for the entire compound\r
425         // operation.\r
426         int compoundStart = index.start;\r
427 \r
428         int delta = 0; // delta in length\r
429 \r
430         StringBuffer log = null;\r
431         if (DEBUG) {\r
432             log = new StringBuffer("CompoundTransliterator{" + getID() +\r
433                                    (incremental ? "}i: IN=" : "}: IN="));\r
434             UtilityExtensions.formatInput(log, text, index);\r
435             System.out.println(Utility.escape(log.toString()));\r
436         }\r
437 \r
438         // Give each transliterator a crack at the run of characters.\r
439         // See comments at the top of the method for more detail.\r
440         for (int i=0; i<trans.length; ++i) {\r
441             index.start = compoundStart; // Reset start\r
442             int limit = index.limit;\r
443 \r
444             if (index.start == index.limit) {\r
445                 // Short circuit for empty range\r
446                 if (DEBUG) {\r
447                     System.out.println("CompoundTransliterator[" + i +\r
448                                        ".." + (trans.length-1) +\r
449                                        (incremental ? "]i: " : "]: ") +\r
450                                        UtilityExtensions.formatInput(text, index) +\r
451                                        " (NOTHING TO DO)");\r
452                 }\r
453                 break;\r
454             }\r
455 \r
456             if (DEBUG) {\r
457                 log.setLength(0);\r
458                 log.append("CompoundTransliterator[" + i + "=" +\r
459                            trans[i].getID() +\r
460                            (incremental ? "]i: " : "]: "));\r
461                 UtilityExtensions.formatInput(log, text, index);\r
462             }\r
463 \r
464             trans[i].filteredTransliterate(text, index, incremental);\r
465 \r
466             // In a properly written transliterator, start == limit after\r
467             // handleTransliterate() returns when incremental is false.\r
468             // Catch cases where the subclass doesn't do this, and throw\r
469             // an exception.  (Just pinning start to limit is a bad idea,\r
470             // because what's probably happening is that the subclass\r
471             // isn't transliterating all the way to the end, and it should\r
472             // in non-incremental mode.)\r
473             if (!incremental && index.start != index.limit) {\r
474                 throw new RuntimeException("ERROR: Incomplete non-incremental transliteration by " + trans[i].getID());\r
475             }\r
476 \r
477             if (DEBUG) {\r
478                 log.append(" => ");\r
479                 UtilityExtensions.formatInput(log, text, index);\r
480                 System.out.println(Utility.escape(log.toString()));\r
481             }\r
482 \r
483             // Cumulative delta for insertions/deletions\r
484             delta += index.limit - limit;\r
485 \r
486             if (incremental) {\r
487                 // In the incremental case, only allow subsequent\r
488                 // transliterators to modify what has already been\r
489                 // completely processed by prior transliterators.  In the\r
490                 // non-incrmental case, allow each transliterator to\r
491                 // process the entire text.\r
492                 index.limit = index.start;\r
493             }\r
494         }\r
495 \r
496         compoundLimit += delta;\r
497 \r
498         // Start is good where it is -- where the last transliterator left\r
499         // it.  Limit needs to be put back where it was, modulo\r
500         // adjustments for deletions/insertions.\r
501         index.limit = compoundLimit;\r
502 \r
503         if (DEBUG) {\r
504             log.setLength(0);\r
505             log.append("CompoundTransliterator{" + getID() +\r
506                        (incremental ? "}i: OUT=" : "}: OUT="));\r
507             UtilityExtensions.formatInput(log, text, index);\r
508             System.out.println(Utility.escape(log.toString()));\r
509         }\r
510     }\r
511 \r
512     /**\r
513      * Compute and set the length of the longest context required by this transliterator.\r
514      * This is <em>preceding</em> context.\r
515      */\r
516     private void computeMaximumContextLength() {\r
517         int max = 0;\r
518         for (int i=0; i<trans.length; ++i) {\r
519             int len = trans[i].getMaximumContextLength();\r
520             if (len > max) {\r
521                 max = len;\r
522             }\r
523         }\r
524         setMaximumContextLength(max);\r
525     }\r
526 \r
527     /**\r
528      * Temporary hack for registry problem. Needs to be replaced by better architecture.\r
529      * @internal\r
530      * @deprecated\r
531      */\r
532     public Transliterator safeClone() {\r
533         UnicodeFilter filter = getFilter();\r
534         if (filter != null && filter instanceof UnicodeSet) {\r
535             filter = new UnicodeSet((UnicodeSet)filter);\r
536         }\r
537         return new CompoundTransliterator(getID(), filter, trans, numAnonymousRBTs);\r
538     }\r
539 }\r