Auto-generating primitive collections for int and char keys.

Motivation:

Currently we have IntObjectMap/HashMap, but it will be useful to support other primitive-based maps.

Modifications:

Moved the code int the current maps to template files and run Groovy code from  common/pom.xml to apply the templates.

Result:

Autogeneration of int and char-based hash maps.
This commit is contained in:
nmittler 2015-04-03 13:36:28 -07:00
parent c9adb41636
commit 3354296c9f
5 changed files with 280 additions and 140 deletions

View File

@ -29,6 +29,13 @@
<name>Netty/Common</name>
<properties>
<collection.template.dir>${project.basedir}/src/main/templates</collection.template.dir>
<collection.template.test.dir>${project.basedir}/src/test/templates</collection.template.test.dir>
<collection.src.dir>${project.build.directory}/generated-sources/collections/java</collection.src.dir>
<collection.testsrc.dir>${project.build.directory}/generated-test-sources/collections/java</collection.testsrc.dir>
</properties>
<dependencies>
<!-- Byte code generator - completely optional -->
<dependency>
@ -55,5 +62,73 @@
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Add generated collection sources. -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${collection.src.dir}</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>${collection.testsrc.dir}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Generate the primitive collections from the template files. -->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>2.0</version>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant-optional</artifactId>
<version>1.5.3-1</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>generate-collections</id>
<phase>generate-sources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>${project.basedir}/src/main/script/codegen.groovy</source>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,39 @@
String[] templateDirs = [properties["collection.template.dir"],
properties["collection.template.test.dir"]]
String[] outputDirs = [properties["collection.src.dir"],
properties["collection.testsrc.dir"]]
templateDirs.eachWithIndex { templateDir, i ->
convertSources templateDir, outputDirs[i]
}
void convertSources(String templateDir, String outputDir) {
String[] keyPrimitives = ["byte", "char", "short", "int", "long"]
String[] keyObjects = ["Byte", "Character", "Short", "Integer", "Long"];
keyPrimitives.eachWithIndex { keyPrimitive, i ->
convertTemplates templateDir, outputDir, keyPrimitive, keyObjects[i]
}
}
void convertTemplates(String templateDir,
String outputDir,
String keyPrimitive,
String keyObject) {
def keyName = keyPrimitive.capitalize()
def replaceFrom = "(^.*)K([^.]+)\\.template\$"
def replaceTo = "\\1" + keyName + "\\2.java"
def hashCodeFn = keyPrimitive.equals("long") ? "(int)(key ^ (key >>> 32))" : "(int) key"
ant.copy(todir: outputDir) {
fileset(dir: templateDir) {
include(name: "**/*.template")
}
filterset() {
filter(token: "K", value: keyName)
filter(token: "k", value: keyPrimitive)
filter(token: "O", value: keyObject)
filter(token: "HASH_CODE", value: hashCodeFn)
}
regexpmapper(from: replaceFrom, to: replaceTo)
}
}

View File

