Skip to content

Commit 603adc6

Browse files
committed
Add guava-testlib and fix tests (which happens to introduce many features)
fixes #4
1 parent 655d7a8 commit 603adc6

File tree

10 files changed

+252
-35
lines changed

10 files changed

+252
-35
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ or (at your option) any later version.
8282

8383
Change log
8484
----------
85+
HEAD
86+
* Improvement: implement null keys
87+
* Improvement: `Map#toString`
88+
* Improvement: `Map#hashCode` + `equals`
89+
* Improvement: `Map.Entry#hashCode` + `equals`
90+
* Improvement: `Map.Entry#toString`
91+
* Improvement: `Map#containsValue` (it is slow but it works)
92+
* Test: use `guava-testlib` for `Map` implementation testing
93+
8594
v1.2.1
8695
* Improvement: release to Maven Central
8796
* Improvement: fix EntrySet.remove method

compactmap/pom.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@
1818
<artifactId>dexx-collections</artifactId>
1919
</dependency>
2020
<dependency>
21-
<groupId>org.testng</groupId>
22-
<artifactId>testng</artifactId>
21+
<groupId>org.junit.jupiter</groupId>
22+
<artifactId>junit-jupiter</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.junit.vintage</groupId>
26+
<artifactId>junit-vintage-engine</artifactId>
27+
<scope>test</scope>
28+
</dependency>
29+
<dependency>
30+
<groupId>com.google.guava</groupId>
31+
<artifactId>guava-testlib</artifactId>
2332
</dependency>
2433
</dependencies>
2534
</project>

compactmap/src/main/java/vlsi/utils/CompactHashMap.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.io.Serializable;
2424
import java.util.Collection;
25+
import java.util.Iterator;
2526
import java.util.Map;
2627
import java.util.Set;
2728

@@ -94,7 +95,7 @@ public boolean containsKey(Object key) {
9495
}
9596

9697
public boolean containsValue(Object value) {
97-
throw new UnsupportedOperationException();
98+
return values().contains(value);
9899
}
99100

100101
public V get(Object key) {
@@ -136,6 +137,64 @@ public Set<Entry<K, V>> entrySet() {
136137
return klass.entrySet(this);
137138
}
138139

140+
public boolean equals(Object o) {
141+
if (o == this) {
142+
return true;
143+
}
144+
145+
if (!(o instanceof Map)) {
146+
return false;
147+
}
148+
149+
Map<?,?> m = (Map<?,?>) o;
150+
if (m.size() != size())
151+
return false;
152+
153+
for (Entry<K, V> e : entrySet()) {
154+
K key = e.getKey();
155+
V value = e.getValue();
156+
157+
if (value == null) {
158+
if (m.get(key) != null || !m.containsKey(key)) {
159+
return false;
160+
}
161+
} else {
162+
if (!value.equals(m.get(key))) {
163+
return false;
164+
}
165+
}
166+
}
167+
168+
return true;
169+
}
170+
171+
@Override
172+
public String toString() {
173+
Iterator<Entry<K, V>> it = entrySet().iterator();
174+
if (!it.hasNext())
175+
return "{}";
176+
177+
StringBuilder sb = new StringBuilder();
178+
sb.append('{');
179+
while (it.hasNext()) {
180+
Entry<K, V> e = it.next();
181+
K key = e.getKey();
182+
V value = e.getValue();
183+
sb.append(key).append('=').append(value);
184+
sb.append(',').append(' ');
185+
}
186+
sb.setLength(sb.length() - 2);
187+
return sb.append('}').toString();
188+
}
189+
190+
public int hashCode() {
191+
int h = 0;
192+
for (Entry<K, V> entry : entrySet()) {
193+
h += entry.hashCode();
194+
}
195+
return h;
196+
}
197+
139198
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
140199
klass.serialize(this, s);
141200
}

