Fix equality between ConfigObject of different types

By pulling equals/hashCode up into AbstractConfigObject
This commit is contained in:
Havoc Pennington 2011-11-12 22:19:24 -05:00
parent 6528ac5d1e
commit 52f34925dc
3 changed files with 65 additions and 59 deletions

View File

@ -3,6 +3,7 @@ package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -569,6 +570,58 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return sb.toString();
}
private static boolean mapEquals(Map<String, ConfigValue> a,
Map<String, ConfigValue> b) {
Set<String> aKeys = a.keySet();
Set<String> bKeys = b.keySet();
if (!aKeys.equals(bKeys))
return false;
for (String key : aKeys) {
if (!a.get(key).equals(b.get(key)))
return false;
}
return true;
}
private static int mapHash(Map<String, ConfigValue> m) {
// the keys have to be sorted, otherwise we could be equal
// to another map but have a different hashcode.
List<String> keys = new ArrayList<String>();
keys.addAll(m.keySet());
Collections.sort(keys);
int valuesHash = 0;
for (String k : keys) {
valuesHash += m.get(k).hashCode();
}
return 41 * (41 + keys.hashCode()) + valuesHash;
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigObject;
}
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof ConfigObject) {
// optimization to avoid unwrapped() for two ConfigObject,
// which is what AbstractConfigValue does.
return canEqual(other) && mapEquals(this, ((ConfigObject) other));
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return mapHash(this);
}
private static UnsupportedOperationException weAreImmutable(String method) {
return new UnsupportedOperationException(
"ConfigObject is immutable, you can't call Map.'" + method

View File

@ -1,17 +1,14 @@
package com.typesafe.config.impl;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;
@ -83,62 +80,6 @@ final class SimpleConfigObject extends AbstractConfigObject {
return value.keySet();
}
private static boolean mapEquals(Map<String, AbstractConfigValue> a,
Map<String, AbstractConfigValue> b) {
Set<String> aKeys = a.keySet();
Set<String> bKeys = b.keySet();
if (!aKeys.equals(bKeys))
return false;
for (String key : aKeys) {
if (!a.get(key).equals(b.get(key)))
return false;
}
return true;
}
private static int mapHash(Map<String, AbstractConfigValue> m) {
// the keys have to be sorted, otherwise we could be equal
// to another map but have a different hashcode.
List<String> keys = new ArrayList<String>();
keys.addAll(m.keySet());
Collections.sort(keys);
int valuesHash = 0;
for (String k : keys) {
valuesHash += m.get(k).hashCode();
}
return 41 * (41 + keys.hashCode()) + valuesHash;
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigObject;
}
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof SimpleConfigObject) {
// optimization to avoid unwrapped() for two SimpleConfigObject
// note: if this included "transformer" then we could never be
// equal to a non-SimpleConfigObject ConfigObject.
return canEqual(other)
&& mapEquals(value, ((SimpleConfigObject) other).value);
} else if (other instanceof ConfigObject) {
return super.equals(other);
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return mapHash(this.value);
}
@Override
public boolean containsValue(Object v) {
return value.containsValue(v);

View File

@ -60,13 +60,25 @@ class ConfigValueTest extends TestUtils {
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val sameAsAMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val bMap = configMap("a" -> 3, "b" -> 4, "c" -> 5)
// different keys is a different case in the equals implementation
val cMap = configMap("x" -> 3, "y" -> 4, "z" -> 5)
val a = new SimpleConfigObject(fakeOrigin(), aMap)
val sameAsA = new SimpleConfigObject(fakeOrigin(), sameAsAMap)
val b = new SimpleConfigObject(fakeOrigin(), bMap)
val c = new SimpleConfigObject(fakeOrigin(), cMap)
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
checkEqualObjects(b, b)
checkEqualObjects(c, c)
checkNotEqualObjects(a, b)
checkNotEqualObjects(a, c)
checkNotEqualObjects(b, c)
val root = a.asRoot()
checkEqualObjects(a, root)
checkNotEqualObjects(root, b)
checkNotEqualObjects(root, c)
}
@Test