2005-07-21 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / corlib / Mono.Globalization.Unicode / SortKeyBuffer.cs
1 using System;
2 using System.IO;
3 using System.Globalization;
4
5 namespace Mono.Globalization.Unicode
6 {
7         // Internal sort key storage that is reused during GetSortKey.
8         internal class SortKeyBuffer
9         {
10                 // l4s = small kana sensitivity, l4t = mark type,
11                 // l4k = katakana flag, l4w = kana width sensitivity
12                 int l1, l2, l3, l4s, l4t, l4k, l4w, l5;
13                 byte [] l1b, l2b, l3b, l4sb, l4tb, l4kb, l4wb, l5b;
14                 int level5LastPos;
15
16                 string source;
17                 bool processLevel2;
18                 bool frenchSort;
19                 bool frenchSorted;
20                 int lcid;
21                 CompareOptions options;
22
23                 public SortKeyBuffer (int lcid)
24                 {
25                 }
26
27                 public void Reset ()
28                 {
29                         l1 = l2 = l3 = l4s = l4t = l4k = l4w = l5 = 0;
30                         level5LastPos = 0;
31                         frenchSorted = false;
32                 }
33
34                 // It is used for CultureInfo.ClearCachedData().
35                 internal void ClearBuffer ()
36                 {
37                         l1b = l2b = l3b = l4sb = l4tb = l4kb = l4wb = l5b = null;
38                 }
39
40                 internal void Initialize (CompareOptions options, int lcid, string s, bool frenchSort)
41                 {
42                         this.source = s;
43                         this.lcid = lcid;
44                         this.options = options;
45                         int len = s.Length;
46                         processLevel2 = (options & CompareOptions.IgnoreNonSpace) == 0;
47                         this.frenchSort = frenchSort;
48
49                         // For Korean text it is likely to be much bigger (for
50                         // Jamo), but even in ko-KR most of the compared
51                         // strings won't be Hangul.
52                         if (l1b == null || l1b.Length < len)
53                                 l1b = new byte [len * 2 + 10];
54
55                         if (processLevel2 && (l2b == null || l2b.Length < len))
56                                 l2b = new byte [len + 10];
57                         if (l3b == null || l3b.Length < len)
58                                 l3b = new byte [len + 10];
59
60                         // This weight is used only in Japanese text.
61                         // We could expand the initial length as well as
62                         // primary length (actually x3), but even in ja-JP
63                         // most of the compared strings won't be Japanese.
64                         if (l4sb == null)
65                                 l4sb = new byte [10];
66                         if (l4tb == null)
67                                 l4tb = new byte [10];
68                         if (l4kb == null)
69                                 l4kb = new byte [10];
70                         if (l4wb == null)
71                                 l4wb = new byte [10];
72
73                         if (l5b == null)
74                                 l5b = new byte [10];
75                 }
76
77                 internal void AppendCJKExtension (byte lv1msb, byte lv1lsb)
78                 {
79                         AppendBufferPrimitive (0xFE, ref l1b, ref l1);
80                         AppendBufferPrimitive (0xFF, ref l1b, ref l1);
81                         AppendBufferPrimitive (lv1msb, ref l1b, ref l1);
82                         AppendBufferPrimitive (lv1lsb, ref l1b, ref l1);
83                         if (processLevel2)
84                                 AppendBufferPrimitive (2, ref l2b, ref l2);
85                         AppendBufferPrimitive (2, ref l3b, ref l3);
86                 }
87
88                 // LAMESPEC: Windows handles some of Hangul Jamo as to have
89                 // more than two primary weight values. However this causes
90                 // incorrect zero-termination. So I just ignore them and
91                 // treat it as usual character.
92                 /*
93                 internal void AppendJamo (byte category, byte lv1msb, byte lv1lsb)
94                 {
95                         AppendNormal (category, lv1msb, 0, 0);
96                         AppendBufferPrimitive (0xFF, ref l1b, ref l1);
97                         AppendBufferPrimitive (lv1lsb, ref l1b, ref l1);
98                         AppendBufferPrimitive (0xFF, ref l1b, ref l1);
99                         // FIXME: those values looks extraneous but might be
100                         // some advanced use. Worthy of digging into it.
101                         AppendBufferPrimitive (0, ref l1b, ref l1);
102                         AppendBufferPrimitive (0xFF, ref l1b, ref l1);
103                         AppendBufferPrimitive (0, ref l1b, ref l1);
104                 }
105                 */
106
107                 // Append sort key value from table normally.
108                 internal void AppendKana (byte category, byte lv1, byte lv2, byte lv3, bool isSmallKana, byte markType, bool isKatakana, bool isHalfWidth)
109                 {
110                         AppendNormal (category, lv1, lv2, lv3);
111
112                         AppendBufferPrimitive ((byte) (isSmallKana ? 0xC4 : 0xE4), ref l4sb, ref l4s);
113                         AppendBufferPrimitive (markType, ref l4tb, ref l4t);
114                         AppendBufferPrimitive ((byte) (isKatakana ? 0xC4 : 0xE4), ref l4kb, ref l4k);
115                         AppendBufferPrimitive ((byte) (isHalfWidth ? 0xC4 : 0xE4), ref l4wb, ref l4w);
116                 }
117
118                 // Append sort key value from table normally.
119                 internal void AppendNormal (byte category, byte lv1, byte lv2, byte lv3)
120                 {
121                         if (lv2 == 0)
122                                 lv2 = 2;
123                         if (lv3 == 0)
124                                 lv3 = 2;
125
126                         // Special weight processing
127                         if (category == 6 && (options & CompareOptions.StringSort) == 0) {
128                                 AppendLevel5 (category, lv1);
129                                 return;
130                         }
131
132                         // non-primary diacritical weight is added to that of
133                         // the previous character (and does not reset level 3
134                         // weight).
135                         if (processLevel2 && category == 1 && l1 > 0) {
136                                 lv2 = (byte) (lv2 + l2b [--l2]);
137                                 lv3 = l3b [--l3];
138                         }
139
140                         if (category != 1) {
141                                 AppendBufferPrimitive (category, ref l1b, ref l1);
142                                 AppendBufferPrimitive (lv1, ref l1b, ref l1);
143                         }
144                         if (processLevel2)
145                                 AppendBufferPrimitive (lv2, ref l2b, ref l2);
146                         AppendBufferPrimitive (lv3, ref l3b, ref l3);
147                 }
148
149                 // Append variable-weight character.
150                 // It uses level 2 index for counting offsets (since level1
151                 // might be longer than 1).
152                 private void AppendLevel5 (byte category, byte lv1)
153                 {
154                         // offset
155 #if false
156                         // If it strictly matches to Windows, offsetValue is always l2.
157                         int offsetValue = l2 - level5LastPos;
158                         // If it strictly matches ti Windows, no 0xFF here.
159                         for (; offsetValue > 8192; offsetValue -= 8192)
160                                 AppendBufferPrimitive (0xFF, ref l5b, ref l5);
161 #else
162                         // LAMESPEC: Windows cannot compute lv5 values for
163                         // those string that has length larger than 8064.
164                         // (It reminds me of SQL Server varchar length).
165                         int offsetValue = (l2 + 1) % 8192;
166 #endif
167                         AppendBufferPrimitive ((byte) ((offsetValue / 64) + 0x80), ref l5b, ref l5);
168                         AppendBufferPrimitive ((byte) (offsetValue % 64 * 4 + 3), ref l5b, ref l5);
169
170                         level5LastPos = l2;
171
172                         // sortkey value
173                         AppendBufferPrimitive (category, ref l5b, ref l5);
174                         AppendBufferPrimitive (lv1, ref l5b, ref l5);
175                 }
176
177                 private void AppendBufferPrimitive (byte value, ref byte [] buf, ref int bidx)
178                 {
179                         buf [bidx++] = value;
180                         if (bidx == buf.Length) {
181                                 byte [] tmp = new byte [bidx * 2];
182                                 Array.Copy (buf, tmp, buf.Length);
183                                 buf = tmp;
184                         }
185                 }
186
187                 public SortKey GetResultAndReset ()
188                 {
189                         SortKey ret = GetResult ();
190                         Reset ();
191                         return ret;
192                 }
193
194                 // For level2-5, 02 is the default and could be cut (implied).
195                 // 02 02 02 -> 0
196                 // 02 03 02 -> 2
197                 // 03 04 05 -> 3
198                 private int GetOptimizedLength (byte [] data, int len, byte defaultValue)
199                 {
200                         int cur = -1;
201                         for (int i = 0; i < len; i++)
202                                 if (data [i] != defaultValue)
203                                         cur = i;
204                         return cur + 1;
205                 }
206
207                 public SortKey GetResult ()
208                 {
209                         if (frenchSort && !frenchSorted) {
210                                 int i = 0;
211                                 for (; i < l2b.Length; i++)
212                                         if (l2b [i] == 0)
213                                                 break;
214                                 Array.Reverse (l2b, 0, i);
215                                 frenchSorted = true;
216                         }
217
218                         l2 = GetOptimizedLength (l2b, l2, 2);
219                         l3 = GetOptimizedLength (l3b, l3, 2);
220                         bool hasJapaneseWeight = (l4s > 0); // snapshot before being optimized
221                         l4s = GetOptimizedLength (l4sb, l4s, 0xE4);
222                         l4t = GetOptimizedLength (l4tb, l4t, 3);
223                         l4k = GetOptimizedLength (l4kb, l4k, 0xE4);
224                         l4w = GetOptimizedLength (l4wb, l4w, 0xE4);
225                         l5 = GetOptimizedLength (l5b, l5, 2);
226
227                         int length = l1 + l2 + l3 + l5 + 5;
228                         int jpLength = l4s + l4t + l4k + l4w;
229                         if (hasJapaneseWeight)
230                                 length += jpLength + 4;
231
232                         byte [] ret = new byte [length];
233                         Array.Copy (l1b, ret, l1);
234                         ret [l1] = 1; // end-of-level mark
235                         int cur = l1 + 1;
236                         if (l2 > 0)
237                                 Array.Copy (l2b, 0, ret, cur, l2);
238                         cur += l2;
239                         ret [cur++] = 1; // end-of-level mark
240                         if (l3 > 0)
241                                 Array.Copy (l3b, 0, ret, cur, l3);
242                         cur += l3;
243                         ret [cur++] = 1; // end-of-level mark
244                         if (hasJapaneseWeight) {
245                                 Array.Copy (l4sb, 0, ret, cur, l4s);
246                                 cur += l4s;
247                                 ret [cur++] = 0xFF; // end-of-jp-subsection
248                                 Array.Copy (l4tb, 0, ret, cur, l4t);
249                                 cur += l4t;
250                                 ret [cur++] = 2; // end-of-jp-middle-subsection
251                                 Array.Copy (l4kb, 0, ret, cur, l4k);
252                                 cur += l4k;
253                                 ret [cur++] = 0xFF; // end-of-jp-subsection
254                                 Array.Copy (l4wb, 0, ret, cur, l4w);
255                                 cur += l4w;
256                                 ret [cur++] = 0xFF; // end-of-jp-subsection
257                         }
258                         ret [cur++] = 1; // end-of-level mark
259                         if (l5 > 0)
260                                 Array.Copy (l5b, 0, ret, cur, l5);
261                         cur += l5;
262                         ret [cur++] = 0; // end-of-data mark
263                         return new SortKey (lcid, source, ret, options, l1, l2, l3, l4s, l4t, l4k, l4w, l5);
264                 }
265         }
266 }