/* * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.IdentityHashMap; import java.util.Set; import java.util.function.BiPredicate; import java.util.stream.IntStream; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static java.util.Map.entry; import static org.testng.Assert.*; /* * @test * @bug 8285295 8178355 * @summary Basic tests for IdentityHashMap * @run testng Basic */ // NOTE: avoid using TestNG's assertEquals/assertNotEquals directly on two IDHM instances, // as its logic for testing collections equality is suspect. Use checkEntries() to assert // that a map's entrySet contains exactly the expected mappings. There are no guarantees about // the identities of Map.Entry instances obtained from the entrySet; however, the keys and // values they contain are guaranteed to have the right identity. // TODO add tests using null keys and values // TODO deeper testing of view collections including iterators, equals, contains, etc. // TODO Map.Entry::setValue public class Basic { /* * Helpers */ record Box(int i) { Box(Box other) { this(other.i()); } } // Checks that a collection contains exactly the given elements and no others, using the // provided predicate for equivalence. Checking is performed both using contains() on the // collection and by simple array searching. The latter is O(N^2) so is suitable only for // small arrays. No two of the given elements can be equivalent according to the predicate. // TODO: read out the elements using iterator and stream and check them too @SafeVarargs private void checkContents(Collection c, BiPredicate p, E... given) { @SuppressWarnings("unchecked") E[] contents = (E[]) c.toArray(); assertEquals(c.size(), given.length); assertEquals(contents.length, given.length); final int LEN = given.length; for (E e : given) { assertTrue(c.contains(e)); } // Fill indexes array with position of a given element in the contents array, // or -1 if the given element cannot be found. int[] indexes = new int[LEN]; outer: for (int i = 0; i < LEN; i++) { for (int j = 0; j < LEN; j++) { if (p.test(given[i], contents[j])) { indexes[i] = j; continue outer; } } indexes[i] = -1; } // If every given element matches a distinct element in the contents array, // the sorted indexes array will be the sequence [0..LEN-1]. Arrays.sort(indexes); assertEquals(indexes, IntStream.range(0, LEN).toArray()); } // Checks that the collection contains the given boxes, by identity. private void checkElements(Collection c, Box... given) { checkContents(c, (b1, b2) -> b1 == b2, given); } // Checks that the collection contains entries that have identical keys and values. // The entries themselves are not checked for identity. @SafeVarargs private void checkEntries(Collection> c, Map.Entry... given) { checkContents(c, (e1, e2) -> e1.getKey() == e2.getKey() && e1.getValue() == e2.getValue(), given); } /* * Setup */ final Box k1a = new Box(17); final Box k1b = new Box(17); // equals but != k1a final Box k2 = new Box(42); final Box v1a = new Box(30); final Box v1b = new Box(30); // equals but != v1a final Box v2 = new Box(99); IdentityHashMap map; IdentityHashMap map2; @BeforeMethod public void setup() { map = new IdentityHashMap<>(); map.put(k1a, v1a); map.put(k1b, v1b); map.put(k2, v2); map2 = new IdentityHashMap<>(); map2.put(k1a, v1a); map2.put(k1b, v1b); map2.put(k2, v2); } /* * Tests */ // containsKey // containsValue // size @Test public void testSizeContainsKeyValue() { assertEquals(map.size(), 3); assertTrue(map.containsKey(k1a)); assertTrue(map.containsKey(k1b)); assertTrue(map.containsKey(k2)); assertFalse(map.containsKey(new Box(k1a))); assertTrue(map.containsValue(v1a)); assertTrue(map.containsValue(v1b)); assertFalse(map.containsValue(new Box(v1a))); assertTrue(map.containsValue(v2)); } // get @Test public void testGet() { assertSame(map.get(k1a), v1a); assertSame(map.get(k1b), v1b); assertSame(map.get(k2), v2); assertNull(map.get(new Box(k1a))); } // getOrDefault @Test public void testGetOrDefault() { Box other = new Box(22); assertSame(map.getOrDefault(k1a, other), v1a); assertSame(map.getOrDefault(k1b, other), v1b); assertSame(map.getOrDefault(new Box(k1a), other), other); assertSame(map.getOrDefault(k2, other), v2); } // clear // isEmpty @Test public void testClearEmpty() { assertFalse(map.isEmpty()); map.clear(); assertTrue(map.isEmpty()); } // hashCode @Test public void testHashCode() { int expected = (System.identityHashCode(k1a) ^ System.identityHashCode(v1a)) + (System.identityHashCode(k1b) ^ System.identityHashCode(v1b)) + (System.identityHashCode(k2) ^ System.identityHashCode(v2)); assertEquals(map.hashCode(), expected); assertEquals(map.entrySet().hashCode(), expected); } // equals @Test public void testEquals() { assertTrue(map.equals(map)); assertTrue(map.equals(map2)); assertTrue(map2.equals(map)); assertTrue(map.keySet().equals(map.keySet())); assertTrue(map.keySet().equals(map2.keySet())); assertTrue(map2.keySet().equals(map.keySet())); assertTrue(map.entrySet().equals(map.entrySet())); assertTrue(map.entrySet().equals(map2.entrySet())); assertTrue(map2.entrySet().equals(map.entrySet())); } // equals @Test public void testEqualsDifferentKey() { map2.remove(k1a); map2.put(new Box(k1a), v1a); assertFalse(map.equals(map2)); assertFalse(map2.equals(map)); assertFalse(map.keySet().equals(map2.keySet())); assertFalse(map2.keySet().equals(map.keySet())); assertFalse(map.entrySet().equals(map2.entrySet())); assertFalse(map2.entrySet().equals(map.entrySet())); } // equals @Test public void testEqualsDifferentValue() { map2.put(k1a, new Box(v1a)); assertFalse(map.equals(map2)); assertFalse(map2.equals(map)); assertTrue(map.keySet().equals(map2.keySet())); assertTrue(map2.keySet().equals(map.keySet())); assertFalse(map.entrySet().equals(map2.entrySet())); assertFalse(map2.entrySet().equals(map.entrySet())); } // equals @Test public void testEqualsNewMapping() { map.put(new Box(k1a), new Box(v1a)); assertFalse(map.equals(map2)); assertFalse(map2.equals(map)); assertFalse(map.keySet().equals(map2.keySet())); assertFalse(map2.keySet().equals(map.keySet())); assertFalse(map.entrySet().equals(map2.entrySet())); assertFalse(map2.entrySet().equals(map.entrySet())); } // equals @Test public void testEqualsMissingMapping() { var tmp = new IdentityHashMap(); tmp.put(k1a, v1a); tmp.put(k1b, v1b); assertFalse(map.equals(tmp)); assertFalse(tmp.equals(map)); assertFalse(map.keySet().equals(tmp.keySet())); assertFalse(tmp.keySet().equals(map.keySet())); assertFalse(map.entrySet().equals(tmp.entrySet())); assertFalse(tmp.entrySet().equals(map.entrySet())); } // keySet equals, contains @Test public void testKeySet() { Set keySet = map.keySet(); checkElements(keySet, k1a, k1b, k2); assertFalse(keySet.contains(new Box(k1a))); assertTrue(map.keySet().equals(map2.keySet())); assertTrue(map2.keySet().equals(map.keySet())); } // keySet remove @Test public void testKeySetNoRemove() { Set keySet = map.keySet(); boolean r = keySet.remove(new Box(k1a)); assertFalse(r); checkElements(keySet, k1a, k1b, k2); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); assertTrue(map.keySet().equals(map2.keySet())); assertTrue(map2.keySet().equals(map.keySet())); } // keySet remove @Test public void testKeySetRemove() { Set keySet = map.keySet(); boolean r = keySet.remove(k1a); assertTrue(r); checkElements(keySet, k1b, k2); checkEntries(map.entrySet(), entry(k1b, v1b), entry(k2, v2)); assertFalse(map.keySet().equals(map2.keySet())); assertFalse(map2.keySet().equals(map.keySet())); } // values @Test public void testValues() { Collection values = map.values(); checkElements(values, v1a, v1b, v2); assertFalse(values.contains(new Box(v1a))); } // values remove @Test public void testValuesNoRemove() { Collection values = map.values(); boolean r = values.remove(new Box(v1a)); assertFalse(r); checkElements(values, v1a, v1b, v2); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // values remove @Test public void testValuesRemove() { Collection values = map.values(); boolean r = values.remove(v1a); assertTrue(r); checkElements(values, v1b, v2); checkEntries(map.entrySet(), entry(k1b, v1b), entry(k2, v2)); } // entrySet equals, contains @Test public void testEntrySet() { Set> entrySet = map.entrySet(); assertFalse(entrySet.contains(entry(new Box(k1a), v1a))); assertFalse(entrySet.contains(entry(k1b, new Box(v1b)))); assertFalse(entrySet.contains(entry(new Box(k2), new Box(v2)))); assertTrue(map.entrySet().equals(map2.entrySet())); checkEntries(entrySet, entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // entrySet remove @Test public void testEntrySetNoRemove() { Set> entrySet = map.entrySet(); boolean r1 = entrySet.remove(entry(new Box(k1a), v1a)); boolean r2 = entrySet.remove(entry(k1a, new Box(v1a))); assertFalse(r1); assertFalse(r2); assertTrue(entrySet.equals(map2.entrySet())); checkEntries(entrySet, entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // entrySet remove @Test public void testEntrySetRemove() { Set> entrySet = map.entrySet(); boolean r = entrySet.remove(Map.entry(k1a, v1a)); assertTrue(r); assertFalse(entrySet.equals(map2.entrySet())); assertFalse(map.entrySet().equals(map2.entrySet())); checkEntries(entrySet, entry(k1b, v1b), entry(k2, v2)); checkEntries(map.entrySet(), entry(k1b, v1b), entry(k2, v2)); } // put @Test public void testPutNew() { Box newKey = new Box(k1a); Box newVal = new Box(v1a); Box r = map.put(newKey, newVal); assertNull(r); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2), entry(newKey, newVal)); } // put @Test public void testPutOverwrite() { Box newVal = new Box(v1a); Box r = map.put(k1a, newVal); assertSame(r, v1a); checkEntries(map.entrySet(), entry(k1a, newVal), entry(k1b, v1b), entry(k2, v2)); } // putAll @Test public void testPutAll() { Box newKey = new Box(k1a); Box newVal = new Box(v1a); Box newValB = new Box(v1b); var argMap = new IdentityHashMap(); argMap.put(newKey, newVal); // new entry argMap.put(k1b, newValB); // will overwrite value map.putAll(argMap); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, newValB), entry(k2, v2), entry(newKey, newVal)); } // putIfAbsent @Test public void testPutIfAbsentNoop() { Box r = map.putIfAbsent(k1a, new Box(v1a)); // no-op assertSame(r, v1a); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // putIfAbsent @Test public void testPutIfAbsentAddsNew() { Box newKey = new Box(k1a); Box newVal = new Box(v1a); Box r = map.putIfAbsent(newKey, newVal); // adds new entry assertNull(r); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2), entry(newKey, newVal)); } // remove(Object) @Test public void testRemoveKey() { Box r = map.remove(k1b); assertSame(r, v1b); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k2, v2)); } // remove(Object, Object) absent key, absent value @Test public void testRemoveAA() { Box k1c = new Box(k1a); Box v1c = new Box(v1a); assertFalse(map.remove(k1c, v1c)); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // remove(Object, Object) absent key, present value @Test public void testRemoveAV() { Box k1c = new Box(k1a); assertFalse(map.remove(k1c, v1a)); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // remove(Object, Object) present key, absent value @Test public void testRemoveKA() { Box v1c = new Box(v1a); assertFalse(map.remove(k1a, v1c)); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // remove(Object, Object) present key, present value @Test public void testRemoveKV() { assertTrue(map.remove(k1a, v1a)); checkEntries(map.entrySet(), entry(k1b, v1b), entry(k2, v2)); } // replace(K, V, V) absent key, absent oldValue @Test public void testReplaceAA() { Box k1c = new Box(k1a); Box v1c = new Box(v1a); Box newVal = new Box(v2); assertFalse(map.replace(k1c, v1c, newVal)); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // replace(K, V, V) absent key, present oldValue @Test public void testReplaceAV() { Box k1c = new Box(k1a); Box newVal = new Box(v2); assertFalse(map.replace(k1c, v1a, newVal)); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // replace(K, V, V) present key, absent oldValue @Test public void testReplaceKA() { Box v1c = new Box(v1a); Box newVal = new Box(v2); assertFalse(map.replace(k1a, v1c, newVal)); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // replace(K, V, V) present key, present oldValue @Test public void testReplaceKV() { Box newVal = new Box(v2); assertTrue(map.replace(k1a, v1a, newVal)); checkEntries(map.entrySet(), entry(k1a, newVal), entry(k1b, v1b), entry(k2, v2)); } // AN: key absent, remappingFunction returns null @Test public void testComputeAN() { Box newKey = new Box(k1a); Box r = map.compute(newKey, (k, v) -> null); assertNull(r); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // AV: key absent, remappingFunction returns non-null value @Test public void testComputeAV() { Box newKey = new Box(k1a); Box newVal = new Box(v1a); Box r = map.compute(newKey, (k, v) -> newVal); assertSame(r, newVal); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2), entry(newKey, newVal)); } // PN: key present, remappingFunction returns null @Test public void testComputePN() { Box r = map.compute(k1a, (k, v) -> null); assertNull(r); checkEntries(map.entrySet(), entry(k1b, v1b), entry(k2, v2)); } // PV: key present, remappingFunction returns non-null value @Test public void testComputePV() { Box newVal = new Box(v1a); Box r = map.compute(k1a, (k, v) -> newVal); assertSame(r, newVal); checkEntries(map.entrySet(), entry(k1a, newVal), entry(k1b, v1b), entry(k2, v2)); } // computeIfAbsent @Test public void testComputeIfAbsentIsCalled() { boolean[] called = new boolean[1]; Box newKey = new Box(k1a); Box newVal = new Box(v1a); Box r = map.computeIfAbsent(newKey, k -> { called[0] = true; return newVal; }); assertSame(r, newVal); assertTrue(called[0]); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2), entry(newKey, newVal)); } // computeIfAbsent @Test public void testComputeIfAbsentNotCalled() { boolean[] called = new boolean[1]; Box r = map.computeIfAbsent(k1a, k -> { called[0] = true; return null; }); assertSame(r, v1a); assertFalse(called[0]); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // computeIfAbsent @Test public void testComputeIfAbsentNullReturn() { boolean[] called = new boolean[1]; Box newKey = new Box(k1a); Box r = map.computeIfAbsent(newKey, k -> { called[0] = true; return null; }); assertNull(r); assertTrue(called[0]); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // computeIfPresent @Test public void testComputeIfPresentIsCalled() { boolean[] called = new boolean[1]; Box newVal = new Box(v1a); Box r = map.computeIfPresent(k1a, (k, v) -> { called[0] = true; return newVal; }); assertSame(r, newVal); assertTrue(called[0]); checkEntries(map.entrySet(), entry(k1a, newVal), entry(k1b, v1b), entry(k2, v2)); } // computeIfPresent @Test public void testComputeIfPresentNotCalled() { boolean[] called = new boolean[1]; Box r = map.computeIfPresent(new Box(k1a), (k, v) -> { called[0] = true; return null; }); assertNull(r); assertFalse(called[0]); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // computeIfPresent @Test public void testComputeIfPresentNullReturn() { boolean[] called = new boolean[1]; Box r = map.computeIfPresent(k1a, (k, v) -> { called[0] = true; return null; }); assertNull(r); assertTrue(called[0]); checkEntries(map.entrySet(), entry(k1b, v1b), entry(k2, v2)); } // merge @Test public void testMergeAbsent() { boolean[] called = new boolean[1]; Box newKey = new Box(k1a); Box newVal = new Box(v1a); Box r = map.merge(newKey, newVal, (v1, v2) -> { called[0] = true; return newVal; }); assertSame(r, newVal); assertFalse(called[0]); checkEntries(map.entrySet(), entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2), entry(newKey, newVal)); } // merge @Test public void testMergePresent() { boolean[] called = new boolean[1]; Box val2 = new Box(47); Box[] mergedVal = new Box[1]; Box r = map.merge(k1a, val2, (v1, v2) -> { called[0] = true; mergedVal[0] = new Box(v1.i + v2.i); return mergedVal[0]; }); assertSame(r, mergedVal[0]); assertTrue(called[0]); checkEntries(map.entrySet(), entry(k1a, mergedVal[0]), entry(k1b, v1b), entry(k2, v2)); } // forEach @Test public void testForEach() { @SuppressWarnings("unchecked") List> entries = new ArrayList<>(); map.forEach((k, v) -> entries.add(entry(k, v))); checkEntries(entries, entry(k1a, v1a), entry(k1b, v1b), entry(k2, v2)); } // replaceAll @Test public void testReplaceAll() { List> replacements = new ArrayList<>(); map.replaceAll((k, v) -> { Box v1 = new Box(v); replacements.add(entry(k, v1)); return v1; }); @SuppressWarnings("unchecked") var replacementArray = (Map.Entry[]) replacements.toArray(Map.Entry[]::new); checkEntries(map.entrySet(), replacementArray); } }