6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @bug 7126277
27 * @run main Collisions -shortrun
28 * @run main/othervm -Djdk.map.althashing.threshold=0 Collisions -shortrun
29 * @summary Ensure Maps behave well with lots of hashCode() collisions.
30 * @author Mike Duigou
31 */
32 import java.util.*;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentSkipListMap;
35
36 public class Collisions {
37
38 /**
39 * Number of elements per map.
40 */
41 private static final int TEST_SIZE = 5000;
42
43 final static class HashableInteger implements Comparable<HashableInteger> {
44
45 final int value;
46 final int hashmask; //yes duplication
47
48 HashableInteger(int value, int hashmask) {
49 this.value = value;
50 this.hashmask = hashmask;
51 }
52
53 @Override
54 public boolean equals(Object obj) {
55 if (obj instanceof HashableInteger) {
56 HashableInteger other = (HashableInteger) obj;
57
58 return other.value == value;
59 }
60
61 return false;
62 }
63
64 @Override
65 public int hashCode() {
66 return value % hashmask;
67 }
68
69 @Override
70 public int compareTo(HashableInteger o) {
71 return value - o.value;
72 }
73
74 @Override
75 public String toString() {
76 return Integer.toString(value);
77 }
78 }
79
80 private static Object[][] makeTestData(int size) {
81 HashableInteger UNIQUE_OBJECTS[] = new HashableInteger[size];
82 HashableInteger COLLIDING_OBJECTS[] = new HashableInteger[size];
83 String UNIQUE_STRINGS[] = new String[size];
84 String COLLIDING_STRINGS[] = new String[size];
85
86 for (int i = 0; i < size; i++) {
87 UNIQUE_OBJECTS[i] = new HashableInteger(i, Integer.MAX_VALUE);
88 COLLIDING_OBJECTS[i] = new HashableInteger(i, 10);
89 UNIQUE_STRINGS[i] = unhash(i);
90 COLLIDING_STRINGS[i] = (0 == i % 2)
91 ? UNIQUE_STRINGS[i / 2]
92 : "\u0000\u0000\u0000\u0000\u0000" + COLLIDING_STRINGS[i - 1];
93 }
94
95 return new Object[][] {
96 new Object[]{"Unique Objects", UNIQUE_OBJECTS},
97 new Object[]{"Colliding Objects", COLLIDING_OBJECTS},
98 new Object[]{"Unique Strings", UNIQUE_STRINGS},
99 new Object[]{"Colliding Strings", COLLIDING_STRINGS}
100 };
101 }
102
103 /**
104 * Returns a string with a hash equal to the argument.
105 *
106 * @return string with a hash equal to the argument.
107 */
108 public static String unhash(int target) {
109 StringBuilder answer = new StringBuilder();
110 if (target < 0) {
111 // String with hash of Integer.MIN_VALUE, 0x80000000
112 answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");
113
114 if (target == Integer.MIN_VALUE) {
115 return answer.toString();
116 }
117 // Find target without sign bit set
118 target = target & Integer.MAX_VALUE;
119 }
120
121 unhash0(answer, target);
122 return answer.toString();
123 }
124
125 private static void unhash0(StringBuilder partial, int target) {
126 int div = target / 31;
127 int rem = target % 31;
128
129 if (div <= Character.MAX_VALUE) {
130 if (div != 0) {
131 partial.append((char) div);
132 }
133 partial.append((char) rem);
134 } else {
135 unhash0(partial, div);
136 partial.append((char) rem);
137 }
138 }
139
140 private static void realMain(String[] args) throws Throwable {
141 boolean shortRun = args.length > 0 && args[0].equals("-shortrun");
142
143 Object[][] mapKeys = makeTestData(shortRun ? (TEST_SIZE / 2) : TEST_SIZE);
144
145 // loop through data sets
146 for (Object[] keys_desc : mapKeys) {
147 Map<Object, Object>[] maps = (Map<Object, Object>[]) new Map[]{
148 new HashMap<>(),
149 new Hashtable<>(),
150 new IdentityHashMap<>(),
151 new LinkedHashMap<>(),
152 new TreeMap<>(),
153 new WeakHashMap<>(),
154 new ConcurrentHashMap<>(),
155 new ConcurrentSkipListMap<>()
156 };
157
158 // for each map type.
159 for (Map<Object, Object> map : maps) {
160 String desc = (String) keys_desc[0];
161 Object[] keys = (Object[]) keys_desc[1];
162 try {
163 testMap(map, desc, keys);
164 } catch(Exception all) {
165 unexpected("Failed for " + map.getClass().getName() + " with " + desc, all);
166 }
167 }
168 }
169 }
170
171 private static <T> void testMap(Map<T, T> map, String keys_desc, T[] keys) {
172 System.out.println(map.getClass() + " : " + keys_desc);
173 System.out.flush();
174 testInsertion(map, keys_desc, keys);
175
176 if (keys[0] instanceof HashableInteger) {
177 testIntegerIteration((Map<HashableInteger, HashableInteger>) map, (HashableInteger[]) keys);
178 } else {
179 testStringIteration((Map<String, String>) map, (String[]) keys);
180 }
181
182 testContainsKey(map, keys_desc, keys);
183
184 testRemove(map, keys_desc, keys);
185
186 map.clear();
187 testInsertion(map, keys_desc, keys);
188 testKeysIteratorRemove(map, keys_desc, keys);
189
190 map.clear();
191 testInsertion(map, keys_desc, keys);
192 testValuesIteratorRemove(map, keys_desc, keys);
193
194 map.clear();
195 testInsertion(map, keys_desc, keys);
196 testEntriesIteratorRemove(map, keys_desc, keys);
197
198 check(map.isEmpty());
199 }
200
201 private static <T> void testInsertion(Map<T, T> map, String keys_desc, T[] keys) {
202 check("map empty", (map.size() == 0) && map.isEmpty());
203
204 for (int i = 0; i < keys.length; i++) {
205 check(String.format("insertion: map expected size m%d != i%d", map.size(), i),
206 map.size() == i);
207 check(String.format("insertion: put(%s[%d])", keys_desc, i), null == map.put(keys[i], keys[i]));
208 check(String.format("insertion: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
209 check(String.format("insertion: containsValue(%s[%d])", keys_desc, i), map.containsValue(keys[i]));
210 }
211
212 check(String.format("map expected size m%d != k%d", map.size(), keys.length),
213 map.size() == keys.length);
214 }
215
216 private static void testIntegerIteration(Map<HashableInteger, HashableInteger> map, HashableInteger[] keys) {
217 check(String.format("map expected size m%d != k%d", map.size(), keys.length),
218 map.size() == keys.length);
219
220 BitSet all = new BitSet(keys.length);
221 for (Map.Entry<HashableInteger, HashableInteger> each : map.entrySet()) {
222 check("Iteration: key already seen", !all.get(each.getKey().value));
223 all.set(each.getKey().value);
224 }
225
226 all.flip(0, keys.length);
227 check("Iteration: some keys not visited", all.isEmpty());
228
229 for (HashableInteger each : map.keySet()) {
230 check("Iteration: key already seen", !all.get(each.value));
231 all.set(each.value);
232 }
233
234 all.flip(0, keys.length);
235 check("Iteration: some keys not visited", all.isEmpty());
236
237 int count = 0;
238 for (HashableInteger each : map.values()) {
239 count++;
240 }
241
242 check(String.format("Iteration: value count matches size m%d != c%d", map.size(), count),
243 map.size() == count);
244 }
245
246 private static void testStringIteration(Map<String, String> map, String[] keys) {
247 check(String.format("map expected size m%d != k%d", map.size(), keys.length),
248 map.size() == keys.length);
249
250 BitSet all = new BitSet(keys.length);
251 for (Map.Entry<String, String> each : map.entrySet()) {
252 String key = each.getKey();
253 boolean longKey = key.length() > 5;
254 int index = key.hashCode() + (longKey ? keys.length / 2 : 0);
255 check("key already seen", !all.get(index));
256 all.set(index);
257 }
258
259 all.flip(0, keys.length);
260 check("some keys not visited", all.isEmpty());
261
262 for (String each : map.keySet()) {
263 boolean longKey = each.length() > 5;
264 int index = each.hashCode() + (longKey ? keys.length / 2 : 0);
265 check("key already seen", !all.get(index));
266 all.set(index);
267 }
268
269 all.flip(0, keys.length);
270 check("some keys not visited", all.isEmpty());
271
272 int count = 0;
273 for (String each : map.values()) {
274 count++;
275 }
276
277 check(String.format("value count matches size m%d != k%d", map.size(), keys.length),
278 map.size() == keys.length);
279 }
280
281 private static <T> void testContainsKey(Map<T, T> map, String keys_desc, T[] keys) {
282 for (int i = 0; i < keys.length; i++) {
283 T each = keys[i];
284 check("containsKey: " + keys_desc + "[" + i + "]" + each, map.containsKey(each));
285 }
286 }
287
288 private static <T> void testRemove(Map<T, T> map, String keys_desc, T[] keys) {
289 check(String.format("remove: map expected size m%d != k%d", map.size(), keys.length),
290 map.size() == keys.length);
291
292 for (int i = 0; i < keys.length; i++) {
293 T each = keys[i];
294 check("remove: " + keys_desc + "[" + i + "]" + each, null != map.remove(each));
295 }
296
297 check(String.format("remove: map empty. size=%d", map.size()),
298 (map.size() == 0) && map.isEmpty());
299 }
300
301 private static <T> void testKeysIteratorRemove(Map<T, T> map, String keys_desc, T[] keys) {
302 check(String.format("remove: map expected size m%d != k%d", map.size(), keys.length),
303 map.size() == keys.length);
304
305 Iterator<T> each = map.keySet().iterator();
306 while (each.hasNext()) {
307 T t = each.next();
308 each.remove();
309 check("not removed: " + each, !map.containsKey(t) );
310 }
311
312 check(String.format("remove: map empty. size=%d", map.size()),
313 (map.size() == 0) && map.isEmpty());
314 }
315
316 private static <T> void testValuesIteratorRemove(Map<T, T> map, String keys_desc, T[] keys) {
317 check(String.format("remove: map expected size m%d != k%d", map.size(), keys.length),
318 map.size() == keys.length);
319
320 Iterator<T> each = map.values().iterator();
321 while (each.hasNext()) {
322 T t = each.next();
323 each.remove();
324 check("not removed: " + each, !map.containsValue(t) );
325 }
326
327 check(String.format("remove: map empty. size=%d", map.size()),
328 (map.size() == 0) && map.isEmpty());
329 }
330
331 private static <T> void testEntriesIteratorRemove(Map<T, T> map, String keys_desc, T[] keys) {
332 check(String.format("remove: map expected size m%d != k%d", map.size(), keys.length),
333 map.size() == keys.length);
334
335 Iterator<Map.Entry<T,T>> each = map.entrySet().iterator();
336 while (each.hasNext()) {
337 Map.Entry<T,T> t = each.next();
338 T key = t.getKey();
339 T value = t.getValue();
340 each.remove();
341 check("not removed: " + each, (map instanceof IdentityHashMap) || !map.entrySet().contains(t) );
342 check("not removed: " + each, !map.containsKey(key) );
343 check("not removed: " + each, !map.containsValue(value));
344 }
345
346 check(String.format("remove: map empty. size=%d", map.size()),
347 (map.size() == 0) && map.isEmpty());
348 }
349
350 //--------------------- Infrastructure ---------------------------
351 static volatile int passed = 0, failed = 0;
352
353 static void pass() {
354 passed++;
355 }
356
357 static void fail() {
358 failed++;
359 (new Error("Failure")).printStackTrace(System.err);
360 }
361
362 static void fail(String msg) {
363 failed++;
364 (new Error("Failure: " + msg)).printStackTrace(System.err);
365 }
366
367 static void abort() {
368 fail();
369 System.exit(1);
370 }
371
372 static void abort(String msg) {
373 fail(msg);
374 System.exit(1);
375 }
376
377 static void unexpected(String msg, Throwable t) {
378 System.err.println("Unexpected: " + msg);
379 unexpected(t);
380 }
381
382 static void unexpected(Throwable t) {
383 failed++;
384 t.printStackTrace(System.err);
385 }
386
387 static void check(boolean cond) {
388 if (cond) {
389 pass();
390 } else {
391 fail();
392 }
393 }
394
395 static void check(String desc, boolean cond) {
396 if (cond) {
397 pass();
398 } else {
399 fail(desc);
400 }
401 }
402
403 static void equal(Object x, Object y) {
404 if (Objects.equals(x, y)) {
405 pass();
406 } else {
407 fail(x + " not equal to " + y);
408 }
409 }
410
411 public static void main(String[] args) throws Throwable {
412 Thread.currentThread().setName(Collisions.class.getName());
413 // Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
414 try {
415 realMain(args);
416 } catch (Throwable t) {
417 unexpected(t);
418 }
419
420 System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
421 if (failed > 0) {
422 throw new Error("Some tests failed");
423 }
424 }
425 }
|
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @bug 7143928
27 * @run testng BasicSerialization
28 * @summary Ensure Maps can be serialized and deserialized.
29 * @author Mike Duigou
30 */
31 import java.io.ByteArrayOutputStream;
32 import java.io.InputStream;
33 import java.io.IOException;
34 import java.io.ObjectInputStream;
35 import java.io.ObjectOutputStream;
36 import java.io.ByteArrayInputStream;
37 import java.lang.reflect.Constructor;
38 import java.lang.reflect.Method;
39 import java.util.*;
40 import java.util.concurrent.ConcurrentHashMap;
41 import java.util.concurrent.ConcurrentSkipListMap;
42
43 import org.testng.annotations.Test;
44 import org.testng.annotations.DataProvider;
45 import static org.testng.Assert.fail;
46 import static org.testng.Assert.assertEquals;
47 import static org.testng.Assert.assertTrue;
48 import static org.testng.Assert.assertFalse;
49 import static org.testng.Assert.assertSame;
50
51 public class BasicSerialization {
52
53 enum IntegerEnum {
54
55 e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
56 e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
57 e20, e21, e22, e23, e24, e25, e26, e27, e28, e29,
58 e30, e31, e32, e33, e34, e35, e36, e37, e38, e39,
59 e40, e41, e42, e43, e44, e45, e46, e47, e48, e49,
60 e50, e51, e52, e53, e54, e55, e56, e57, e58, e59,
61 e60, e61, e62, e63, e64, e65, e66, e67, e68, e69,
62 e70, e71, e72, e73, e74, e75, e76, e77, e78, e79,
63 e80, e81, e82, e83, e84, e85, e86, e87, e88, e89,
64 e90, e91, e92, e93, e94, e95, e96, e97, e98, e99,
65 EXTRA_KEY;
66 public static final int SIZE = values().length;
67 };
68 private static final int TEST_SIZE = IntegerEnum.SIZE - 1;
69 /**
70 * Realized keys ensure that there is always a hard ref to all test objects.
71 */
72 private static final IntegerEnum[] KEYS = new IntegerEnum[TEST_SIZE];
73 /**
74 * Realized values ensure that there is always a hard ref to all test
75 * objects.
76 */
77 private static final String[] VALUES = new String[TEST_SIZE];
78
79 static {
80 IntegerEnum[] keys = IntegerEnum.values();
81 for (int each = 0; each < TEST_SIZE; each++) {
82 KEYS[each] = keys[each];
83 VALUES[each] = keys[each].name();
84 }
85 }
86 private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY;
87 private static final String EXTRA_VALUE = IntegerEnum.EXTRA_KEY.name();
88
89 public static <K, V> Map<K, V> mapClone(Map<K, V> map) {
90 Method cloneMethod;
91
92 try {
93 cloneMethod = map.getClass().getMethod("clone", new Class[]{});
94 } catch (NoSuchMethodException | SecurityException all) {
95 cloneMethod = null;
96 }
97
98 if (null != cloneMethod) {
99 try {
100 Map<K, V> result = (Map<K, V>)cloneMethod.invoke(map, new Object[]{});
101 return result;
102 } catch (Exception all) {
103 fail("clone() failed " + map.getClass().getSimpleName(), all);
104 return null;
105 }
106 } else {
107 Constructor<? extends Map> copyConstructor;
108 try {
109 copyConstructor = (Constructor<? extends Map>)map.getClass().getConstructor(new Class[]{Map.class});
110
111 Map<K, V> result = (Map<K, V>)copyConstructor.newInstance(new Object[]{map});
112
113 return result;
114 } catch (Exception all) {
115 return serialClone(map);
116 }
117 }
118 }
119
120 @Test(dataProvider = "Map<IntegerEnum,String>")
121 public void testSerialization(String description, Map<IntegerEnum, String> map) {
122 Object foo = new Object();
123
124 Map<IntegerEnum, String> clone = mapClone(map);
125 Map<IntegerEnum, String> serialClone = serialClone(map);
126
127 assertEquals(map, map, description + ":should equal self");
128 assertEquals(clone, map, description + ":should equal clone");
129 assertEquals(map, clone, description + ": should equal orginal map");
130 assertEquals(serialClone, map, description + ": should equal deserialized clone");
131 assertEquals(map, serialClone, description + ": should equal original map");
132 assertEquals(serialClone, clone, description + ": deserialized clone should equal clone");
133 assertEquals(clone, serialClone, description + ": clone should equal deserialized clone");
134
135 assertFalse(map.containsKey(EXTRA_KEY), description + ":unexpected key");
136 assertFalse(clone.containsKey(EXTRA_KEY), description + ":unexpected key");
137 assertFalse(serialClone.containsKey(EXTRA_KEY), description + ":unexpected key");
138 map.put(EXTRA_KEY, EXTRA_VALUE);
139 clone.put(EXTRA_KEY, EXTRA_VALUE);
140 serialClone.put(EXTRA_KEY, EXTRA_VALUE);
141 assertTrue(map.containsKey(EXTRA_KEY), description + ":missing key");
142 assertTrue(clone.containsKey(EXTRA_KEY), description + ":missing key");
143 assertTrue(serialClone.containsKey(EXTRA_KEY), description + ":missing key");
144 assertSame(map.get(EXTRA_KEY), EXTRA_VALUE, description + ":wrong value");
145 assertSame(clone.get(EXTRA_KEY), EXTRA_VALUE, description + ":wrong value");
146 assertSame(serialClone.get(EXTRA_KEY), EXTRA_VALUE, description + ":wrong value");
147
148 assertEquals(map, map, description + ":should equal self");
149 assertEquals(clone, map, description + ":should equal clone");
150 assertEquals(map, clone, description + ": should equal orginal map");
151 assertEquals(serialClone, map, description + ": should equal deserialized clone");
152 assertEquals(map, serialClone, description + ": should equal original map");
153 assertEquals(serialClone, clone, description + ": deserialized clone should equal clone");
154 assertEquals(clone, serialClone, description + ": clone should equal deserialized clone");
155 }
156
157 static byte[] serializedForm(Object obj) {
158 try {
159 ByteArrayOutputStream baos = new ByteArrayOutputStream();
160 new ObjectOutputStream(baos).writeObject(obj);
161 return baos.toByteArray();
162 } catch (IOException e) {
163 fail("Unexpected Exception", e);
164 return null;
165 }
166 }
167
168 static Object readObject(byte[] bytes) throws IOException, ClassNotFoundException {
169 InputStream is = new ByteArrayInputStream(bytes);
170 return new ObjectInputStream(is).readObject();
171 }
172
173 @SuppressWarnings("unchecked")
174 static <T> T serialClone(T obj) {
175 try {
176 return (T)readObject(serializedForm(obj));
177 } catch (IOException | ClassNotFoundException e) {
178 fail("Unexpected Exception", e);
179 return null;
180 }
181 }
182
183 @DataProvider(name = "Map<IntegerEnum,String>", parallel = true)
184 private static Iterator<Object[]> makeMaps() {
185 return Arrays.asList(
186 // empty
187 new Object[]{"HashMap", new HashMap()},
188 new Object[]{"LinkedHashMap", new LinkedHashMap()},
189 new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(new HashMap(), IntegerEnum.class, String.class)},
190 new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(new HashMap())},
191 // null hostile
192 new Object[]{"EnumMap", new EnumMap(IntegerEnum.class)},
193 new Object[]{"Hashtable", new Hashtable()},
194 new Object[]{"TreeMap", new TreeMap()},
195 new Object[]{"ConcurrentHashMap", new ConcurrentHashMap()},
196 new Object[]{"ConcurrentSkipListMap", new ConcurrentSkipListMap()},
197 new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(new ConcurrentHashMap(), IntegerEnum.class, String.class)},
198 new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(new EnumMap(IntegerEnum.class))},
199 // filled
200 new Object[]{"HashMap", fillMap(new HashMap())},
201 new Object[]{"LinkedHashMap", fillMap(new LinkedHashMap())},
202 new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(fillMap(new HashMap()), IntegerEnum.class, String.class)},
203 new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(fillMap(new HashMap()))},
204 // null hostile
205 new Object[]{"EnumMap", fillMap(new EnumMap(IntegerEnum.class))},
206 new Object[]{"Hashtable", fillMap(new Hashtable())},
207 new Object[]{"TreeMap", fillMap(new TreeMap())},
208 new Object[]{"ConcurrentHashMap", fillMap(new ConcurrentHashMap())},
209 new Object[]{"ConcurrentSkipListMap", fillMap(new ConcurrentSkipListMap())},
210 new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(fillMap(new ConcurrentHashMap()), IntegerEnum.class, String.class)},
211 new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(fillMap(new EnumMap(IntegerEnum.class)))}).iterator();
212 }
213
214 private static Map<IntegerEnum, String> fillMap(Map<IntegerEnum, String> result) {
215 for (int each = 0; each < TEST_SIZE; each++) {
216 result.put(KEYS[each], VALUES[each]);
217 }
218
219 return result;
220 }
221 }
|