blob: e3e809dce5dfa3eb28a98d63013e3555009c5d2c [file] [log] [blame]
// © 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");
}
}
}