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 <[email protected]> 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; } /**
