From 3354296c9ff955e4315d32e0f85019ab209475f3 Mon Sep 17 00:00:00 2001 From: nmittler Date: Fri, 3 Apr 2015 13:36:28 -0700 Subject: [PATCH] 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. --- common/pom.xml | 75 ++++++ common/src/main/script/codegen.groovy | 39 ++++ .../util/collection/KObjectHashMap.template} | 69 +++--- .../util/collection/KObjectMap.template} | 18 +- .../KObjectHashMapTest.template} | 219 ++++++++++-------- 5 files changed, 280 insertions(+), 140 deletions(-) create mode 100644 common/src/main/script/codegen.groovy rename common/src/main/{java/io/netty/util/collection/IntObjectHashMap.java => templates/io/netty/util/collection/KObjectHashMap.template} (91%) rename common/src/main/{java/io/netty/util/collection/IntObjectMap.java => templates/io/netty/util/collection/KObjectMap.template} (90%) rename common/src/test/{java/io/netty/util/collection/IntObjectHashMapTest.java => templates/io.netty.util.collection/KObjectHashMapTest.template} (67%) diff --git a/common/pom.xml b/common/pom.xml index 0b6e4168ad..6ee12906dc 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -29,6 +29,13 @@ Netty/Common + + ${project.basedir}/src/main/templates + ${project.basedir}/src/test/templates + ${project.build.directory}/generated-sources/collections/java + ${project.build.directory}/generated-test-sources/collections/java + + @@ -55,5 +62,73 @@ true + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.9.1 + + + add-source + generate-sources + + add-source + + + + ${collection.src.dir} + + + + + add-test-source + generate-test-sources + + add-test-source + + + + ${collection.testsrc.dir} + + + + + + + + + org.codehaus.gmaven + groovy-maven-plugin + 2.0 + + + org.codehaus.groovy + groovy-all + 2.4.3 + + + ant + ant-optional + 1.5.3-1 + + + + + generate-collections + generate-sources + + execute + + + ${project.basedir}/src/main/script/codegen.groovy + + + + + + diff --git a/common/src/main/script/codegen.groovy b/common/src/main/script/codegen.groovy new file mode 100644 index 0000000000..fc7ee83680 --- /dev/null +++ b/common/src/main/script/codegen.groovy @@ -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) + } +} \ No newline at end of file diff --git a/common/src/main/java/io/netty/util/collection/IntObjectHashMap.java b/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template similarity index 91% rename from common/src/main/java/io/netty/util/collection/IntObjectHashMap.java rename to common/src/main/templates/io/netty/util/collection/KObjectHashMap.template index 4ed354dd3a..291234d016 100644 --- a/common/src/main/java/io/netty/util/collection/IntObjectHashMap.java +++ b/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template @@ -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 The value type stored in the map. */ -public class IntObjectHashMap implements IntObjectMap, Iterable> { +public class @K@ObjectHashMap implements @K@ObjectMap, Iterable<@K@ObjectMap.Entry> { /** Default initial capacity. Used if not specified in the constructor */ public static final int DEFAULT_CAPACITY = 11; @@ -50,20 +50,20 @@ public class IntObjectHashMap implements IntObjectMap, Iterable 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 implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable sourceMap) { + public void putAll(@K@ObjectMap sourceMap) { if (sourceMap instanceof IntObjectHashMap) { // Optimization - iterate through the arrays. - IntObjectHashMap source = (IntObjectHashMap) sourceMap; + @K@ObjectHashMap source = (@K@ObjectHashMap) 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 implements IntObjectMap, Iterable implements IntObjectMap, Iterable= 0; } @@ -212,8 +212,8 @@ public class IntObjectHashMap implements IntObjectMap, Iterable implements IntObjectMap, Iterable iterator() { return new Iterator() { - final Iterator> iter = IntObjectHashMap.this.iterator(); + final Iterator> iter = @K@ObjectHashMap.this.iterator(); @Override public boolean hasNext() { return iter.hasNext(); @@ -278,7 +278,7 @@ public class IntObjectHashMap implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable implements IntObjectMap, Iterable the value type stored in the map. */ -public interface IntObjectMap { +public interface @K@ObjectMap { /** * An Entry in the map. @@ -32,7 +32,7 @@ public interface IntObjectMap { /** * Gets the key for this entry. */ - int key(); + @k@ key(); /** * Gets the value for this entry. @@ -51,7 +51,7 @@ public interface IntObjectMap { * @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 { * @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 sourceMap); + void putAll(@K@ObjectMap sourceMap); /** * Removes the entry with the specified key. @@ -73,7 +73,7 @@ public interface IntObjectMap { * @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 { /** * 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 { /** * Gets the keys contained in this map. */ - int[] keys(); + @k@[] keys(); /** * Gets the values contained in this map. diff --git a/common/src/test/java/io/netty/util/collection/IntObjectHashMapTest.java b/common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template similarity index 67% rename from common/src/test/java/io/netty/util/collection/IntObjectHashMapTest.java rename to common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template index 470dc490e5..5969e0a1b7 100644 --- a/common/src/test/java/io/netty/util/collection/IntObjectHashMapTest.java +++ b/common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template @@ -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 map; + private @K@ObjectHashMap map; @Before public void setup() { - map = new IntObjectHashMap(); + map = new @K@ObjectHashMap(); } @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 map2 = new IntObjectHashMap(); + @K@ObjectHashMap map2 = new @K@ObjectHashMap(); 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 found = new HashSet(); - for (IntObjectMap.Entry entry : map.entries()) { + Set<@O@> found = new HashSet<@O@>(); + for (@K@ObjectMap.Entry 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 expected = new HashSet(); - 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 found = new HashSet(); - 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 expected = new HashSet(); @@ -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 map = new IntObjectHashMap(sz); + @K@ObjectHashMap map = new @K@ObjectHashMap(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 map1 = new IntObjectHashMap(); - IntObjectHashMap map2 = new IntObjectHashMap(); + @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 map = new IntObjectHashMap(startTableSize); + @K@ObjectHashMap<@O@> map = new @K@ObjectHashMap<@O@>(startTableSize); // Reference map which implementation we trust to be correct, will mirror all operations. - HashMap goodMap = new HashMap(); + 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());