1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2007, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.dev.test.util; 10 11 import java.util.Arrays; 12 import java.util.Iterator; 13 14 import org.junit.Test; 15 import org.junit.runner.RunWith; 16 import org.junit.runners.JUnit4; 17 18 import com.ibm.icu.dev.test.TestFmwk; 19 import com.ibm.icu.impl.TextTrieMap; 20 21 @RunWith(JUnit4.class) 22 public class TextTrieMapTest extends TestFmwk { 23 24 private static final Integer SUN = new Integer(1); 25 private static final Integer MON = new Integer(2); 26 private static final Integer TUE = new Integer(3); 27 private static final Integer WED = new Integer(4); 28 private static final Integer THU = new Integer(5); 29 private static final Integer FRI = new Integer(6); 30 private static final Integer SAT = new Integer(7); 31 32 private static final Integer SUP1 = new Integer(8); 33 private static final Integer SUP2 = new Integer(9); 34 private static final Integer SUP3 = new Integer(10); 35 private static final Integer SUP4 = new Integer(11); 36 37 private static final Integer FOO = new Integer(-1); 38 private static final Integer BAR = new Integer(-2); 39 40 private static final Object[][] TESTDATA = { 41 {"Sunday", SUN}, 42 {"Monday", MON}, 43 {"Tuesday", TUE}, 44 {"Wednesday", WED}, 45 {"Thursday", THU}, 46 {"Friday", FRI}, 47 {"Saturday", SAT}, 48 {"Sun", SUN}, 49 {"Mon", MON}, 50 {"Tue", TUE}, 51 {"Wed", WED}, 52 {"Thu", THU}, 53 {"Fri", FRI}, 54 {"Sat", SAT}, 55 {"S", SUN}, 56 {"M", MON}, 57 {"T", TUE}, 58 {"W", WED}, 59 {"T", THU}, 60 {"F", FRI}, 61 {"S", SAT}, 62 {"L", SUP1}, // L, 0xD83D, 0xDCFA 63 {"L1", SUP2}, // L, 0xD83D, 0xDCFA, 1 64 {"L", SUP3}, // L, 0xD83D, 0xDCFB 65 {"L", SUP4}, // L, 0xD83C, 0xDCCF 66 }; 67 68 private static final Object[][] TESTCASES = { 69 {"Sunday", SUN, SUN}, 70 {"sunday", null, SUN}, 71 {"Mo", MON, MON}, 72 {"mo", null, MON}, 73 {"Thursday Friday", THU, THU}, 74 {"T", new Object[]{TUE, THU}, new Object[]{TUE, THU}}, 75 {"TEST", new Object[]{TUE, THU}, new Object[]{TUE, THU}}, 76 {"SUN", new Object[]{SUN, SAT}, SUN}, 77 {"super", null, SUN}, 78 {"NO", null, null}, 79 {"L", SUP1, SUP1}, 80 {"l", null, SUP1}, 81 }; 82 83 private static final Object[][] TESTCASES_PARSE = { 84 { 85 "Sunday", 86 new Object[]{ 87 new Object[]{SAT,SUN}, new Object[]{SAT,SUN}, // matches on "S" 88 null, null, // matches on "Su" 89 SUN, SUN, // matches on "Sun" 90 null, null, // matches on "Sund" 91 null, null, // matches on "Sunda" 92 SUN, SUN, // matches on "Sunday" 93 } 94 }, 95 { 96 "sunday", 97 new Object[]{ 98 null, new Object[]{SAT,SUN}, // matches on "s" 99 null, null, // matches on "su" 100 null, SUN, // matches on "sun" 101 null, null, // matches on "sund" 102 null, null, // matches on "sunda" 103 null, SUN, // matches on "sunday" 104 } 105 }, 106 { 107 "MMM", 108 new Object[]{ 109 MON, MON, // matches on "M" 110 // no more matches in data 111 } 112 }, 113 { 114 "BBB", 115 new Object[]{ 116 // no matches in data 117 } 118 }, 119 { 120 "l12", 121 new Object[]{ 122 null, null, // matches on "L" 123 null, SUP1, // matches on "L" 124 null, SUP2, // matches on "L1" 125 // no more matches in data 126 } 127 }, 128 { 129 "L", 130 new Object[] { 131 null, null, // matches on "L" 132 SUP3, SUP3, // matches on "L" 133 } 134 }, 135 { 136 "L", 137 new Object[] { 138 null, null, // matches on "L" 139 SUP4, SUP4, // matches on "L" 140 } 141 } 142 }; 143 144 @Test 145 public void TestCaseSensitive() { 146 Iterator itr = null; 147 TextTrieMap map = new TextTrieMap(false); 148 for (int i = 0; i < TESTDATA.length; i++) { 149 map.put((String)TESTDATA[i][0], TESTDATA[i][1]); 150 } 151 152 logln("Test for get(String)"); 153 for (int i = 0; i < TESTCASES.length; i++) { 154 itr = map.get((String)TESTCASES[i][0]); 155 checkResult("get(String) case " + i, itr, TESTCASES[i][1]); 156 } 157 158 logln("Test for get(String, int)"); 159 StringBuffer textBuf = new StringBuffer(); 160 for (int i = 0; i < TESTCASES.length; i++) { 161 textBuf.setLength(0); 162 for (int j = 0; j < i; j++) { 163 textBuf.append('X'); 164 } 165 textBuf.append(TESTCASES[i][0]); 166 itr = map.get(textBuf.toString(), i); 167 checkResult("get(String, int) case " + i, itr, TESTCASES[i][1]); 168 } 169 170 logln("Test for ParseState"); 171 for (int i = 0; i < TESTCASES_PARSE.length; i++) { 172 String test = (String) TESTCASES_PARSE[i][0]; 173 Object[] expecteds = (Object[]) TESTCASES_PARSE[i][1]; 174 checkParse(map, test, expecteds, true); 175 } 176 177 // Add duplicated entry 178 map.put("Sunday", FOO); 179 // Add duplicated entry with different casing 180 map.put("sunday", BAR); 181 182 // Make sure the all entries are returned 183 itr = map.get("Sunday"); 184 checkResult("Get Sunday", itr, new Object[]{FOO, SUN}); 185 } 186 187 @Test 188 public void TestCaseInsensitive() { 189 Iterator itr = null; 190 TextTrieMap map = new TextTrieMap(true); 191 for (int i = 0; i < TESTDATA.length; i++) { 192 map.put((String)TESTDATA[i][0], TESTDATA[i][1]); 193 } 194 195 logln("Test for get(String)"); 196 for (int i = 0; i < TESTCASES.length; i++) { 197 itr = map.get((String)TESTCASES[i][0]); 198 checkResult("get(String) case " + i, itr, TESTCASES[i][2]); 199 } 200 201 logln("Test for get(String, int)"); 202 StringBuffer textBuf = new StringBuffer(); 203 for (int i = 0; i < TESTCASES.length; i++) { 204 textBuf.setLength(0); 205 for (int j = 0; j < i; j++) { 206 textBuf.append('X'); 207 } 208 textBuf.append(TESTCASES[i][0]); 209 itr = map.get(textBuf.toString(), i); 210 checkResult("get(String, int) case " + i, itr, TESTCASES[i][2]); 211 } 212 213 logln("Test for ParseState"); 214 for (int i = 0; i < TESTCASES_PARSE.length; i++) { 215 String test = (String) TESTCASES_PARSE[i][0]; 216 Object[] expecteds = (Object[]) TESTCASES_PARSE[i][1]; 217 checkParse(map, test, expecteds, false); 218 } 219 220 // Add duplicated entry 221 map.put("Sunday", FOO); 222 // Add duplicated entry with different casing 223 map.put("sunday", BAR); 224 225 // Make sure the all entries are returned 226 itr = map.get("Sunday"); 227 checkResult("Get Sunday", itr, new Object[]{SUN, FOO, BAR}); 228 } 229 230 private void checkParse(TextTrieMap map, String text, Object[] rawExpecteds, boolean caseSensitive) { 231 // rawExpecteds has even-valued indices for case sensitive and odd-valued indicies for case insensitive 232 // Get out only the values that we want. 233 Object[] expecteds = null; 234 for (int i=rawExpecteds.length/2-1; i>=0; i--) { 235 int j = i*2+(caseSensitive?0:1); 236 if (rawExpecteds[j] != null) { 237 if (expecteds == null) { 238 expecteds = new Object[i+1]; 239 } 240 expecteds[i] = rawExpecteds[j]; 241 } 242 } 243 if (expecteds == null) { 244 expecteds = new Object[0]; 245 } 246 247 TextTrieMap.ParseState state = null; 248 for (int charOffset=0, cpOffset=0; charOffset < text.length(); cpOffset++) { 249 int cp = Character.codePointAt(text, charOffset); 250 if (state == null) { 251 state = map.openParseState(cp); 252 } 253 if (state == null) { 254 assertEquals("Expected matches, but no matches are available", 0, expecteds.length); 255 break; 256 } 257 state.accept(cp); 258 if (cpOffset < expecteds.length - 1) { 259 assertFalse( 260 "In middle of parse sequence, but atEnd() is true: '" + text + "' offset " + charOffset, 261 state.atEnd()); 262 } else if (cpOffset == expecteds.length) { 263 // Note: it possible for atEnd() to be either true or false at expecteds.length - 1; 264 // if true, we are at the end of the input string; if false, there is still input string 265 // left to be consumed, but we don't know if there are remaining matches. 266 assertTrue( 267 "At end of parse sequence, but atEnd() is false: '" + text + "' offset " + charOffset, 268 state.atEnd()); 269 break; 270 } 271 Object expected = expecteds[cpOffset]; 272 Iterator actual = state.getCurrentMatches(); 273 checkResult("ParseState '" + text + "' offset " + charOffset, actual, expected); 274 charOffset += Character.charCount(cp); 275 } 276 } 277 278 private boolean eql(Object o1, Object o2) { 279 if (o1 == null || o2 == null) { 280 if (o1 == null && o2 == null) { 281 return true; 282 } 283 return false; 284 } 285 return o1.equals(o2); 286 } 287 288 private void checkResult(String memo, Iterator itr, Object expected) { 289 if (itr == null) { 290 if (expected != null) { 291 String expectedStr = (expected instanceof Object[]) 292 ? Arrays.toString((Object[]) expected) 293 : expected.toString(); 294 errln("FAIL: Empty results: " + memo + ": Expected: " + expectedStr); 295 } 296 return; 297 } 298 if (expected == null && itr != null) { 299 errln("FAIL: Empty result is expected"); 300 return; 301 } 302 303 Object[] exp; 304 if (expected instanceof Object[]) { 305 exp = (Object[])expected; 306 } else { 307 exp = new Object[]{expected}; 308 } 309 310 boolean[] found = new boolean[exp.length]; 311 while (itr.hasNext()) { 312 Object val = itr.next(); 313 for (int i = 0; i < exp.length; i++) { 314 if (eql(exp[i], val)) { 315 found[i] = true; 316 } 317 } 318 } 319 for (int i = 0; i < exp.length; i++) { 320 if (found[i] == false) { 321 errln("FAIL: The search result does not contain " + exp[i]); 322 } 323 } 324 } 325 } 326