Home | History | Annotate | Download | only in util
      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