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) 2011, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 ******************************************************************************* 8 * created on: 2011jan08 9 * created by: Markus W. Scherer 10 * ported from ICU4C bytestrietest.h/.cpp 11 */ 12 13 package com.ibm.icu.dev.test.util; 14 15 import java.nio.ByteBuffer; 16 import java.util.NoSuchElementException; 17 18 import org.junit.Test; 19 import org.junit.runner.RunWith; 20 import org.junit.runners.JUnit4; 21 22 import com.ibm.icu.dev.test.TestFmwk; 23 import com.ibm.icu.util.BytesTrie; 24 import com.ibm.icu.util.BytesTrieBuilder; 25 import com.ibm.icu.util.StringTrieBuilder; 26 27 @RunWith(JUnit4.class) 28 public class BytesTrieTest extends TestFmwk { 29 public BytesTrieTest() {} 30 31 // All test functions have a TestNN prefix where NN is a double-digit number. 32 // This is so that when tests are run in sorted order 33 // the simpler ones are run first. 34 // If there is a problem, the simpler ones are easier to step through. 35 36 @Test 37 public void Test00Builder() { 38 builder_.clear(); 39 try { 40 builder_.build(StringTrieBuilder.Option.FAST); 41 errln("BytesTrieBuilder().build() did not throw IndexOutOfBoundsException"); 42 return; 43 } catch(IndexOutOfBoundsException e) { 44 // good 45 } 46 try { 47 byte[] equal=new byte[] { 0x3d }; // "=" 48 builder_.add(equal, 1, 0).add(equal, 1, 1); 49 errln("BytesTrieBuilder.add() did not detect duplicates"); 50 return; 51 } catch(IllegalArgumentException e) { 52 // good 53 } 54 } 55 56 private static final class StringAndValue { 57 public StringAndValue(String str, int val) { 58 s=str; 59 bytes=new byte[s.length()]; 60 for(int i=0; i<bytes.length; ++i) { 61 bytes[i]=(byte)s.charAt(i); 62 } 63 value=val; 64 } 65 66 public String s; 67 public byte[] bytes; 68 public int value; 69 } 70 // Note: C++ StringAndValue initializers converted to Java syntax 71 // with Eclipse Find/Replace regular expressions: 72 // Find: \{ (".*", [-0-9xa-fA-F]+) \} 73 // Replace with: new StringAndValue($1) 74 75 @Test 76 public void Test10Empty() { 77 final StringAndValue[] data={ 78 new StringAndValue("", 0) 79 }; 80 checkData(data); 81 } 82 83 @Test 84 public void Test11_a() { 85 final StringAndValue[] data={ 86 new StringAndValue("a", 1) 87 }; 88 checkData(data); 89 } 90 91 @Test 92 public void Test12_a_ab() { 93 final StringAndValue[] data={ 94 new StringAndValue("a", 1), 95 new StringAndValue("ab", 100) 96 }; 97 checkData(data); 98 } 99 100 @Test 101 public void Test20ShortestBranch() { 102 final StringAndValue[] data={ 103 new StringAndValue("a", 1000), 104 new StringAndValue("b", 2000) 105 }; 106 checkData(data); 107 } 108 109 @Test 110 public void Test21Branches() { 111 final StringAndValue[] data={ 112 new StringAndValue("a", 0x10), 113 new StringAndValue("cc", 0x40), 114 new StringAndValue("e", 0x100), 115 new StringAndValue("ggg", 0x400), 116 new StringAndValue("i", 0x1000), 117 new StringAndValue("kkkk", 0x4000), 118 new StringAndValue("n", 0x10000), 119 new StringAndValue("ppppp", 0x40000), 120 new StringAndValue("r", 0x100000), 121 new StringAndValue("sss", 0x200000), 122 new StringAndValue("t", 0x400000), 123 new StringAndValue("uu", 0x800000), 124 new StringAndValue("vv", 0x7fffffff), 125 new StringAndValue("zz", 0x80000000) 126 }; 127 for(int length=2; length<=data.length; ++length) { 128 logln("TestBranches length="+length); 129 checkData(data, length); 130 } 131 } 132 133 @Test 134 public void Test22LongSequence() { 135 final StringAndValue[] data={ 136 new StringAndValue("a", -1), 137 // sequence of linear-match nodes 138 new StringAndValue("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", -2), 139 // more than 256 bytes 140 new StringAndValue( 141 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"+ 142 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"+ 143 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"+ 144 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"+ 145 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"+ 146 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", -3) 147 }; 148 checkData(data); 149 } 150 151 @Test 152 public void Test23LongBranch() { 153 // Split-branch and interesting compact-integer values. 154 final StringAndValue[] data={ 155 new StringAndValue("a", -2), 156 new StringAndValue("b", -1), 157 new StringAndValue("c", 0), 158 new StringAndValue("d2", 1), 159 new StringAndValue("f", 0x3f), 160 new StringAndValue("g", 0x40), 161 new StringAndValue("h", 0x41), 162 new StringAndValue("j23", 0x1900), 163 new StringAndValue("j24", 0x19ff), 164 new StringAndValue("j25", 0x1a00), 165 new StringAndValue("k2", 0x1a80), 166 new StringAndValue("k3", 0x1aff), 167 new StringAndValue("l234567890", 0x1b00), 168 new StringAndValue("l234567890123", 0x1b01), 169 new StringAndValue("nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn", 0x10ffff), 170 new StringAndValue("oooooooooooooooooooooooooooooooooooooooooooooooooooooo", 0x110000), 171 new StringAndValue("pppppppppppppppppppppppppppppppppppppppppppppppppppppp", 0x120000), 172 new StringAndValue("r", 0x333333), 173 new StringAndValue("s2345", 0x4444444), 174 new StringAndValue("t234567890", 0x77777777), 175 new StringAndValue("z", 0x80000001) 176 }; 177 checkData(data); 178 } 179 180 @Test 181 public void Test24ValuesForState() { 182 // Check that saveState() and resetToState() interact properly 183 // with next() and current(). 184 final StringAndValue[] data={ 185 new StringAndValue("a", -1), 186 new StringAndValue("ab", -2), 187 new StringAndValue("abc", -3), 188 new StringAndValue("abcd", -4), 189 new StringAndValue("abcde", -5), 190 new StringAndValue("abcdef", -6) 191 }; 192 checkData(data); 193 } 194 195 @Test 196 public void Test30Compact() { 197 // Duplicate trailing strings and values provide opportunities for compacting. 198 final StringAndValue[] data={ 199 new StringAndValue("+", 0), 200 new StringAndValue("+august", 8), 201 new StringAndValue("+december", 12), 202 new StringAndValue("+july", 7), 203 new StringAndValue("+june", 6), 204 new StringAndValue("+november", 11), 205 new StringAndValue("+october", 10), 206 new StringAndValue("+september", 9), 207 new StringAndValue("-", 0), 208 new StringAndValue("-august", 8), 209 new StringAndValue("-december", 12), 210 new StringAndValue("-july", 7), 211 new StringAndValue("-june", 6), 212 new StringAndValue("-november", 11), 213 new StringAndValue("-october", 10), 214 new StringAndValue("-september", 9), 215 // The l+n branch (with its sub-nodes) is a duplicate but will be written 216 // both times because each time it follows a different linear-match node. 217 new StringAndValue("xjuly", 7), 218 new StringAndValue("xjune", 6) 219 }; 220 checkData(data); 221 } 222 223 public BytesTrie buildMonthsTrie(StringTrieBuilder.Option buildOption) { 224 // All types of nodes leading to the same value, 225 // for code coverage of recursive functions. 226 // In particular, we need a lot of branches on some single level 227 // to exercise a split-branch node. 228 final StringAndValue[] data={ 229 new StringAndValue("august", 8), 230 new StringAndValue("jan", 1), 231 new StringAndValue("jan.", 1), 232 new StringAndValue("jana", 1), 233 new StringAndValue("janbb", 1), 234 new StringAndValue("janc", 1), 235 new StringAndValue("janddd", 1), 236 new StringAndValue("janee", 1), 237 new StringAndValue("janef", 1), 238 new StringAndValue("janf", 1), 239 new StringAndValue("jangg", 1), 240 new StringAndValue("janh", 1), 241 new StringAndValue("janiiii", 1), 242 new StringAndValue("janj", 1), 243 new StringAndValue("jankk", 1), 244 new StringAndValue("jankl", 1), 245 new StringAndValue("jankmm", 1), 246 new StringAndValue("janl", 1), 247 new StringAndValue("janm", 1), 248 new StringAndValue("jannnnnnnnnnnnnnnnnnnnnnnnnnnnn", 1), 249 new StringAndValue("jano", 1), 250 new StringAndValue("janpp", 1), 251 new StringAndValue("janqqq", 1), 252 new StringAndValue("janr", 1), 253 new StringAndValue("januar", 1), 254 new StringAndValue("january", 1), 255 new StringAndValue("july", 7), 256 new StringAndValue("jun", 6), 257 new StringAndValue("jun.", 6), 258 new StringAndValue("june", 6) 259 }; 260 return buildTrie(data, data.length, buildOption); 261 } 262 263 @Test 264 public void Test40GetUniqueValue() { 265 BytesTrie trie=buildMonthsTrie(StringTrieBuilder.Option.FAST); 266 long uniqueValue; 267 if((uniqueValue=trie.getUniqueValue())!=0) { 268 errln("unique value at root"); 269 } 270 trie.next('j'); 271 trie.next('a'); 272 trie.next('n'); 273 // getUniqueValue() directly after next() 274 if((uniqueValue=trie.getUniqueValue())!=((1<<1)|1)) { 275 errln("not unique value 1 after \"jan\": instead "+uniqueValue); 276 } 277 trie.first('j'); 278 trie.next('u'); 279 if((uniqueValue=trie.getUniqueValue())!=0) { 280 errln("unique value after \"ju\""); 281 } 282 if(trie.next('n')!=BytesTrie.Result.INTERMEDIATE_VALUE || 6!=trie.getValue()) { 283 errln("not normal value 6 after \"jun\""); 284 } 285 // getUniqueValue() after getValue() 286 if((uniqueValue=trie.getUniqueValue())!=((6<<1)|1)) { 287 errln("not unique value 6 after \"jun\""); 288 } 289 // getUniqueValue() from within a linear-match node 290 trie.first('a'); 291 trie.next('u'); 292 if((uniqueValue=trie.getUniqueValue())!=((8<<1)|1)) { 293 errln("not unique value 8 after \"au\""); 294 } 295 } 296 297 @Test 298 public void Test41GetNextBytes() { 299 BytesTrie trie=buildMonthsTrie(StringTrieBuilder.Option.SMALL); 300 StringBuilder buffer=new StringBuilder(); 301 int count=trie.getNextBytes(buffer); 302 if(count!=2 || !"aj".contentEquals(buffer)) { 303 errln("months getNextBytes()!=[aj] at root"); 304 } 305 trie.next('j'); 306 trie.next('a'); 307 trie.next('n'); 308 // getNextBytes() directly after next() 309 buffer.setLength(0); 310 count=trie.getNextBytes(buffer); 311 if(count!=20 || !".abcdefghijklmnopqru".contentEquals(buffer)) { 312 errln("months getNextBytes()!=[.abcdefghijklmnopqru] after \"jan\""); 313 } 314 // getNextBytes() after getValue() 315 trie.getValue(); // next() had returned BytesTrie.Result.INTERMEDIATE_VALUE. 316 buffer.setLength(0); 317 count=trie.getNextBytes(buffer); 318 if(count!=20 || !".abcdefghijklmnopqru".contentEquals(buffer)) { 319 errln("months getNextBytes()!=[.abcdefghijklmnopqru] after \"jan\"+getValue()"); 320 } 321 // getNextBytes() from a linear-match node 322 trie.next('u'); 323 buffer.setLength(0); 324 count=trie.getNextBytes(buffer); 325 if(count!=1 || !"a".contentEquals(buffer)) { 326 errln("months getNextBytes()!=[a] after \"janu\""); 327 } 328 trie.next('a'); 329 buffer.setLength(0); 330 count=trie.getNextBytes(buffer); 331 if(count!=1 || !"r".contentEquals(buffer)) { 332 errln("months getNextBytes()!=[r] after \"janua\""); 333 } 334 trie.next('r'); 335 trie.next('y'); 336 // getNextBytes() after a final match 337 buffer.setLength(0); 338 count=trie.getNextBytes(buffer); 339 if(count!=0 || buffer.length()!=0) { 340 errln("months getNextBytes()!=[] after \"january\""); 341 } 342 } 343 344 @Test 345 public void Test50IteratorFromBranch() { 346 BytesTrie trie=buildMonthsTrie(StringTrieBuilder.Option.FAST); 347 // Go to a branch node. 348 trie.next('j'); 349 trie.next('a'); 350 trie.next('n'); 351 BytesTrie.Iterator iter=trie.iterator(); 352 // Expected data: Same as in buildMonthsTrie(), except only the suffixes 353 // following "jan". 354 final StringAndValue[] data={ 355 new StringAndValue("", 1), 356 new StringAndValue(".", 1), 357 new StringAndValue("a", 1), 358 new StringAndValue("bb", 1), 359 new StringAndValue("c", 1), 360 new StringAndValue("ddd", 1), 361 new StringAndValue("ee", 1), 362 new StringAndValue("ef", 1), 363 new StringAndValue("f", 1), 364 new StringAndValue("gg", 1), 365 new StringAndValue("h", 1), 366 new StringAndValue("iiii", 1), 367 new StringAndValue("j", 1), 368 new StringAndValue("kk", 1), 369 new StringAndValue("kl", 1), 370 new StringAndValue("kmm", 1), 371 new StringAndValue("l", 1), 372 new StringAndValue("m", 1), 373 new StringAndValue("nnnnnnnnnnnnnnnnnnnnnnnnnnnn", 1), 374 new StringAndValue("o", 1), 375 new StringAndValue("pp", 1), 376 new StringAndValue("qqq", 1), 377 new StringAndValue("r", 1), 378 new StringAndValue("uar", 1), 379 new StringAndValue("uary", 1) 380 }; 381 checkIterator(iter, data); 382 // Reset, and we should get the same result. 383 logln("after iter.reset()"); 384 checkIterator(iter.reset(), data); 385 } 386 387 @Test 388 public void Test51IteratorFromLinearMatch() { 389 BytesTrie trie=buildMonthsTrie(StringTrieBuilder.Option.SMALL); 390 // Go into a linear-match node. 391 trie.next('j'); 392 trie.next('a'); 393 trie.next('n'); 394 trie.next('u'); 395 trie.next('a'); 396 BytesTrie.Iterator iter=trie.iterator(); 397 // Expected data: Same as in buildMonthsTrie(), except only the suffixes 398 // following "janua". 399 final StringAndValue[] data={ 400 new StringAndValue("r", 1), 401 new StringAndValue("ry", 1) 402 }; 403 checkIterator(iter, data); 404 // Reset, and we should get the same result. 405 logln("after iter.reset()"); 406 checkIterator(iter.reset(), data); 407 } 408 409 @Test 410 public void Test52TruncatingIteratorFromRoot() { 411 BytesTrie trie=buildMonthsTrie(StringTrieBuilder.Option.FAST); 412 BytesTrie.Iterator iter=trie.iterator(4); 413 // Expected data: Same as in buildMonthsTrie(), except only the first 4 characters 414 // of each string, and no string duplicates from the truncation. 415 final StringAndValue[] data={ 416 new StringAndValue("augu", -1), 417 new StringAndValue("jan", 1), 418 new StringAndValue("jan.", 1), 419 new StringAndValue("jana", 1), 420 new StringAndValue("janb", -1), 421 new StringAndValue("janc", 1), 422 new StringAndValue("jand", -1), 423 new StringAndValue("jane", -1), 424 new StringAndValue("janf", 1), 425 new StringAndValue("jang", -1), 426 new StringAndValue("janh", 1), 427 new StringAndValue("jani", -1), 428 new StringAndValue("janj", 1), 429 new StringAndValue("jank", -1), 430 new StringAndValue("janl", 1), 431 new StringAndValue("janm", 1), 432 new StringAndValue("jann", -1), 433 new StringAndValue("jano", 1), 434 new StringAndValue("janp", -1), 435 new StringAndValue("janq", -1), 436 new StringAndValue("janr", 1), 437 new StringAndValue("janu", -1), 438 new StringAndValue("july", 7), 439 new StringAndValue("jun", 6), 440 new StringAndValue("jun.", 6), 441 new StringAndValue("june", 6) 442 }; 443 checkIterator(iter, data); 444 // Reset, and we should get the same result. 445 logln("after iter.reset()"); 446 checkIterator(iter.reset(), data); 447 } 448 449 @Test 450 public void Test53TruncatingIteratorFromLinearMatchShort() { 451 final StringAndValue[] data={ 452 new StringAndValue("abcdef", 10), 453 new StringAndValue("abcdepq", 200), 454 new StringAndValue("abcdeyz", 3000) 455 }; 456 BytesTrie trie=buildTrie(data, data.length, StringTrieBuilder.Option.FAST); 457 // Go into a linear-match node. 458 trie.next('a'); 459 trie.next('b'); 460 // Truncate within the linear-match node. 461 BytesTrie.Iterator iter=trie.iterator(2); 462 final StringAndValue[] expected={ 463 new StringAndValue("cd", -1) 464 }; 465 checkIterator(iter, expected); 466 // Reset, and we should get the same result. 467 logln("after iter.reset()"); 468 checkIterator(iter.reset(), expected); 469 } 470 471 @Test 472 public void Test54TruncatingIteratorFromLinearMatchLong() { 473 final StringAndValue[] data={ 474 new StringAndValue("abcdef", 10), 475 new StringAndValue("abcdepq", 200), 476 new StringAndValue("abcdeyz", 3000) 477 }; 478 BytesTrie trie=buildTrie(data, data.length, StringTrieBuilder.Option.FAST); 479 // Go into a linear-match node. 480 trie.next('a'); 481 trie.next('b'); 482 trie.next('c'); 483 // Truncate after the linear-match node. 484 BytesTrie.Iterator iter=trie.iterator(3); 485 final StringAndValue[] expected={ 486 new StringAndValue("def", 10), 487 new StringAndValue("dep", -1), 488 new StringAndValue("dey", -1) 489 }; 490 checkIterator(iter, expected); 491 // Reset, and we should get the same result. 492 logln("after iter.reset()"); 493 checkIterator(iter.reset(), expected); 494 } 495 496 @Test 497 public void Test59IteratorFromBytes() { 498 final StringAndValue[] data={ 499 new StringAndValue("mm", 3), 500 new StringAndValue("mmm", 33), 501 new StringAndValue("mmnop", 333) 502 }; 503 builder_.clear(); 504 for(StringAndValue item : data) { 505 builder_.add(item.bytes, item.bytes.length, item.value); 506 } 507 ByteBuffer trieBytes=builder_.buildByteBuffer(StringTrieBuilder.Option.FAST); 508 checkIterator( 509 BytesTrie.iterator(trieBytes.array(), trieBytes.arrayOffset()+trieBytes.position(), 0), 510 data); 511 } 512 513 private void checkData(StringAndValue data[]) { 514 checkData(data, data.length); 515 } 516 517 private void checkData(StringAndValue data[], int dataLength) { 518 logln("checkData(dataLength="+dataLength+", fast)"); 519 checkData(data, dataLength, StringTrieBuilder.Option.FAST); 520 logln("checkData(dataLength="+dataLength+", small)"); 521 checkData(data, dataLength, StringTrieBuilder.Option.SMALL); 522 } 523 524 private void checkData(StringAndValue data[], int dataLength, StringTrieBuilder.Option buildOption) { 525 BytesTrie trie=buildTrie(data, dataLength, buildOption); 526 checkFirst(trie, data, dataLength); 527 checkNext(trie, data, dataLength); 528 checkNextWithState(trie, data, dataLength); 529 checkNextString(trie, data, dataLength); 530 checkIterator(trie, data, dataLength); 531 } 532 533 private BytesTrie buildTrie(StringAndValue data[], int dataLength, 534 StringTrieBuilder.Option buildOption) { 535 // Add the items to the trie builder in an interesting (not trivial, not random) order. 536 int index, step; 537 if((dataLength&1)!=0) { 538 // Odd number of items. 539 index=dataLength/2; 540 step=2; 541 } else if((dataLength%3)!=0) { 542 // Not a multiple of 3. 543 index=dataLength/5; 544 step=3; 545 } else { 546 index=dataLength-1; 547 step=-1; 548 } 549 builder_.clear(); 550 for(int i=0; i<dataLength; ++i) { 551 builder_.add(data[index].bytes, data[index].bytes.length, data[index].value); 552 index=(index+step)%dataLength; 553 } 554 BytesTrie trie=builder_.build(buildOption); 555 try { 556 builder_.add(/* "zzz" */ new byte[] { 0x7a, 0x7a, 0x7a }, 0, 999); 557 errln("builder.build().add(zzz) did not throw IllegalStateException"); 558 } catch(IllegalStateException e) { 559 // good 560 } 561 ByteBuffer trieBytes=builder_.buildByteBuffer(buildOption); 562 logln("serialized trie size: "+trieBytes.remaining()+" bytes\n"); 563 // Tries from either build() method should be identical but 564 // BytesTrie does not implement equals(). 565 // We just return either one. 566 if((dataLength&1)!=0) { 567 return trie; 568 } else { 569 return new BytesTrie(trieBytes.array(), trieBytes.arrayOffset()+trieBytes.position()); 570 } 571 } 572 573 private void checkFirst(BytesTrie trie, StringAndValue data[], int dataLength) { 574 for(int i=0; i<dataLength; ++i) { 575 if(data[i].s.length()==0) { 576 continue; // skip empty string 577 } 578 int c=data[i].bytes[0]; 579 BytesTrie.Result firstResult=trie.first(c); 580 int firstValue=firstResult.hasValue() ? trie.getValue() : -1; 581 int nextC=data[i].s.length()>1 ? data[i].bytes[1] : 0; 582 BytesTrie.Result nextResult=trie.next(nextC); 583 if(firstResult!=trie.reset().next(c) || 584 firstResult!=trie.current() || 585 firstValue!=(firstResult.hasValue() ? trie.getValue() : -1) || 586 nextResult!=trie.next(nextC) 587 ) { 588 errln(String.format("trie.first(%c)!=trie.reset().next(same) for %s", 589 c, data[i].s)); 590 } 591 } 592 trie.reset(); 593 } 594 595 private void checkNext(BytesTrie trie, StringAndValue data[], int dataLength) { 596 BytesTrie.State state=new BytesTrie.State(); 597 for(int i=0; i<dataLength; ++i) { 598 int stringLength=data[i].s.length(); 599 BytesTrie.Result result; 600 if( !(result=trie.next(data[i].bytes, 0, stringLength)).hasValue() || 601 result!=trie.current() 602 ) { 603 errln("trie does not seem to contain "+data[i].s); 604 } else if(trie.getValue()!=data[i].value) { 605 errln(String.format("trie value for %s is %d=0x%x instead of expected %d=0x%x", 606 data[i].s, 607 trie.getValue(), trie.getValue(), 608 data[i].value, data[i].value)); 609 } else if(result!=trie.current() || trie.getValue()!=data[i].value) { 610 errln("trie value for "+data[i].s+" changes when repeating current()/getValue()"); 611 } 612 trie.reset(); 613 result=trie.current(); 614 for(int j=0; j<stringLength; ++j) { 615 if(!result.hasNext()) { 616 errln(String.format("trie.current()!=hasNext before end of %s (at index %d)", 617 data[i].s, j)); 618 break; 619 } 620 if(result==BytesTrie.Result.INTERMEDIATE_VALUE) { 621 trie.getValue(); 622 if(trie.current()!=BytesTrie.Result.INTERMEDIATE_VALUE) { 623 errln(String.format("trie.getValue().current()!=BytesTrie.Result.INTERMEDIATE_VALUE "+ 624 "before end of %s (at index %d)", data[i].s, j)); 625 break; 626 } 627 } 628 result=trie.next(data[i].bytes[j]); 629 if(!result.matches()) { 630 errln(String.format("trie.next()=BytesTrie.Result.NO_MATCH "+ 631 "before end of %s (at index %d)", data[i].s, j)); 632 break; 633 } 634 if(result!=trie.current()) { 635 errln(String.format("trie.next()!=following current() "+ 636 "before end of %s (at index %d)", data[i].s, j)); 637 break; 638 } 639 } 640 if(!result.hasValue()) { 641 errln("trie.next()!=hasValue at the end of "+data[i].s); 642 continue; 643 } 644 trie.getValue(); 645 if(result!=trie.current()) { 646 errln("trie.current() != current()+getValue()+current() after end of "+ 647 data[i].s); 648 } 649 // Compare the final current() with whether next() can actually continue. 650 trie.saveState(state); 651 boolean nextContinues=false; 652 for(int c=0x20; c<0x7f; ++c) { 653 if(trie.resetToState(state).next(c).matches()) { 654 nextContinues=true; 655 break; 656 } 657 } 658 if((result==BytesTrie.Result.INTERMEDIATE_VALUE)!=nextContinues) { 659 errln("(trie.current()==BytesTrie.Result.INTERMEDIATE_VALUE) contradicts "+ 660 "(trie.next(some UChar)!=BytesTrie.Result.NO_MATCH) after end of "+data[i].s); 661 } 662 trie.reset(); 663 } 664 } 665 666 private void checkNextWithState(BytesTrie trie, StringAndValue data[], int dataLength) { 667 BytesTrie.State noState=new BytesTrie.State(), state=new BytesTrie.State(); 668 for(int i=0; i<dataLength; ++i) { 669 if((i&1)==0) { 670 try { 671 trie.resetToState(noState); 672 errln("trie.resetToState(noState) should throw an IllegalArgumentException"); 673 } catch(IllegalArgumentException e) { 674 // good 675 } 676 } 677 byte[] expectedString=data[i].bytes; 678 int stringLength=data[i].s.length(); 679 int partialLength=stringLength/3; 680 for(int j=0; j<partialLength; ++j) { 681 if(!trie.next(expectedString[j]).matches()) { 682 errln("trie.next()=BytesTrie.Result.NO_MATCH for a prefix of "+data[i].s); 683 return; 684 } 685 } 686 trie.saveState(state); 687 BytesTrie.Result resultAtState=trie.current(); 688 BytesTrie.Result result; 689 int valueAtState=-99; 690 if(resultAtState.hasValue()) { 691 valueAtState=trie.getValue(); 692 } 693 result=trie.next(0); // mismatch 694 if(result!=BytesTrie.Result.NO_MATCH || result!=trie.current()) { 695 errln("trie.next(0) matched after part of "+data[i].s); 696 } 697 if( resultAtState!=trie.resetToState(state).current() || 698 (resultAtState.hasValue() && valueAtState!=trie.getValue()) 699 ) { 700 errln("trie.next(part of "+data[i].s+") changes current()/getValue() after "+ 701 "saveState/next(0)/resetToState"); 702 } else if(!(result=trie.next(expectedString, partialLength, stringLength)).hasValue() || 703 result!=trie.current()) { 704 errln("trie.next(rest of "+data[i].s+") does not seem to contain "+data[i].s+" after "+ 705 "saveState/next(0)/resetToState"); 706 } else if(!(result=trie.resetToState(state). 707 next(expectedString, partialLength, stringLength)).hasValue() || 708 result!=trie.current()) { 709 errln("trie does not seem to contain "+data[i].s+ 710 " after saveState/next(rest)/resetToState"); 711 } else if(trie.getValue()!=data[i].value) { 712 errln(String.format("trie value for %s is %d=0x%x instead of expected %d=0x%x", 713 data[i].s, 714 trie.getValue(), trie.getValue(), 715 data[i].value, data[i].value)); 716 } 717 trie.reset(); 718 } 719 } 720 721 // next(string) is also tested in other functions, 722 // but here we try to go partway through the string, and then beyond it. 723 private void checkNextString(BytesTrie trie, StringAndValue data[], int dataLength) { 724 for(int i=0; i<dataLength; ++i) { 725 byte[] expectedString=data[i].bytes; 726 int stringLength=data[i].s.length(); 727 if(!trie.next(expectedString, 0, stringLength/2).matches()) { 728 errln("trie.next(up to middle of string)=BytesTrie.Result.NO_MATCH for "+data[i].s); 729 continue; 730 } 731 // Test that we stop properly at the end of the string. 732 trie.next(expectedString, stringLength/2, stringLength); 733 if(trie.next(0).matches()) { 734 errln("trie.next(string+NUL)!=BytesTrie.Result.NO_MATCH for "+data[i].s); 735 } 736 trie.reset(); 737 } 738 } 739 740 private void checkIterator(BytesTrie trie, StringAndValue data[], int dataLength) { 741 checkIterator(trie.iterator(), data, dataLength); 742 } 743 744 private void checkIterator(BytesTrie.Iterator iter, StringAndValue data[]) { 745 checkIterator(iter, data, data.length); 746 } 747 748 private void checkIterator(BytesTrie.Iterator iter, StringAndValue data[], int dataLength) { 749 for(int i=0; i<dataLength; ++i) { 750 if(!iter.hasNext()) { 751 errln("trie iterator hasNext()=false for item "+i+": "+data[i].s); 752 break; 753 } 754 BytesTrie.Entry entry=iter.next(); 755 StringBuilder bytesString=new StringBuilder(); 756 for(int j=0; j<entry.bytesLength(); ++j) { 757 bytesString.append((char)(entry.byteAt(j)&0xff)); 758 } 759 if(!data[i].s.contentEquals(bytesString)) { 760 errln(String.format("trie iterator next().getString()=%s but expected %s for item %d", 761 bytesString, data[i].s, i)); 762 } 763 if(entry.value!=data[i].value) { 764 errln(String.format("trie iterator next().getValue()=%d=0x%x but expected %d=0x%x for item %d: %s", 765 entry.value, entry.value, 766 data[i].value, data[i].value, 767 i, data[i].s)); 768 } 769 } 770 if(iter.hasNext()) { 771 errln("trie iterator hasNext()=true after all items"); 772 } 773 try { 774 iter.next(); 775 errln("trie iterator next() did not throw NoSuchElementException after all items"); 776 } catch(NoSuchElementException e) { 777 // good 778 } 779 } 780 781 private BytesTrieBuilder builder_=new BytesTrieBuilder(); 782 } 783