/* ******************************************************************************* * Copyright (C) 2009, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.dev.test.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.impl.Trie2; import com.ibm.icu.impl.Trie2Writable; import com.ibm.icu.impl.Trie2_16; import com.ibm.icu.impl.Trie2_32; public class Trie2Test extends TestFmwk { /** * Constructor */ public Trie2Test() { } // public methods ----------------------------------------------- public static void main(String arg[]) { Trie2Test test = new Trie2Test(); try { test.run(arg); } catch (Exception e) { test.errln("Error testing trietest"); } } // // TestAPI. Check that all API methods can be called, and do at least some minimal // operation correctly. This is not a full test of correct behavior. // public void TestTrie2API() { // Trie2.createFromSerialized() // This function is well exercised by TestRanges(). // Trie2.getVersion(InputStream is, boolean anyEndianOk) // try { Trie2Writable trie = new Trie2Writable(0,0); ByteArrayOutputStream os = new ByteArrayOutputStream(); trie.toTrie2_16().serialize(os); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); assertEquals(null, 2, Trie2.getVersion(is, true)); } catch (IOException e) { errln(where() + e.toString()); } // Equals & hashCode // { Trie2Writable trieWA = new Trie2Writable(0,0); Trie2Writable trieWB = new Trie2Writable(0,0); Trie2 trieA = trieWA; Trie2 trieB = trieWB; assertTrue("", trieA.equals(trieB)); assertEquals("", trieA, trieB); assertEquals("", trieA.hashCode(), trieB.hashCode()); trieWA.set(500, 2); assertNotEquals("", trieA, trieB); // Note that the hash codes do not strictly need to be different, // but it's highly likely that something is wrong if they are the same. assertNotEquals("", trieA.hashCode(), trieB.hashCode()); trieWB.set(500, 2); trieA = trieWA.toTrie2_16(); assertEquals("", trieA, trieB); assertEquals("", trieA.hashCode(), trieB.hashCode()); } // // Iterator creation // { Trie2Writable trie = new Trie2Writable(17,0); Iterator it; it = trie.iterator(); Trie2.Range r = it.next(); assertEquals("", 0, r.startCodePoint); assertEquals("", 0x10ffff, r.endCodePoint); assertEquals("", 17, r.value); assertEquals("", false, r.leadSurrogate); r = it.next(); assertEquals("", 0xd800, r.startCodePoint); assertEquals("", 0xdbff, r.endCodePoint); assertEquals("", 17, r.value); assertEquals("", true, r.leadSurrogate); int i = 0; for (Trie2.Range rr: trie) { switch (i) { case 0: assertEquals("", 0, rr.startCodePoint); assertEquals("", 0x10ffff, rr.endCodePoint); assertEquals("", 17, rr.value); assertEquals("", false, rr.leadSurrogate); break; case 1: assertEquals("", 0xd800, rr.startCodePoint); assertEquals("", 0xdbff, rr.endCodePoint); assertEquals("", 17, rr.value); assertEquals("", true, rr.leadSurrogate); break; default: errln(where() + " Unexpected iteration result"); } i++; } } // Iteration with a value mapping function // { Trie2Writable trie = new Trie2Writable(0xbadfeed, 0); trie.set(0x10123, 42); Trie2.ValueMapper vm = new Trie2.ValueMapper() { public int map(int v) { if (v == 0xbadfeed) { v = 42; } return v; } }; Iterator it = trie.iterator(vm); Trie2.Range r = it.next(); assertEquals("", 0, r.startCodePoint); assertEquals("", 0x10ffff, r.endCodePoint); assertEquals("", 42, r.value); assertEquals("", false, r.leadSurrogate); } // Iteration over a leading surrogate range. // { Trie2Writable trie = new Trie2Writable(0xdefa17, 0); trie.set(0x2f810, 10); Iterator it = trie.iteratorForLeadSurrogate((char)0xd87e); Trie2.Range r = it.next(); assertEquals("", 0x2f800, r.startCodePoint); assertEquals("", 0x2f80f, r.endCodePoint); assertEquals("", 0xdefa17, r.value); assertEquals("", false, r.leadSurrogate); r = it.next(); assertEquals("", 0x2f810, r.startCodePoint); assertEquals("", 0x2f810, r.endCodePoint); assertEquals("", 10, r.value); assertEquals("", false, r.leadSurrogate); r = it.next(); assertEquals("", 0x2f811, r.startCodePoint); assertEquals("", 0x2fbff, r.endCodePoint); assertEquals("", 0xdefa17, r.value); assertEquals("", false, r.leadSurrogate); assertFalse("", it.hasNext()); } // Iteration over a leading surrogate range with a ValueMapper. // { Trie2Writable trie = new Trie2Writable(0xdefa17, 0); trie.set(0x2f810, 10); Trie2.ValueMapper m = new Trie2.ValueMapper() { public int map(int in) { if (in==10) { in = 0xdefa17; } return in; } }; Iterator it = trie.iteratorForLeadSurrogate((char)0xd87e, m); Trie2.Range r = it.next(); assertEquals("", 0x2f800, r.startCodePoint); assertEquals("", 0x2fbff, r.endCodePoint); assertEquals("", 0xdefa17, r.value); assertEquals("", false, r.leadSurrogate); assertFalse("", it.hasNext()); } // Trie2.serialize() // Test the implementation in Trie2, which is used with Read Only Tries. // { Trie2Writable trie = new Trie2Writable(101, 0); trie.setRange(0xf000, 0x3c000, 200, true); trie.set(0xffee, 300); Trie2_16 frozen16 = trie.toTrie2_16(); Trie2_32 frozen32 = trie.toTrie2_32(); assertEquals("", trie, frozen16); assertEquals("", trie, frozen32); assertEquals("", frozen16, frozen32); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { frozen16.serialize(os); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); Trie2 unserialized16 = Trie2.createFromSerialized(is); assertEquals("", trie, unserialized16); assertEquals("", Trie2_16.class, unserialized16.getClass()); os.reset(); frozen32.serialize(os); is = new ByteArrayInputStream(os.toByteArray()); Trie2 unserialized32 = Trie2.createFromSerialized(is); assertEquals("", trie, unserialized32); assertEquals("", Trie2_32.class, unserialized32.getClass()); } catch (IOException e) { errln(where() + " Unexpected exception: " + e); } } } public void TestTrie2WritableAPI() { // // Trie2Writable methods. Check that all functions are present and // nominally working. Not an in-depth test. // // Trie2Writable constructor Trie2 t1 = new Trie2Writable(6, 666); // Constructor from another Trie2 Trie2 t2 = new Trie2Writable(t1); assertTrue("", t1.equals(t2)); // Set / Get Trie2Writable t1w = new Trie2Writable(10, 666); t1w.set(0x4567, 99); assertEquals("", 10, t1w.get(0x4566)); assertEquals("", 99, t1w.get(0x4567)); assertEquals("", 666, t1w.get(-1)); assertEquals("", 666, t1w.get(0x110000)); // SetRange t1w = new Trie2Writable(10, 666); t1w.setRange(13 /*start*/, 6666 /*end*/, 7788 /*value*/, false /*overwrite */); t1w.setRange(6000, 7000, 9900, true); assertEquals("", 10, t1w.get(12)); assertEquals("", 7788, t1w.get(13)); assertEquals("", 7788, t1w.get(5999)); assertEquals("", 9900, t1w.get(6000)); assertEquals("", 9900, t1w.get(7000)); assertEquals("", 10, t1w.get(7001)); assertEquals("", 666, t1w.get(0x110000)); // setRange from a Trie2.Range // (Ranges are more commonly created by iterating over a Trie2, // but create one by hand here) Trie2.Range r = new Trie2.Range(); r.startCodePoint = 50; r.endCodePoint = 52; r.value = 0x12345678; r.leadSurrogate = false; t1w = new Trie2Writable(0, 0xbad); t1w.setRange(r, true); assertEquals(null, 0, t1w.get(49)); assertEquals("", 0x12345678, t1w.get(50)); assertEquals("", 0x12345678, t1w.get(52)); assertEquals("", 0, t1w.get(53)); // setForLeadSurrogateCodeUnit / getFromU16SingleLead t1w = new Trie2Writable(10, 0xbad); assertEquals("", 10, t1w.getFromU16SingleLead((char)0x0d801)); t1w.setForLeadSurrogateCodeUnit((char)0xd801, 5000); t1w.set(0xd801, 6000); assertEquals("", 5000, t1w.getFromU16SingleLead((char)0x0d801)); assertEquals("", 6000, t1w.get(0x0d801)); // get(). Is covered by nearly every other test. // Trie2_16 getAsFrozen_16() t1w = new Trie2Writable(10, 666); t1w.set(42, 5555); t1w.set(0x1ff00, 224); Trie2_16 t1_16 = t1w.toTrie2_16(); assertTrue("", t1w.equals(t1_16)); // alter the writable Trie2 and then re-freeze. t1w.set(152, 129); t1_16 = t1w.toTrie2_16(); assertTrue("", t1w.equals(t1_16)); assertEquals("", 129, t1w.get(152)); // Trie2_32 getAsFrozen_32() // t1w = new Trie2Writable(10, 666); t1w.set(42, 5555); t1w.set(0x1ff00, 224); Trie2_32 t1_32 = t1w.toTrie2_32(); assertTrue("", t1w.equals(t1_32)); // alter the writable Trie2 and then re-freeze. t1w.set(152, 129); assertNotEquals("", t1_32, t1w); t1_32 = t1w.toTrie2_32(); assertTrue("", t1w.equals(t1_32)); assertEquals("", 129, t1w.get(152)); // serialize(OutputStream os, ValueWidth width) // ByteArrayOutputStream os = new ByteArrayOutputStream(); t1w = new Trie2Writable(0, 0xbad); t1w.set(0x41, 0x100); t1w.set(0xc2, 0x200); t1w.set(0x404, 0x300); t1w.set(0xd903, 0x500); t1w.set(0xdd29, 0x600); t1w.set(0x1055d3, 0x700); t1w.setForLeadSurrogateCodeUnit((char)0xda1a, 0x800); try { // Serialize to 16 bits. int serializedLen = t1w.toTrie2_16().serialize(os); // Fragile test. Serialized length could change with changes to compaction. // But it should not change unexpectedly. assertEquals("", 3508, serializedLen); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); Trie2 t1ws16 = Trie2.createFromSerialized(is); assertEquals("", t1ws16.getClass(), Trie2_16.class); assertEquals("", t1w, t1ws16); // Serialize to 32 bits os.reset(); serializedLen = t1w.toTrie2_32().serialize(os); // Fragile test. Serialized length could change with changes to compaction. // But it should not change unexpectedly. assertEquals("", 4332, serializedLen); is = new ByteArrayInputStream(os.toByteArray()); Trie2 t1ws32 = Trie2.createFromSerialized(is); assertEquals("", t1ws32.getClass(), Trie2_32.class); assertEquals("", t1w, t1ws32); } catch (IOException e) { errln(where() + e.toString()); } } public void TestCharSequenceIterator() { String text = "abc123\ud800\udc01 "; // Includes a Unicode supplemental character String vals = "LLLNNNX?S"; Trie2Writable tw = new Trie2Writable(0, 666); tw.setRange('a', 'z', 'L', false); tw.setRange('1', '9', 'N', false); tw.set(' ', 'S'); tw.set(0x10001, 'X'); Trie2.CharSequenceIterator it = tw.charSequenceIterator(text, 0); // Check forwards iteration. Trie2.CharSequenceValues ir; int i; for (i=0; it.hasNext(); i++) { ir = it.next(); int expectedCP = Character.codePointAt(text, i); assertEquals("" + " i="+i, expectedCP, ir.codePoint); assertEquals("" + " i="+i, i, ir.index); assertEquals("" + " i="+i, vals.charAt(i), ir.value); if (expectedCP >= 0x10000) { i++; } } assertEquals("", text.length(), i); // Check reverse iteration, starting at an intermediate point. it.set(5); for (i=5; it.hasPrevious(); ) { ir = it.previous(); int expectedCP = Character.codePointBefore(text, i); i -= (expectedCP < 0x10000? 1 : 2); assertEquals("" + " i="+i, expectedCP, ir.codePoint); assertEquals("" + " i="+i, i, ir.index); assertEquals("" + " i="+i, vals.charAt(i), ir.value); } assertEquals("", 0, i); } // // Port of Tests from ICU4C ... // // setRanges array elements are // {start Code point, limit CP, value, overwrite} // // There must be an entry with limit 0 and with the intialValue. // It may be preceded by an entry with negative limit and the errorValue. // // checkRanges array elemets are // { limit code point, value} // // The expected value range is from the previous boundary's limit to before // this boundary's limit // String[] trieNames = {"setRanges1", "setRanges2", "setRanges3", "setRangesEmpty", "setRangesSingleValue"}; /* set consecutive ranges, even with value 0 */ private static int[][] setRanges1 ={ { 0, 0, 0, 0 }, { 0, 0x40, 0, 0 }, { 0x40, 0xe7, 0x1234, 0 }, { 0xe7, 0x3400, 0, 0 }, { 0x3400, 0x9fa6, 0x6162, 0 }, { 0x9fa6, 0xda9e, 0x3132, 0 }, { 0xdada, 0xeeee, 0x87ff, 0 }, { 0xeeee, 0x11111, 1, 0 }, { 0x11111, 0x44444, 0x6162, 0 }, { 0x44444, 0x60003, 0, 0 }, { 0xf0003, 0xf0004, 0xf, 0 }, { 0xf0004, 0xf0006, 0x10, 0 }, { 0xf0006, 0xf0007, 0x11, 0 }, { 0xf0007, 0xf0040, 0x12, 0 }, { 0xf0040, 0x110000, 0, 0 } }; private static int[][] checkRanges1 = { { 0, 0 }, { 0x40, 0 }, { 0xe7, 0x1234 }, { 0x3400, 0 }, { 0x9fa6, 0x6162 }, { 0xda9e, 0x3132 }, { 0xdada, 0 }, { 0xeeee, 0x87ff }, { 0x11111, 1 }, { 0x44444, 0x6162 }, { 0xf0003, 0 }, { 0xf0004, 0xf }, { 0xf0006, 0x10 }, { 0xf0007, 0x11 }, { 0xf0040, 0x12 }, { 0x110000, 0 } }; /* set some interesting overlapping ranges */ private static int [][] setRanges2={ { 0, 0, 0, 0 }, { 0x21, 0x7f, 0x5555, 1 }, { 0x2f800, 0x2fedc, 0x7a, 1 }, { 0x72, 0xdd, 3, 1 }, { 0xdd, 0xde, 4, 0 }, { 0x201, 0x240, 6, 1 }, /* 3 consecutive blocks with the same pattern but */ { 0x241, 0x280, 6, 1 }, /* discontiguous value ranges, testing utrie2_enum() */ { 0x281, 0x2c0, 6, 1 }, { 0x2f987, 0x2fa98, 5, 1 }, { 0x2f777, 0x2f883, 0, 1 }, { 0x2f900, 0x2ffaa, 1, 0 }, { 0x2ffaa, 0x2ffab, 2, 1 }, { 0x2ffbb, 0x2ffc0, 7, 1 } }; private static int[] [] checkRanges2={ { 0, 0 }, { 0x21, 0 }, { 0x72, 0x5555 }, { 0xdd, 3 }, { 0xde, 4 }, { 0x201, 0 }, { 0x240, 6 }, { 0x241, 0 }, { 0x280, 6 }, { 0x281, 0 }, { 0x2c0, 6 }, { 0x2f883, 0 }, { 0x2f987, 0x7a }, { 0x2fa98, 5 }, { 0x2fedc, 0x7a }, { 0x2ffaa, 1 }, { 0x2ffab, 2 }, { 0x2ffbb, 0 }, { 0x2ffc0, 7 }, { 0x110000, 0 } }; /* private static int[] [] checkRanges2_d800={ { 0x10000, 0 }, { 0x10400, 0 } }; private static int[][] checkRanges2_d87e={ { 0x2f800, 6 }, { 0x2f883, 0 }, { 0x2f987, 0x7a }, { 0x2fa98, 5 }, { 0x2fc00, 0x7a } }; private static int[][] checkRanges2_d87f={ { 0x2fc00, 0 }, { 0x2fedc, 0x7a }, { 0x2ffaa, 1 }, { 0x2ffab, 2 }, { 0x2ffbb, 0 }, { 0x2ffc0, 7 }, { 0x30000, 0 } }; private static int[][] checkRanges2_dbff={ { 0x10fc00, 0 }, { 0x110000, 0 } }; */ /* use a non-zero initial value */ private static int[][] setRanges3={ { 0, 0, 9, 0 }, // non-zero initial value. { 0x31, 0xa4, 1, 0 }, { 0x3400, 0x6789, 2, 0 }, { 0x8000, 0x89ab, 9, 1 }, { 0x9000, 0xa000, 4, 1 }, { 0xabcd, 0xbcde, 3, 1 }, { 0x55555, 0x110000, 6, 1 }, /* highStart=(char)0xd800 && cp<(char)0xdc00); assertEquals(testName, range.value, trie.getFromU16SingleLead((char)cp)); } else { assertEquals(testName, range.value, trie.get(cp)); } } } } // Was testTrieRanges in ICU4C. Renamed to not conflict with ICU4J test framework. private void checkTrieRanges(String testName, String serializedName, boolean withClone, int[][] setRanges, int [][] checkRanges) throws IOException { // Run tests against Tries that were built by ICU4C and serialized. String fileName16 = "Trie2Test." + serializedName + ".16.tri2"; String fileName32 = "Trie2Test." + serializedName + ".32.tri2"; InputStream is = Trie2Test.class.getResourceAsStream(fileName16); Trie2 trie16 = Trie2.createFromSerialized(is); is.close(); trieGettersTest(testName, trie16, checkRanges); is = Trie2Test.class.getResourceAsStream(fileName32); Trie2 trie32 = Trie2.createFromSerialized(is); is.close(); trieGettersTest(testName, trie32, checkRanges); // Run the same tests against locally contructed Tries. Trie2Writable trieW = genTrieFromSetRanges(setRanges); trieGettersTest(testName, trieW, checkRanges); assertEquals("", trieW, trie16); // Locally built tries must be assertEquals("", trieW, trie32); // the same as those imported from ICU4C Trie2_32 trie32a = trieW.toTrie2_32(); trieGettersTest(testName, trie32a, checkRanges); Trie2_16 trie16a = trieW.toTrie2_16(); trieGettersTest(testName, trie16a, checkRanges); } // Was "TrieTest" in trie2test.c public void TestRanges() throws IOException { checkTrieRanges("set1", "setRanges1", false, setRanges1, checkRanges1); checkTrieRanges("set2-overlap", "setRanges2", false, setRanges2, checkRanges2); checkTrieRanges("set3-initial-9", "setRanges3", false, setRanges3, checkRanges3); checkTrieRanges("set-empty", "setRangesEmpty", false, setRangesEmpty, checkRangesEmpty); checkTrieRanges("set-single-value", "setRangesSingleValue", false, setRangesSingleValue, checkRangesSingleValue); checkTrieRanges("set2-overlap.withClone", "setRanges2", true, setRanges2, checkRanges2); } private String where() { StackTraceElement[] st = new Throwable().getStackTrace(); String w = "File: " + st[1].getFileName() + ", Line " + st[1].getLineNumber(); return w; } }