diff --git a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index 8af75e0d..4344ca59 100644 --- a/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -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 diff --git a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 9ba462ff..1c2a0ba2 100644 --- a/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -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); diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index df5dbeaf..fd347705 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -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