You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/05/23 23:06:35 UTC
(cayenne) 07/08: Cleanup: HashCodeBuilder is deprecated in favor of plain Java alternatives
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit f5ab0eb4e5e49f085ca1aeaa0d88b31fd9e9ddc7
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sat May 23 18:27:06 2026 -0400
Cleanup: HashCodeBuilder is deprecated in favor of plain Java alternatives
---
.../cayenne/dbsync/reverse/dbload/ExportedKey.java | 15 +-
.../java/org/apache/cayenne/ObjectIdCompound.java | 428 ++++++++++-----------
.../java/org/apache/cayenne/ObjectIdSingle.java | 28 +-
.../cayenne/access/jdbc/ColumnDescriptor.java | 4 +-
.../org/apache/cayenne/event/EventSubject.java | 6 +-
.../java/org/apache/cayenne/exp/Expression.java | 9 +-
.../org/apache/cayenne/util/HashCodeBuilder.java | 4 +-
.../main/java/org/apache/cayenne/util/Util.java | 6 +-
8 files changed, 244 insertions(+), 256 deletions(-)
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ExportedKey.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ExportedKey.java
index ba06ad8c9..374c36f1d 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ExportedKey.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ExportedKey.java
@@ -25,7 +25,6 @@ import java.util.Objects;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.util.CompareToBuilder;
-import org.apache.cayenne.util.HashCodeBuilder;
import org.apache.cayenne.util.Util;
/**
@@ -92,11 +91,7 @@ public class ExportedKey implements Comparable<ExportedKey> {
@Override
public int hashCode() {
- return new HashCodeBuilder()
- .append(pk)
- .append(fk)
- .append(keySeq)
- .toHashCode();
+ return Objects.hash(pk, fk, keySeq);
}
@Override
@@ -198,13 +193,7 @@ public class ExportedKey implements Comparable<ExportedKey> {
@Override
public int hashCode() {
- return new HashCodeBuilder()
- .append(catalog)
- .append(schema)
- .append(table)
- .append(column)
- .append(name)
- .toHashCode();
+ return Objects.hash(catalog, schema, table, column, name);
}
/**
diff --git a/cayenne/src/main/java/org/apache/cayenne/ObjectIdCompound.java b/cayenne/src/main/java/org/apache/cayenne/ObjectIdCompound.java
index 15614437a..d124a0c0c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ObjectIdCompound.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ObjectIdCompound.java
@@ -19,7 +19,6 @@
package org.apache.cayenne;
-import org.apache.cayenne.util.HashCodeBuilder;
import java.util.Objects;
import java.util.Arrays;
@@ -29,227 +28,216 @@ import java.util.Map;
/**
* Compound {@link ObjectId}
+ *
* @since 4.2
*/
class ObjectIdCompound implements ObjectId {
- private static final long serialVersionUID = -2265029098344119323L;
-
- protected final String entityName;
- protected final Map<String, Object> objectIdKeys;
-
- protected Map<String, Object> replacementIdMap;
-
- // hash code is transient to make sure id is portable across VM
- private transient int hashCode;
-
- // exists for deserialization with Hessian and similar
- @SuppressWarnings("unused")
- private ObjectIdCompound() {
- entityName = null;
- objectIdKeys = Collections.emptyMap();
- }
-
- /**
- * Creates a portable permanent ObjectId as a compound primary key.
- *
- * @param entityName
- * The entity name which this object id is for
- * @param idMap
- * Keys are usually the attribute names for each part of the
- * primary key. Values are unique when taken as a whole.
- * @since 1.2
- */
- ObjectIdCompound(String entityName, Map<String, ?> idMap) {
- this.entityName = entityName;
-
- if (idMap == null || idMap.size() == 0) {
- this.objectIdKeys = Collections.emptyMap();
- return;
- }
- this.objectIdKeys = wrapIdMap(idMap);
- }
-
- @SuppressWarnings("unchecked")
- private Map<String, Object> wrapIdMap(Map<String, ?> m) {
- if(m.getClass() == HashMap.class) {
- return (Map<String, Object>)m;
- } else {
- // we have to create a copy of the map, otherwise we may run into serialization problems with hessian
- return new HashMap<>(m);
- }
- }
-
- /**
- * Is this is temporary object id (used for objects which are not yet
- * persisted to the data store).
- */
- @Override
- public boolean isTemporary() {
- return false;
- }
-
- /**
- * @since 1.2
- */
- @Override
- public String getEntityName() {
- return entityName;
- }
-
- /**
- * Get the binary temporary object id. Null if this object id is permanent
- * (persisted to the data store).
- */
- @Override
- public byte[] getKey() {
- return null;
- }
-
- /**
- * Returns an unmodifiable Map of persistent id values, essentially a
- * primary key map. For temporary id returns replacement id, if it was
- * already created. Otherwise returns an empty map.
- */
- @Override
- public Map<String, Object> getIdSnapshot() {
- return Collections.unmodifiableMap(objectIdKeys);
- }
-
- @Override
- public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
-
- if (!(object instanceof ObjectIdCompound)) {
- return false;
- }
-
- ObjectIdCompound id = (ObjectIdCompound) object;
- if (!Objects.equals(entityName, id.entityName)) {
- return false;
- }
-
- if (id.objectIdKeys.size() != objectIdKeys.size()) {
- return false;
- }
-
- for (Map.Entry<String, ?> entry : objectIdKeys.entrySet()) {
- String entryKey = entry.getKey();
- Object entryValue = entry.getValue();
-
- if (entryValue == null
- && (id.objectIdKeys.get(entryKey) != null || !id.objectIdKeys.containsKey(entryKey))) {
- return false;
- } else if (!valueEquals(entryValue, id.objectIdKeys.get(entryKey))) {
- return false;
- }
- }
-
- return true;
- }
-
- private boolean valueEquals(Object o1, Object o2) {
- if (o1 == o2) {
- return true;
- }
-
- if (o2 == null) {
- return false;
- }
-
- if (o1 instanceof Number n1) {
- return o2 instanceof Number n2 && n1.longValue() == n2.longValue();
- }
-
- return Objects.deepEquals(o1, o2);
- }
-
- @Override
- public int hashCode() {
- if(hashCode != 0) {
- return hashCode;
- }
-
- HashCodeBuilder builder = new HashCodeBuilder().append(entityName.hashCode());
-
- // handle multiple keys - must sort the keys to use with HashCodeBuilder
- String[] keys = objectIdKeys.keySet().toArray(new String[0]);
- Arrays.sort(keys);
- for (int i = 0; i < keys.length; i++) {
- // HashCodeBuilder will take care of processing object if it
- // happens to be a primitive array such as byte[]
-
- // also we don't have to append the key hashcode, its index will work
- builder.append(i);
-
- Object value = objectIdKeys.get(keys[i]);
- // must reconcile all possible numeric types
- if (value instanceof Number number) {
- builder.append(number.longValue());
- } else {
- builder.append(value);
- }
- }
- return hashCode = builder.toHashCode();
- }
-
- /**
- * Returns a non-null mutable map that can be used to append replacement id
- * values. This allows to incrementally build a replacement GlobalID.
- *
- * @since 1.2
- */
- @Override
- public Map<String, Object> getReplacementIdMap() {
- if (replacementIdMap == null) {
- replacementIdMap = new HashMap<>();
- }
-
- return replacementIdMap;
- }
-
- /**
- * Creates and returns a replacement ObjectId. No validation of ID is done.
- *
- * @since 1.2
- */
- @Override
- public ObjectId createReplacementId() {
- if(replacementIdMap == null) {
- return this;
- }
- // merge existing and replaced ids to handle a replaced subset of a compound primary key
- Map<String, Object> newIdMap = new HashMap<>(objectIdKeys);
- newIdMap.putAll(replacementIdMap);
- return ObjectId.of(entityName, newIdMap);
- }
-
- /**
- * Returns true if there is full or partial replacement id attached to this
- * id. This method is preferable to "!getReplacementIdMap().isEmpty()" as
- * it avoids unneeded replacement id map creation.
- */
- @Override
- public boolean isReplacementIdAttached() {
- return replacementIdMap != null && !replacementIdMap.isEmpty();
- }
-
- /**
- * A standard toString method used for debugging. It is guaranteed to
- * produce the same string if two ObjectIds are equal.
- */
- @Override
- public String toString() {
- StringBuilder buffer = new StringBuilder().append("<ObjectId:").append(entityName);
- // ensure consistent order of the keys, so that toString could be
- // used as a unique key, just like id itself
- String[] keys = objectIdKeys.keySet().toArray(new String[0]);
- Arrays.sort(keys);
- for (String key : keys) {
- buffer.append(", ").append(key).append("=").append(objectIdKeys.get(key));
- }
- return buffer.append(">").toString();
- }
+ private static final long serialVersionUID = -2265029098344119323L;
+
+ protected final String entityName;
+ protected final Map<String, Object> objectIdKeys;
+
+ protected Map<String, Object> replacementIdMap;
+
+ // hash code is transient to make sure id is portable across VM
+ private transient int hashCode;
+
+ // exists for deserialization with Hessian and similar
+ @SuppressWarnings("unused")
+ private ObjectIdCompound() {
+ entityName = null;
+ objectIdKeys = Collections.emptyMap();
+ }
+
+ /**
+ * Creates a portable permanent ObjectId as a compound primary key.
+ *
+ * @param entityName The entity name which this object id is for
+ * @param idMap Keys are usually the attribute names for each part of the
+ * primary key. Values are unique when taken as a whole.
+ * @since 1.2
+ */
+ ObjectIdCompound(String entityName, Map<String, ?> idMap) {
+ this.entityName = entityName;
+
+ if (idMap == null || idMap.size() == 0) {
+ this.objectIdKeys = Collections.emptyMap();
+ return;
+ }
+ this.objectIdKeys = wrapIdMap(idMap);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, Object> wrapIdMap(Map<String, ?> m) {
+ if (m.getClass() == HashMap.class) {
+ return (Map<String, Object>) m;
+ } else {
+ // we have to create a copy of the map, otherwise we may run into serialization problems with hessian
+ return new HashMap<>(m);
+ }
+ }
+
+ /**
+ * Is this is temporary object id (used for objects which are not yet
+ * persisted to the data store).
+ */
+ @Override
+ public boolean isTemporary() {
+ return false;
+ }
+
+ /**
+ * @since 1.2
+ */
+ @Override
+ public String getEntityName() {
+ return entityName;
+ }
+
+ /**
+ * Get the binary temporary object id. Null if this object id is permanent
+ * (persisted to the data store).
+ */
+ @Override
+ public byte[] getKey() {
+ return null;
+ }
+
+ /**
+ * Returns an unmodifiable Map of persistent id values, essentially a
+ * primary key map. For temporary id returns replacement id, if it was
+ * already created. Otherwise returns an empty map.
+ */
+ @Override
+ public Map<String, Object> getIdSnapshot() {
+ return Collections.unmodifiableMap(objectIdKeys);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+
+ if (!(object instanceof ObjectIdCompound)) {
+ return false;
+ }
+
+ ObjectIdCompound id = (ObjectIdCompound) object;
+ if (!Objects.equals(entityName, id.entityName)) {
+ return false;
+ }
+
+ if (id.objectIdKeys.size() != objectIdKeys.size()) {
+ return false;
+ }
+
+ for (Map.Entry<String, ?> entry : objectIdKeys.entrySet()) {
+ String entryKey = entry.getKey();
+ Object entryValue = entry.getValue();
+
+ if (entryValue == null
+ && (id.objectIdKeys.get(entryKey) != null || !id.objectIdKeys.containsKey(entryKey))) {
+ return false;
+ } else if (!valueEquals(entryValue, id.objectIdKeys.get(entryKey))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean valueEquals(Object o1, Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+
+ if (o2 == null) {
+ return false;
+ }
+
+ if (o1 instanceof Number n1) {
+ return o2 instanceof Number n2 && n1.longValue() == n2.longValue();
+ }
+
+ return Objects.deepEquals(o1, o2);
+ }
+
+ @Override
+ public int hashCode() {
+ if (hashCode != 0) {
+ return hashCode;
+ }
+
+ // sort keys for order-independence
+ String[] keys = objectIdKeys.keySet().toArray(new String[0]);
+ Arrays.sort(keys);
+ int result = entityName.hashCode();
+ for (int i = 0; i < keys.length; i++) {
+ result = 31 * result + i;
+ Object value = objectIdKeys.get(keys[i]);
+ // reconcile all numeric types via long; use content hash for arrays
+ result = 31 * result + (value instanceof Number n ? Long.hashCode(n.longValue()) : ObjectIdSingle.deepHashCode(value));
+ }
+ return hashCode = result;
+ }
+
+ /**
+ * Returns a non-null mutable map that can be used to append replacement id
+ * values. This allows to incrementally build a replacement GlobalID.
+ *
+ * @since 1.2
+ */
+ @Override
+ public Map<String, Object> getReplacementIdMap() {
+ if (replacementIdMap == null) {
+ replacementIdMap = new HashMap<>();
+ }
+
+ return replacementIdMap;
+ }
+
+ /**
+ * Creates and returns a replacement ObjectId. No validation of ID is done.
+ *
+ * @since 1.2
+ */
+ @Override
+ public ObjectId createReplacementId() {
+ if (replacementIdMap == null) {
+ return this;
+ }
+ // merge existing and replaced ids to handle a replaced subset of a compound primary key
+ Map<String, Object> newIdMap = new HashMap<>(objectIdKeys);
+ newIdMap.putAll(replacementIdMap);
+ return ObjectId.of(entityName, newIdMap);
+ }
+
+ /**
+ * Returns true if there is full or partial replacement id attached to this
+ * id. This method is preferable to "!getReplacementIdMap().isEmpty()" as
+ * it avoids unneeded replacement id map creation.
+ */
+ @Override
+ public boolean isReplacementIdAttached() {
+ return replacementIdMap != null && !replacementIdMap.isEmpty();
+ }
+
+ /**
+ * A standard toString method used for debugging. It is guaranteed to
+ * produce the same string if two ObjectIds are equal.
+ */
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder().append("<ObjectId:").append(entityName);
+ // ensure consistent order of the keys, so that toString could be
+ // used as a unique key, just like id itself
+ String[] keys = objectIdKeys.keySet().toArray(new String[0]);
+ Arrays.sort(keys);
+ for (String key : keys) {
+ buffer.append(", ").append(key).append("=").append(objectIdKeys.get(key));
+ }
+ return buffer.append(">").toString();
+ }
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/ObjectIdSingle.java b/cayenne/src/main/java/org/apache/cayenne/ObjectIdSingle.java
index ee0df252e..c868db55a 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ObjectIdSingle.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ObjectIdSingle.java
@@ -19,15 +19,16 @@
package org.apache.cayenne;
+import org.apache.cayenne.util.SingleEntryMap;
+
+import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
-
-import org.apache.cayenne.util.HashCodeBuilder;
-import org.apache.cayenne.util.SingleEntryMap;
import java.util.Objects;
/**
* {@link ObjectId} with single non-numeric value
+ *
* @since 4.2
*/
class ObjectIdSingle implements ObjectId {
@@ -77,7 +78,7 @@ class ObjectIdSingle implements ObjectId {
@Override
public Map<String, Object> getReplacementIdMap() {
- if(replacementId == null) {
+ if (replacementId == null) {
replacementId = new SingleEntryMap<>(keyName);
}
return replacementId;
@@ -116,7 +117,7 @@ class ObjectIdSingle implements ObjectId {
return false;
}
ObjectIdSingle that = (ObjectIdSingle) o;
- if(!Objects.equals(entityName, that.entityName)) {
+ if (!Objects.equals(entityName, that.entityName)) {
return false;
}
return Objects.deepEquals(value, that.value);
@@ -124,9 +125,22 @@ class ObjectIdSingle implements ObjectId {
@Override
public int hashCode() {
- if(hashCode == 0) {
- hashCode = new HashCodeBuilder().append(entityName).append(value).toHashCode();
+ if (hashCode == 0) {
+ hashCode = 31 * Objects.hashCode(entityName) + deepHashCode(value);
}
return hashCode;
}
+
+ static int deepHashCode(Object v) {
+ if (v instanceof byte[] a) return Arrays.hashCode(a);
+ if (v instanceof int[] a) return Arrays.hashCode(a);
+ if (v instanceof long[] a) return Arrays.hashCode(a);
+ if (v instanceof short[] a) return Arrays.hashCode(a);
+ if (v instanceof char[] a) return Arrays.hashCode(a);
+ if (v instanceof float[] a) return Arrays.hashCode(a);
+ if (v instanceof double[] a) return Arrays.hashCode(a);
+ if (v instanceof boolean[] a) return Arrays.hashCode(a);
+ if (v instanceof Object[] a) return Arrays.deepHashCode(a);
+ return Objects.hashCode(v);
+ }
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
index 73a415583..74d7c09d9 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
@@ -26,7 +26,6 @@ import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.util.HashCodeBuilder;
import java.util.Objects;
import org.apache.cayenne.util.ToStringBuilder;
@@ -192,8 +191,7 @@ public class ColumnDescriptor {
*/
@Override
public int hashCode() {
- return new HashCodeBuilder(23, 43).append(name).append(namePrefix).append(procedureName).append(tableName)
- .append(dataRowKey).toHashCode();
+ return Objects.hash(name, namePrefix, procedureName, tableName, dataRowKey);
}
/**
diff --git a/cayenne/src/main/java/org/apache/cayenne/event/EventSubject.java b/cayenne/src/main/java/org/apache/cayenne/event/EventSubject.java
index 7cc0fdc16..27ea695cd 100644
--- a/cayenne/src/main/java/org/apache/cayenne/event/EventSubject.java
+++ b/cayenne/src/main/java/org/apache/cayenne/event/EventSubject.java
@@ -22,7 +22,7 @@ package org.apache.cayenne.event;
import java.io.Serializable;
import java.util.Map;
-import org.apache.cayenne.util.HashCodeBuilder;
+import java.util.Objects;
import org.apache.cayenne.util.WeakValueMap;
/**
@@ -103,9 +103,7 @@ public class EventSubject implements Serializable {
@Override
public int hashCode() {
- return new HashCodeBuilder(17, 3)
- .append(_fullyQualifiedSubjectName)
- .toHashCode();
+ return Objects.hashCode(_fullyQualifiedSubjectName);
}
public String getSubjectName() {
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne/src/main/java/org/apache/cayenne/exp/Expression.java
index 6f25938b0..46f0f1e38 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -32,7 +32,6 @@ import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
import org.apache.cayenne.exp.parser.ASTScalar;
import org.apache.cayenne.util.ConversionUtil;
-import org.apache.cayenne.util.HashCodeBuilder;
import org.apache.cayenne.util.XMLEncoder;
import java.util.Objects;
@@ -296,12 +295,12 @@ public abstract class Expression implements Serializable, XMLSerializable {
@Override
public int hashCode() {
- HashCodeBuilder builder = new HashCodeBuilder().append(getType());
+ int result = getType();
int opCount = getOperandCount();
- for(int i=0; i<opCount; i++) {
- builder.append(getOperand(i));
+ for (int i = 0; i < opCount; i++) {
+ result = 31 * result + Objects.hashCode(getOperand(i));
}
- return builder.toHashCode();
+ return result;
}
/**
diff --git a/cayenne/src/main/java/org/apache/cayenne/util/HashCodeBuilder.java b/cayenne/src/main/java/org/apache/cayenne/util/HashCodeBuilder.java
index e189a363c..dd1e9e512 100644
--- a/cayenne/src/main/java/org/apache/cayenne/util/HashCodeBuilder.java
+++ b/cayenne/src/main/java/org/apache/cayenne/util/HashCodeBuilder.java
@@ -21,9 +21,11 @@ package org.apache.cayenne.util;
/**
* Assists in implementing {@link Object#hashCode()} methods. The code is based on
* HashCodeBuilder from commons-lang 2.1.
- *
+ *
* @since 3.0
+ * @deprecated use {@link java.util.Objects#hash(Object...)} or a manual {@code 31 * h + Objects.hashCode(x)} loop
*/
+@Deprecated(since = "5.0", forRemoval = true)
public class HashCodeBuilder {
/**
diff --git a/cayenne/src/main/java/org/apache/cayenne/util/Util.java b/cayenne/src/main/java/org/apache/cayenne/util/Util.java
index 02d9bfd4e..7ff057a54 100644
--- a/cayenne/src/main/java/org/apache/cayenne/util/Util.java
+++ b/cayenne/src/main/java/org/apache/cayenne/util/Util.java
@@ -590,11 +590,11 @@ public class Util {
*/
@Deprecated(since = "5.0", forRemoval = true)
public static int hashCode(Collection<?> c) {
- HashCodeBuilder builder = new HashCodeBuilder();
+ int result = 17;
for (Object o : c) {
- builder.append(o);
+ result = 37 * result + Objects.hashCode(o);
}
- return builder.toHashCode();
+ return result;
}
/**