Sunday, December 9, 2007

Generic equal and toString

When writing unit tests in many cases it's required to implement toString to display the DTO (Data Transfer Object) in the log file or in the console for debugging.

In this case a good solution is to use generic DTO implementaion which implement "toString" method in a generic way using reflection, The method pass on all the getters methods recusivly and print the fields values, If a new field is added or removed the toString code doesn't need to be change.

In the same way it can be also used to implement generic "equals" method which compare each two getters of two objects recusevly.

This implementaion is also suitable for tests and I don't recommened to use that where performance issues are important since it use reflection.

Here is the implementation code

/**
* Generic DTO class

* Implements a functionality such as toString(), equals() , etc.
*/
public abstract class GenericDTO implements Serializable
{

private static final Logger logger = Logger.getInstance();

/**
* A String representation of all the get methods.
*
* @return A String concatenations toString of the returned values of all the get methods.

* The get methods do not have parameters
*/
public String toString()
{
Method[] methods = this.getClass().getMethods();
StringBuffer result = new StringBuffer();
boolean comma = false;
try
{
int length = methods.length;
result.append("[");
for (int i = 0; i < length; i++)
{
String methodName = methods[i].getName();
if (methodName.startsWith("get") && !methodName.startsWith("getClass"))
{
if (comma)
{
result.append(", ");
}
else
{
comma = true;
}

Object o = methods[i].invoke(this, null);
result.append(methodName.substring(3)); //after the get
result.append("():");
if (o==null)
{
result.append("null");
}
else
{
result.append(o.toString());
}
}

}
result.append("]");
}
catch (IllegalArgumentException e)
{
logger.error("toString()", e);
}
catch (IllegalAccessException e)
{
logger.error("toString()", e);
}
catch (InvocationTargetException e)
{
logger.error("toString()", e);
}
return result.toString();
}

/**
* Indicates whether some other object is "equal to" this one

* An AbstractDTO is equal to another if and only if it is the same class and have

* the same values of the get methods.
*
* @return true if it the same class and the returned values of the get methods are equal
* false otherwise
*/
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
boolean result = false;
try
{
if (obj.getClass() == this.getClass())
{
result = true;
Method[] objMethods = obj.getClass().getMethods();
int methodsLength = objMethods.length;

int i = 0;
while (i < methodsLength && result)
{
String methodName = objMethods[i].getName();
if (methodName.startsWith("get"))
{
// invoke on my object
Object objReturnedObject = objMethods[i].invoke(this, null);
// invoke on the parameter object
Object myReturnedObject = objMethods[i].invoke(obj, null);

if (objReturnedObject==null)
{
if (myReturnedObject!=null)
{
result = false;
}
}
else
{
if (!objReturnedObject.equals(myReturnedObject))
{
result = false;
}
}
}
i++;
}
}
}
catch (IllegalArgumentException e)
{
logger.error("equals()", e);
return false;
}
catch (IllegalAccessException e)
{
logger.error("equals()", e);
return false;
}
catch (InvocationTargetException e)
{
logger.error("equals()", e);
return false;
}

return result;
}

}

4 comments:

Anonymous said...

Here's a thought: instead of having all your objects extend one central class (which is a limitation), use AOP. With AspectJ you can intercept the invocation of toString and invoke your generic toString implementation.

Anonymous said...

Ever heard of Apache Commons!

Anonymous said...

More precisely, Commons Lang.. ToStringBuilder and EqualsBuilder already do what you describe...and then some. Plus, there's HashCodeBuilder as well, becuase you don't want to override equals() without overriding hashCode().

Andy Y said...

There are a couple of very good articles on equals and hashcodes from Java Practices with static helper classes for building them. Whilst commons-lang is a very good way of getting an equals/hashcode quickly I have found that under a huge amount of strain of the object recycling was too high & I had to abandon them in favour of these two classes. I would recommend using these in conjunction with commons lang.

Links

 
RSS Feeds Submission Directory