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> <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> <dependencies>
<!-- Byte code generator - completely optional --> <!-- Byte code generator - completely optional -->
<dependency> <dependency>
@ -55,5 +62,73 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
</dependencies> </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> </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; 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. * 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 * 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. * remove can approach O(N) for full maps, which makes a small loadFactor recommended.
* *
* @param <V> The value type stored in the map. * @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 */ /** Default initial capacity. Used if not specified in the constructor */
public static final int DEFAULT_CAPACITY = 11; 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}. */ /** The load factor for the map. Used to calculate {@link #maxSize}. */
private final float loadFactor; private final float loadFactor;
private int[] keys; private @k@[] keys;
private V[] values; private V[] values;
private Collection<V> valueCollection; private Collection<V> valueCollection;
private int size; private int size;
public IntObjectHashMap() { public @K@ObjectHashMap() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
} }
public IntObjectHashMap(int initialCapacity) { public @K@ObjectHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); this(initialCapacity, DEFAULT_LOAD_FACTOR);
} }
public IntObjectHashMap(int initialCapacity, float loadFactor) { public @K@ObjectHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 1) { if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity must be >= 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); int capacity = adjustCapacity(initialCapacity);
// Allocate the arrays. // Allocate the arrays.
keys = new int[capacity]; keys = new @k@[capacity];
@SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[capacity]; V[] temp = (V[]) new Object[capacity];
values = temp; values = temp;
@ -98,13 +98,13 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
} }
@Override @Override
public V get(int key) { public V get(@k@ key) {
int index = indexOf(key); int index = indexOf(key);
return index == -1 ? null : toExternal(values[index]); return index == -1 ? null : toExternal(values[index]);
} }
@Override @Override
public V put(int key, V value) { public V put(@k@ key, V value) {
int startIndex = hashIndex(key); int startIndex = hashIndex(key);
int index = startIndex; int index = startIndex;
@ -136,10 +136,10 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
} }
@Override @Override
public void putAll(IntObjectMap<V> sourceMap) { public void putAll(@K@ObjectMap<V> sourceMap) {
if (sourceMap instanceof IntObjectHashMap) { if (sourceMap instanceof IntObjectHashMap) {
// Optimization - iterate through the arrays. // 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) { for (int i = 0; i < source.values.length; ++i) {
V sourceValue = source.values[i]; V sourceValue = source.values[i];
if (sourceValue != null) { if (sourceValue != null) {
@ -156,7 +156,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
} }
@Override @Override
public V remove(int key) { public V remove(@k@ key) {
int index = indexOf(key); int index = indexOf(key);
if (index == -1) { if (index == -1) {
return null; return null;
@ -179,13 +179,13 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
@Override @Override
public void clear() { public void clear() {
Arrays.fill(keys, 0); Arrays.fill(keys, (@k@) 0);
Arrays.fill(values, null); Arrays.fill(values, null);
size = 0; size = 0;
} }
@Override @Override
public boolean containsKey(int key) { public boolean containsKey(@k@ key) {
return indexOf(key) >= 0; return indexOf(key) >= 0;
} }
@ -212,8 +212,8 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
} }
@Override @Override
public int[] keys() { public @k@[] keys() {
int[] outKeys = new int[size()]; @k@[] outKeys = new @k@[size()];
int targetIx = 0; int targetIx = 0;
for (int i = 0; i < values.length; ++i) { for (int i = 0; i < values.length; ++i) {
if (values[i] != null) { if (values[i] != null) {
@ -244,7 +244,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return new Iterator<V>() { return new Iterator<V>() {
final Iterator<Entry<V>> iter = IntObjectHashMap.this.iterator(); final Iterator<Entry<V>> iter = @K@ObjectHashMap.this.iterator();
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return iter.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 // 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. // capacity cannot be used as input for hashing but the size can.
int hash = size; 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. // 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, // 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. // 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 // 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 // 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. // elements, but with different history of puts/removes, due to conflicts.
hash ^= key; hash ^= hashCode(key);
} }
return hash; return hash;
} }
@ -296,18 +296,18 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
if (this == obj) { if (this == obj) {
return true; return true;
} }
if (!(obj instanceof IntObjectMap)) { if (!(obj instanceof @K@ObjectMap)) {
return false; return false;
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
IntObjectMap other = (IntObjectMap) obj; @K@ObjectMap other = (@K@ObjectMap) obj;
if (size != other.size()) { if (size != other.size()) {
return false; return false;
} }
for (int i = 0; i < values.length; ++i) { for (int i = 0; i < values.length; ++i) {
V value = values[i]; V value = values[i];
if (value != null) { if (value != null) {
int key = keys[i]; @k@ key = keys[i];
Object otherValue = other.get(key); Object otherValue = other.get(key);
if (value == NULL_VALUE) { if (value == NULL_VALUE) {
if (otherValue != null) { 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. * @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. * @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 startIndex = hashIndex(key);
int index = startIndex; int index = startIndex;
@ -350,9 +350,16 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
/** /**
* Returns the hashed index for the given key. * 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. // 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. * @param newCapacity the new capacity for the map.
*/ */
private void rehash(int newCapacity) { private void rehash(int newCapacity) {
int[] oldKeys = keys; @k@[] oldKeys = keys;
V[] oldVals = values; V[] oldVals = values;
keys = new int[newCapacity]; keys = new @k@[newCapacity];
@SuppressWarnings({ "unchecked", "SuspiciousArrayCast" }) @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[newCapacity]; V[] temp = (V[]) new Object[newCapacity];
values = temp; values = temp;
@ -444,7 +451,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
if (oldVal != null) { if (oldVal != null) {
// Inlined put(), but much simpler: we don't need to worry about // Inlined put(), but much simpler: we don't need to worry about
// duplicated keys, growing/rehashing, or failing to insert. // duplicated keys, growing/rehashing, or failing to insert.
int oldKey = oldKeys[i]; @k@ oldKey = oldKeys[i];
int index = hashIndex(oldKey); int index = hashIndex(oldKey);
for (;;) { for (;;) {
@ -512,7 +519,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// into the Iterator object (potentially making loop optimization much easier). // into the Iterator object (potentially making loop optimization much easier).
@Override @Override
public int key() { public @k@ key() {
return keys[entryIndex]; 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. * Helper method called by {@link #toString()} in order to convert a single map key into a string.
*/ */
protected String keyToString(int key) { protected String keyToString(@k@ key) {
return Integer.toString(key); return @O@.toString(key);
} }
} }

View File

@ -17,11 +17,11 @@ package io.netty.util.collection;
import java.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. * @param <V> the value type stored in the map.
*/ */
public interface IntObjectMap<V> { public interface @K@ObjectMap<V> {
/** /**
* An Entry in the map. * An Entry in the map.
@ -32,7 +32,7 @@ public interface IntObjectMap<V> {
/** /**
* Gets the key for this entry. * Gets the key for this entry.
*/ */
int key(); @k@ key();
/** /**
* Gets the value for this entry. * 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. * @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. * @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. * Puts the given entry into the map.
@ -60,12 +60,12 @@ public interface IntObjectMap<V> {
* @param value the value of the entry. * @param value the value of the entry.
* @return the previous value for this key or {@code null} if there was no previous mapping. * @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. * 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. * 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. * @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. * @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. * 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. * 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. * 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. * Gets the keys contained in this map.
*/ */
int[] keys(); @k@[] keys();
/** /**
* Gets the values contained in this map. * Gets the values contained in this map.

View File

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