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