You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2020/03/14 14:21:48 UTC
[commons-collections] 05/20: Ensure hashCode hashes the same
properties as the equality.
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git
commit 03543e5f9b5133d4026a080b62839440bebda7e6
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Tue Mar 10 00:46:41 2020 +0000
Ensure hashCode hashes the same properties as the equality.
Since HashFunctionIdentity is an interface there is no control over what
is hashed. Add a hash function to the HashFunctionValidator to ensure
the hash code is the same if two hash functions are equal according to
the hashFunctionIdentity.
Note: Since Shape is final we use the properties directly and not through the get methods.
---
.../bloomfilter/hasher/HashFunctionValidator.java | 30 +++++++++++++++
.../collections4/bloomfilter/hasher/Shape.java | 21 +++++------
.../collections4/bloomfilter/hasher/ShapeTest.java | 44 ++++++++++++++++++++--
3 files changed, 80 insertions(+), 15 deletions(-)
diff --git a/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/HashFunctionValidator.java b/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/HashFunctionValidator.java
index 21d2cb0..a79b67f 100644
--- a/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/HashFunctionValidator.java
+++ b/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/HashFunctionValidator.java
@@ -16,6 +16,9 @@
*/
package org.apache.commons.collections4.bloomfilter.hasher;
+import java.util.Locale;
+import java.util.Objects;
+
/**
* Contains validation for hash functions.
*/
@@ -24,6 +27,33 @@ final class HashFunctionValidator {
private HashFunctionValidator() {}
/**
+ * Generates a hash code for the identity of the hash function. The hash code is
+ * generated using the same properties as those tested in
+ * {@link #areEqual(HashFunctionIdentity, HashFunctionIdentity)}, that is the
+ * signedness, process type and name. The name is not case specific and is converted
+ * to lower-case using the {@link Locale#ROOT root locale}.
+ *
+ * <p>The generated value is suitable for use in generation of a hash code that satisfies
+ * the contract of {@link Object#hashCode()} if the {@link Object#equals(Object)} method
+ * is implemented using {@link #areEqual(HashFunctionIdentity, HashFunctionIdentity)}. That
+ * is two objects considered equal will have the same hash code.
+ *
+ * <p>If the hash function identity is a field within a larger object the generated hash code
+ * should be incorporated into the entire hash, for example using
+ * {@link Objects#hash(Object...)}.
+ *
+ * @param a hash function.
+ * @return hash code
+ * @see String#toLowerCase(Locale)
+ * @see Locale#ROOT
+ */
+ static int hash(HashFunctionIdentity a) {
+ return Objects.hash(a.getSignedness(),
+ a.getProcessType(),
+ a.getName().toLowerCase(Locale.ROOT));
+ }
+
+ /**
* Compares the identity of the two hash functions. The functions are considered
* equal if the signedness, process type and name are equal. The name is not
* case specific.
diff --git a/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/Shape.java b/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/Shape.java
index 0a08ad5..427452f 100644
--- a/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/Shape.java
+++ b/src/main/java/org/apache/commons/collections4/bloomfilter/hasher/Shape.java
@@ -338,17 +338,21 @@ public final class Shape {
public boolean equals(final Object o) {
if (o instanceof Shape) {
final Shape other = (Shape) o;
- return
- getNumberOfBits() == other.getNumberOfBits() &&
- getNumberOfHashFunctions() == other.getNumberOfHashFunctions() &&
- HashFunctionValidator.areEqual(getHashFunctionIdentity(),
- other.getHashFunctionIdentity());
+ return numberOfBits == other.numberOfBits &&
+ numberOfHashFunctions == other.numberOfHashFunctions &&
+ HashFunctionValidator.areEqual(hashFunctionIdentity,
+ other.hashFunctionIdentity);
}
return false;
}
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
private int generateHashCode() {
- return Objects.hash(hashFunctionIdentity, numberOfBits, numberOfHashFunctions);
+ return Objects.hash(numberOfBits, numberOfHashFunctions, HashFunctionValidator.hash(hashFunctionIdentity));
}
/**
@@ -411,11 +415,6 @@ public final class Shape {
}
@Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
public String toString() {
return String.format("Shape[ %s n=%s m=%s k=%s ]",
HashFunctionIdentity.asCommonString(hashFunctionIdentity),
diff --git a/src/test/java/org/apache/commons/collections4/bloomfilter/hasher/ShapeTest.java b/src/test/java/org/apache/commons/collections4/bloomfilter/hasher/ShapeTest.java
index 0c04c07..b403104 100644
--- a/src/test/java/org/apache/commons/collections4/bloomfilter/hasher/ShapeTest.java
+++ b/src/test/java/org/apache/commons/collections4/bloomfilter/hasher/ShapeTest.java
@@ -20,7 +20,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
-import java.util.Objects;
+import org.apache.commons.collections4.bloomfilter.hasher.HashFunctionIdentity.ProcessType;
+import org.apache.commons.collections4.bloomfilter.hasher.HashFunctionIdentity.Signedness;
+
+import java.util.ArrayList;
import org.junit.Test;
@@ -458,11 +461,44 @@ public class ShapeTest {
}
/**
- * Test that hashCode equals hashCode of hashFunctionIdentity
+ * Test that hashCode satisfies the contract between {@link Object#hashCode()} and
+ * {@link Object#equals(Object)}. Equal shapes must have the same hash code.
*/
@Test
public void hashCodeTest() {
- final int hashCode = Objects.hash(testFunction, 24, 3);
- assertEquals(hashCode, shape.hashCode());
+ // Hash function equality is based on process type, signedness and name (case insensitive)
+ final ArrayList<HashFunctionIdentity> list = new ArrayList<>();
+ list.add(new HashFunctionIdentityImpl("Provider", "Name", Signedness.SIGNED, ProcessType.ITERATIVE, 0L));
+ // Provider changes
+ list.add(new HashFunctionIdentityImpl("PROVIDER", "Name", Signedness.SIGNED, ProcessType.ITERATIVE, 0L));
+ list.add(new HashFunctionIdentityImpl("Provider2", "Name", Signedness.SIGNED, ProcessType.ITERATIVE, 0L));
+ // Name changes
+ list.add(new HashFunctionIdentityImpl("Provider", "name", Signedness.SIGNED, ProcessType.ITERATIVE, 0L));
+ list.add(new HashFunctionIdentityImpl("Provider", "NAME", Signedness.SIGNED, ProcessType.ITERATIVE, 0L));
+ list.add(new HashFunctionIdentityImpl("Provider", "Other", Signedness.SIGNED, ProcessType.ITERATIVE, 0L));
+ // Signedness changes
+ list.add(new HashFunctionIdentityImpl("Provider", "Name", Signedness.UNSIGNED, ProcessType.ITERATIVE, 0L));
+ // ProcessType changes
+ list.add(new HashFunctionIdentityImpl("Provider", "Name", Signedness.SIGNED, ProcessType.CYCLIC, 0L));
+ // Signature changes
+ list.add(new HashFunctionIdentityImpl("Provider", "Name", Signedness.SIGNED, ProcessType.ITERATIVE, 1L));
+
+ // Create shapes that only differ in the hash function.
+ final int numberOfItems = 30;
+ final int numberOfBits = 3000;
+ final int numberOfHashFunctions = 10;
+ final Shape shape1 = new Shape(list.get(0), numberOfItems, numberOfBits, numberOfHashFunctions);
+ assertEquals(shape1, shape1);
+
+ // Try variations
+ for (int i = 1; i < list.size(); i++) {
+ final Shape shape2 = new Shape(list.get(i), numberOfItems, numberOfBits, numberOfHashFunctions);
+ assertEquals(shape2, shape2);
+
+ // Equal shapes must have the same hash code
+ if (shape1.equals(shape2)) {
+ assertEquals(shape1.hashCode(), shape2.hashCode());
+ }
+ }
}
}