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:
parent
6928a2d79f
commit
386fd89597
@ -28,6 +28,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>
|
||||
@ -54,5 +61,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>
|
||||
|
||||
|
39
common/src/main/script/codegen.groovy
Normal file
39
common/src/main/script/codegen.groovy
Normal 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)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
@ -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());
|
Loading…
Reference in New Issue
Block a user