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