]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/tools/misc/src/com/ibm/icu/dev/tool/translit/UnicodeSetCloseOver.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / tools / misc / src / com / ibm / icu / dev / tool / translit / UnicodeSetCloseOver.java
1 /*\r
2 **********************************************************************\r
3 * Copyright (c) 2003-2010, International Business Machines\r
4 * Corporation and others.  All Rights Reserved.\r
5 **********************************************************************\r
6 * Author: Alan Liu\r
7 * Created: February 11 2003\r
8 * Since: ICU 2.6\r
9 **********************************************************************\r
10 */\r
11 package com.ibm.icu.dev.tool.translit;\r
12 \r
13 import java.io.FileOutputStream;\r
14 import java.io.IOException;\r
15 import java.io.PrintStream;\r
16 import java.util.Collections;\r
17 import java.util.Comparator;\r
18 import java.util.HashMap;\r
19 import java.util.Iterator;\r
20 import java.util.Locale;\r
21 import java.util.Map;\r
22 import java.util.Set;\r
23 import java.util.TreeSet;\r
24 import java.util.Vector;\r
25 \r
26 import com.ibm.icu.impl.Utility;\r
27 import com.ibm.icu.lang.UCharacter;\r
28 import com.ibm.icu.text.BreakIterator;\r
29 import com.ibm.icu.text.UTF16;\r
30 import com.ibm.icu.text.UnicodeSet;\r
31 \r
32 /**\r
33  * This class produces the data tables used by the closeOver() method\r
34  * of UnicodeSet.\r
35  *\r
36  * Whenever the Unicode database changes, this tool must be re-run\r
37  * (AFTER the data file(s) underlying ICU4J are udpated).\r
38  *\r
39  * The output of this tool should then be pasted into the appropriate\r
40  * files:\r
41  *\r
42  * ICU4J: com.ibm.icu.text.UnicodeSet.java\r
43  * ICU4C: /icu/source/common/uniset.cpp\r
44  */\r
45 class UnicodeSetCloseOver {\r
46 \r
47     // Our output files\r
48     static final String JAVA_OUT          = "to_UnicodeSet.java";\r
49     static final String JAVA_CHARPROP_OUT = "to_UCharacterProperty.java";\r
50     static final String C_SET_OUT         = "to_uniset.cpp";\r
51     static final String C_UCHAR_OUT       = "to_uchar.c";\r
52 \r
53     // Source code "do not edit" warning\r
54     static final String WARNING = "MACHINE-GENERATED; Unicode version " +\r
55         UCharacter.getUnicodeVersion() +\r
56         "; DO NOT EDIT; See " +\r
57         UnicodeSetCloseOver.class.getName();\r
58 \r
59     // Case folding options flag.  This must correspond to the options\r
60     // used in UnicodeSet.closeOver() in Java and C++.\r
61     static final boolean DEFAULT_CASE_MAP = true; // false for Turkish\r
62 \r
63     public static void main(String[] args) throws IOException {\r
64         System.out.println("This tool will generate several output files.  Each is named according");\r
65         System.out.println("the target file.  For example, the contents of to_UnicodeSet.java should");\r
66         System.out.println("be pasted into UnicodeSet.java.");\r
67         System.out.println();\r
68 \r
69         generateCaseData();\r
70     }\r
71 \r
72     /**\r
73      * Create a map of String => Set.  The String in this case is a\r
74      * folded string for which\r
75      * UCharacter.foldCase(folded. DEFAULT_CASE_MAP).equals(folded).\r
76      * The Set contains all single-character strings x for which\r
77      * UCharacter.foldCase(x, DEFAULT_CASE_MAP).equals(folded), as\r
78      * well as folded itself.\r
79      */\r
80     static Map createCaseFoldEquivalencyClasses() {\r
81         Map equivClasses = new HashMap();\r
82         for (int i = 0; i <= 0x10FFFF; ++i) {\r
83             int cat = UCharacter.getType(i);\r
84             if (cat == Character.UNASSIGNED || cat == Character.PRIVATE_USE)\r
85                 continue;\r
86 \r
87             String cp = UTF16.valueOf(i);\r
88             String folded = UCharacter.foldCase(cp, DEFAULT_CASE_MAP);\r
89             if (folded.equals(cp)) continue;\r
90 \r
91             // At this point, have different case folding.  Add\r
92             // the code point and its folded equivalent into the\r
93             // equivalency class.\r
94             TreeSet s = (TreeSet) equivClasses.get(folded);\r
95             if (s == null) {\r
96                 s = new TreeSet();\r
97                 s.add(folded); // add the case fold result itself\r
98                 equivClasses.put(folded, s);\r
99             }\r
100             s.add(cp);\r
101         }\r
102         return equivClasses;\r
103     }\r
104 \r
105     /**\r
106      * Analyze the case fold equivalency classes.  Break them into two\r
107      * groups: 'pairs', and 'nonpairs'.  Create a tally of the length\r
108      * configurations of the nonpairs.\r
109      *\r
110      * Length configurations of equivalency classes, as of Unicode\r
111      * 3.2.  Most of the classes (83%) have two single codepoints.\r
112      * Here "112:28" means there are 28 equivalency classes with 2\r
113      * single codepoints and one string of length 2.\r
114      *\r
115      * 11:656\r
116      * 111:16\r
117      * 1111:3\r
118      * 112:28\r
119      * 113:2\r
120      * 12:31\r
121      * 13:12\r
122      * 22:38\r
123      *\r
124      * Note: This method does not count the frequencies of the\r
125      * different length configurations (as shown above after ':'); it\r
126      * merely records which configurations occur.\r
127      *\r
128      * @param pairs Accumulate equivalency classes that consist of\r
129      * exactly two codepoints here.  This is 83+% of the classes.\r
130      * E.g., {"a", "A"}.\r
131      * @param nonpairs Accumulate other equivalency classes here, as\r
132      * lists of strings.  E,g, {"st", "\uFB05", "\uFB06"}.\r
133      * @param lengths Accumulate a list of unique length structures,\r
134      * not including pairs.  Each length structure is represented by a\r
135      * string of digits.  The digit string "12" means the equivalency\r
136      * class contains a single code point and a string of length 2.\r
137      * Typical contents of 'lengths': { "111", "1111", "112",\r
138      * "113", "12", "13", "22" }.  Note the absence of "11".\r
139      */\r
140     static void analyzeCaseData(Map equivClasses,\r
141                                 StringBuffer pairs,\r
142                                 Vector nonpairs,\r
143                                 Vector lengths) {\r
144         Iterator i = new TreeSet(equivClasses.keySet()).iterator();\r
145         StringBuffer buf = new StringBuffer();\r
146         while (i.hasNext()) {\r
147             Object key = i.next();\r
148             Vector v = new Vector((Set) equivClasses.get(key));\r
149             if (v.size() == 2) {\r
150                 String a = (String) v.elementAt(0);\r
151                 String b = (String) v.elementAt(1);\r
152                 if (a.length() == 1 && b.length() == 1) {\r
153                     pairs.append(a).append(b);\r
154                     continue;\r
155                     // Note that pairs are included in 'lengths'\r
156                 }\r
157             }\r
158             String[] a = new String[v.size()];\r
159             v.toArray(a);\r
160             nonpairs.add(a);\r
161             //int singleCount = 0;\r
162             //int stringCount = 0;\r
163             // Make a string of the lengths, e.g., "111" means 3\r
164             // single code points; "13" means a single code point\r
165             // and a string of length 3.\r
166             v.clear();\r
167             for (int j=0; j<a.length; ++j) {\r
168                 v.add(new Integer(a[j].length()));\r
169             }\r
170             Collections.sort(v);\r
171             buf.setLength(0);\r
172             for (int j=0; j<v.size(); ++j) {\r
173                 buf.append(String.valueOf(v.elementAt(j)));\r
174             }\r
175             if (!lengths.contains(buf.toString())) {\r
176                 lengths.add(buf.toString());\r
177             }\r
178         }\r
179     }\r
180 \r
181     static void generateCaseData() throws IOException {\r
182 \r
183         Map equivClasses = createCaseFoldEquivalencyClasses();\r
184 \r
185         // Accumulate equivalency classes that consist of exactly\r
186         // two codepoints here.  This is 83+% of the classes.\r
187         // E.g., {"a", "A"}.\r
188         StringBuffer pairs = new StringBuffer();\r
189 \r
190         // Accumulate other equivalency classes here, as lists\r
191         // of strings.  E,g, {"st", "\uFB05", "\uFB06"}.\r
192         Vector nonpairs = new Vector(); // contains String[]\r
193         Vector lengths = new Vector(); // "111", "12", "22", etc.\r
194 \r
195         analyzeCaseData(equivClasses, pairs, nonpairs, lengths);\r
196 \r
197         //-------------------------------------------------------------\r
198         // Emit Java source\r
199         PrintStream out = new PrintStream(new FileOutputStream(JAVA_OUT));\r
200         System.out.println("Writing " + JAVA_OUT);\r
201 \r
202         out.println("    // " + WARNING);\r
203         out.println("    private static final String CASE_PAIRS =");\r
204         out.println(Utility.formatForSource(pairs.toString()) + ";");\r
205         out.println();\r
206         out.println("    // " + WARNING);\r
207         out.println("    private static final String[][] CASE_NONPAIRS = {");\r
208         for (int j=0; j<nonpairs.size(); ++j) {\r
209             String[] a = (String[]) nonpairs.elementAt(j);\r
210             out.print("        {");\r
211             for (int k=0; k<a.length; ++k) {\r
212                 if (k != 0) out.print(", ");\r
213                 out.print(Utility.format1ForSource(a[k]));\r
214             }\r
215             out.println("},");\r
216         }\r
217         out.println("    };");\r
218 \r
219         //-------------------------------------------------------------\r
220         // Emit C++ source\r
221 \r
222         // In C++, the pairs are again emitted in an array, but this\r
223         // array is the final representation form -- it will not be\r
224         // reprocessed into a hash.  It will be binary searched by\r
225         // looking at the even elements [0], [2], [4], etc., and\r
226         // ignoring the odd elements.  The even elements must contain\r
227         // the folded members of the pairs.  That is, in the pair\r
228         // {'A', 'a'}, the even element must be 'a', not 'A'.  Then a\r
229         // code point to be located is first folded ('Y' => 'y') then\r
230         // it binary searched against [0]='A', [2]='B', etc.  When a\r
231         // match is found at k, the pair is [k], [k+1].\r
232 \r
233         out = new PrintStream(new FileOutputStream(C_SET_OUT));\r
234         System.out.println("Writing " + C_SET_OUT);\r
235 \r
236         // Sort the pairs.  They must be ordered by the folded element.\r
237         // Store these as two-character strings, with charAt(0) being\r
238         // the folded member of the pair.\r
239         TreeSet sortPairs = new TreeSet(new Comparator() {\r
240             public int compare(Object a, Object b) {\r
241                 return ((int) ((String) a).charAt(0)) -\r
242                        ((int) ((String) b).charAt(0));\r
243             }\r
244             public boolean equals(Object obj) {\r
245                 return false;\r
246             }\r
247         });\r
248         for (int i=0; i<pairs.length(); i+=2) {\r
249             String a = String.valueOf(pairs.charAt(i));\r
250             String b = String.valueOf(pairs.charAt(i+1));\r
251             String folded = UCharacter.foldCase(a, DEFAULT_CASE_MAP);\r
252             if (a.equals(folded)) {\r
253                 sortPairs.add(a + b);\r
254             } else {\r
255                 sortPairs.add(b + a);\r
256             }\r
257         }\r
258 \r
259         // Emit the pairs\r
260         out.println("// " + WARNING);\r
261         out.println("static const UChar CASE_PAIRS[] = {");\r
262         Iterator it = sortPairs.iterator();\r
263         while (it.hasNext()) {\r
264             out.print("    ");\r
265             int n = 0;\r
266             while (n++ < 5 && it.hasNext()) {\r
267                 String s = (String) it.next();\r
268                 //out.print((int) s.charAt(0) + "," +\r
269                 //                 (int) s.charAt(1) + ",");\r
270                 out.print("0x" + Utility.hex(s.charAt(0)) + ",0x" +\r
271                                  Utility.hex(s.charAt(1)) + ",");\r
272             }\r
273             out.println();\r
274         }\r
275         out.println("};");\r
276         out.println();\r
277 \r
278         // The non-pairs are encoded in the following way.  All the\r
279         // single codepoints in each class are grouped together\r
280         // followed by a zero.  Then each multi-character string is\r
281         // added, followed by a zero.  Finally, another zero is added.\r
282         // Some examples:\r
283         //  {"iQ", "R"}           =>  [ 'R', 0, 'i', 'Q', 0, 0 ]\r
284         //  {"S", "D", "F", "G"}  =>  [ 'S', 'D', 'F', 'G', 0, 0 ]\r
285         //  {"jW", "jY"}          =>  [ 0, 'j', 'W', 0, 'j', 'Y', 0, 0 ]\r
286         // The end-result is a short, flat array of UChar values that\r
287         // can be used to initialize a UChar[] array in C.\r
288         \r
289         int maxLen = 0; // Maximum encoded length of any class, including zeros\r
290         out.println("// " + WARNING);\r
291         out.println("static const CaseEquivClass CASE_NONPAIRS[] = {");\r
292         for (int j=0; j<nonpairs.size(); ++j) {\r
293             int len = 0;\r
294             String[] a = (String[]) nonpairs.elementAt(j);\r
295             out.print("    {");\r
296             // Emit single code points\r
297             for (int k=0; k<a.length; ++k) {\r
298                 if (a[k].length() != 1) continue;\r
299                 //out.print((int) a[k].charAt(0) + ",");\r
300                 out.print("0x"+Utility.hex(a[k].charAt(0)) + ",");\r
301                 ++len;\r
302             }\r
303             out.print("0,  "); // End of single code points\r
304             ++len;\r
305             // Emit multi-character strings\r
306             for (int k=0; k<a.length; ++k) {\r
307                 if (a[k].length() == 1) continue;\r
308                 for (int m=0; m<a[k].length(); ++m) {\r
309                     //out.print((int) a[k].charAt(m) + ",");\r
310                     out.print("0x"+Utility.hex(a[k].charAt(m)) + ",");\r
311                     ++len;\r
312                 }\r
313                 out.print("0, "); // End of string\r
314                 ++len;\r
315             }\r
316             out.println("0},"); // End of equivalency class\r
317             ++len;\r
318             if (len > maxLen) maxLen = len;\r
319         }\r
320         out.println("};");\r
321 \r
322         // Make sure the CaseEquivClass data can fit.\r
323         if (maxLen > 8) {\r
324             throw new RuntimeException("Must adjust CaseEquivClass to accomodate " + maxLen + " UChars");\r
325         }\r
326 \r
327         // Also make sure that we can map into this array using a\r
328         // CompactByteArray.  We could do this check above, but we\r
329         // keep it here, adjacent to the maxLen check.  We use one\r
330         // value (-1 == 255) to indicate "no value."\r
331         if (nonpairs.size() > 255) {\r
332             throw new RuntimeException("Too many CASE_NONPAIRS array elements to be indexed by a CompactByteArray");\r
333         }\r
334 \r
335         //-------------------------------------------------------------\r
336         // Case-unique set:  All characters c for which closeOver(c)==c.\r
337 \r
338         // UPDATE: Instead of using this, we're using the related\r
339         // notion of Case_Sensitive.  See below.  Note that\r
340         // Case_Sensitive != ^Case_Unique.\r
341 \r
342         if (false) {\r
343             UnicodeSet caseUnique = new UnicodeSet();\r
344             for (int i = 0; i <= 0x10FFFF; ++i) {\r
345                 String cp = UTF16.valueOf(i);\r
346                 if (equivClasses.get(UCharacter.foldCase(cp, DEFAULT_CASE_MAP)) == null) {\r
347                     caseUnique.add(i);\r
348                 }\r
349             }\r
350             // out.println("caseUnique = " + caseUnique.toPattern(true));\r
351         }\r
352 \r
353         UnicodeSet caseSensitive = getCaseSensitive();\r
354         //System.out.println("caseSensitive = " + caseSensitive.toPattern(true));\r
355 \r
356         // Now for C, emit an array of ranges\r
357         out = new PrintStream(new FileOutputStream(C_UCHAR_OUT));\r
358         System.out.println("Writing " + C_UCHAR_OUT);\r
359 \r
360         out.println("/* " + WARNING + " */");\r
361         emitUCharRangesArray(out, caseSensitive, "CASE_SENSITIVE_RANGES");\r
362 \r
363         // For Java, emit a string with the ranges (each pair of chars\r
364         // in the string is a range).\r
365         out = new PrintStream(new FileOutputStream(JAVA_CHARPROP_OUT));\r
366         System.out.println("Writing " + JAVA_CHARPROP_OUT);\r
367         out.println("    // " + WARNING);\r
368         emitRangesString(out, caseSensitive, "CASE_SENSITIVE_RANGES");\r
369     }\r
370 \r
371     /**\r
372      * Create the set of case-sensitive characters.  These are characters\r
373      * that participate in any case mapping operation as a source or\r
374      * as a member of a target string.\r
375      */\r
376     static UnicodeSet getCaseSensitive() {\r
377         UnicodeSet caseSensitive = new UnicodeSet();\r
378         Locale loc = Locale.US;\r
379         BreakIterator bi = BreakIterator.getTitleInstance(loc);\r
380         for (int c = 0; c <= 0x10FFFF; ++c) {\r
381             String cp = UTF16.valueOf(c);\r
382             for (int j=0; j<4; ++j) {\r
383                 String s = null;\r
384                 switch (j) {\r
385                 case 0: s = UCharacter.toUpperCase(loc, cp); break;\r
386                 case 1: s = UCharacter.toLowerCase(loc, cp); break;\r
387                 case 2: s = UCharacter.toTitleCase(loc, cp, bi); break;\r
388                 case 3: s = UCharacter.foldCase(cp, DEFAULT_CASE_MAP); break;\r
389                 }\r
390                 if (!s.equals(cp)) {\r
391                     int cc;\r
392                     for (int k=0; k<s.length(); k+=UTF16.getCharCount(cc)) {\r
393                         cc = UTF16.charAt(s, k);\r
394                         caseSensitive.add(cc);\r
395                     }\r
396                     for (int k=0; k<cp.length(); k+=UTF16.getCharCount(cc)) {\r
397                         cc = UTF16.charAt(cp, k);\r
398                         caseSensitive.add(cc);\r
399                     }\r
400                 }\r
401             }\r
402             // Also do the single-codepoint API.  This shouldn't add any\r
403             // code points, but we do it for completeness.\r
404             for (int j=0; j<4; ++j) {\r
405                 int d = 0;\r
406                 switch (j) {\r
407                 case 0: d = UCharacter.toUpperCase(c); break;\r
408                 case 1: d = UCharacter.toLowerCase(c); break;\r
409                 case 2: d = UCharacter.toTitleCase(c); break;\r
410                 case 3: d = UCharacter.foldCase(c, DEFAULT_CASE_MAP); break;\r
411                 }\r
412                 if (d != c) {\r
413                     if (!caseSensitive.contains(c) ||\r
414                         !caseSensitive.contains(d)) {\r
415                         System.out.println("Warning: code point " + c +\r
416                                            " => " + d + " created NEW MAPPING"+\r
417                                            " for Case_Sensitive");\r
418                     }\r
419                     caseSensitive.add(c);\r
420                     caseSensitive.add(d);\r
421                 }\r
422             }\r
423         }\r
424         return caseSensitive;\r
425     }\r
426 \r
427     /**\r
428      * Given a UnicodeSet, emit it as an array of UChar pairs.  Each\r
429      * pair will be the start/end of a range.  Code points >= U+10000\r
430      * will be represented as surrogate pairs.\r
431      */\r
432     static void emitUCharRangesArray(PrintStream out, UnicodeSet set, String id) {\r
433         // Store the pairs in a StringBuffer.  This handles surrogate\r
434         // representation.\r
435         StringBuffer buf = new StringBuffer();\r
436         for (int i=0; i<set.getRangeCount(); ++i) {\r
437             UTF16.append(buf, set.getRangeStart(i));\r
438             UTF16.append(buf, set.getRangeEnd(i));\r
439         }\r
440         // Emit the pairs\r
441         out.println("static const UChar " + id + "[] = {");\r
442         for (int i=0; i<buf.length(); ) {\r
443             out.print("    ");\r
444             for (int n=0; n++<10 && i<buf.length(); ++i) {\r
445                 out.print("0x" + Utility.hex(buf.charAt(i), 4) + ',');\r
446             }\r
447             out.println();\r
448         }\r
449         out.println("};");\r
450         out.println("#define " + id + "_LENGTH (sizeof(" + id +\r
451                     ")/sizeof(" + id + "[0]))");\r
452     }\r
453 \r
454     /**\r
455      * Given a UnicodeSet, emit it as a Java string.  The most economical\r
456      * format is not the pattern, but instead a pairs list, with each\r
457      * range pair represented as two adjacent characters.\r
458      */\r
459     static void emitRangesString(PrintStream out, UnicodeSet set, String id) {\r
460         // Store the pairs in a StringBuffer.  This handles surrogate\r
461         // representation.\r
462         StringBuffer buf = new StringBuffer();\r
463         for (int i=0; i<set.getRangeCount(); ++i) {\r
464             UTF16.append(buf, set.getRangeStart(i));\r
465             UTF16.append(buf, set.getRangeEnd(i));\r
466         }\r
467         // Emit the pairs\r
468         out.println("    private static final String " + id + " =");\r
469         out.println(Utility.formatForSource(buf.toString()) + ";");\r
470     }\r
471 }\r