You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ba...@apache.org on 2006/04/24 09:00:01 UTC
svn commit: r396461 - in /jakarta/commons/proper/lang/trunk/src:
java/org/apache/commons/lang/builder/ test/org/apache/commons/lang/builder/
Author: bayard
Date: Mon Apr 24 00:00:00 2006
New Revision: 396461
URL: http://svn.apache.org/viewcvs?rev=396461&view=rev
Log:
Adding new excludeFields functionality for EqualsBuilder and HashCodeBuilder, as submitted by Pete Gieser, Bugzilla: #39315
Modified:
jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/EqualsBuilder.java
jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/HashCodeBuilder.java
jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/EqualsBuilderTest.java
jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/HashCodeBuilderTest.java
Modified: jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/EqualsBuilder.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/EqualsBuilder.java?rev=396461&r1=396460&r2=396461&view=diff
==============================================================================
--- jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/EqualsBuilder.java (original)
+++ jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/EqualsBuilder.java Mon Apr 24 00:00:00 2006
@@ -18,6 +18,9 @@
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* <p>Assists in implementing {@link Object#equals(Object)} methods.</p>
@@ -118,7 +121,30 @@
* @return <code>true</code> if the two Objects have tested equals.
*/
public static boolean reflectionEquals(Object lhs, Object rhs) {
- return reflectionEquals(lhs, rhs, false, null);
+ return reflectionEquals(lhs, rhs, false, null, null);
+ }
+
+ /**
+ * <p>This method uses reflection to determine if the two <code>Object</code>s
+ * are equal.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.</p>
+ *
+ * <p>Transient members will be not be tested, as they are likely derived
+ * fields, and not part of the value of the Object.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ * @param excludeFields array of field names to exclude from testing
+ * @return <code>true</code> if the two Objects have tested equals.
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, String[] excludeFields) {
+ return reflectionEquals(lhs, rhs, false, null, excludeFields);
}
/**
@@ -142,7 +168,7 @@
* @return <code>true</code> if the two Objects have tested equals.
*/
public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients) {
- return reflectionEquals(lhs, rhs, testTransients, null);
+ return reflectionEquals(lhs, rhs, testTransients, null, null);
}
/**
@@ -171,6 +197,37 @@
* @since 2.0
*/
public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class reflectUpToClass) {
+ return reflectionEquals(lhs, rhs, testTransients, reflectUpToClass, null);
+ }
+
+ /**
+ * <p>This method uses reflection to determine if the two <code>Object</code>s
+ * are equal.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is also
+ * not as efficient as testing explicitly.</p>
+ *
+ * <p>If the testTransients parameter is set to <code>true</code>, transient
+ * members will be tested, otherwise they are ignored, as they are likely
+ * derived fields, and not part of the value of the <code>Object</code>.</p>
+ *
+ * <p>Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass. A null superclass is treated
+ * as java.lang.Object.</p>
+ *
+ * @param lhs <code>this</code> object
+ * @param rhs the other object
+ * @param testTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive),
+ * may be <code>null</code>
+ * @param excludeFields array of field names to exclude from testing
+ * @return <code>true</code> if the two Objects have tested equals.
+ * @since 2.0
+ */
+ public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class reflectUpToClass,
+ String[] excludeFields) {
if (lhs == rhs) {
return true;
}
@@ -202,10 +259,10 @@
}
EqualsBuilder equalsBuilder = new EqualsBuilder();
try {
- reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients);
+ reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
testClass = testClass.getSuperclass();
- reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients);
+ reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
}
} catch (IllegalArgumentException e) {
// In this case, we tried to test a subclass vs. a superclass and
@@ -227,18 +284,22 @@
* @param clazz the class to append details of
* @param builder the builder to append to
* @param useTransients whether to test transient fields
+ * @param excludeFields array of field names to exclude from testing
*/
private static void reflectionAppend(
Object lhs,
Object rhs,
Class clazz,
EqualsBuilder builder,
- boolean useTransients) {
+ boolean useTransients,
+ String[] excludeFields) {
Field[] fields = clazz.getDeclaredFields();
+ List excludedFieldList = excludeFields != null ? Arrays.asList(excludeFields) : Collections.EMPTY_LIST;
AccessibleObject.setAccessible(fields, true);
for (int i = 0; i < fields.length && builder.isEquals; i++) {
Field f = fields[i];
- if ((f.getName().indexOf('$') == -1)
+ if (!excludedFieldList.contains(f.getName())
+ && (f.getName().indexOf('$') == -1)
&& (useTransients || !Modifier.isTransient(f.getModifiers()))
&& (!Modifier.isStatic(f.getModifiers()))) {
try {
Modified: jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/HashCodeBuilder.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/HashCodeBuilder.java?rev=396461&r1=396460&r2=396461&view=diff
==============================================================================
--- jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/HashCodeBuilder.java (original)
+++ jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/builder/HashCodeBuilder.java Mon Apr 24 00:00:00 2006
@@ -18,6 +18,9 @@
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* <p>Assists in implementing {@link Object#hashCode()} methods.</p>
@@ -147,7 +150,32 @@
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static int reflectionHashCode(Object object) {
- return reflectionHashCode(17, 37, object, false, null);
+ return reflectionHashCode(17, 37, object, false, null, null);
+ }
+
+ /**
+ * <p>This method uses reflection to build a valid hash code.</p>
+ *
+ * <p>This constructor uses two hard coded choices for the constants
+ * needed to build a hash code.</p>
+ *
+ * <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
+ * fields. This means that it will throw a security exception if run under
+ * a security manager, if the permissions are not set up correctly. It is
+ * also not as efficient as testing explicitly.</p>
+ *
+ * <p>Transient members will be not be used, as they are likely derived
+ * fields, and not part of the value of the <code>Object</code>.</p>
+ *
+ * <p>Static fields will not be tested. Superclass fields will be included.</p>
+ *
+ * @param object the Object to create a <code>hashCode</code> for
+ * @param excludeFields array of field names to exclude from use in calculation of hash code
+ * @return int hash code
+ * @throws IllegalArgumentException if the object is <code>null</code>
+ */
+ public static int reflectionHashCode(Object object, String[] excludeFields) {
+ return reflectionHashCode(17, 37, object, false, null, excludeFields);
}
/**
@@ -173,7 +201,7 @@
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static int reflectionHashCode(Object object, boolean testTransients) {
- return reflectionHashCode(17, 37, object, testTransients, null);
+ return reflectionHashCode(17, 37, object, testTransients, null, null);
}
/**
@@ -202,7 +230,7 @@
*/
public static int reflectionHashCode(
int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object) {
- return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null);
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null, null);
}
/**
@@ -234,7 +262,7 @@
public static int reflectionHashCode(
int initialNonZeroOddNumber, int multiplierNonZeroOddNumber,
Object object, boolean testTransients) {
- return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null);
+ return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null, null);
}
/**
@@ -263,6 +291,7 @@
* @param testTransients whether to include transient fields
* @param reflectUpToClass the superclass to reflect up to (inclusive),
* may be <code>null</code>
+ * @param excludeFields array of field names to exclude from use in calculation of hash code
* @return int hash code
* @throws IllegalArgumentException if the Object is <code>null</code>
* @throws IllegalArgumentException if the number is zero or even
@@ -273,17 +302,18 @@
int multiplierNonZeroOddNumber,
Object object,
boolean testTransients,
- Class reflectUpToClass) {
+ Class reflectUpToClass,
+ String[] excludeFields) {
if (object == null) {
throw new IllegalArgumentException("The object to build a hash code for must not be null");
}
HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber);
Class clazz = object.getClass();
- reflectionAppend(object, clazz, builder, testTransients);
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
clazz = clazz.getSuperclass();
- reflectionAppend(object, clazz, builder, testTransients);
+ reflectionAppend(object, clazz, builder, testTransients, excludeFields);
}
return builder.toHashCode();
}
@@ -297,12 +327,19 @@
* @param builder the builder to append to
* @param useTransients whether to use transient fields
*/
- private static void reflectionAppend(Object object, Class clazz, HashCodeBuilder builder, boolean useTransients) {
+ private static void reflectionAppend(
+ Object object,
+ Class clazz,
+ HashCodeBuilder builder,
+ boolean useTransients,
+ String[] excludeFields) {
Field[] fields = clazz.getDeclaredFields();
+ List excludedFieldList = excludeFields != null ? Arrays.asList(excludeFields) : Collections.EMPTY_LIST;
AccessibleObject.setAccessible(fields, true);
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
- if ((f.getName().indexOf('$') == -1)
+ if (!excludedFieldList.contains(f.getName())
+ && (f.getName().indexOf('$') == -1)
&& (useTransients || !Modifier.isTransient(f.getModifiers()))
&& (!Modifier.isStatic(f.getModifiers()))) {
try {
Modified: jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/EqualsBuilderTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/EqualsBuilderTest.java?rev=396461&r1=396460&r2=396461&view=diff
==============================================================================
--- jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/EqualsBuilderTest.java (original)
+++ jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/EqualsBuilderTest.java Mon Apr 24 00:00:00 2006
@@ -15,6 +15,7 @@
*/
package org.apache.commons.lang.builder;
+import java.lang.reflect.Field;
import java.util.Arrays;
import junit.framework.Test;
@@ -955,5 +956,41 @@
// causes an NPE in 2.0 according to:
// http://issues.apache.org/bugzilla/show_bug.cgi?id=33067
new EqualsBuilder().append(x1, x2);
+ }
+
+ public void testReflectionEqualsExcludeFields() throws Exception {
+ TestObjectWithMultipleFields x1 = new TestObjectWithMultipleFields(1, 2, 3);
+ TestObjectWithMultipleFields x2 = new TestObjectWithMultipleFields(1, 3, 4);
+
+ // not equal when including all fields
+ assertTrue(!EqualsBuilder.reflectionEquals(x1, x2));
+
+ // doesn't barf on null, empty array, or non-existent field, but still tests as not equal
+ assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, null));
+ assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {}));
+ assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {"xxx"}));
+
+ // not equal if only one of the differing fields excluded
+ assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {"two"}));
+ assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {"three"}));
+
+ // equal if both differing fields excluded
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, new String[] {"two", "three"}));
+
+ // still equal as long as both differing fields are among excluded
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, new String[] {"one", "two", "three"}));
+ assertTrue(EqualsBuilder.reflectionEquals(x1, x2, new String[] {"one", "two", "three", "xxx"}));
+ }
+
+ static class TestObjectWithMultipleFields {
+ private TestObject one;
+ private TestObject two;
+ private TestObject three;
+
+ public TestObjectWithMultipleFields(int one, int two, int three) {
+ this.one = new TestObject(one);
+ this.two = new TestObject(two);
+ this.three = new TestObject(three);
+ }
}
}
Modified: jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/HashCodeBuilderTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/HashCodeBuilderTest.java?rev=396461&r1=396460&r2=396461&view=diff
==============================================================================
--- jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/HashCodeBuilderTest.java (original)
+++ jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/builder/HashCodeBuilderTest.java Mon Apr 24 00:00:00 2006
@@ -437,4 +437,33 @@
assertEquals( (((17 * 37 + 0) * 37 + 1) * 37 + 1), new HashCodeBuilder(17, 37).append(obj).toHashCode());
}
+ public void testReflectionHashCodeExcludeFields() throws Exception {
+ TestObjectWithMultipleFields x = new TestObjectWithMultipleFields(1, 2, 3);
+
+ assertEquals((((17 * 37 + 1) * 37 + 2) * 37 + 3), HashCodeBuilder.reflectionHashCode(x));
+
+ assertEquals((((17 * 37 + 1) * 37 + 2) * 37 + 3), HashCodeBuilder.reflectionHashCode(x, null));
+ assertEquals((((17 * 37 + 1) * 37 + 2) * 37 + 3), HashCodeBuilder.reflectionHashCode(x, new String[] {}));
+ assertEquals((((17 * 37 + 1) * 37 + 2) * 37 + 3), HashCodeBuilder.reflectionHashCode(x, new String[] {"xxx"}));
+
+ assertEquals(((17 * 37 + 1) * 37 + 3), HashCodeBuilder.reflectionHashCode(x, new String[] {"two"}));
+ assertEquals(((17 * 37 + 1) * 37 + 2), HashCodeBuilder.reflectionHashCode(x, new String[] {"three"}));
+
+ assertEquals((17 * 37 + 1), HashCodeBuilder.reflectionHashCode(x, new String[] {"two", "three"}));
+
+ assertEquals(17, HashCodeBuilder.reflectionHashCode(x, new String[] {"one", "two", "three"}));
+ assertEquals(17, HashCodeBuilder.reflectionHashCode(x, new String[] {"one", "two", "three", "xxx"}));
+ }
+
+ static class TestObjectWithMultipleFields {
+ private int one = 0;
+ private int two = 0;
+ private int three = 0;
+
+ public TestObjectWithMultipleFields(int one, int two, int three) {
+ this.one = one;
+ this.two = two;
+ this.three = three;
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org