@ -23,14 +23,14 @@ import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A hash map implementation of {@link IntObjectMap} that uses open addressing for keys.
* A hash map implementation of {@link @K@ObjectMap} that uses open addressing for keys.
* To minimize the memory footprint, this class uses open addressing rather than chaining.
* Collisions are resolved using linear probing. Deletions implement compaction, so cost of
* remove can approach O(N) for full maps, which makes a small loadFactor recommended.
*
* @param <V> The value type stored in the map.
*/
public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectMap.Entry<V>> {
public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectMap.Entry<V>> {
/** Default initial capacity. Used if not specified in the constructor */
public static final int DEFAULT_CAPACITY = 11;
@ -50,20 +50,20 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
/** The load factor for the map. Used to calculate {@link #maxSize}. */
private final float loadFactor;
private int[] keys;
private @k@[] keys;
private V[] values;
private Collection<V> valueCollection;
private int size;
public IntObjectHashMap() {
public @K@ObjectHashMap() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public IntObjectHashMap(int initialCapacity) {
public @K@ObjectHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public IntObjectHashMap(int initialCapacity, float loadFactor) {
public @K@ObjectHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity must be >= 1");
}
@ -79,7 +79,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
int capacity = adjustCapacity(initialCapacity);
// Allocate the arrays.
keys = new int[capacity];
keys = new @k@[capacity];
@SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[capacity];
values = temp;
@ -98,13 +98,13 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
}
@Override
public V get(int key) {
public V get(@k@ key) {
int index = indexOf(key);
return index == -1 ? null : toExternal(values[index]);
}
@Override
public V put(int key, V value) {
public V put(@k@ key, V value) {
int startIndex = hashIndex(key);
int index = startIndex;
@ -136,10 +136,10 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
}
@Override
public void putAll(IntObjectMap<V> sourceMap) {
public void putAll(@K@ObjectMap<V> sourceMap) {
if (sourceMap instanceof IntObjectHashMap) {
// Optimization - iterate through the arrays.
IntObjectHashMap<V> source = (IntObjectHashMap<V>) sourceMap;
@K@ObjectHashMap<V> source = (@K@ObjectHashMap<V>) sourceMap;
for (int i = 0; i < source.values.length; ++i) {
V sourceValue = source.values[i];
if (sourceValue != null) {
@ -156,7 +156,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
}
@Override
public V remove(int key) {
public V remove(@k@ key) {
int index = indexOf(key);
if (index == -1) {
return null;
@ -179,13 +179,13 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
@Override
public void clear() {
Arrays.fill(keys, 0);
Arrays.fill(keys, (@k@) 0);
Arrays.fill(values, null);
size = 0;
}
@Override
public boolean containsKey(int key) {
public boolean containsKey(@k@ key) {
return indexOf(key) >= 0;
}
@ -212,8 +212,8 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
}
@Override
public int[] keys() {
int[] outKeys = new int[size()];
public @k@[] keys() {
@k@[] outKeys = new @k@[size()];
int targetIx = 0;
for (int i = 0; i < values.length; ++i) {
if (values[i] != null) {
@ -244,7 +244,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
final Iterator<Entry<V>> iter = IntObjectHashMap.this.iterator();
final Iterator<Entry<V>> iter = @K@ObjectHashMap.this.iterator();
@Override
public boolean hasNext() {
return iter.hasNext();
@ -278,7 +278,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// array, which may have different lengths for two maps of same size(), so the
// capacity cannot be used as input for hashing but the size can.
int hash = size;
for (int key : keys) {
for (@k@ key : keys) {
// 0 can be a valid key or unused slot, but won't impact the hashcode in either case.
// This way we can use a cheap loop without conditionals, or hard-to-unroll operations,
// or the devastatingly bad memory locality of visiting value objects.
@ -286,7 +286,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// of terms, only their values; since the map is an unordered collection and
// entries can end up in different positions in different maps that have the same
// elements, but with different history of puts/removes, due to conflicts.
hash ^= key;
hash ^= hashCode(key);
}
return hash;
}
@ -296,18 +296,18 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
if (this == obj) {
return true;
}
if (!(obj instanceof IntObjectMap)) {
if (!(obj instanceof @K@ObjectMap)) {
return false;
}
@SuppressWarnings("rawtypes")
IntObjectMap other = (IntObjectMap) obj;
@K@ObjectMap other = (@K@ObjectMap) obj;
if (size != other.size()) {
return false;
}
for (int i = 0; i < values.length; ++i) {
V value = values[i];
if (value != null) {
int key = keys[i];
@k@ key = keys[i];
Object otherValue = other.get(key);
if (value == NULL_VALUE) {
if (otherValue != null) {
@ -327,7 +327,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
* @param key the key for an entry in the map.
* @return the index where the key was found, or {@code -1} if no entry is found for that key.
*/
private int indexOf(int key) {
private int indexOf(@k@ key) {
int startIndex = hashIndex(key);
int index = startIndex;
@ -350,9 +350,16 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
/**
* Returns the hashed index for the given key.
*/
private int hashIndex(int key) {
private int hashIndex(@k@ key) {
// Allowing for negative keys by adding the length after the first mod operation.
return (key % keys.length + keys.length) % keys.length;
return (hashCode(key) % keys.length + keys.length) % keys.length;
}
/**
* Returns the hash code for the key.
*/
private static int hashCode(@k@ key) {
return @HASH_CODE@;
}
/**
@ -428,10 +435,10 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
* @param newCapacity the new capacity for the map.
*/
private void rehash(int newCapacity) {
int[] oldKeys = keys;
@k@[] oldKeys = keys;
V[] oldVals = values;
keys = new int[newCapacity];
keys = new @k@[newCapacity];
@SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[newCapacity];
values = temp;
@ -444,7 +451,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
if (oldVal != null) {
// Inlined put(), but much simpler: we don't need to worry about
// duplicated keys, growing/rehashing, or failing to insert.
int oldKey = oldKeys[i];
@k@ oldKey = oldKeys[i];
int index = hashIndex(oldKey);
for (;;) {
@ -512,7 +519,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// into the Iterator object (potentially making loop optimization much easier).
@Override
public int key() {
public @k@ key() {
return keys[entryIndex];
}
@ -546,7 +553,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
/**
* Helper method called by {@link #toString()} in order to convert a single map key into a string.
*/
protected String keyToString(int key) {
return Integer.toString(key);
protected String keyToString(@k@ key) {
return @O@.toString(key);
}
}

View File

@ -17,11 +17,11 @@ package io.netty.util.collection;
import java.util.Collection;
/**
* Interface for a primitive map that uses {@code int}s as keys.
* Interface for a primitive map that uses {@code @k@}s as keys.
*
* @param <V> the value type stored in the map.
*/
public interface IntObjectMap<V> {
public interface @K@ObjectMap<V> {
/**
* An Entry in the map.
@ -32,7 +32,7 @@ public interface IntObjectMap<V> {
/**
* Gets the key for this entry.
*/
int key();
@k@ key();
/**
* Gets the value for this entry.
@ -51,7 +51,7 @@ public interface IntObjectMap<V> {
* @param key the key whose associated value is to be returned.
* @return the value or {@code null} if the key was not found in the map.
*/
V get(int key);
V get(@k@ key);
/**
* Puts the given entry into the map.
@ -60,12 +60,12 @@ public interface IntObjectMap<V> {
* @param value the value of the entry.
* @return the previous value for this key or {@code null} if there was no previous mapping.
*/
V put(int key, V value);
V put(@k@ key, V value);
/**
* Puts all of the entries from the given map into this map.
*/
void putAll(IntObjectMap<V> sourceMap);
void putAll(@K@ObjectMap<V> sourceMap);
/**
* Removes the entry with the specified key.
@ -73,7 +73,7 @@ public interface IntObjectMap<V> {
* @param key the key for the entry to be removed from this map.
* @return the previous value for the key, or {@code null} if there was no mapping.
*/
V remove(int key);
V remove(@k@ key);
/**
* Returns the number of entries contained in this map.
@ -94,7 +94,7 @@ public interface IntObjectMap<V> {
/**
* Indicates whether or not this map contains a value for the specified key.
*/
boolean containsKey(int key);
boolean containsKey(@k@ key);
/**
* Indicates whether or not the map contains the specified value.
@ -109,7 +109,7 @@ public interface IntObjectMap<V> {
/**
* Gets the keys contained in this map.
*/
int[] keys();
@k@[] keys();
/**
* Gets the values contained in this map.

View File

@ -27,9 +27,9 @@ import java.util.Set;
import static org.junit.Assert.*;
/**
* Tests for {@link IntObjectHashMap}.
* Tests for {@link @K@ObjectHashMap}.
*/
public class IntObjectHashMapTest {
public class @K@ObjectHashMapTest {
private static class Value {
private final String name;
@ -69,69 +69,72 @@ public class IntObjectHashMapTest {
}
}
private IntObjectHashMap<Value> map;
private @K@ObjectHashMap<Value> map;
@Before
public void setup() {
map = new IntObjectHashMap<Value>();
map = new @K@ObjectHashMap<Value>();
}
@Test
public void putNewMappingShouldSucceed() {
Value v = new Value("v");
assertNull(map.put(1, v));
@k@ key = 1;
assertNull(map.put(key, v));
assertEquals(1, map.size());
assertTrue(map.containsKey(1));
assertTrue(map.containsKey(key));
assertTrue(map.containsValue(v));
assertEquals(v, map.get(1));
assertEquals(v, map.get(key));
}
@Test
public void putShouldReplaceValue() {
Value v1 = new Value("v1");
assertNull(map.put(1, v1));
@k@ key = 1;
assertNull(map.put(key, v1));
// Replace the value.
Value v2 = new Value("v2");
assertSame(v1, map.put(1, v2));
assertSame(v1, map.put(key, v2));
assertEquals(1, map.size());
assertTrue(map.containsKey(1));
assertTrue(map.containsKey(key));
assertTrue(map.containsValue(v2));
assertEquals(v2, map.get(1));
assertEquals(v2, map.get(key));
}
@Test
public void putShouldGrowMap() {
for (int i = 0; i < 10000; ++i) {
Value v = new Value(Integer.toString(i));
assertNull(map.put(i, v));
assertEquals(i + 1, map.size());
assertTrue(map.containsKey(i));
for (@k@ key = 0; key < (@k@) 255; ++key) {
Value v = new Value(@O@.toString(key));
assertNull(map.put(key, v));
assertEquals(key + 1, map.size());
assertTrue(map.containsKey(key));
assertTrue(map.containsValue(v));
assertEquals(v, map.get(i));
assertEquals(v, map.get(key));
}
}
@Test
public void negativeKeyShouldSucceed() {
Value v = new Value("v");
map.put(-3, v);
map.put((@k@) -3, v);
assertEquals(1, map.size());
assertEquals(v, map.get(-3));
assertEquals(v, map.get((@k@) -3));
}
@Test
public void removeMissingValueShouldReturnNull() {
assertNull(map.remove(1));
assertNull(map.remove((@k@) 1));
assertEquals(0, map.size());
}
@Test
public void removeShouldReturnPreviousValue() {
Value v = new Value("v");
map.put(1, v);
assertSame(v, map.remove(1));
@k@ key = 1;
map.put(key, v);
assertSame(v, map.remove(key));
}
/**
@ -142,8 +145,8 @@ public class IntObjectHashMapTest {
*/
@Test
public void noFreeSlotsShouldRehash() {
for (int i = 0; i < 10; ++i) {
map.put(i, new Value(Integer.toString(i)));
for (@k@ i = 0; i < 10; ++i) {
map.put(i, new Value(@O@.toString(i)));
// Now mark it as REMOVED so that size won't cause the rehash.
map.remove(i);
assertEquals(0, map.size());
@ -151,26 +154,30 @@ public class IntObjectHashMapTest {
// Now add an entry to force the rehash since no FREE slots are available in the map.
Value v = new Value("v");
map.put(1, v);
@k@ key = 1;
map.put(key, v);
assertEquals(1, map.size());
assertSame(v, map.get(1));
assertSame(v, map.get(key));
}
@Test
public void putAllShouldSucceed() {
@k@ k1 = 1;
@k@ k2 = 2;
@k@ k3 = 3;
Value v1 = new Value("v1");
Value v2 = new Value("v2");
Value v3 = new Value("v3");
map.put(1, v1);
map.put(2, v2);
map.put(3, v3);
map.put(k1, v1);
map.put(k2, v2);
map.put(k3, v3);
IntObjectHashMap<Value> map2 = new IntObjectHashMap<Value>();
@K@ObjectHashMap<Value> map2 = new @K@ObjectHashMap<Value>();
map2.putAll(map);
assertEquals(3, map2.size());
assertSame(v1, map2.get(1));
assertSame(v2, map2.get(2));
assertSame(v3, map2.get(3));
assertSame(v1, map2.get(k1));
assertSame(v2, map2.get(k2));
assertSame(v3, map2.get(k3));
}
@Test
@ -178,9 +185,9 @@ public class IntObjectHashMapTest {
Value v1 = new Value("v1");
Value v2 = new Value("v2");
Value v3 = new Value("v3");
map.put(1, v1);
map.put(2, v2);
map.put(3, v3);
map.put((@k@) 1, v1);
map.put((@k@) 2, v2);
map.put((@k@) 3, v3);
map.clear();
assertEquals(0, map.size());
assertTrue(map.isEmpty());
@ -188,77 +195,85 @@ public class IntObjectHashMapTest {
@Test
public void containsValueShouldFindNull() {
map.put(1, new Value("v1"));
map.put(2, null);
map.put(3, new Value("v2"));
map.put((@k@) 1, new Value("v1"));
map.put((@k@) 2, null);
map.put((@k@) 3, new Value("v2"));
assertTrue(map.containsValue(null));
}
@Test
public void containsValueShouldFindInstance() {
Value v = new Value("v1");
map.put(1, new Value("v2"));
map.put(2, new Value("v3"));
map.put(3, v);
map.put((@k@) 1, new Value("v2"));
map.put((@k@) 2, new Value("v3"));
map.put((@k@) 3, v);
assertTrue(map.containsValue(v));
}
@Test
public void containsValueShouldFindEquivalentValue() {
map.put(1, new Value("v1"));
map.put(2, new Value("v2"));
map.put(3, new Value("v3"));
map.put((@k@) 1, new Value("v1"));
map.put((@k@) 2, new Value("v2"));
map.put((@k@) 3, new Value("v3"));
assertTrue(map.containsValue(new Value("v2")));
}
@Test
public void containsValueNotFindMissingValue() {
map.put(1, new Value("v1"));
map.put(2, new Value("v2"));
map.put(3, new Value("v3"));
map.put((@k@) 1, new Value("v1"));
map.put((@k@) 2, new Value("v2"));
map.put((@k@) 3, new Value("v3"));
assertFalse(map.containsValue(new Value("v4")));
}
@Test
public void iteratorShouldTraverseEntries() {
map.put(1, new Value("v1"));
map.put(2, new Value("v2"));
map.put(3, new Value("v3"));
@k@ k1 = 1;
@k@ k2 = 2;
@k@ k3 = 3;
@k@ k4 = 4;
map.put(k1, new Value("v1"));
map.put(k2, new Value("v2"));
map.put(k3, new Value("v3"));
// Add and then immediately remove another entry.
map.put(4, new Value("v4"));
map.remove(4);
map.put(k4, new Value("v4"));
map.remove(k4);
Set<Integer> found = new HashSet<Integer>();
for (IntObjectMap.Entry<Value> entry : map.entries()) {
Set<@O@> found = new HashSet<@O@>();
for (@K@ObjectMap.Entry<Value> entry : map.entries()) {
assertTrue(found.add(entry.key()));
}
assertEquals(3, found.size());
assertTrue(found.contains(1));
assertTrue(found.contains(2));
assertTrue(found.contains(3));
assertTrue(found.contains(k1));
assertTrue(found.contains(k2));
assertTrue(found.contains(k3));
}
@Test
public void keysShouldBeReturned() {
map.put(1, new Value("v1"));
map.put(2, new Value("v2"));
map.put(3, new Value("v3"));
@k@ k1 = 1;
@k@ k2 = 2;
@k@ k3 = 3;
@k@ k4 = 4;
map.put(k1, new Value("v1"));
map.put(k2, new Value("v2"));
map.put(k3, new Value("v3"));
// Add and then immediately remove another entry.
map.put(4, new Value("v4"));
map.remove(4);
map.put(k4, new Value("v4"));
map.remove(k4);
int[] keys = map.keys();
@k@[] keys = map.keys();
assertEquals(3, keys.length);
Set<Integer> expected = new HashSet<Integer>();
expected.add(1);
expected.add(2);
expected.add(3);
Set<@O@> expected = new HashSet<@O@>();
expected.add(k1);
expected.add(k2);
expected.add(k3);
Set<Integer> found = new HashSet<Integer>();
for (int key : keys) {
Set<@O@> found = new HashSet<@O@>();
for (@k@ key : keys) {
assertTrue(found.add(key));
}
assertEquals(expected, found);
@ -266,16 +281,20 @@ public class IntObjectHashMapTest {
@Test
public void valuesShouldBeReturned() {
@k@ k1 = 1;
@k@ k2 = 2;
@k@ k3 = 3;
@k@ k4 = 4;
Value v1 = new Value("v1");
Value v2 = new Value("v2");
Value v3 = new Value("v3");
map.put(1, v1);
map.put(2, v2);
map.put(3, v3);
map.put(k1, v1);
map.put(k2, v2);
map.put(k3, v3);
// Add and then immediately remove another entry.
map.put(4, new Value("v4"));
map.remove(4);
map.put(k4, new Value("v4"));
map.remove(k4);
// Ensure values() return all values.
Set<Value> expected = new HashSet<Value>();
@ -305,9 +324,9 @@ public class IntObjectHashMapTest {
public void mapShouldSupportHashingConflicts() {
for (int mod = 0; mod < 10; ++mod) {
for (int sz = 1; sz <= 101; sz += 2) {
IntObjectHashMap<String> map = new IntObjectHashMap<String>(sz);
@K@ObjectHashMap<String> map = new @K@ObjectHashMap<String>(sz);
for (int i = 0; i < 100; ++i) {
map.put(i * mod, "");
map.put((@k@)(i * mod), "");
}
}
}
@ -315,34 +334,34 @@ public class IntObjectHashMapTest {
@Test
public void hashcodeEqualsTest() {
IntObjectHashMap<Integer> map1 = new IntObjectHashMap<Integer>();
IntObjectHashMap<Integer> map2 = new IntObjectHashMap<Integer>();
@K@ObjectHashMap<@O@> map1 = new @K@ObjectHashMap<@O@>();
@K@ObjectHashMap<@O@> map2 = new @K@ObjectHashMap<@O@>();
Random rnd = new Random(0);
while (map1.size() < 100) {
int key = rnd.nextInt(100);
map1.put(key, key);
map2.put(key, key);
@k@ key = (@k@) rnd.nextInt(100);
map1.put(key, @O@.valueOf(key));
map2.put(key, @O@.valueOf(key));
}
assertEquals(map1.hashCode(), map2.hashCode());
assertEquals(map1, map2);
// Remove one "middle" element, maps should now be non-equals.
int[] keys = map1.keys();
@k@[] keys = map1.keys();
map2.remove(keys[50]);
assertFalse(map1.equals(map2));
// Put it back; will likely be in a different position, but maps will be equal again.
map2.put(keys[50], map1.keys()[50]);
map2.put(keys[50], @O@.valueOf(map1.keys()[50]));
assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode());
// Make map2 have one extra element, will be non-equal.
map2.put(1000, 1000);
map2.put((@k@) 100, (@k@) 100);
assertFalse(map1.equals(map2));
// Rebuild map2 with elements in a different order, again the maps should be equal.
// (These tests with same elements in different order also show that the hashCode
// function does not depend on the internal ordering of entries.)
map2.clear();
Arrays.sort(keys);
for (int key : keys) {
map2.put(key, key);
for (@k@ key : keys) {
map2.put(key, @O@.valueOf(key));
}
assertEquals(map1.hashCode(), map2.hashCode());
assertEquals(map1, map2);
@ -365,25 +384,25 @@ public class IntObjectHashMapTest {
// This size is also chosen so after the single rehash, the map will be densely
// populated, getting close to a second rehash but not triggering it.
int startTableSize = 1105;
IntObjectHashMap<Integer> map = new IntObjectHashMap<Integer>(startTableSize);
@K@ObjectHashMap<@O@> map = new @K@ObjectHashMap<@O@>(startTableSize);
// Reference map which implementation we trust to be correct, will mirror all operations.
HashMap<Integer, Integer> goodMap = new HashMap<Integer, Integer>();
HashMap<@O@, @O@> goodMap = new HashMap<@O@, @O@>();
// Add initial population.
for (int i = 0; i < baseSize / 4; ++i) {
int key = rnd.nextInt(baseSize);
assertEquals(goodMap.put(key, key), map.put(key, key));
@k@ key = (@k@) rnd.nextInt(baseSize);
assertEquals(goodMap.put(key, @O@.valueOf(key)), map.put(key, @O@.valueOf(key)));
// 50% elements are multiple of a divisor of startTableSize => more conflicts.
key = rnd.nextInt(baseSize) * 17;
assertEquals(goodMap.put(key, key), map.put(key, key));
key = (@k@) (rnd.nextInt(baseSize) * 17);
assertEquals(goodMap.put(key, @O@.valueOf(key)), map.put(key, @O@.valueOf(key)));
}
// Now do some mixed adds and removes for further fuzzing
// Rehash will happen here, but only once, and the final size will be closer to max.
for (int i = 0; i < baseSize * 1000; ++i) {
int key = rnd.nextInt(baseSize);
@k@ key = (@k@) rnd.nextInt(baseSize);
if (rnd.nextDouble() >= 0.2) {
assertEquals(goodMap.put(key, key), map.put(key, key));
assertEquals(goodMap.put(key, @O@.valueOf(key)), map.put(key, @O@.valueOf(key)));
} else {
assertEquals(goodMap.remove(key), map.remove(key));
}
@ -392,7 +411,7 @@ public class IntObjectHashMapTest {
// Final batch of fuzzing, only searches and removes.
int removeSize = map.size() / 2;
while (removeSize > 0) {
int key = rnd.nextInt(baseSize);
@k@ key = (@k@) rnd.nextInt(baseSize);
boolean found = goodMap.containsKey(key);
assertEquals(found, map.containsKey(key));
assertEquals(goodMap.remove(key), map.remove(key));
@ -403,16 +422,16 @@ public class IntObjectHashMapTest {
// Now gotta write some code to compare the final maps, as equals() won't work.
assertEquals(goodMap.size(), map.size());
Integer[] goodKeys = goodMap.keySet().toArray(new Integer[goodMap.size()]);
@O@[] goodKeys = goodMap.keySet().toArray(new @O@[goodMap.size()]);
Arrays.sort(goodKeys);
int [] keys = map.keys();
@k@ [] keys = map.keys();
Arrays.sort(keys);
for (int i = 0; i < goodKeys.length; ++i) {
assertEquals((int) goodKeys[i], keys[i]);
assertEquals((@k@) goodKeys[i], keys[i]);
}
// Finally drain the map.
for (int key : map.keys()) {
for (@k@ key : map.keys()) {
assertEquals(goodMap.remove(key), map.remove(key));
}
assertTrue(map.isEmpty());