EqualsHash

For an actual type parameter Person this factory generates a class PersonWithEqualsHash that extends class Person. PersonWithEqualsHash has a public equals() and a public hashCode() method that can be used to test two PersonWithEqualsHash objects for equality and calculate a hash code for a PersonWithEqualsHash object, respectively.

In equals(), the corresponding primitive attributes of the caller and the actual parameter are compared directly and the class type attributes are compared using their respective equals() method. This is possible, because the Java root class Object declares a public method equals(), so that all objects are guaranteed to have one (unlike clone(), for example).

Analogously, the generated hashCode() reads the values of the primitive attributes directly, and calls hashCode() on the class type attributes, which are all guaranteed to declare hashCode(). The resulting values are combined to a single one using bitwise exclusive-or.

Note that the factory works only for actual parameters that do not declare private attributes. The factory would generate code that would try to access these private attributes, and because Java forbids subclasses to access the private attributes of its superclasses and the generated class is a subclass of the actual type parameter this could not be compiled.

Factory/Java Source

The source can be found in the Factory/Java .zip-archive in factory/classes/examples/EqualsHash.factory:

<param> <var> T </var> </param>

package test;

public class
<apply>
    <apply>
        <class> factory.Toolbox </class>
        <method> getRelativeName </method>
        <args>
            <var> T </var>
        </args>
    </apply>
    <method> concat </method>
    <args>
        <const> "WithEqualsHash" </const>
    </args>
</apply>
extends <var> T </var>
{                    
    public boolean equals(Object o) {
        // classes have to be equal
        if(!o.getClass().equals(this.getClass()))
            return false;        

        <var> T </var> a = (<var> T </var>) o;
        
        // iterate over all attributes of T
        <for> <var> I </var>
        <apply>
            <class> factory.Toolbox </class>
            <method> getAllFields </method>
            <args> <var> T </var> </args>
        </apply>
        <body>
            <if>
            <apply>
                <apply>
                    <var> I </var> <method> getType </method>
                </apply>
                <method> isPrimitive </method>
            </apply>
            <then>
                if(
                    this.
                    <apply>
                        <var> I </var> <method> getName </method>
                    </apply>
                    !=
                    a.
                    <apply>
                        <var> I </var> <method> getName </method>
                    </apply>
                )
                    return false;
            </then>
            <else>    
                if(    this.
                    <apply>
                        <var> I </var> <method> getName </method>
                    </apply>
                    .equals(
                        a.
                        <apply>
                            <var> I </var> <method> getName </method>
                        </apply>
                    ) == false
                )
                    return false;
            </else>
            </if>            
        </body> </for>
        return true;
    }

    // requirement: a.equals(b) => a.hashCode()==b.hashCode()
    public int hashCode() {
        int code = 0;
        
        // iterate over all attributes of T
        <for> <var> I </var>
        <apply>
            <class> factory.Toolbox </class>
            <method> getAllFields </method>
            <args> <var> T </var> </args>
        </apply>
        <body>
            <if>
            <apply>
                <apply>
                    <var> I </var> <method> getType </method>
                </apply>
                <method> isPrimitive </method>
            </apply>
            <then>
                code ^=
                    (int)
                    this.
                    <apply>
                        <var> I </var>
                        <method> getName </method>
                    </apply>
                    ;    
            </then>
            <else>
                code ^=
                    this.
                    <apply>
                        <var> I </var>
                        <method> getName </method>
                    </apply>
                    .hashCode();
            </else>
            </if>            
        </body> </for>
        
        return code;
    }
}

Test Program

The following unparameterized factory can be found in factory/src/test/EqualsHashTest.factory in the Factory/Java .zip-archive. It applies factory EqualsHash to class Person, instantiates three objects a, b and c of the resulting class PersonWithEqualsHash and sets their attributes to arbitrary values. The values for b and c are the same, so that they have the same hash code and a call to equals() on them returns true.

package test;

public class EqualsHashTest {
    public static void main(String argv[]) {
        <let> <var> T </var>
        <apply>
            <factory> examples/EqualsHash </factory>
            <args> <const> test.Person </const> </args>
        </apply>
        <body>
            <var> T </var> a = new <var> T </var>();
            a.name = "Mr.X";
            a.plz = 4711;
            a.id = 1;
           
            <var> T </var> b = new <var> T </var>();
            b.name = "Mr.Y";
            b.plz = 4712;
            b.id = 2;
   
            <var> T </var> c = new <var> T </var>();
            c.name = "Mr.Y";
            c.plz = 4712;
            c.id = 2;
           
            System.out.println("a.hashCode()=="+a.hashCode());
            System.out.println("b.hashCode()=="+b.hashCode());
            System.out.println("c.hashCode()=="+c.hashCode());
           
            System.out.println("a.equals(b)=="+a.equals(b));
            System.out.println("a.equals(c)=="+a.equals(c));
            System.out.println("b.equals(c)=="+b.equals(c));
        </body> </let>
    }
}


This factory can be compiled in the factory directory with
java -classpath classes factory.Factory -javad src -classd classes src/test/EqualsHashTest
and run with
java -classpath classes test.EqualsHashTest
Execution of the class yields the following textual output:
a.hashCode()==2400265
b.hashCode()==2400282
c.hashCode()==2400282
a.equals(b)==false
a.equals(c)==false
b.equals(c)==true