| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2016, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.dev.test.translit; |
| |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.SortedSet; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import com.ibm.icu.dev.test.TestBoilerplate; |
| import com.ibm.icu.dev.test.TestFmwk; |
| import com.ibm.icu.dev.util.CollectionUtilities; |
| import com.ibm.icu.dev.util.UnicodeMap; |
| import com.ibm.icu.dev.util.UnicodeMap.EntryRange; |
| import com.ibm.icu.dev.util.UnicodeMapIterator; |
| import com.ibm.icu.impl.Utility; |
| import com.ibm.icu.lang.UCharacter; |
| import com.ibm.icu.lang.UProperty; |
| import com.ibm.icu.text.UTF16; |
| import com.ibm.icu.text.UnicodeSet; |
| |
| /** |
| * @test |
| * @summary General test of UnicodeSet |
| */ |
| @RunWith(JUnit4.class) |
| public class UnicodeMapTest extends TestFmwk { |
| |
| static final int MODIFY_TEST_LIMIT = 32; |
| static final int MODIFY_TEST_ITERATIONS = 100000; |
| |
| @Test |
| public void TestIterations() { |
| UnicodeMap<Double> foo = new UnicodeMap(); |
| checkToString(foo, ""); |
| foo.put(3, 6d).put(5, 10d); |
| checkToString(foo, "0003=6.0\n0005=10.0\n"); |
| foo.put(0x10FFFF, 666d); |
| checkToString(foo, "0003=6.0\n0005=10.0\n10FFFF=666.0\n"); |
| foo.put("neg", -555d); |
| checkToString(foo, "0003=6.0\n0005=10.0\n10FFFF=666.0\n006E,0065,0067=-555.0\n"); |
| |
| double i = 0; |
| for (EntryRange<Double> entryRange : foo.entryRanges()) { |
| i += entryRange.value; |
| } |
| assertEquals("EntryRange<T>", 127d, i); |
| } |
| |
| public void checkToString(UnicodeMap<Double> foo, String expected) { |
| assertEquals("EntryRange<T>", expected, CollectionUtilities.join(foo.entryRanges(), "\n") + (foo.size() == 0 ? "" : "\n")); |
| assertEquals("EntryRange<T>", expected, foo.toString()); |
| } |
| |
| @Test |
| public void TestRemove() { |
| UnicodeMap<Double> foo = new UnicodeMap() |
| .putAll(0x20, 0x29, -2d) |
| .put("abc", 3d) |
| .put("xy", 2d) |
| .put("mark", 4d) |
| .freeze(); |
| UnicodeMap<Double> fii = new UnicodeMap() |
| .putAll(0x21, 0x25, -2d) |
| .putAll(0x26, 0x28, -3d) |
| .put("abc", 3d) |
| .put("mark", 999d) |
| .freeze(); |
| |
| UnicodeMap<Double> afterFiiRemoval = new UnicodeMap() |
| .put(0x20, -2d) |
| .putAll(0x26, 0x29, -2d) |
| .put("xy", 2d) |
| .put("mark", 4d) |
| .freeze(); |
| |
| UnicodeMap<Double> afterFiiRetained = new UnicodeMap() |
| .putAll(0x21, 0x25, -2d) |
| .put("abc", 3d) |
| .freeze(); |
| |
| UnicodeMap<Double> test = new UnicodeMap<Double>().putAll(foo) |
| .removeAll(fii); |
| assertEquals("removeAll", afterFiiRemoval, test); |
| |
| test = new UnicodeMap<Double>().putAll(foo) |
| .retainAll(fii); |
| assertEquals("retainAll", afterFiiRetained, test); |
| } |
| |
| @Test |
| public void TestAMonkey() { |
| SortedMap<String,Integer> stayWithMe = new TreeMap<String,Integer>(OneFirstComparator); |
| |
| UnicodeMap<Integer> me = new UnicodeMap<Integer>().putAll(stayWithMe); |
| // check one special case, removal near end |
| me.putAll(0x10FFFE, 0x10FFFF, 666); |
| me.remove(0x10FFFF); |
| |
| int iterations = 100000; |
| SortedMap<String,Integer> test = new TreeMap(); |
| |
| Random rand = new Random(0); |
| String other; |
| Integer value; |
| // try modifications |
| for (int i = 0; i < iterations ; ++i) { |
| switch(rand.nextInt(20)) { |
| case 0: |
| logln("clear"); |
| stayWithMe.clear(); |
| me.clear(); |
| break; |
| case 1: |
| fillRandomMap(rand, 5, test); |
| logln("putAll\t" + test); |
| stayWithMe.putAll(test); |
| me.putAll(test); |
| break; |
| case 2: case 3: case 4: case 5: case 6: case 7: case 8: |
| other = getRandomKey(rand); |
| // if (other.equals("\uDBFF\uDFFF") && me.containsKey(0x10FFFF) && me.get(0x10FFFF).equals(me.get(0x10FFFE))) { |
| // System.out.println("Remove\t" + other + "\n" + me); |
| // } |
| logln("remove\t" + other); |
| stayWithMe.remove(other); |
| try { |
| me.remove(other); |
| } catch (IllegalArgumentException e) { |
| errln("remove\t" + other + "\tfailed: " + e.getMessage() + "\n" + me); |
| me.clear(); |
| stayWithMe.clear(); |
| } |
| break; |
| default: |
| other = getRandomKey(rand); |
| value = rand.nextInt(50)+50; |
| logln("put\t" + other + " = " + value); |
| stayWithMe.put(other, value); |
| me.put(other,value); |
| break; |
| } |
| checkEquals(me, stayWithMe); |
| } |
| } |
| |
| /** |
| * @param rand |
| * @param nextInt |
| * @param test |
| * @return |
| */ |
| private SortedMap<String, Integer> fillRandomMap(Random rand, int max, SortedMap<String, Integer> test) { |
| test.clear(); |
| max = rand.nextInt(max); |
| for (int i = 0; i < max; ++i) { |
| test.put(getRandomKey(rand), rand.nextInt(50)+50); |
| } |
| return test; |
| } |
| |
| Set temp = new HashSet(); |
| /** |
| * @param me |
| * @param stayWithMe |
| */ |
| private void checkEquals(UnicodeMap<Integer> me, SortedMap<String, Integer> stayWithMe) { |
| temp.clear(); |
| for (Entry<String, Integer> e : me.entrySet()) { |
| temp.add(e); |
| } |
| Set<Entry<String, Integer>> entrySet = stayWithMe.entrySet(); |
| if (!entrySet.equals(temp)) { |
| logln(me.entrySet().toString()); |
| logln(me.toString()); |
| assertEquals("are in parallel", entrySet, temp); |
| // we failed. Reset and start again |
| entrySet.clear(); |
| temp.clear(); |
| return; |
| } |
| for (String key : stayWithMe.keySet()) { |
| assertEquals("containsKey", stayWithMe.containsKey(key), me.containsKey(key)); |
| Integer value = stayWithMe.get(key); |
| assertEquals("get", value, me.get(key)); |
| assertEquals("containsValue", stayWithMe.containsValue(value), me.containsValue(value)); |
| int cp = UnicodeSet.getSingleCodePoint(key); |
| if (cp != Integer.MAX_VALUE) { |
| assertEquals("get", value, me.get(cp)); |
| } |
| } |
| Set<String> nonCodePointStrings = stayWithMe.tailMap("").keySet(); |
| if (nonCodePointStrings.size() == 0) nonCodePointStrings = null; // for parallel api |
| assertEquals("getNonRangeStrings", nonCodePointStrings, me.getNonRangeStrings()); |
| |
| TreeSet<Integer> values = new TreeSet<Integer>(stayWithMe.values()); |
| TreeSet<Integer> myValues = new TreeSet<Integer>(me.values()); |
| assertEquals("values", myValues, values); |
| |
| for (String key : stayWithMe.keySet()) { |
| assertEquals("containsKey", stayWithMe.containsKey(key), me.containsKey(key)); |
| } |
| } |
| |
| static Comparator<String> OneFirstComparator = new Comparator<String>() { |
| @Override |
| public int compare(String o1, String o2) { |
| int cp1 = UnicodeSet.getSingleCodePoint(o1); |
| int cp2 = UnicodeSet.getSingleCodePoint(o2); |
| int result = cp1 - cp2; |
| if (result != 0) { |
| return result; |
| } |
| if (cp1 == Integer.MAX_VALUE) { |
| return o1.compareTo(o2); |
| } |
| return 0; |
| } |
| |
| }; |
| |
| /** |
| * @param rand |
| * @param others |
| * @return |
| */ |
| private String getRandomKey(Random rand) { |
| int r = rand.nextInt(30); |
| if (r == 0) { |
| return UTF16.valueOf(r); |
| } else if (r < 10) { |
| return UTF16.valueOf('A'-1+r); |
| } else if (r < 20) { |
| return UTF16.valueOf(0x10FFFF - (r-10)); |
| // } else if (r == 20) { |
| // return ""; |
| } |
| return "a" + UTF16.valueOf(r + 'a'-1); |
| } |
| |
| @Test |
| public void TestModify() { |
| Random random = new Random(0); |
| UnicodeMap unicodeMap = new UnicodeMap(); |
| HashMap hashMap = new HashMap(); |
| String[] values = {null, "the", "quick", "brown", "fox"}; |
| for (int count = 1; count <= MODIFY_TEST_ITERATIONS; ++count) { |
| String value = values[random.nextInt(values.length)]; |
| int start = random.nextInt(MODIFY_TEST_LIMIT); // test limited range |
| int end = random.nextInt(MODIFY_TEST_LIMIT); |
| if (start > end) { |
| int temp = start; |
| start = end; |
| end = temp; |
| } |
| int modCount = count & 0xFF; |
| if (modCount == 0 && isVerbose()) { |
| logln("***"+count); |
| logln(unicodeMap.toString()); |
| } |
| unicodeMap.putAll(start, end, value); |
| if (modCount == 1 && isVerbose()) { |
| logln(">>>\t" + Utility.hex(start) + ".." + Utility.hex(end) + "\t" + value); |
| logln(unicodeMap.toString()); |
| } |
| for (int i = start; i <= end; ++i) { |
| hashMap.put(new Integer(i), value); |
| } |
| if (!hasSameValues(unicodeMap, hashMap)) { |
| errln("Failed at " + count); |
| } |
| } |
| } |
| |
| private boolean hasSameValues(UnicodeMap unicodeMap, HashMap hashMap) { |
| for (int i = 0; i < MODIFY_TEST_LIMIT; ++i) { |
| Object unicodeMapValue = unicodeMap.getValue(i); |
| Object hashMapValue = hashMap.get(new Integer(i)); |
| if (unicodeMapValue != hashMapValue) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Test |
| public void TestCloneAsThawed11721 () { |
| UnicodeMap<Integer> test = new UnicodeMap().put("abc", 3).freeze(); |
| UnicodeMap<Integer> copy = test.cloneAsThawed(); |
| copy.put("def", 4); |
| assertEquals("original-abc", (Integer) 3, test.get("abc")); |
| assertNull("original-def", test.get("def")); |
| assertEquals("copy-def", (Integer) 4, copy.get("def")); |
| } |
| |
| private static final int LIMIT = 0x15; // limit to make testing more realistic in terms of collisions |
| private static final int ITERATIONS = 1000000; |
| private static final boolean SHOW_PROGRESS = false; |
| private static final boolean DEBUG = false; |
| |
| SortedSet<String> log = new TreeSet<String>(); |
| static String[] TEST_VALUES = {"A", "B", "C", "D", "E", "F"}; |
| static Random random = new Random(12345); |
| |
| @Test |
| public void TestUnicodeMapRandom() { |
| // do random change to both, then compare |
| random.setSeed(12345); // reproducible results |
| logln("Comparing against HashMap"); |
| UnicodeMap<String> map1 = new UnicodeMap(); |
| Map<Integer, String> map2 = new HashMap<Integer, String>(); |
| for (int counter = 0; counter < ITERATIONS; ++counter) { |
| int start = random.nextInt(LIMIT); |
| String value = TEST_VALUES[random.nextInt(TEST_VALUES.length)]; |
| String logline = Utility.hex(start) + "\t" + value; |
| if (SHOW_PROGRESS) logln(counter + "\t" + logline); |
| log.add(logline); |
| if (DEBUG && counter == 144) { |
| System.out.println(" debug"); |
| } |
| map1.put(start, value); |
| map2.put(start, value); |
| check(map1, map2, counter); |
| } |
| checkNext(map1, map2, LIMIT); |
| } |
| |
| private static final int propEnum = UProperty.GENERAL_CATEGORY; |
| |
| @Test |
| public void TestUnicodeMapGeneralCategory() { |
| logln("Setting General Category"); |
| UnicodeMap<String> map1 = new UnicodeMap(); |
| Map<Integer, String> map2 = new HashMap<Integer, String>(); |
| //Map<Integer, String> map3 = new TreeMap<Integer, String>(); |
| map1 = new UnicodeMap<String>(); |
| map2 = new TreeMap<Integer,String>(); |
| for (int cp = 0;;) { |
| int enumValue = UCharacter.getIntPropertyValue(cp, propEnum); |
| //if (enumValue <= 0) continue; // for smaller set |
| String value = UCharacter.getPropertyValueName(propEnum,enumValue, UProperty.NameChoice.LONG); |
| map1.put(cp, value); |
| map2.put(cp, value); |
| cp++; |
| // Unicode is huge, skip over large parts of it. |
| if (cp == 0x08FF) { // General Scripts Area. |
| cp = 0xD700; // Hangul Syllables Area. |
| } else if (cp == 0x100FF) { // General Scripts Area. |
| cp = 0x1F000; // Symbols Area. |
| } else if (cp == 0x200FF) { // CJK Unified Ideographs Extensions. |
| cp = 0x10FF00; // Supplementary Private Use Area-B. |
| } else if (cp == 0x10FFFF) { // The end of Unicode. |
| break; |
| } |
| } |
| checkNext(map1, map2, Integer.MAX_VALUE); |
| |
| logln("Comparing General Category"); |
| check(map1, map2, -1); |
| logln("Comparing Values"); |
| Set<String> values1 = map1.getAvailableValues(new TreeSet<String>()); |
| Set<String> values2 = new TreeSet<String>(map2.values()); |
| if (!TestBoilerplate.verifySetsIdentical(this, values1, values2)) { |
| throw new IllegalArgumentException("Halting"); |
| } |
| logln("Comparing Sets"); |
| for (Iterator<String> it = values1.iterator(); it.hasNext();) { |
| String value = it.next(); |
| logln(value == null ? "null" : value); |
| UnicodeSet set1 = map1.keySet(value); |
| UnicodeSet set2 = TestBoilerplate.getSet(map2, value); |
| if (!TestBoilerplate.verifySetsIdentical(this, set1, set2)) { |
| throw new IllegalArgumentException("Halting"); |
| } |
| } |
| } |
| |
| @Test |
| public void TestAUnicodeMap2() { |
| UnicodeMap foo = new UnicodeMap(); |
| @SuppressWarnings("unused") |
| int hash = foo.hashCode(); // make sure doesn't NPE |
| @SuppressWarnings("unused") |
| Set fii = foo.stringKeys(); // make sure doesn't NPE |
| } |
| |
| @Test |
| public void TestAUnicodeMapInverse() { |
| UnicodeMap<Character> foo1 = new UnicodeMap<Character>() |
| .putAll('a', 'z', 'b') |
| .put("ab", 'c') |
| .put('x', 'b') |
| .put("xy", 'c') |
| ; |
| Map<Character, UnicodeSet> target = new HashMap<Character, UnicodeSet>(); |
| foo1.addInverseTo(target); |
| UnicodeMap<Character> reverse = new UnicodeMap().putAllInverse(target); |
| assertEquals("", foo1, reverse); |
| } |
| |
| private void checkNext(UnicodeMap<String> map1, Map<Integer,String> map2, int limit) { |
| logln("Comparing nextRange"); |
| Map localMap = new TreeMap(); |
| UnicodeMapIterator<String> mi = new UnicodeMapIterator<String>(map1); |
| while (mi.nextRange()) { |
| logln(Utility.hex(mi.codepoint) + ".." + Utility.hex(mi.codepointEnd) + " => " + mi.value); |
| for (int i = mi.codepoint; i <= mi.codepointEnd; ++i) { |
| //if (i >= limit) continue; |
| localMap.put(i, mi.value); |
| } |
| } |
| checkMap(map2, localMap); |
| |
| logln("Comparing next"); |
| mi.reset(); |
| localMap = new TreeMap(); |
| // String lastValue = null; |
| while (mi.next()) { |
| // if (!UnicodeMap.areEqual(lastValue, mi.value)) { |
| // // System.out.println("Change: " + Utility.hex(mi.codepoint) + " => " + mi.value); |
| // lastValue = mi.value; |
| // } |
| //if (mi.codepoint >= limit) continue; |
| localMap.put(mi.codepoint, mi.value); |
| } |
| checkMap(map2, localMap); |
| } |
| |
| public void check(UnicodeMap<String> map1, Map<Integer,String> map2, int counter) { |
| for (int i = 0; i < LIMIT; ++i) { |
| String value1 = map1.getValue(i); |
| String value2 = map2.get(i); |
| if (!UnicodeMap.areEqual(value1, value2)) { |
| errln(counter + " Difference at " + Utility.hex(i) |
| + "\t UnicodeMap: " + value1 |
| + "\t HashMap: " + value2); |
| errln("UnicodeMap: " + map1); |
| errln("Log: " + TestBoilerplate.show(log)); |
| errln("HashMap: " + TestBoilerplate.show(map2)); |
| } |
| } |
| } |
| |
| void checkMap(Map m1, Map m2) { |
| if (m1.equals(m2)) return; |
| StringBuilder buffer = new StringBuilder(); |
| Set m1entries = m1.entrySet(); |
| Set m2entries = m2.entrySet(); |
| getEntries("\r\nIn First, and not Second", m1entries, m2entries, buffer, 20); |
| getEntries("\r\nIn Second, and not First", m2entries, m1entries, buffer, 20); |
| errln(buffer.toString()); |
| } |
| |
| static Comparator<Map.Entry<Integer, String>> ENTRY_COMPARATOR = new Comparator<Map.Entry<Integer, String>>() { |
| @Override |
| public int compare(Map.Entry<Integer, String> o1, Map.Entry<Integer, String> o2) { |
| if (o1 == o2) return 0; |
| if (o1 == null) return -1; |
| if (o2 == null) return 1; |
| Map.Entry<Integer, String> a = o1; |
| Map.Entry<Integer, String> b = o2; |
| int result = compare2(a.getKey(), b.getKey()); |
| if (result != 0) return result; |
| return compare2(a.getValue(), b.getValue()); |
| } |
| private <T extends Comparable> int compare2(T o1, T o2) { |
| if (o1 == o2) return 0; |
| if (o1 == null) return -1; |
| if (o2 == null) return 1; |
| return o1.compareTo(o2); |
| } |
| }; |
| |
| private void getEntries(String title, Set<Map.Entry<Integer,String>> m1entries, Set<Map.Entry<Integer, String>> m2entries, StringBuilder buffer, int limit) { |
| Set<Map.Entry<Integer, String>> m1_m2 = new TreeSet<Map.Entry<Integer, String>>(ENTRY_COMPARATOR); |
| m1_m2.addAll(m1entries); |
| m1_m2.removeAll(m2entries); |
| buffer.append(title + ": " + m1_m2.size() + "\r\n"); |
| for (Entry<Integer, String> entry : m1_m2) { |
| if (limit-- < 0) return; |
| buffer.append(entry.getKey()).append(" => ") |
| .append(entry.getValue()).append("\r\n"); |
| } |
| } |
| } |