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;
     }
 
     /**