/* * Copyright (C) 2011 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect; import static com.google.common.collect.MapMakerInternalMap.DRAIN_THRESHOLD; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Equivalence; import com.google.common.collect.MapMakerInternalMap.InternalEntry; import com.google.common.collect.MapMakerInternalMap.Segment; import com.google.common.collect.MapMakerInternalMap.Strength; import com.google.common.collect.MapMakerInternalMap.WeakValueEntry; import com.google.common.collect.MapMakerInternalMap.WeakValueReference; import com.google.common.testing.NullPointerTester; import java.lang.ref.Reference; import java.util.concurrent.atomic.AtomicReferenceArray; import junit.framework.TestCase; /** * @author Charles Fry */ @SuppressWarnings("deprecation") // many tests of deprecated methods public class MapMakerInternalMapTest extends TestCase { static final int SMALL_MAX_SIZE = DRAIN_THRESHOLD * 5; private static <K, V> MapMakerInternalMap<K, V, ? extends InternalEntry<K, V, ?>, ? extends Segment<K, V, ?, ?>> makeMap(MapMaker maker) { return MapMakerInternalMap.create(maker); } private static MapMaker createMapMaker() { MapMaker maker = new MapMaker(); maker.useCustomMap = true; return maker; } // constructor tests public void testDefaults() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker()); assertSame(Strength.STRONG, map.keyStrength()); assertSame(Strength.STRONG, map.valueStrength()); assertSame(map.keyStrength().defaultEquivalence(), map.keyEquivalence); assertSame(map.valueStrength().defaultEquivalence(), map.valueEquivalence()); assertThat(map.entryHelper) .isInstanceOf(MapMakerInternalMap.StrongKeyStrongValueEntry.Helper.class); assertEquals(4, map.concurrencyLevel); // concurrency level assertThat(map.segments).hasLength(4); // initial capacity / concurrency level assertEquals(16 / map.segments.length, map.segments[0].table.length()); } public void testSetKeyEquivalence() { Equivalence<Object> testEquivalence = new Equivalence<Object>() { @Override protected boolean doEquivalent(Object a, Object b) { return false; } @Override protected int doHash(Object t) { return 0; } }; MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().keyEquivalence(testEquivalence)); assertSame(testEquivalence, map.keyEquivalence); assertSame(map.valueStrength().defaultEquivalence(), map.valueEquivalence()); } public void testSetConcurrencyLevel() { // round up to nearest power of two checkConcurrencyLevel(1, 1); checkConcurrencyLevel(2, 2); checkConcurrencyLevel(3, 4); checkConcurrencyLevel(4, 4); checkConcurrencyLevel(5, 8); checkConcurrencyLevel(6, 8); checkConcurrencyLevel(7, 8); checkConcurrencyLevel(8, 8); } private static void checkConcurrencyLevel(int concurrencyLevel, int segmentCount) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(concurrencyLevel)); assertThat(map.segments).hasLength(segmentCount); } public void testSetInitialCapacity() { // share capacity over each segment, then round up to nearest power of two checkInitialCapacity(1, 0, 1); checkInitialCapacity(1, 1, 1); checkInitialCapacity(1, 2, 2); checkInitialCapacity(1, 3, 4); checkInitialCapacity(1, 4, 4); checkInitialCapacity(1, 5, 8); checkInitialCapacity(1, 6, 8); checkInitialCapacity(1, 7, 8); checkInitialCapacity(1, 8, 8); checkInitialCapacity(2, 0, 1); checkInitialCapacity(2, 1, 1); checkInitialCapacity(2, 2, 1); checkInitialCapacity(2, 3, 2); checkInitialCapacity(2, 4, 2); checkInitialCapacity(2, 5, 4); checkInitialCapacity(2, 6, 4); checkInitialCapacity(2, 7, 4); checkInitialCapacity(2, 8, 4); checkInitialCapacity(4, 0, 1); checkInitialCapacity(4, 1, 1); checkInitialCapacity(4, 2, 1); checkInitialCapacity(4, 3, 1); checkInitialCapacity(4, 4, 1); checkInitialCapacity(4, 5, 2); checkInitialCapacity(4, 6, 2); checkInitialCapacity(4, 7, 2); checkInitialCapacity(4, 8, 2); } private static void checkInitialCapacity( int concurrencyLevel, int initialCapacity, int segmentSize) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap( createMapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(initialCapacity)); for (int i = 0; i < map.segments.length; i++) { assertEquals(segmentSize, map.segments[i].table.length()); } } public void testSetMaximumSize() { // vary maximumSize wrt concurrencyLevel for (int maxSize = 1; maxSize < 8; maxSize++) { checkMaximumSize(1, 8, maxSize); checkMaximumSize(2, 8, maxSize); checkMaximumSize(4, 8, maxSize); checkMaximumSize(8, 8, maxSize); } checkMaximumSize(1, 8, Integer.MAX_VALUE); checkMaximumSize(2, 8, Integer.MAX_VALUE); checkMaximumSize(4, 8, Integer.MAX_VALUE); checkMaximumSize(8, 8, Integer.MAX_VALUE); // vary initial capacity wrt maximumSize for (int capacity = 0; capacity < 8; capacity++) { checkMaximumSize(1, capacity, 4); checkMaximumSize(2, capacity, 4); checkMaximumSize(4, capacity, 4); checkMaximumSize(8, capacity, 4); } } private static void checkMaximumSize(int concurrencyLevel, int initialCapacity, int maxSize) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap( createMapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(initialCapacity)); int totalCapacity = 0; for (int i = 0; i < map.segments.length; i++) { totalCapacity += map.segments[i].maxSegmentSize; } assertTrue("totalCapcity=" + totalCapacity + ", maxSize=" + maxSize, totalCapacity <= maxSize); } public void testSetWeakKeys() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().weakKeys()); checkStrength(map, Strength.WEAK, Strength.STRONG); assertThat(map.entryHelper) .isInstanceOf(MapMakerInternalMap.WeakKeyStrongValueEntry.Helper.class); } public void testSetWeakValues() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().weakValues()); checkStrength(map, Strength.STRONG, Strength.WEAK); assertThat(map.entryHelper) .isInstanceOf(MapMakerInternalMap.StrongKeyWeakValueEntry.Helper.class); } private static void checkStrength( MapMakerInternalMap<Object, Object, ?, ?> map, Strength keyStrength, Strength valueStrength) { assertSame(keyStrength, map.keyStrength()); assertSame(valueStrength, map.valueStrength()); assertSame(keyStrength.defaultEquivalence(), map.keyEquivalence); assertSame(valueStrength.defaultEquivalence(), map.valueEquivalence()); } // Segment core tests public void testNewEntry() { for (MapMaker maker : allWeakValueStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker); Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); Object valueOne = new Object(); int hashOne = map.hash(keyOne); InternalEntry<Object, Object, ?> entryOne = segment.newEntryForTesting(keyOne, hashOne, null); WeakValueReference<Object, Object, ?> valueRefOne = segment.newWeakValueReferenceForTesting(entryOne, valueOne); assertSame(valueOne, valueRefOne.get()); segment.setWeakValueReferenceForTesting(entryOne, valueRefOne); assertSame(keyOne, entryOne.getKey()); assertEquals(hashOne, entryOne.getHash()); assertNull(entryOne.getNext()); assertSame(valueRefOne, segment.getWeakValueReferenceForTesting(entryOne)); Object keyTwo = new Object(); Object valueTwo = new Object(); int hashTwo = map.hash(keyTwo); InternalEntry<Object, Object, ?> entryTwo = segment.newEntryForTesting(keyTwo, hashTwo, entryOne); WeakValueReference<Object, Object, ?> valueRefTwo = segment.newWeakValueReferenceForTesting(entryTwo, valueTwo); assertSame(valueTwo, valueRefTwo.get()); segment.setWeakValueReferenceForTesting(entryTwo, valueRefTwo); assertSame(keyTwo, entryTwo.getKey()); assertEquals(hashTwo, entryTwo.getHash()); assertSame(entryOne, entryTwo.getNext()); assertSame(valueRefTwo, segment.getWeakValueReferenceForTesting(entryTwo)); } } public void testCopyEntry() { for (MapMaker maker : allWeakValueStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker); Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); Object valueOne = new Object(); int hashOne = map.hash(keyOne); InternalEntry<Object, Object, ?> entryOne = segment.newEntryForTesting(keyOne, hashOne, null); segment.setValueForTesting(entryOne, valueOne); Object keyTwo = new Object(); Object valueTwo = new Object(); int hashTwo = map.hash(keyTwo); InternalEntry<Object, Object, ?> entryTwo = segment.newEntryForTesting(keyTwo, hashTwo, null); segment.setValueForTesting(entryTwo, valueTwo); InternalEntry<Object, Object, ?> copyOne = segment.copyForTesting(entryOne, null); assertSame(keyOne, entryOne.getKey()); assertEquals(hashOne, entryOne.getHash()); assertNull(entryOne.getNext()); assertSame(valueOne, copyOne.getValue()); InternalEntry<Object, Object, ?> copyTwo = segment.copyForTesting(entryTwo, copyOne); assertSame(keyTwo, copyTwo.getKey()); assertEquals(hashTwo, copyTwo.getHash()); assertSame(copyOne, copyTwo.getNext()); assertSame(valueTwo, copyTwo.getValue()); } } public void testSegmentGetAndContains() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object value = new Object(); AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; int index = hash & (table.length() - 1); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); segment.setValueForTesting(entry, value); assertNull(segment.get(key, hash)); // count == 0 segment.setTableEntryForTesting(index, entry); assertNull(segment.get(key, hash)); assertFalse(segment.containsKey(key, hash)); assertFalse(segment.containsValue(value)); // count == 1 segment.count++; assertSame(value, segment.get(key, hash)); assertTrue(segment.containsKey(key, hash)); assertTrue(segment.containsValue(value)); // don't see absent values now that count > 0 assertNull(segment.get(new Object(), hash)); // null key InternalEntry<Object, Object, ?> nullEntry = segment.newEntryForTesting(null, hash, entry); Object nullValue = new Object(); WeakValueReference<Object, Object, ?> nullValueRef = segment.newWeakValueReferenceForTesting(nullEntry, nullValue); segment.setWeakValueReferenceForTesting(nullEntry, nullValueRef); segment.setTableEntryForTesting(index, nullEntry); // skip the null key assertSame(value, segment.get(key, hash)); assertTrue(segment.containsKey(key, hash)); assertTrue(segment.containsValue(value)); assertFalse(segment.containsValue(nullValue)); // hash collision InternalEntry<Object, Object, ?> dummyEntry = segment.newEntryForTesting(new Object(), hash, entry); Object dummyValue = new Object(); WeakValueReference<Object, Object, ?> dummyValueRef = segment.newWeakValueReferenceForTesting(dummyEntry, dummyValue); segment.setWeakValueReferenceForTesting(dummyEntry, dummyValueRef); segment.setTableEntryForTesting(index, dummyEntry); assertSame(value, segment.get(key, hash)); assertTrue(segment.containsKey(key, hash)); assertTrue(segment.containsValue(value)); assertTrue(segment.containsValue(dummyValue)); // key collision dummyEntry = segment.newEntryForTesting(key, hash, entry); dummyValue = new Object(); dummyValueRef = segment.newWeakValueReferenceForTesting(dummyEntry, dummyValue); segment.setWeakValueReferenceForTesting(dummyEntry, dummyValueRef); segment.setTableEntryForTesting(index, dummyEntry); // returns the most recent entry assertSame(dummyValue, segment.get(key, hash)); assertTrue(segment.containsKey(key, hash)); assertTrue(segment.containsValue(value)); assertTrue(segment.containsValue(dummyValue)); } public void testSegmentReplaceValue() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; int index = hash & (table.length() - 1); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); // no entry assertFalse(segment.replace(key, hash, oldValue, newValue)); assertEquals(0, segment.count); // same value segment.setTableEntryForTesting(index, entry); segment.count++; assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); assertTrue(segment.replace(key, hash, oldValue, newValue)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); // different value assertFalse(segment.replace(key, hash, oldValue, newValue)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); // cleared segment.setWeakValueReferenceForTesting(entry, oldValueRef); oldValueRef.clear(); assertFalse(segment.replace(key, hash, oldValue, newValue)); assertEquals(0, segment.count); assertNull(segment.get(key, hash)); } public void testSegmentReplace() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; int index = hash & (table.length() - 1); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); // no entry assertNull(segment.replace(key, hash, newValue)); assertEquals(0, segment.count); // same key segment.setTableEntryForTesting(index, entry); segment.count++; assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); assertSame(oldValue, segment.replace(key, hash, newValue)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); // cleared segment.setWeakValueReferenceForTesting(entry, oldValueRef); oldValueRef.clear(); assertNull(segment.replace(key, hash, newValue)); assertEquals(0, segment.count); assertNull(segment.get(key, hash)); } public void testSegmentPut() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); // no entry assertEquals(0, segment.count); assertNull(segment.put(key, hash, oldValue, false)); assertEquals(1, segment.count); // same key assertSame(oldValue, segment.put(key, hash, newValue, false)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); // cleared InternalEntry<Object, Object, ?> entry = segment.getEntry(key, hash); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); assertNull(segment.put(key, hash, newValue, false)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); } public void testSegmentPutIfAbsent() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); // no entry assertEquals(0, segment.count); assertNull(segment.put(key, hash, oldValue, true)); assertEquals(1, segment.count); // same key assertSame(oldValue, segment.put(key, hash, newValue, true)); assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); // cleared InternalEntry<Object, Object, ?> entry = segment.getEntry(key, hash); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); assertNull(segment.put(key, hash, newValue, true)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); } public void testSegmentPut_expand() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; assertEquals(1, segment.table.length()); int count = 1024; for (int i = 0; i < count; i++) { Object key = new Object(); Object value = new Object(); int hash = map.hash(key); assertNull(segment.put(key, hash, value, false)); assertTrue(segment.table.length() > i); } } public void testSegmentRemove() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; int index = hash & (table.length() - 1); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); // no entry assertEquals(0, segment.count); assertNull(segment.remove(key, hash)); assertEquals(0, segment.count); // same key segment.setTableEntryForTesting(index, entry); segment.count++; assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); assertSame(oldValue, segment.remove(key, hash)); assertEquals(0, segment.count); assertNull(segment.get(key, hash)); // cleared segment.setTableEntryForTesting(index, entry); segment.count++; assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); assertNull(segment.remove(key, hash)); assertEquals(0, segment.count); assertNull(segment.get(key, hash)); } public void testSegmentRemoveValue() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; int index = hash & (table.length() - 1); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); // no entry assertEquals(0, segment.count); assertNull(segment.remove(key, hash)); assertEquals(0, segment.count); // same value segment.setTableEntryForTesting(index, entry); segment.count++; assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); assertTrue(segment.remove(key, hash, oldValue)); assertEquals(0, segment.count); assertNull(segment.get(key, hash)); // different value segment.setTableEntryForTesting(index, entry); segment.count++; assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); assertFalse(segment.remove(key, hash, newValue)); assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); // cleared assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); assertFalse(segment.remove(key, hash, oldValue)); assertEquals(0, segment.count); assertNull(segment.get(key, hash)); } public void testExpand() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; assertEquals(1, segment.table.length()); // manually add elements to avoid expansion int originalCount = 1024; InternalEntry<Object, Object, ?> entry = null; for (int i = 0; i < originalCount; i++) { Object key = new Object(); Object value = new Object(); int hash = map.hash(key); // chain all entries together as we only have a single bucket entry = segment.newEntryForTesting(key, hash, entry); segment.setValueForTesting(entry, value); } segment.setTableEntryForTesting(0, entry); segment.count = originalCount; ImmutableMap<Object, Object> originalMap = ImmutableMap.copyOf(map); assertEquals(originalCount, originalMap.size()); assertEquals(originalMap, map); for (int i = 1; i <= originalCount * 2; i *= 2) { if (i > 1) { segment.expand(); } assertEquals(i, segment.table.length()); assertEquals(originalCount, countLiveEntries(map)); assertEquals(originalCount, segment.count); assertEquals(originalMap, map); } } public void testRemoveFromChain() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; // create 3 objects and chain them together Object keyOne = new Object(); Object valueOne = new Object(); int hashOne = map.hash(keyOne); InternalEntry<Object, Object, ?> entryOne = segment.newEntryForTesting(keyOne, hashOne, null); segment.setValueForTesting(entryOne, valueOne); Object keyTwo = new Object(); Object valueTwo = new Object(); int hashTwo = map.hash(keyTwo); InternalEntry<Object, Object, ?> entryTwo = segment.newEntryForTesting(keyTwo, hashTwo, entryOne); segment.setValueForTesting(entryTwo, valueTwo); Object keyThree = new Object(); Object valueThree = new Object(); int hashThree = map.hash(keyThree); InternalEntry<Object, Object, ?> entryThree = segment.newEntryForTesting(keyThree, hashThree, entryTwo); segment.setValueForTesting(entryThree, valueThree); // alone assertNull(segment.removeFromChainForTesting(entryOne, entryOne)); // head assertSame(entryOne, segment.removeFromChainForTesting(entryTwo, entryTwo)); // middle InternalEntry<Object, Object, ?> newFirst = segment.removeFromChainForTesting(entryThree, entryTwo); assertSame(keyThree, newFirst.getKey()); assertSame(valueThree, newFirst.getValue()); assertEquals(hashThree, newFirst.getHash()); assertSame(entryOne, newFirst.getNext()); // tail (remaining entries are copied in reverse order) newFirst = segment.removeFromChainForTesting(entryThree, entryOne); assertSame(keyTwo, newFirst.getKey()); assertSame(valueTwo, newFirst.getValue()); assertEquals(hashTwo, newFirst.getHash()); newFirst = newFirst.getNext(); assertSame(keyThree, newFirst.getKey()); assertSame(valueThree, newFirst.getValue()); assertEquals(hashThree, newFirst.getHash()); assertNull(newFirst.getNext()); } public void testExpand_cleanup() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; assertEquals(1, segment.table.length()); // manually add elements to avoid expansion // 1/3 null keys, 1/3 null values int originalCount = 1024; InternalEntry<Object, Object, ?> entry = null; for (int i = 0; i < originalCount; i++) { Object key = new Object(); Object value = (i % 3 == 0) ? null : new Object(); int hash = map.hash(key); if (i % 3 == 1) { key = null; } // chain all entries together as we only have a single bucket entry = segment.newEntryForTesting(key, hash, entry); segment.setValueForTesting(entry, value); } segment.setTableEntryForTesting(0, entry); segment.count = originalCount; int liveCount = originalCount / 3; assertEquals(1, segment.table.length()); assertEquals(liveCount, countLiveEntries(map)); ImmutableMap<Object, Object> originalMap = ImmutableMap.copyOf(map); assertEquals(liveCount, originalMap.size()); // can't compare map contents until cleanup occurs for (int i = 1; i <= originalCount * 2; i *= 2) { if (i > 1) { segment.expand(); } assertEquals(i, segment.table.length()); assertEquals(liveCount, countLiveEntries(map)); // expansion cleanup is sloppy, with a goal of avoiding unnecessary copies assertTrue(segment.count >= liveCount); assertTrue(segment.count <= originalCount); assertEquals(originalMap, ImmutableMap.copyOf(map)); } } private static <K, V> int countLiveEntries(MapMakerInternalMap<K, V, ?, ?> map) { int result = 0; for (Segment<K, V, ?, ?> segment : map.segments) { AtomicReferenceArray<? extends InternalEntry<K, V, ?>> table = segment.table; for (int i = 0; i < table.length(); i++) { for (InternalEntry<K, V, ?> e = table.get(i); e != null; e = e.getNext()) { if (map.isLiveForTesting(e)) { result++; } } } } return result; } public void testClear() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; assertEquals(1, table.length()); Object key = new Object(); Object value = new Object(); int hash = map.hash(key); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); segment.setValueForTesting(entry, value); segment.setTableEntryForTesting(0, entry); segment.readCount.incrementAndGet(); segment.count = 1; assertSame(entry, table.get(0)); segment.clear(); assertNull(table.get(0)); assertEquals(0, segment.readCount.get()); assertEquals(0, segment.count); } public void testRemoveEntry() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; assertEquals(1, table.length()); Object key = new Object(); Object value = new Object(); int hash = map.hash(key); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); segment.setValueForTesting(entry, value); // remove absent assertFalse(segment.removeTableEntryForTesting(entry)); segment.setTableEntryForTesting(0, entry); segment.count = 1; assertTrue(segment.removeTableEntryForTesting(entry)); assertEquals(0, segment.count); assertNull(table.get(0)); } public void testClearValue() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; assertEquals(1, table.length()); Object key = new Object(); Object value = new Object(); int hash = map.hash(key); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); segment.setValueForTesting(entry, value); WeakValueReference<Object, Object, ?> valueRef = segment.getWeakValueReferenceForTesting(entry); // clear absent assertFalse(segment.clearValueForTesting(key, hash, valueRef)); segment.setTableEntryForTesting(0, entry); // don't increment count; this is used during computation assertTrue(segment.clearValueForTesting(key, hash, valueRef)); // no notification sent with clearValue assertEquals(0, segment.count); assertNull(table.get(0)); // clear wrong value reference segment.setTableEntryForTesting(0, entry); WeakValueReference<Object, Object, ?> otherValueRef = segment.newWeakValueReferenceForTesting(entry, value); segment.setWeakValueReferenceForTesting(entry, otherValueRef); assertFalse(segment.clearValueForTesting(key, hash, valueRef)); segment.setWeakValueReferenceForTesting(entry, valueRef); assertTrue(segment.clearValueForTesting(key, hash, valueRef)); } // reference queues public void testDrainKeyReferenceQueueOnWrite() { for (MapMaker maker : allWeakKeyStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker.concurrencyLevel(1)); if (maker.getKeyStrength() == Strength.WEAK) { Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); Object valueTwo = new Object(); map.put(keyOne, valueOne); InternalEntry<Object, Object, ?> entry = segment.getEntry(keyOne, hashOne); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) entry; reference.enqueue(); map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(1, map.size()); assertNull(segment.getKeyReferenceQueueForTesting().poll()); } } } public void testDrainValueReferenceQueueOnWrite() { for (MapMaker maker : allWeakValueStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker.concurrencyLevel(1)); if (maker.getValueStrength() == Strength.WEAK) { Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); Object valueTwo = new Object(); map.put(keyOne, valueOne); @SuppressWarnings("unchecked") WeakValueEntry<Object, Object, ?> entry = (WeakValueEntry<Object, Object, ?>) segment.getEntry(keyOne, hashOne); WeakValueReference<Object, Object, ?> valueReference = entry.getValueReference(); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) valueReference; reference.enqueue(); map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(1, map.size()); assertNull(segment.getValueReferenceQueueForTesting().poll()); } } } public void testDrainKeyReferenceQueueOnRead() { for (MapMaker maker : allWeakKeyStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker.concurrencyLevel(1)); if (maker.getKeyStrength() == Strength.WEAK) { Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); map.put(keyOne, valueOne); InternalEntry<Object, Object, ?> entry = segment.getEntry(keyOne, hashOne); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) entry; reference.enqueue(); for (int i = 0; i < SMALL_MAX_SIZE; i++) { Object unused = map.get(keyTwo); } assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(0, map.size()); assertNull(segment.getKeyReferenceQueueForTesting().poll()); } } } public void testDrainValueReferenceQueueOnRead() { for (MapMaker maker : allWeakValueStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker.concurrencyLevel(1)); if (maker.getValueStrength() == Strength.WEAK) { Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); map.put(keyOne, valueOne); @SuppressWarnings("unchecked") WeakValueEntry<Object, Object, ?> entry = (WeakValueEntry<Object, Object, ?>) segment.getEntry(keyOne, hashOne); WeakValueReference<Object, Object, ?> valueReference = entry.getValueReference(); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) valueReference; reference.enqueue(); for (int i = 0; i < SMALL_MAX_SIZE; i++) { Object unused = map.get(keyTwo); } assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(0, map.size()); assertNull(segment.getValueReferenceQueueForTesting().poll()); } } } // utility methods private static Iterable<MapMaker> allWeakKeyStrengthMakers() { return ImmutableList.of( createMapMaker().weakKeys(), createMapMaker().weakKeys().weakValues()); } private static Iterable<MapMaker> allWeakValueStrengthMakers() { return ImmutableList.of( createMapMaker().weakValues(), createMapMaker().weakKeys().weakValues()); } public void testNullParameters() throws Exception { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicInstanceMethods(makeMap(createMapMaker())); } }