Grails – generic methods for equals and hashCode calculation

December 27, 2012 at 23:11

In modern versions of Grails, you can use the Groovy annotation EqualsAndHashCode for generating the equals and hashCode methods of your domain classes using an AST transformation, but if you are in any 1.x.x Grails version (that uses a Groovy version prior to 1.8.0), you won’t be able to use this.

I’ve seen tons of duplicated code (Tolkien fans 😀 would refer to it as “The Programmers Bane”) in the projects I’ve worked for hashCode and equals methods, even in the ones that use the Apache Commons helpers (EqualsBuilder and HashCodeBuilder), so I decided to post a pair of methods that I usually use to work out this problem:

import org.apache.commons.lang.builder.EqualsBuilder
import org.apache.commons.lang.builder.HashCodeBuilder
class DomainClassUtils {
	...
	static boolean equals(Object one, Object another) {
		one != null && another != null && 
		((obj.respondsTo('instanceOf') && obj.instanceOf(this.getClass())) || 
		 this.getClass().isInstance(obj)) && 
		(one.is(another) || 
		 one.getClass().equalsProperties.inject(new EqualsBuilder()) {
			builder, property ->
			builder.append(one[property], another[property])
		}.isEquals())
	}
	
	static int hashCode(Object one) {
		one.getClass().equalsProperties.inject(new HashCodeBuilder()) {
			builder, property ->
			builder.append(this[property])
		}.toHashCode()
	}
}

This methods will calculate equals and hashcode for domain class objects (note the use of the instanceOf method if available) that declares a static property named equalsProperties. For example:

import DomainClassUtils

class SomeDomainClass {
	Integer someProperty
	String anotherProperty
	AnotherDomainClass andAnotherProperty

	static equalsProperties = ['someProperty', 'andAnotherProperty']

	boolean equals(Object obj) {
		DomainClassUtils.equals(this, obj)
	}

	int hashCode() {
		DomainClassUtils.hashCode(this)
	}
}

This still involves duplicating the DomainClassUtils method invokations code, but this can be easily avoided injecting the methods using your prefered mechanism (in increasing order of preference, I could list the following ones: inheritance, composition, metaprogramming, category/mixin transformation, your custom AST transformation).