2 *******************************************************************************
3 * Copyright (C) 2001-2010, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 *******************************************************************************
8 package com.ibm.icu.dev.test.bidi;
10 import java.util.Arrays;
12 import com.ibm.icu.dev.test.TestFmwk;
13 import com.ibm.icu.impl.Utility;
14 import com.ibm.icu.lang.UCharacter;
15 import com.ibm.icu.text.Bidi;
16 import com.ibm.icu.text.BidiRun;
17 import com.ibm.icu.util.VersionInfo;
20 * A base class for the Bidi test suite.
22 * @author Lina Kemmel, Matitiahu Allouche
25 public class BidiTest extends TestFmwk {
27 protected static final char[] charFromDirProp = {
28 /* L R EN ES ET AN CS B S WS ON */
29 0x61, 0x5d0, 0x30, 0x2f, 0x25, 0x660, 0x2c, 0xa, 0x9, 0x20, 0x26,
30 /* LRE LRO AL RLE RLO PDF NSM BN */
31 0x202a, 0x202d, 0x627, 0x202b, 0x202e, 0x202c, 0x308, 0x200c
35 initCharFromDirProps();
38 private static void initCharFromDirProps() {
39 final VersionInfo ucd401 = VersionInfo.getInstance(4, 0, 1, 0);
40 VersionInfo ucdVersion = VersionInfo.getInstance(0, 0, 0, 0);
42 /* lazy initialization */
43 if (ucdVersion.getMajor() > 0) {
47 ucdVersion = UCharacter.getUnicodeVersion();
48 if (ucdVersion.compareTo(ucd401) >= 0) {
49 /* Unicode 4.0.1 changes bidi classes for +-/ */
50 /* change ES character from / to + */
51 charFromDirProp[TestData.ES] = 0x2b;
55 protected boolean assertEquals(String message, String expected, String actual,
56 String src, String mode, String option,
58 if (expected == null || actual == null) {
59 return super.assertEquals(message, expected, actual);
61 if (expected.equals(actual)) {
67 errcontln("source : \"" + Utility.escape(src) + "\"");
69 errcontln("expected : \"" + Utility.escape(expected) + "\"");
70 errcontln("actual : \"" + Utility.escape(actual) + "\"");
72 errcontln("reordering mode : " + mode);
75 errcontln("reordering option : " + option);
78 errcontln("paragraph level : " + level);
83 protected static String valueOf(int[] array) {
84 StringBuffer result = new StringBuffer(array.length * 4);
85 for (int i = 0; i < array.length; i++) {
87 result.append(array[i]);
89 return result.toString();
92 private static final String[] modeDescriptions = {
94 "REORDER_NUMBERS_SPECIAL",
95 "REORDER_GROUP_NUMBERS_WITH_R",
97 "REORDER_INVERSE_NUMBERS_AS_L",
98 "REORDER_INVERSE_LIKE_DIRECT",
99 "REORDER_INVERSE_FOR_NUMBERS_SPECIAL"
102 protected static String modeToString(int mode) {
103 if (mode < Bidi.REORDER_DEFAULT ||
104 mode > Bidi.REORDER_INVERSE_FOR_NUMBERS_SPECIAL) {
107 return modeDescriptions[mode];
110 private static final short SETPARA_MASK = Bidi.OPTION_INSERT_MARKS |
111 Bidi.OPTION_REMOVE_CONTROLS | Bidi.OPTION_STREAMING;
113 private static final String[] setParaDescriptions = {
114 "OPTION_INSERT_MARKS",
115 "OPTION_REMOVE_CONTROLS",
119 protected static String spOptionsToString(int option) {
120 return optionToString(option, SETPARA_MASK, setParaDescriptions);
123 private static final int MAX_WRITE_REORDERED_OPTION = Bidi.OUTPUT_REVERSE;
124 private static final int REORDER_MASK = (MAX_WRITE_REORDERED_OPTION << 1) - 1;
126 private static final String[] writeReorderedDescriptions = {
127 "KEEP_BASE_COMBINING", // 1
129 "INSERT_LRM_FOR_NUMERIC", // 4
130 "REMOVE_BIDI_CONTROLS", // 8
131 "OUTPUT_REVERSE" // 16
134 public static String wrOptionsToString(int option) {
135 return optionToString(option, REORDER_MASK, writeReorderedDescriptions);
137 public static String optionToString(int option, int mask,
138 String[] descriptions) {
139 StringBuffer desc = new StringBuffer(50);
141 if ((option &= mask) == 0) {
146 for (int i = 0; option > 0; i++, option >>= 1) {
147 if ((option & 1) != 0) {
148 if (desc.length() > 0) {
151 desc.append(descriptions[i]);
154 return desc.toString();
157 static final String columnString =
158 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
159 static final char[] columns = columnString.toCharArray();
160 private static final int TABLE_SIZE = 256;
161 private static boolean tablesInitialized = false;
162 private static char[] pseudoToUChar;
163 private static char[] UCharToPseudo; /* used for Unicode chars < 0x0100 */
164 private static char[] UCharToPseud2; /* used for Unicode chars >=0x0100 */
166 static void buildPseudoTables()
168 The rules for pseudo-Bidi are as follows:
176 - A-F == Arabic Letters 0631-0636
177 - G-V == Hebrew letters 05d7-05ea
178 - W-Z == Unassigned RTL 08d0-08d3
179 - 0-5 == western digits 0030-0035
180 - 6-9 == Arabic-Indic digits 0666-0669
181 - ` == Combining Grave Accent 0300 (NSM)
182 - ~ == Delete 007f (BN)
183 - | == Paragraph Separator 2029 (B)
184 - _ == Info Separator 1 001f (S)
185 All other characters represent themselves as Latin-1, with the corresponding
193 /* initialize all tables to unknown */
194 pseudoToUChar = new char[TABLE_SIZE];
195 UCharToPseudo = new char[TABLE_SIZE];
196 UCharToPseud2 = new char[TABLE_SIZE];
197 for (i = 0; i < TABLE_SIZE; i++) {
198 pseudoToUChar[i] = 0xFFFD;
199 UCharToPseudo[i] = '?';
200 UCharToPseud2[i] = '?';
202 /* initialize non letters or digits */
203 pseudoToUChar[ 0 ] = 0x0000; UCharToPseudo[0x00] = 0 ;
204 pseudoToUChar[' '] = 0x0020; UCharToPseudo[0x20] = ' ';
205 pseudoToUChar['!'] = 0x0021; UCharToPseudo[0x21] = '!';
206 pseudoToUChar['"'] = 0x0022; UCharToPseudo[0x22] = '"';
207 pseudoToUChar['#'] = 0x0023; UCharToPseudo[0x23] = '#';
208 pseudoToUChar['$'] = 0x0024; UCharToPseudo[0x24] = '$';
209 pseudoToUChar['%'] = 0x0025; UCharToPseudo[0x25] = '%';
210 pseudoToUChar['\'']= 0x0027; UCharToPseudo[0x27] = '\'';
211 pseudoToUChar['('] = 0x0028; UCharToPseudo[0x28] = '(';
212 pseudoToUChar[')'] = 0x0029; UCharToPseudo[0x29] = ')';
213 pseudoToUChar['*'] = 0x002A; UCharToPseudo[0x2A] = '*';
214 pseudoToUChar['+'] = 0x002B; UCharToPseudo[0x2B] = '+';
215 pseudoToUChar[','] = 0x002C; UCharToPseudo[0x2C] = ',';
216 pseudoToUChar['-'] = 0x002D; UCharToPseudo[0x2D] = '-';
217 pseudoToUChar['.'] = 0x002E; UCharToPseudo[0x2E] = '.';
218 pseudoToUChar['/'] = 0x002F; UCharToPseudo[0x2F] = '/';
219 pseudoToUChar[':'] = 0x003A; UCharToPseudo[0x3A] = ':';
220 pseudoToUChar[';'] = 0x003B; UCharToPseudo[0x3B] = ';';
221 pseudoToUChar['<'] = 0x003C; UCharToPseudo[0x3C] = '<';
222 pseudoToUChar['='] = 0x003D; UCharToPseudo[0x3D] = '=';
223 pseudoToUChar['>'] = 0x003E; UCharToPseudo[0x3E] = '>';
224 pseudoToUChar['?'] = 0x003F; UCharToPseudo[0x3F] = '?';
225 pseudoToUChar['\\']= 0x005C; UCharToPseudo[0x5C] = '\\';
226 /* initialize specially used characters */
227 pseudoToUChar['`'] = 0x0300; UCharToPseud2[0x00] = '`'; /* NSM */
228 pseudoToUChar['@'] = 0x200E; UCharToPseud2[0x0E] = '@'; /* LRM */
229 pseudoToUChar['&'] = 0x200F; UCharToPseud2[0x0F] = '&'; /* RLM */
230 pseudoToUChar['_'] = 0x001F; UCharToPseudo[0x1F] = '_'; /* S */
231 pseudoToUChar['|'] = 0x2029; UCharToPseud2[0x29] = '|'; /* B */
232 pseudoToUChar['['] = 0x202A; UCharToPseud2[0x2A] = '['; /* LRE */
233 pseudoToUChar[']'] = 0x202B; UCharToPseud2[0x2B] = ']'; /* RLE */
234 pseudoToUChar['^'] = 0x202C; UCharToPseud2[0x2C] = '^'; /* PDF */
235 pseudoToUChar['{'] = 0x202D; UCharToPseud2[0x2D] = '{'; /* LRO */
236 pseudoToUChar['}'] = 0x202E; UCharToPseud2[0x2E] = '}'; /* RLO */
237 pseudoToUChar['~'] = 0x007F; UCharToPseudo[0x7F] = '~'; /* BN */
238 /* initialize western digits */
239 for (i = 0, uchar = 0x0030; i < 6; i++, uchar++) {
241 pseudoToUChar[c] = uchar;
242 UCharToPseudo[uchar & 0x00ff] = c;
244 /* initialize Hindi digits */
245 for (i = 6, uchar = 0x0666; i < 10; i++, uchar++) {
247 pseudoToUChar[c] = uchar;
248 UCharToPseud2[uchar & 0x00ff] = c;
250 /* initialize Arabic letters */
251 for (i = 10, uchar = 0x0631; i < 16; i++, uchar++) {
253 pseudoToUChar[c] = uchar;
254 UCharToPseud2[uchar & 0x00ff] = c;
256 /* initialize Hebrew letters */
257 for (i = 16, uchar = 0x05D7; i < 32; i++, uchar++) {
259 pseudoToUChar[c] = uchar;
260 UCharToPseud2[uchar & 0x00ff] = c;
262 /* initialize Unassigned code points */
263 for (i = 32, uchar = 0x08D0; i < 36; i++, uchar++) {
265 pseudoToUChar[c] = uchar;
266 UCharToPseud2[uchar & 0x00ff] = c;
268 /* initialize Latin lower case letters */
269 for (i = 36, uchar = 0x0061; i < 62; i++, uchar++) {
271 pseudoToUChar[c] = uchar;
272 UCharToPseudo[uchar & 0x00ff] = c;
274 tablesInitialized = true;
277 /*----------------------------------------------------------------------*/
279 static String pseudoToU16(String input)
280 /* This function converts a pseudo-Bidi string into a char string.
281 It returns the char string.
284 int len = input.length();
285 char[] output = new char[len];
287 if (!tablesInitialized) {
290 for (i = 0; i < len; i++)
291 output[i] = pseudoToUChar[input.charAt(i)];
292 return new String(output);
295 /*----------------------------------------------------------------------*/
297 static String u16ToPseudo(String input)
298 /* This function converts a char string into a pseudo-Bidi string.
299 It returns the pseudo-Bidi string.
302 int len = input.length();
303 char[] output = new char[len];
306 if (!tablesInitialized) {
309 for (i = 0; i < len; i++)
311 uchar = input.charAt(i);
312 output[i] = uchar < 0x0100 ? UCharToPseudo[uchar] :
313 UCharToPseud2[uchar & 0x00ff];
315 return new String(output);
318 void errcont(String message) {
319 msg(message, ERR, false, false);
322 void errcontln(String message) {
323 msg(message, ERR, false, true);
326 void printCaseInfo(Bidi bidi, String src, String dst)
328 int length = bidi.getProcessedLength();
329 byte[] levels = bidi.getLevels();
330 char[] levelChars = new char[length];
332 int runCount = bidi.countRuns();
333 errcontln("========================================");
334 errcontln("Processed length: " + length);
335 for (int i = 0; i < length; i++) {
339 } else if (lev < columns.length) {
340 levelChars[i] = columns[lev];
345 errcontln("Levels: " + new String(levelChars));
346 errcontln("Source: " + src);
347 errcontln("Result: " + dst);
348 errcontln("Direction: " + bidi.getDirection());
349 errcontln("paraLevel: " + Byte.toString(bidi.getParaLevel()));
350 errcontln("reorderingMode: " + modeToString(bidi.getReorderingMode()));
351 errcontln("reorderingOptions: " + spOptionsToString(bidi.getReorderingOptions()));
352 errcont("Runs: " + runCount + " => logicalStart.length/level: ");
353 for (int i = 0; i < runCount; i++) {
355 run = bidi.getVisualRun(i);
356 errcont(" " + run.getStart() + "." + run.getLength() + "/" +
357 run.getEmbeddingLevel());
362 static final String mates1 = "<>()[]{}";
363 static final String mates2 = "><)(][}{";
364 static final char[] mates1Chars = mates1.toCharArray();
365 static final char[] mates2Chars = mates2.toCharArray();
367 boolean matchingPair(Bidi bidi, int i, char c1, char c2)
372 /* For REORDER_RUNS_ONLY, it would not be correct to check levels[i],
373 so we use the appropriate run's level, which is good for all cases.
375 if (bidi.getLogicalRun(i).getDirection() == 0) {
378 for (int k = 0; k < mates1Chars.length; k++) {
379 if ((c1 == mates1Chars[k]) && (c2 == mates2Chars[k])) {
386 boolean checkWhatYouCan(Bidi bidi, String src, String dst)
388 int i, idx, logLimit, visLimit;
389 boolean testOK, errMap, errDst;
390 char[] srcChars = src.toCharArray();
391 char[] dstChars = dst.toCharArray();
392 int[] visMap = bidi.getVisualMap();
393 int[] logMap = bidi.getLogicalMap();
396 errMap = errDst = false;
397 logLimit = bidi.getProcessedLength();
398 visLimit = bidi.getResultLength();
399 if (visLimit > dstChars.length) {
400 visLimit = dstChars.length;
402 char[] accumSrc = new char[logLimit];
403 char[] accumDst = new char[visLimit];
404 Arrays.fill(accumSrc, '?');
405 Arrays.fill(accumDst, '?');
407 if (logMap.length != logLimit) {
410 for (i = 0; i < logLimit; i++) {
411 idx = bidi.getVisualIndex(i);
412 if (idx != logMap[i]) {
415 if (idx == Bidi.MAP_NOWHERE) {
418 if (idx >= visLimit) {
421 accumDst[idx] = srcChars[i];
422 if (!matchingPair(bidi, i, srcChars[i], dstChars[idx])) {
428 printCaseInfo(bidi, src, dst);
431 errln("Mismatch between getLogicalMap() and getVisualIndex()");
432 errcont("Map :" + valueOf(logMap));
435 for (i = 0; i < logLimit; i++) {
436 errcont(" " + bidi.getVisualIndex(i));
442 printCaseInfo(bidi, src, dst);
445 errln("Source does not map to Result");
446 errcontln("We got: " + new String(accumDst));
449 errMap = errDst = false;
450 if (visMap.length != visLimit) {
453 for (i = 0; i < visLimit; i++) {
454 idx = bidi.getLogicalIndex(i);
455 if (idx != visMap[i]) {
458 if (idx == Bidi.MAP_NOWHERE) {
461 if (idx >= logLimit) {
464 accumSrc[idx] = dstChars[i];
465 if (!matchingPair(bidi, idx, srcChars[idx], dstChars[i])) {
471 printCaseInfo(bidi, src, dst);
474 errln("Mismatch between getVisualMap() and getLogicalIndex()");
475 errcont("Map :" + valueOf(visMap));
478 for (i = 0; i < visLimit; i++) {
479 errcont(" " + bidi.getLogicalIndex(i));
485 printCaseInfo(bidi, src, dst);
488 errln("Result does not map to Source");
489 errcontln("We got: " + new String(accumSrc));