compactmap/src/main/java/vlsi/utils/CompactHashMapClass.java

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,21 @@ abstract class CompactHashMapClass<K, V> {
3636
// "new String" is required to avoid clashing with regular strings
3737
public static final String REMOVED_OBJECT = new String("Non existing mapping value");
3838

39+
// dexx does not support null, so we wrap null
40+
private static final Object NULL = new Object();
41+
3942
public CompactHashMapClass(com.github.andrewoma.dexx.collection.Map<K, Integer> key2slot) {
4043
this.key2slot = key2slot;
4144
}
4245

46+
private K maskNull(K key) {
47+
return key == null ? (K) NULL : key;
48+
}
49+
50+
private K unmaskNull(K key) {
51+
return key == NULL ? null : key;
52+
}
53+
4354
protected Map<K, V> getDefaultValues() {
4455
return Collections.emptyMap();
4556
}
@@ -52,9 +63,10 @@ public V get(CompactHashMap<K, V> map, K key) {
5263
}
5364

5465
private Object getInternal(CompactHashMap<K, V> map, Object key) {
55-
final Integer slot = key2slot.get((K) key);
66+
K nonNullKey = maskNull((K) key);
67+
final Integer slot = key2slot.get(nonNullKey);
5668
if (slot == null)
57-
return getDefaultValues().get(key);
69+
return getDefaultValues().get(nonNullKey);
5870

5971
return getValueFromSlot(map, slot);
6072
}
@@ -73,13 +85,14 @@ protected static Object getValueFromSlot(CompactHashMap map, int slot) {
7385
}
7486

7587
public V put(CompactHashMap<K, V> map, K key, Object value) {
76-
Integer slot = key2slot.get(key);
88+
K nonNullKey = maskNull(key);
89+
Integer slot = key2slot.get(nonNullKey);
7790
Object prevValue = REMOVED_OBJECT;
7891
if (slot == null) {
79-
prevValue = getDefaultValues().get(key);
92+
prevValue = getDefaultValues().get(nonNullKey);
8093

8194
// Try put value as "default"
82-
Map<K, V> newDef = CompactHashMapDefaultValues.getNewDefaultValues(getDefaultValues(), key, value);
95+
Map<K, V> newDef = CompactHashMapDefaultValues.getNewDefaultValues(getDefaultValues(), nonNullKey, value);
8396
if (newDef != null) {
8497
map.klass = getMapWithEmptyDefaults().getNewDefaultClass(newDef);
8598
return (V) prevValue;
@@ -88,7 +101,7 @@ public V put(CompactHashMap<K, V> map, K key, Object value) {
88101
if (value == REMOVED_OBJECT)
89102
return (V) prevValue;
90103
// The value is not default -- put using regular way
91-
slot = createNewSlot(map, key);
104+
slot = createNewSlot(map, nonNullKey);
92105
}
93106

94107
switch (slot) {
@@ -173,9 +186,10 @@ private int removedSlotsCount(CompactHashMap<K, V> map) {
173186
public boolean containsKey(CompactHashMap<K, V> map, Object key) {
174187
// We cannot use plain getInternal here since we will be unable to distinguish
175188
// existing, but null default value
176-
final Integer slot = key2slot.get((K) key);
189+
K nonNullKey = maskNull((K) key);
190+
final Integer slot = key2slot.get(nonNullKey);
177191
if (slot == null)
178-
return getDefaultValues().containsKey(key);
192+
return getDefaultValues().containsKey(nonNullKey);
179193

180194
return getValueFromSlot(map, slot) != REMOVED_OBJECT;
181195
}
@@ -202,7 +216,7 @@ public void serialize(final CompactHashMap<K, V> map, final ObjectOutputStream s
202216
for (Pair<K, Integer> entry : key2slot) {
203217
Object value = getValueFromSlot(map, entry.component2());
204218
if (value == REMOVED_OBJECT) continue;
205-
s.writeObject(entry.component1());
219+
s.writeObject(unmaskNull(entry.component1()));
206220
s.writeObject(value);
207221
}
208222

@@ -273,11 +287,6 @@ public int size() {
273287
return map.size();
274288
}
275289

276-
@Override
277-
public boolean contains(Object o) {
278-
return map.containsValue(o);
279-
}
280-
281290
@Override
282291
public Iterator<V> iterator() {
283292
return new ValueIterator<K, V>(map);
@@ -306,8 +315,13 @@ public boolean contains(Object o) {
306315
if (!(o instanceof Map.Entry))
307316
return false;
308317
Map.Entry<K, V> e = (Map.Entry<K, V>) o;
309-
Object mapValue = map.get(e.getKey());
310-
return mapValue == e.getValue() || mapValue != null && mapValue.equals(e.getValue());
318+
K key = e.getKey();
319+
V value = e.getValue();
320+
V ourValue = map.get(key);
321+
if (value == null) {
322+
return ourValue == null && map.containsKey(key);
323+
}
324+
return value.equals(ourValue);
311325
}
312326

313327
@Override
@@ -381,7 +395,11 @@ public Map.Entry<K, V> nextEntry() {
381395
}
382396

383397
public void remove() {
398+
if (current == null) {
399+
throw new IllegalStateException();
400+
}
384401
map.remove(current.getKey());
402+
current = null;
385403
}
386404
}
387405

@@ -428,7 +446,7 @@ public SimpleEntry(CompactHashMap<K, V> map, K key, V value) {
428446
}
429447

430448
public K getKey() {
431-
return key;
449+
return map.klass.unmaskNull(key);
432450
}
433451

434452
public V getValue() {
@@ -439,6 +457,29 @@ public V setValue(V value) {
439457
this.value = value;
440458
return map.put(key, value);
441459
}
460+
461+
private static boolean eq(Object o1, Object o2) {
462+
return o1 == null ? o2 == null : o1.equals(o2);
463+
}
464+
465+
public boolean equals(Object o) {
466+
if (!(o instanceof Map.Entry)) {
467+
return false;
468+
}
469+
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
470+
471+
return eq(getKey(), e.getKey()) && eq(value, e.getValue());
472+
}
473+
474+
public int hashCode() {
475+
return (key == NULL ? 0 : key.hashCode()) ^
476+
(value == null ? 0 : value.hashCode());
477+
}
478+
479+
@Override
480+
public String toString() {
481+
return map.klass.unmaskNull(key) + "=" + value;
482+
}
442483
}
443484

444485
}

compactmap/src/test/java/vlsi/utils/CompactHashMapClassTest.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@
1919

2020
package vlsi.utils;
2121

22-
import org.testng.Assert;
23-
import org.testng.annotations.BeforeMethod;
24-
import org.testng.annotations.Test;
22+
import org.junit.Assert;
23+
import org.junit.Before;
24+
import org.junit.Test;
2525

2626
import java.io.*;
27+
import java.util.HashMap;
28+
import java.util.Iterator;
2729
import java.util.Map;
2830

2931
public class CompactHashMapClassTest {
30-
@BeforeMethod
32+
@Before
3133
public void clearDefaults() {
3234
CompactHashMapDefaultValues.clear();
3335
}
@@ -233,7 +235,7 @@ public void deleteNullWorks() {
233235
public void deleteFromEntrySet() {
234236
CompactHashMap<String, Object> map = new CompactHashMap<String, Object>();
235237
map.put("k1", "v1");
236-
Assert.assertTrue(map.entrySet().remove(new Map.Entry() {
238+
Assert.assertTrue("entry was removed", map.entrySet().remove(new Map.Entry() {
237239
public Object getKey() {
238240
return "k1";
239241
}
@@ -245,7 +247,7 @@ public Object getValue() {
245247
public Object setValue(Object value) {
246248
return null;
247249
}
248-
}), "entry was removed");
250+
}));
249251
Assert.assertEquals(map.size(), 0);
250252
}
251253
}

0 commit comments

Comments
 (0)